diff --git a/Makefile.in b/Makefile.in index 50c34452..18452b75 100644 --- a/Makefile.in +++ b/Makefile.in @@ -51,15 +51,15 @@ LIBS = @READLINE_LIBS@ @LIBS@ $(ADDLIBS) HFILES = config.h es.h gc.h input.h prim.h print.h sigmsgs.h \ stdenv.h syntax.h term.h token.h var.h CFILES = access.c closure.c conv.c dict.c eval.c except.c fd.c gc.c glob.c \ - glom.c input.c heredoc.c history.c list.c main.c match.c open.c opt.c \ + 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 \ - 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 + readline.c sigmsgs.c signal.c split.c status.c str.c syntax.c term.c \ + token.c tree.c util.c var.c vec.c version.c y.tab.c dump.c OFILES = access.o closure.o conv.o dict.o eval.o except.o fd.o gc.o glob.o \ - glom.o input.o heredoc.o history.o list.o main.o match.o open.o opt.o \ + 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 \ - 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 + readline.o sigmsgs.o signal.o split.o status.o str.o syntax.o term.o \ + token.o tree.o util.o var.o vec.o version.o y.tab.o OTHER = Makefile parse.y mksignal GEN = esdump y.tab.h y.output sigmsgs.c initial.c @@ -131,7 +131,6 @@ glob.o : glob.c es.h config.h stdenv.h gc.h glom.o : glom.c es.h config.h stdenv.h gc.h input.o : input.c es.h config.h stdenv.h input.h token.h heredoc.o : heredoc.c es.h config.h stdenv.h gc.h input.h syntax.h token.h -history.o : history.c es.h config.h stdenv.h gc.h input.h token.h list.o : list.c es.h config.h stdenv.h gc.h main.o : main.c es.h config.h stdenv.h match.o : match.c es.h config.h stdenv.h @@ -141,6 +140,7 @@ prim.o : prim.c es.h config.h stdenv.h prim.h prim-ctl.o : prim-ctl.c es.h config.h stdenv.h prim.h prim-etc.o : prim-etc.c es.h config.h stdenv.h prim.h prim-io.o : prim-io.c es.h config.h stdenv.h gc.h prim.h +readline.o : readline.c es.h config.h stdenv.h prim.h prim-sys.o : prim-sys.c es.h config.h stdenv.h prim.h print.o : print.c es.h config.h stdenv.h print.h proc.o : proc.c es.h config.h stdenv.h prim.h diff --git a/access.c b/access.c index 44b0f75b..2cdf2d5a 100644 --- a/access.c +++ b/access.c @@ -168,7 +168,7 @@ PRIM(access) { if (suffix) err = str("%s: %s", suffix, err); gcenable(); - fail("$&access", err); + fail("$&access", "%s", err); RefEnd(err); } diff --git a/completion.es b/completion.es new file mode 100644 index 00000000..b408bd32 --- /dev/null +++ b/completion.es @@ -0,0 +1,189 @@ +# completion.es -- demo of programmable completion in es + +# This file exists to explore some options for programmable completion in +# es; it's not an endorsement of any particular design for completion. +# However, this setup already performs much better than the current +# "built-in" readline completion that es has, with surprisingly little +# direct support from the readline library. This corresponds well with +# how much es is built on top of es, and (hopefully) indicates that es +# could switch between different line-editing libraries while using a +# single common user-visible completion "API". +# +# Syntax isn't handled really robustly with this setup, and probably +# requires some kind of internal parsing machinery to do right; for +# example, we don't have great behavior with `$(var1 var2[TAB]`, +# `let (f[TAB]`, `let (a = b) command[TAB]`, or `cmd > fi[TAB]`. +# +# Some hook functions back syntax and ideally modifying the hook functions' +# completion functions (e.g., `%complete-%create`) would also modify how +# the syntax is completed (e.g., `command arg > fi[TAB]`). In theory, this +# could even extend to things like %complete-%seq and %complete-%pipe, +# though there's some trickiness in designing how that would actually be +# executed. +# +# We also want good automatic de-quoting and re-quoting of terms, and +# splitting arguments in a syntax-aware way. +# +# Closer integration with readline is another open question. For example, +# it's common to have specific key bindings refer to specific types of +# completion. How do we implement that? Moreover, how do we do so in a +# way that works with .inputrc? How might we design this in a way that's +# library-agnostic? +# +# This setup produces a fairly large amount of overhead in the es function +# namespace. We would likely want to reduce that overhead, especially since +# all of this has absolutely no value in the non-interactive case. Perhaps +# all of the per-command completions should come from autoloadable files and +# be marked noexport. I suspect that while it's quite nice to have good +# coverage for autocompletion, in practical use, only a few commands are +# actually auto-completed in any interactive session. + + +# +# Base/dispatcher completion function +# + +# %complete is called by $&readline whenever the user hits "tab". It is +# called with two arguments: 'prefix' contains the entire line (in string +# form) before the current word being completed, and 'word' is the current +# word. +# +# It uses some fairly simple heuristics to try to decide what kind of +# completion it is performing, and then dispatches to other completion +# functions. While the heuristics leave something to be desired, calling +# out to other functions (and allowing those other functions to recursively +# call %complete again) enables quite a bit of power, especially given +# how much of es' "internal" behavior is based on hook functions. + +fn %complete prefix word { + if {~ $word '$&'*} { + # Primitive completion. So far, no need to make a function of this. + result $word^<={~~ '$&'^<=$&primitives $word^*} + } {~ $word '$#'*} { + result '$#'^<={%var-complete <={~~ $word '$#'*}} + } {~ $word '$^'*} { + result '$^'^<={%var-complete <={~~ $word '$^'*}} + } {~ $word '$'*} { + result '$'^<={%var-complete <={~~ $word '$'*}} + } { + let (line = <={%split ' '\t $prefix}) { + # Basic "start-of-command" detection. + if {~ $line () || + ~ $^line *'<=' || + ~ $^line *'{' || + ~ $^line *'|' || + ~ $^line *'`' || + ~ $^line *'|['*']' || + ~ $^line *'&' + } { + # Command-position completion. + %whatis-complete $word + } { + # Strip the first term from the line. + %complete-fn $line(1) <={%flatten ' ' $line(2 ...)} $word + } + } + } +} + + +# %complete-fn finds if necessary, and evaluates if possible, a particular +# completion function. Calling this is strongly recommended instead of +# directly calling `%complete-$fnname` for any completion function other +# than those found in this file. + +completion-path = /home/jpco/git/es-fork/completions + +fn %complete-fn func prefix word { + if {~ $#(fn-%complete-^$func) 0} { + let (f = ()) { + f = <={access -n complete-$func^.es -1 -f $completion-path} + if {!~ $f ()} { + . $f + } + } + } + if {!~ $#(fn-%complete-^$func) 0} { + %complete-^$func $prefix $word + } { + %file-complete {} $word + } +} + + +# +# Completion logic for built-in concepts. +# + +# These functions (named according to the pattern %foo-complete) provide +# completion for specific internal behaviors in the shell. They're pulled +# out of %complete largely so that they can be called by per-command +# completions. + +# Completion of variable names. + +fn %var-complete word { + result $word^<={~~ (<=$&vars <=$&internals) $word^*} +} + +# Generic command-position completion. +# This calls out to %complete-%pathsearch, which is what should be +# overridden when %pathsearch is overridden. + +fn %whatis-complete word { + if {~ $word (/* ./* ../* '~'*)} { + %file-complete @ {access -x -- $*} $word + } { + result $word^<={~~ ( + local let for fn %closure match + <={~~ (<=$&vars <=$&internals) 'fn-'^*} + ) $word^*} <={%complete-fn %pathsearch '' $word} + } +} + +# %file-complete calls out to %complete-%home to perform tilde completion, +# and to %home to perform tilde expansion for subdirectories. +# The `filter` argument allows callers to only get specific files, like +# directories or executable files. + +fn %file-complete filter word { + # Defining the %completion-to-file function during %complete signals to + # the line editing library that the results of this function are meant + # to be treated as files, and defines a function for the line editing + # library to use to map from each entry to a valid file. This enables + # nice behavior for things like path-searching commands; see + # completions/complete-%pathsearch.es for an example of this. + fn-%completion-to-file = result + + let (files = (); homepat = ()) { + if {!~ <={homepat = <={~~ $word '~'*'/'*}} ()} { + let (homedir = (); path = $homepat(2)) { + if {~ $homepat(1) ''} { + homedir = <=%home + } { + homedir = <={%home $homepat(1)} + } + result '~'^$homepat(1)^'/'^<={~~ <={%file-complete $filter $homedir/$path} $homedir/*} + } + } {!~ <={homepat = <={~~ $word '~'*}} ()} { + result '~'^<={%complete-%home '' $homepat} + } { + for (f = $word^*) { + if {$filter $f} { + if {access -d -- $f} { + files = $files $f + } {access -- $f} { + files = $files $f + } + } + } + result $files + } + } +} + +fn %pid-complete word { + result $word^<={~~ <=%apids $word^*} +} + +# TODO: %fd-complete? %ifs-complete? diff --git a/completions/complete-%and.es b/completions/complete-%and.es new file mode 100644 index 00000000..6481862b --- /dev/null +++ b/completions/complete-%and.es @@ -0,0 +1 @@ +fn %complete-%and _ word {%whatis-complete $word} diff --git a/completions/complete-%append.es b/completions/complete-%append.es new file mode 100644 index 00000000..839b1104 --- /dev/null +++ b/completions/complete-%append.es @@ -0,0 +1 @@ +fn %complete-%append p w {%complete-fn %openfile 'a '^$p $w} diff --git a/completions/complete-%background.es b/completions/complete-%background.es new file mode 100644 index 00000000..3f905866 --- /dev/null +++ b/completions/complete-%background.es @@ -0,0 +1 @@ +fn-%complete-%background = %complete diff --git a/completions/complete-%create.es b/completions/complete-%create.es new file mode 100644 index 00000000..1508aff2 --- /dev/null +++ b/completions/complete-%create.es @@ -0,0 +1 @@ +fn %complete-%create p w {%complete-fn %openfile 'w '^$p $w} diff --git a/completions/complete-%home.es b/completions/complete-%home.es new file mode 100644 index 00000000..eda29f6a --- /dev/null +++ b/completions/complete-%home.es @@ -0,0 +1,4 @@ +fn %complete-%home _ word { + result $word^<={~~ `` \n {awk -F: '{print $1}' /etc/passwd} $word^*} +} + diff --git a/completions/complete-%is-interactive.es b/completions/complete-%is-interactive.es new file mode 100644 index 00000000..f96fa377 --- /dev/null +++ b/completions/complete-%is-interactive.es @@ -0,0 +1 @@ +fn-%complete-%is-interactive = {result} diff --git a/completions/complete-%not.es b/completions/complete-%not.es new file mode 100644 index 00000000..20e25ca6 --- /dev/null +++ b/completions/complete-%not.es @@ -0,0 +1 @@ +fn-%complete-%not = %complete diff --git a/completions/complete-%open-append.es b/completions/complete-%open-append.es new file mode 100644 index 00000000..a1c02573 --- /dev/null +++ b/completions/complete-%open-append.es @@ -0,0 +1 @@ +fn %complete-%open-append p w {%complete-fn %openfile 'a+ '^$p $w} diff --git a/completions/complete-%open-create.es b/completions/complete-%open-create.es new file mode 100644 index 00000000..15a6f617 --- /dev/null +++ b/completions/complete-%open-create.es @@ -0,0 +1 @@ +fn %complete-%open-create p w {%complete-fn %openfile 'w+ '^$p $w} diff --git a/completions/complete-%open-write.es b/completions/complete-%open-write.es new file mode 100644 index 00000000..ebbd228a --- /dev/null +++ b/completions/complete-%open-write.es @@ -0,0 +1 @@ +fn %complete-%open-write p w {%complete-fn %openfile 'r+ '^$p $w} diff --git a/completions/complete-%open.es b/completions/complete-%open.es new file mode 100644 index 00000000..a9c0c57d --- /dev/null +++ b/completions/complete-%open.es @@ -0,0 +1 @@ +fn %complete-%open p w {%complete-fn %openfile 'r '^$p $w} diff --git a/completions/complete-%openfile.es b/completions/complete-%openfile.es new file mode 100644 index 00000000..fd07e25e --- /dev/null +++ b/completions/complete-%openfile.es @@ -0,0 +1,21 @@ +fn %complete-%openfile prefix word { + let (cmd = <={%split ' '\t $prefix}) { + if {~ $#cmd 0} { + # mode + result $word^<={~~ (r w a r+ w+ a+) $word^*} + } {~ $#cmd 1} { + # fd + if {~ $cmd(1) r*} { + result 0 + } { + result 1 + } + } {~ $#cmd 2} { + # file + %file-complete {} $word + } { + # cmd: pass-through completion + %complete <={%flatten ' ' $cmd(4 ...)} $word + } + } +} diff --git a/completions/complete-%or.es b/completions/complete-%or.es new file mode 100644 index 00000000..61c6ee8a --- /dev/null +++ b/completions/complete-%or.es @@ -0,0 +1 @@ +fn %complete-%or _ word {%whatis-complete $word} diff --git a/completions/complete-%pathsearch.es b/completions/complete-%pathsearch.es new file mode 100644 index 00000000..66721408 --- /dev/null +++ b/completions/complete-%pathsearch.es @@ -0,0 +1,16 @@ +fn %complete-%pathsearch _ word { + fn %completion-to-file f { + catch @ e {result $f} { + # Like %pathsearch, but don't filter file types. + access -n $f -1e $path + } + } + let (files = ()) { + for (p = $path) + for (w = $p/$word^*) + if {access -x -- $w} { + files = $files <={~~ $w $p/*} + } + result $files + } +} diff --git a/completions/complete-%read.es b/completions/complete-%read.es new file mode 100644 index 00000000..4aee7903 --- /dev/null +++ b/completions/complete-%read.es @@ -0,0 +1 @@ +fn-%complete-%read = {result} diff --git a/completions/complete-%run.es b/completions/complete-%run.es new file mode 100644 index 00000000..5d05e7e1 --- /dev/null +++ b/completions/complete-%run.es @@ -0,0 +1,23 @@ +fn %complete-%run prefix word { + let (cmd = <={%split ' '\t $prefix}) + if {~ $#cmd 0} { + let (result = ()) { + # enforce an absolute path + for (r = <={%file-complete @ {access -x $*} $word}) { + if {~ $r /* ./* ../*} { + result = $result $r + } { + result = $result ./$r + } + } + result $result + } + } {~ $#cmd 1} { + # assume basename of the first term + let (ps = <={%split '/' $cmd(1)}) result $ps($#ps) + } { + # try to pass through to completion on second term + %complete <={%flatten ' ' $cmd(2 ...)} $word + } +} + diff --git a/completions/complete-%var.es b/completions/complete-%var.es new file mode 100644 index 00000000..1ccea71d --- /dev/null +++ b/completions/complete-%var.es @@ -0,0 +1,3 @@ +fn %complete-%var _ word { + %var-complete $word +} diff --git a/completions/complete-%whatis.es b/completions/complete-%whatis.es new file mode 100644 index 00000000..8a69dae9 --- /dev/null +++ b/completions/complete-%whatis.es @@ -0,0 +1,3 @@ +fn %complete-%whatis _ word { + %whatis-complete $word +} diff --git a/completions/complete-cd.es b/completions/complete-cd.es new file mode 100644 index 00000000..9fb7c0e2 --- /dev/null +++ b/completions/complete-cd.es @@ -0,0 +1,4 @@ +fn %complete-cd _ word { + %file-complete @ {access -d -- $*} $word +} + diff --git a/completions/complete-eval.es b/completions/complete-eval.es new file mode 100644 index 00000000..40ecf582 --- /dev/null +++ b/completions/complete-eval.es @@ -0,0 +1 @@ +fn-%complete-eval = %complete diff --git a/completions/complete-exec.es b/completions/complete-exec.es new file mode 100644 index 00000000..8c8dd289 --- /dev/null +++ b/completions/complete-exec.es @@ -0,0 +1 @@ +fn-%complete-exec = %complete diff --git a/completions/complete-false.es b/completions/complete-false.es new file mode 100644 index 00000000..1bc4a232 --- /dev/null +++ b/completions/complete-false.es @@ -0,0 +1 @@ +fn-%complete-false = {result} diff --git a/completions/complete-if.es b/completions/complete-if.es new file mode 100644 index 00000000..2f0c5ded --- /dev/null +++ b/completions/complete-if.es @@ -0,0 +1 @@ +fn %complete-if _ word {%whatis-complete $word} diff --git a/completions/complete-ls.es b/completions/complete-ls.es new file mode 100644 index 00000000..89df9412 --- /dev/null +++ b/completions/complete-ls.es @@ -0,0 +1,17 @@ +# Very incomplete ls completion to see how --option= completion works. +# Not great so far! +# TODO: enable --opt[TAB] to complete to '--option=', not '--option= '. +# TODO: some kind of fanciness to enable good short-option support? + +fn %complete-ls _ word { + if {~ $word -*} { + result $word^<={~~ ( + --all + --author + --block-size= + --color= + ) $word^*} + } { + %file-complete {} $word + } +} diff --git a/completions/complete-man.es b/completions/complete-man.es new file mode 100644 index 00000000..dbe55cce --- /dev/null +++ b/completions/complete-man.es @@ -0,0 +1,35 @@ +# Total support for `man` arguments is surprisingly complicated. +# This covers `man blah` and `man 1 blah` at least. + +fn %complete-man prefix word { + let (sections = 1 1p n l 8 3 3p 0 0p 2 3type 5 4 9 6 7) { + if {~ $#MANPATH 0} { + MANPATH = `manpath + } + for (a = <={%split ' '\t $prefix}) if {~ $a $sections} { + sections = $a + break + } + let (result = (); manpath = <={%fsplit : $MANPATH}) { + # This whole `for` kills performance on `man [TAB]` :/ + # Slightly buggy :/ + for (fi = $manpath/man$sections/$word^*) { + if {access $fi} { + let (sp = <={%fsplit . <={ + ~~ $fi $manpath/man$sections/* + }}) { + if {~ $sp($#sp) gz} { + let (nsp = 1 2 $sp) + sp = $nsp(3 ... $#sp) + } { + let (nsp = 1 $sp) + sp = $nsp(2 ... $#sp) + } + result = $result <={%flatten . $sp} + } + } + } + result $result + } + } +} diff --git a/completions/complete-newpgrp.es b/completions/complete-newpgrp.es new file mode 100644 index 00000000..55a52435 --- /dev/null +++ b/completions/complete-newpgrp.es @@ -0,0 +1 @@ +fn %complete-newpgrp = {result} diff --git a/completions/complete-throw.es b/completions/complete-throw.es new file mode 100644 index 00000000..758a1a29 --- /dev/null +++ b/completions/complete-throw.es @@ -0,0 +1,52 @@ +fn %complete-throw prefix word { + let (cmd = <={%split ' '\t $prefix}) { + if {~ $#cmd 0} { + return $word^<={~~ (break eof error retry return signal) $word^*} + } + match $cmd(1) ( + (break eof retry return) {result ()} # no good guesses :/ + error { + if {~ $#cmd 1} { + %whatis-complete $word + } { + result () + } + } + signal { + # The shell should be able to give us this list... + result $word^<={~~ ( + sigabrt + sigalrm + sigbus + sigchld + sigcont + sigfpe + sighup + sigill + sigint + sigkill + sigpipe + sigpoll + sigprof + sigquit + sigsegv + sigstop + sigtstp + sigsys + sigterm + sigtrap + sigttin + sigttou + sigurg + sigusr1 + sigusr2 + sigvtalrm + sigxcpu + sigxfsz + sigwinch + ) $word^*} + } + * {result ()} # Not sure :/ + ) + } +} diff --git a/completions/complete-time.es b/completions/complete-time.es new file mode 100644 index 00000000..6ebd22fd --- /dev/null +++ b/completions/complete-time.es @@ -0,0 +1 @@ +fn-%complete-time = %complete diff --git a/completions/complete-true.es b/completions/complete-true.es new file mode 100644 index 00000000..5b99323d --- /dev/null +++ b/completions/complete-true.es @@ -0,0 +1 @@ +fn-%complete-true = {result} diff --git a/completions/complete-unwind-protect.es b/completions/complete-unwind-protect.es new file mode 100644 index 00000000..16f742df --- /dev/null +++ b/completions/complete-unwind-protect.es @@ -0,0 +1 @@ +fn %complete-unwind-protect _ word {%whatis-complete $word} diff --git a/completions/complete-var.es b/completions/complete-var.es new file mode 100644 index 00000000..5a0f70fd --- /dev/null +++ b/completions/complete-var.es @@ -0,0 +1 @@ +fn-%complete-var = %complete-fn %var diff --git a/completions/complete-wait.es b/completions/complete-wait.es new file mode 100644 index 00000000..8e584ff2 --- /dev/null +++ b/completions/complete-wait.es @@ -0,0 +1,4 @@ +fn %complete-wait _ word { + %pid-complete $word +} + diff --git a/completions/complete-whatis.es b/completions/complete-whatis.es new file mode 100644 index 00000000..53b07a36 --- /dev/null +++ b/completions/complete-whatis.es @@ -0,0 +1 @@ +fn-%complete-whatis = %complete-fn whatis diff --git a/completions/complete-while.es b/completions/complete-while.es new file mode 100644 index 00000000..63f530dd --- /dev/null +++ b/completions/complete-while.es @@ -0,0 +1 @@ +fn %complete-while _ word {%whatis-complete $word} diff --git a/configure.ac b/configure.ac index 2e98fcd8..69b7b9f2 100644 --- a/configure.ac +++ b/configure.ac @@ -77,8 +77,8 @@ dnl Checks for library functions. AC_TYPE_GETGROUPS AC_FUNC_MMAP -AC_CHECK_FUNCS(strerror strtol lstat setrlimit sigrelse sighold sigaction \ -sysconf sigsetjmp getrusage gettimeofday mmap mprotect) +AC_CHECK_FUNCS(strerror strtol lseek lstat setrlimit sigrelse sighold \ +sigaction sysconf sigsetjmp getrusage gettimeofday mmap mprotect) AC_CACHE_CHECK(whether getenv can be redefined, es_cv_local_getenv, [if test "$ac_cv_header_stdlib_h" = no || test "$ac_cv_header_stdc" = no; then diff --git a/es.h b/es.h index cf298c09..5cf81667 100644 --- a/es.h +++ b/es.h @@ -188,6 +188,7 @@ extern List *extractmatches(List *subjects, List *patterns, StrList *quotes); extern void initvars(void); extern void initenv(char **envp, Boolean protected); +extern void initgetenv(void); extern void hidevariables(void); extern void validatevar(const char *var); extern List *varlookup(const char *name, Binding *binding); @@ -197,7 +198,6 @@ extern Vector *mkenv(void); extern void setnoexport(List *list); extern void addtolist(void *arg, char *key, void *value); extern List *listvars(Boolean internal); -extern List *varswithprefix(char *prefix); typedef struct Push Push; extern Push *pushlist; @@ -288,7 +288,7 @@ extern Boolean streq2(const char *s, const char *t1, const char *t2); /* input.c */ extern char *prompt, *prompt2; -extern Tree *parse(char *esprompt1, char *esprompt2); +extern Tree *parse(List *); extern Tree *parsestring(const char *str); extern Boolean isinteractive(void); extern Boolean isfromfd(void); @@ -305,8 +305,16 @@ extern List *runstring(const char *str, const char *name, int flags); #define run_printcmds 32 /* -x */ #define run_lisptrees 64 /* -L and defined(LISPTREES) */ + +/* readline.c */ + #if HAVE_READLINE -extern Boolean resetterminal; +extern void inithistory(void); + +extern void sethistory(char *file); +extern void loghistory(char *cmd); +extern void setmaxhistorylength(int length); +extern void rlsetup(void); #endif @@ -337,7 +345,6 @@ extern List *esoptend(void); extern List *prim(char *s, List *list, Binding *binding, int evalflags); extern void initprims(void); -extern List *primswithprefix(char *prefix); /* split.c */ @@ -403,6 +410,9 @@ extern void gcdisable(void); /* disable collections */ extern Boolean gcisblocked(void); /* is collection disabled? */ /* operations with pspace, the explicitly-collected gc space for parse tree building */ +extern void *createpspace(void); +extern void *setpspace(void *); + extern void *palloc(size_t n, Tag *t); /* allocate n with collection tag t, but in pspace */ extern void *pseal(void *p); /* collect pspace into gcspace with root p */ extern char *pdup(const char *s); /* copy a 0-terminated string into pspace */ diff --git a/gc.c b/gc.c index 3a87db72..a586076a 100644 --- a/gc.c +++ b/gc.c @@ -22,8 +22,7 @@ struct Space { #define MIN_minpspace 1000 #if GCPROTECT -#define NSPACES 12 -#define FIRSTSPACE 1 +#define NSPACES 10 #endif #if HAVE_SYSCONF @@ -46,7 +45,7 @@ static Space *spaces; #endif static Root *globalrootlist, *exceptionrootlist; static size_t minspace = MIN_minspace; /* minimum number of bytes in a new space */ -static size_t minpspace = MIN_minpspace; +static size_t minpspace = MIN_minpspace; /* minimum number of bytes in a new pspace */ /* @@ -207,7 +206,7 @@ static void deprecate(Space *space) { assert(space != NULL); for (base = space; base->next != NULL; base = base->next) ; - assert(&spaces[0] <= base && base < &spaces[NSPACES]); + assert(space == pspace || (&spaces[0] <= base && base < &spaces[NSPACES])); for (;;) { invalidate(space->bot, SPACESIZE(space)); if (space == base) @@ -303,6 +302,7 @@ extern void *forward(void *p) { tag = TAG(p); assert(tag != NULL); + if (FORWARDED(tag)) { np = FOLLOW(tag); assert(TAG(np)->magic == TAGMAGIC); @@ -421,7 +421,7 @@ extern void gc(void) { for (; new->next != NULL; new = new->next) ; if (++new >= &spaces[NSPACES]) - new = &spaces[FIRSTSPACE]; + new = &spaces[0]; new = mkspace(new, NULL, minspace); #else new = newspace(NULL); @@ -530,7 +530,9 @@ extern void *pseal(void *p) { #endif deprecate(pspace); #if GCPROTECT - pspace = mkspace(base, NULL, minpspace); + /* TODO: integrate pspace with GCPROTECT better? */ + /* pspace = mkspace(base, NULL, minpspace); */ + pspace = newpspace(NULL); #else pspace = newpspace(NULL); #endif @@ -545,15 +547,23 @@ extern void initgc(void) { initmmu(); spaces = ealloc(NSPACES * sizeof (Space)); memzero(spaces, NSPACES * sizeof (Space)); - new = mkspace(&spaces[FIRSTSPACE], NULL, minspace); - pspace = mkspace(&spaces[0], NULL, minpspace); + new = mkspace(&spaces[0], NULL, minspace); #else new = newspace(NULL); - pspace = newpspace(NULL); #endif old = NULL; } +extern void *createpspace(void) { + return (void *)newpspace(NULL); +} + +extern void *setpspace(void *new) { + void *old = (void *)pspace; + pspace = (Space *)new; + return old; +} + /* * allocation diff --git a/heredoc.c b/heredoc.c index d9de728c..da2bca38 100644 --- a/heredoc.c +++ b/heredoc.c @@ -43,11 +43,11 @@ extern Tree *snarfheredoc(const char *eof, Boolean quoted) { yyerror("here document eof-marker contains a newline"); return NULL; } - ignoreeof = TRUE; + input->ignoreeof = TRUE; for (tree = NULL, tailp = &tree, buf = openbuffer(0);;) { int c; - print_prompt2(); + increment_line(); for (s = (unsigned char *) eof; (c = GETC()) == *s; s++) ; if (*s == '\0' && (c == '\n' || c == EOF)) { @@ -63,7 +63,7 @@ extern Tree *snarfheredoc(const char *eof, Boolean quoted) { if (c == EOF) { yyerror("incomplete here document"); freebuffer(buf); - ignoreeof = FALSE; + input->ignoreeof = FALSE; return NULL; } if (c == '$' && !quoted && (c = GETC()) != '$') { @@ -78,7 +78,7 @@ extern Tree *snarfheredoc(const char *eof, Boolean quoted) { var = getherevar(); if (var == NULL) { freebuffer(buf); - ignoreeof = FALSE; + input->ignoreeof = FALSE; return NULL; } *tailp = treecons(var, NULL); @@ -92,7 +92,7 @@ extern Tree *snarfheredoc(const char *eof, Boolean quoted) { } } - ignoreeof = FALSE; + input->ignoreeof = FALSE; return tree->CDR == NULL ? tree->CAR : tree; } diff --git a/history.c b/history.c deleted file mode 100644 index 7fb1b101..00000000 --- a/history.c +++ /dev/null @@ -1,134 +0,0 @@ -/* history.c -- control the history file ($Revision: 1.1.1.1 $) */ - -#include "es.h" -#include "gc.h" -#include "input.h" - - -/* - * constants - */ - -#define BUFSIZE ((size_t) 4096) /* buffer size to fill reads into */ - - -/* - * globals - */ - -static Buffer *histbuffer = NULL; - -#if HAVE_READLINE -#include - -Boolean reloadhistory = FALSE; -static char *history; - -#if 0 -/* These split history file entries by timestamp, which allows readline to pick up - * multi-line commands correctly across process boundaries. Disabled by default, - * because it leaves the history file itself kind of ugly. */ -static int history_write_timestamps = 1; -static char history_comment_char = '#'; -#endif -#endif - - -/* - * histbuffer -- build the history line during input and dump it as a gc-string - */ - - -extern void newhistbuffer(void) { - assert(histbuffer == NULL); - histbuffer = openbuffer(BUFSIZE); -} - -extern void addhistbuffer(char c) { - if (histbuffer == NULL) - return; - histbuffer = bufputc(histbuffer, c); -} - -extern char *dumphistbuffer(void) { - char *s; - size_t len; - assert(histbuffer != NULL); - - s = sealcountedbuffer(histbuffer); - histbuffer = NULL; - - len = strlen(s); - if (len > 0 && s[len - 1] == '\n') - s[len - 1] = '\0'; - return s; -} - - -/* - * history file - */ - -#if HAVE_READLINE -extern void setmaxhistorylength(int len) { - static int currenthistlen = -1; /* unlimited */ - if (len != currenthistlen) { - switch (len) { - case -1: - unstifle_history(); - break; - case 0: - clear_history(); - FALLTHROUGH; - default: - stifle_history(len); - } - currenthistlen = len; - } -} - -extern void loghistory(char *cmd) { - int err; - if (cmd == NULL) - return; - add_history(cmd); - if (history == NULL) - return; - - if ((err = append_history(1, history))) { - eprint("history(%s): %s\n", history, esstrerror(err)); - vardef("history", NULL, NULL); - } -} - -static void reload_history(void) { - /* Attempt to populate readline history with new history file. */ - if (history != NULL) - read_history(history); - using_history(); - - reloadhistory = FALSE; -} - -extern void sethistory(char *file) { - if (reloadhistory) - reload_history(); - reloadhistory = TRUE; - history = file; -} - -extern void checkreloadhistory(void) { - if (reloadhistory) - reload_history(); -} - -/* - * initialization - */ - -/* inithistory -- called at dawn of time from main() */ -extern void inithistory(void) { - /* declare the global roots */ - globalroot(&history); /* history file */ -} -#endif diff --git a/initial.es b/initial.es index 987919a0..85a2d14a 100644 --- a/initial.es +++ b/initial.es @@ -648,10 +648,44 @@ if {~ <=$&primitives writehistory} { # The parsed code is executed only if it is non-empty, because otherwise # result gets set to zero when it should not be. -fn-%parse = $&parse fn-%batch-loop = $&batchloop fn-%is-interactive = $&isinteractive +if {~ <=$&primitives readline} { + fn-%read-line = $&readline + # add completion logic + . completion.es +} { + fn %read-line prompt { + echo -n $prompt + %read + } +} + +fn %parse prompt { + if %is-interactive { + let (in = (); p = $prompt(1)) + unwind-protect { + $&parse @ mode { + if {~ $mode heredoc} { + p = 'heredoc> ' + } + let (r = <={%read-line $p}) { + in = $in $r + p = $prompt(2) + result $r + } + } + } { + if {!~ $#fn-%write-history 0} { + %write-history <={%flatten \n $in} + } + } + } { + $&parse $&read + } +} + fn %interactive-loop { let (result = <=true) { catch @ e type msg { diff --git a/input.c b/input.c index 4a878dfb..3e4eb730 100644 --- a/input.c +++ b/input.c @@ -22,14 +22,6 @@ */ Input *input; -char *prompt, *prompt2; - -Boolean ignoreeof = FALSE; -Boolean resetterminal = FALSE; - -#if HAVE_READLINE -#include -#endif /* @@ -40,20 +32,22 @@ Boolean resetterminal = FALSE; static const char *locate(Input *in, const char *s) { return (in->runflags & run_interactive) ? s - : str("%s:%d: %s", in->name, in->lineno, s); + : mprint("%s:%d: %s", in->name, in->lineno, s); } -static const char *error = NULL; +static int eoffill(Input UNUSED *in); /* yyerror -- yacc error entry point */ extern void yyerror(const char *s) { -#if sgi - /* this is so that trip.es works */ - if (streq(s, "Syntax error")) - s = "syntax error"; -#endif - if (error == NULL) /* first error is generally the most informative */ - error = locate(input, s); + /* TODO: more graceful handling for memory exhaustion? + * if we're here, then we're probably hopelessly lost */ + if (streq(s, "memory exhausted")) { + input->fd = -1; + input->fill = eoffill; + input->runflags &= ~run_interactive; + input->error = s; + } else if (input->error == NULL) /* first error is generally the most informative */ + input->error = locate(input, s); } /* warn -- print a warning */ @@ -108,11 +102,8 @@ extern void unget(Input *in, int c) { /* get -- get a character, filter out nulls */ static int get(Input *in) { int c; - Boolean uf = (in->fill == ungetfill); while ((c = (in->buf < in->bufend ? *in->buf++ : (*in->fill)(in))) == '\0') warn("null character ignored"); - if (!uf && c != EOF) - addhistbuffer((char)c); return c; } @@ -136,69 +127,19 @@ static int eoffill(Input UNUSED *in) { return EOF; } -#if HAVE_READLINE -/* callreadline -- readline wrapper */ -static char *callreadline(char *prompt0) { - char *r; - Ref(char *volatile, prompt, prompt0); - if (prompt == NULL) - prompt = ""; /* bug fix for readline 2.0 */ - checkreloadhistory(); - if (resetterminal) { - rl_reset_terminal(NULL); - resetterminal = FALSE; - } - if (RL_ISSTATE(RL_STATE_INITIALIZED)) - rl_reset_screen_size(); - if (!setjmp(slowlabel)) { - slow = TRUE; - r = readline(prompt); - } else { - r = NULL; - errno = EINTR; - } - slow = FALSE; - SIGCHK(); - RefEnd(prompt); - return r; -} -#endif - - /* fdfill -- fill input buffer by reading from a file descriptor */ static int fdfill(Input *in) { long nread; - assert(in->buf == in->bufend); - assert(in->fd >= 0); - -#if HAVE_READLINE - if (in->runflags & run_interactive && in->fd == 0) { - char *rlinebuf = NULL; - do { - rlinebuf = callreadline(prompt); - } while (rlinebuf == NULL && errno == EINTR); - if (rlinebuf == NULL) - nread = 0; - else { - nread = strlen(rlinebuf) + 1; - if (in->buflen < (unsigned int)nread) { - while (in->buflen < (unsigned int)nread) - in->buflen *= 2; - in->bufbegin = erealloc(in->bufbegin, in->buflen); - } - memcpy(in->bufbegin, rlinebuf, nread - 1); - in->bufbegin[nread - 1] = '\n'; - efree(rlinebuf); - } - } else -#endif + if (in->fd < 0) + fail("$&parse", "cannot read from closed file descriptor"); + do { nread = read(in->fd, (char *) in->bufbegin, in->buflen); SIGCHK(); } while (nread == -1 && errno == EINTR); if (nread <= 0) { - if (!ignoreeof) { + if (!in->ignoreeof) { close(in->fd); in->fd = -1; in->fill = eoffill; @@ -214,55 +155,135 @@ static int fdfill(Input *in) { return *in->buf++; } +static List *fillcmd = NULL; + +static int cmdfill(Input *in) { + char *read; + List *cmd, *result; + size_t nread; + int oldf; + + assert(in->buf == in->bufend); + + if (fillcmd == NULL) + return fdfill(in); + + oldf = dup(0); + if (in->fd >= 0) { + if (dup2(in->fd, 0) == -1) + fail("$&parse", "dup2: %s", esstrerror(errno)); + } else + close(0); + + cmd = append(fillcmd, mklist(mkstr(in->ignoreeof ? "heredoc" : "normal"), NULL)); + + ExceptionHandler + + result = eval(cmd, NULL, 0); + + CatchException (e) + + mvfd(oldf, 0); + throw(e); + + EndExceptionHandler + + mvfd(oldf, 0); + + if (result == NULL) { /* eof */ + if (!in->ignoreeof) { + close(in->fd); + in->fd = -1; + in->fill = eoffill; + in->runflags &= ~run_interactive; + } + return EOF; + } + read = str("%L\n", result, " "); + + if ((nread = strlen(read)) > in->buflen) { + in->bufbegin = erealloc(in->bufbegin, nread); + in->buflen = nread; + } + memcpy(in->bufbegin, read, nread); + + in->buf = in->bufbegin; + in->bufend = &in->buf[nread]; + + return *in->buf++; +} /* * the input loop */ -/* parse -- call yyparse(), but disable garbage collection and catch errors */ -extern Tree *parse(char *pr1, char *pr2) { +/* parse -- call yyparse() and catch errors */ +extern Tree *parse(List *fc) { int result; - assert(error == NULL); + void *oldpspace; + Ref(List *, oldfillcmd, fillcmd); - inityy(); - emptyherequeue(); + /* TODO: change this error message */ + if (input->parsing) + fail("$&parse", "cannot perform nested parsing"); + assert(input->error == NULL); + + /* TODO: update this check -- + * + * $ es -c '<={$&parse {result echo hello world}}' + * + * should work. also, ignoreeof + */ if (ISEOF(input)) throw(mklist(mkstr("eof"), NULL)); -#if HAVE_READLINE - prompt = (pr1 == NULL) ? "" : pr1; -#else - if (pr1 != NULL) - eprint("%s", pr1); -#endif - prompt2 = pr2; + fillcmd = fc; + input->parsing = TRUE; + oldpspace = setpspace(input->pspace); - result = yyparse(); + inityy(input); + emptyherequeue(); - if (result || error != NULL) { - assert(error != NULL); - Ref(const char *, e, error); - error = NULL; + ExceptionHandler + + result = yyparse(); + + CatchException (e) + + input->parsing = FALSE; + fillcmd = NULL; pseal(NULL); + input->pspace = setpspace(oldpspace); + throw(e); + + EndExceptionHandler + + input->parsing = FALSE; + fillcmd = oldfillcmd; + RefEnd(oldfillcmd); + + if (result || input->error != NULL) { + const char *e = input->error; + assert(e != NULL); + input->error = NULL; + pseal(NULL); + input->pspace = setpspace(oldpspace); fail("$&parse", "%s", e); - RefEnd(e); } + Ref(Tree *, pt, pseal(input->parsetree)); + input->pspace = setpspace(oldpspace); #if LISPTREES - Ref(Tree *, pt, pseal(parsetree)); if (input->runflags & run_lisptrees) eprint("%B\n", pt); - RefReturn(pt); -#else - return pseal(parsetree); #endif - + RefReturn(pt); } /* resetparser -- clear parser errors in the signal handler */ extern void resetparser(void) { - error = NULL; + input->error = NULL; } /* runinput -- run from an input source */ @@ -339,7 +360,7 @@ extern List *runfd(int fd, const char *name, int flags) { memzero(&in, sizeof (Input)); in.lineno = 1; - in.fill = fdfill; + in.fill = cmdfill; in.cleanup = fdcleanup; in.fd = fd; registerfd(&in.fd, TRUE); @@ -347,6 +368,7 @@ extern List *runfd(int fd, const char *name, int flags) { in.bufbegin = in.buf = ealloc(in.buflen); in.bufend = in.bufbegin; in.name = (name == NULL) ? str("fd %d", fd) : name; + in.pspace = createpspace(); RefAdd(in.name); result = runinput(&in, flags); @@ -385,6 +407,7 @@ extern List *runstring(const char *str, const char *name, int flags) { in.bufbegin = in.buf = buf; in.bufend = in.buf + in.buflen; in.cleanup = stringcleanup; + in.pspace = createpspace(); /* TODO: use special string-input pspace? */ RefAdd(in.name); result = runinput(&in, flags); @@ -402,7 +425,7 @@ extern Tree *parseinput(Input *in) { input = in; ExceptionHandler - result = parse(NULL, NULL); + result = parse(NULL); if (get(in) != EOF) fail("$&parse", "more than one value in term"); CatchException (e) @@ -437,6 +460,7 @@ extern Tree *parsestring(const char *str) { in.bufbegin = in.buf = buf; in.bufend = in.buf + in.buflen; in.cleanup = stringcleanup; + in.pspace = createpspace(); /* TODO: use special string-input pspace? */ RefAdd(in.name); result = parseinput(&in); @@ -449,112 +473,11 @@ extern Boolean isinteractive(void) { return input == NULL ? FALSE : ((input->runflags & run_interactive) != 0); } -/* isfromfd -- is the innermost input source reading from a file descriptor? */ extern Boolean isfromfd(void) { - return input == NULL ? FALSE : (input->fill == fdfill); + return input == NULL ? FALSE : (input->fill == fdfill || input->fill == cmdfill); } -/* - * readline integration. - */ -#if HAVE_READLINE -/* quote -- teach readline how to quote a word in es during completion */ -static char *quote(char *text, int type, char *qp) { - char *p, *r; - - /* worst-case size: string is 100% quote characters which will all be - * doubled, plus initial and final quotes and \0 */ - p = r = ealloc(strlen(text) * 2 + 3); - /* supply opening quote if not already present */ - if (*qp != '\'') - *p++ = '\''; - while (*text) { - /* double any quotes for es quote-escaping rules */ - if (*text == '\'') - *p++ = '\''; - *p++ = *text++; - } - if (type == SINGLE_MATCH) - *p++ = '\''; - *p = '\0'; - return r; -} - -/* unquote -- teach es how to unquote a word */ -static char *unquote(char *text, int quote_char) { - char *p, *r; - - p = r = ealloc(strlen(text) + 1); - while (*text) { - *p++ = *text++; - if (quote_char && *(text - 1) == '\'' && *text == '\'') - ++text; - } - *p = '\0'; - return r; -} - -static char *complprefix; -static List *(*wordslistgen)(char *); - -static char *list_completion_function(const char *text, int state) { - static char **matches = NULL; - static int matches_idx, matches_len; - int i, rlen; - char *result; - - const int pfx_len = strlen(complprefix); - - if (!state) { - const char *name = &text[pfx_len]; - - Vector *vm = vectorize(wordslistgen((char *)name)); - matches = vm->vector; - matches_len = vm->count; - matches_idx = 0; - } - - if (!matches || matches_idx >= matches_len) - return NULL; - - rlen = strlen(matches[matches_idx]); - result = ealloc(rlen + pfx_len + 1); - for (i = 0; i < pfx_len; i++) - result[i] = complprefix[i]; - strcpy(&result[pfx_len], matches[matches_idx]); - result[rlen + pfx_len] = '\0'; - - matches_idx++; - return result; -} - -char **builtin_completion(const char *text, int UNUSED start, int UNUSED end) { - char **matches = NULL; - - if (*text == '$') { - wordslistgen = varswithprefix; - complprefix = "$"; - switch (text[1]) { - case '&': - wordslistgen = primswithprefix; - complprefix = "$&"; - break; - case '^': complprefix = "$^"; break; - case '#': complprefix = "$#"; break; - } - matches = rl_completion_matches(text, list_completion_function); - } - - /* ~foo => username. ~foo/bar already gets completed as filename. */ - if (!matches && *text == '~' && !strchr(text, '/')) - matches = rl_completion_matches(text, rl_username_completion_function); - - return matches; -} -#endif /* HAVE_READLINE */ - - /* * initialization */ @@ -564,23 +487,5 @@ extern void initinput(void) { input = NULL; /* declare the global roots */ - globalroot(&error); /* parse errors */ - globalroot(&prompt); /* main prompt */ - globalroot(&prompt2); /* secondary prompt */ - -#if HAVE_READLINE - rl_readline_name = "es"; - - /* these two word_break_characters exclude '&' due to primitive completion */ - rl_completer_word_break_characters = " \t\n\\'`$><=;|{()}"; - rl_basic_word_break_characters = " \t\n\\'`$><=;|{()}"; - rl_completer_quote_characters = "'"; - rl_special_prefixes = "$"; - - rl_attempted_completion_function = builtin_completion; - - rl_filename_quote_characters = " \t\n\\`'$><=;|&{()}"; - rl_filename_quoting_function = quote; - rl_filename_dequoting_function = unquote; -#endif + globalroot(&fillcmd); } diff --git a/input.h b/input.h index 698b763c..095f8d3e 100644 --- a/input.h +++ b/input.h @@ -2,10 +2,12 @@ #define MAXUNGET 2 /* maximum 2 character pushback */ +typedef enum { NW, RW, KW } WordState; #include "token.h" /* for YYSTYPE */ typedef struct Input Input; struct Input { + /* reading state */ int (*get)(Input *self); int (*fill)(Input *self), (*rfill)(Input *self); void (*cleanup)(Input *self); @@ -18,6 +20,19 @@ struct Input { int lineno; int fd; int runflags; + Boolean ignoreeof; + + /* parsing state */ + void *pspace; + Boolean parsing; + Tree *parsetree; + const char *error; + + /* lexing state */ + WordState ws; + Boolean goterror, dollar; + size_t bufsize; + char *tokenbuf; }; @@ -35,15 +50,17 @@ extern void yyerror(const char *s); /* token.c */ +/* this is very awkward. how to otherwise get YYSTYPE? */ +#include "token.h" + extern const char dnw[]; extern int yylex(YYSTYPE *y); -extern void inityy(void); -extern void print_prompt2(void); +extern void inityy(Input *in); +extern void increment_line(void); /* parse.y */ -extern Tree *parsetree; extern int yyparse(void); diff --git a/parse.y b/parse.y index 4fd5a844..16253c77 100644 --- a/parse.y +++ b/parse.y @@ -40,8 +40,8 @@ %% -es : line end { parsetree = $1; YYACCEPT; } - | error end { yyerrok; parsetree = NULL; YYABORT; } +es : line end { input->parsetree = $1; YYACCEPT; } + | error end { yyerrok; input->parsetree = NULL; YYABORT; } end : NL { if (!readheredocs(FALSE)) YYABORT; } | ENDFILE { if (!readheredocs(TRUE)) YYABORT; } diff --git a/prim-etc.c b/prim-etc.c index c4152fe3..26fe2c46 100644 --- a/prim-etc.c +++ b/prim-etc.c @@ -148,55 +148,12 @@ PRIM(var) { return list; } -static void loginput(char *input) { - char *c; - List *fn = varlookup("fn-%write-history", NULL); - if (!isinteractive() || !isfromfd() || fn == NULL) - return; - for (c = input;; c++) - switch (*c) { - case '#': case '\n': return; - case ' ': case '\t': break; - default: goto writeit; - } -writeit: - gcdisable(); - Ref(List *, list, append(fn, mklist(mkstr(input), NULL))); - gcenable(); - eval(list, NULL, 0); - RefEnd(list); -} - PRIM(parse) { - List *result; - Ref(char *, prompt1, NULL); - Ref(char *, prompt2, NULL); - Ref(List *, lp, list); - if (lp != NULL) { - prompt1 = getstr(lp->term); - if ((lp = lp->next) != NULL) - prompt2 = getstr(lp->term); - } - RefEnd(lp); - newhistbuffer(); - - Ref(Tree *, tree, NULL); - ExceptionHandler - tree = parse(prompt1, prompt2); - CatchException (ex) - Ref(List *, e, ex); - loginput(dumphistbuffer()); - throw(e); - RefEnd(e); - EndExceptionHandler - - loginput(dumphistbuffer()); - result = (tree == NULL) + Tree *tree = parse(list); + return (tree == NULL) ? NULL : mklist(mkterm(NULL, mkclosure(gcmk(nThunk, tree), NULL)), NULL); - RefEnd3(tree, prompt2, prompt1); - return result; } PRIM(exitonfalse) { @@ -302,47 +259,6 @@ PRIM(setmaxevaldepth) { RefReturn(lp); } -#if HAVE_READLINE -PRIM(sethistory) { - if (list == NULL) { - sethistory(NULL); - return NULL; - } - Ref(List *, lp, list); - sethistory(getstr(lp->term)); - RefReturn(lp); -} - -PRIM(writehistory) { - if (list == NULL || list->next != NULL) - fail("$&writehistory", "usage: $&writehistory command"); - loghistory(getstr(list->term)); - return NULL; -} - -PRIM(setmaxhistorylength) { - char *s; - int n; - if (list == NULL) { - setmaxhistorylength(-1); /* unlimited */ - return NULL; - } - if (list->next != NULL) - fail("$&setmaxhistorylength", "usage: $&setmaxhistorylength [limit]"); - Ref(List *, lp, list); - n = (int)strtol(getstr(lp->term), &s, 0); - if (n < 0 || (s != NULL && *s != '\0')) - fail("$&setmaxhistorylength", "max-history-length must be set to a positive integer"); - setmaxhistorylength(n); - RefReturn(lp); -} - -PRIM(resetterminal) { - resetterminal = TRUE; - return ltrue; -} -#endif - /* * initialization @@ -371,11 +287,5 @@ extern Dict *initprims_etc(Dict *primdict) { X(exitonfalse); X(noreturn); X(setmaxevaldepth); -#if HAVE_READLINE - X(sethistory); - X(writehistory); - X(resetterminal); - X(setmaxhistorylength); -#endif return primdict; } diff --git a/prim-io.c b/prim-io.c index cd68cffe..8bc64772 100644 --- a/prim-io.c +++ b/prim-io.c @@ -435,11 +435,32 @@ PRIM(read) { freebuffer(buffer); buffer = openbuffer(0); - while ((c = read1(fd)) != EOF && c != '\n') - if (c == '\0') - fail("$&read", "%%read: null character encountered"); - else - buffer = bufputc(buffer, c); +#if HAVE_LSEEK + if (lseek(fd, 0, SEEK_CUR) < 0) { +#endif + while ((c = read1(fd)) != EOF && c != '\n') + if (c == '\0') + fail("$&read", "%%read: null character encountered"); + else + buffer = bufputc(buffer, c); +#if HAVE_LSEEK + } else { + int n; + char *p; + char s[BUFSIZE]; + c = EOF; + while ((n = read(fd, s, BUFSIZE)) > 0) { + c = 0; + if ((p = strchr(s, '\n')) == NULL) + buffer = bufncat(buffer, s, n); + else { + buffer = bufncat(buffer, s, (p - s)); + lseek(fd, 1 + ((p - s) - n), SEEK_CUR); + break; + } + } + } +#endif if (c == EOF && buffer->current == 0) { freebuffer(buffer); diff --git a/prim.c b/prim.c index 74d7866f..6062a45c 100644 --- a/prim.c +++ b/prim.c @@ -13,20 +13,6 @@ extern List *prim(char *s, List *list, Binding *binding, int evalflags) { return (p->prim)(list, binding, evalflags); } -static char *list_prefix; - -static void listwithprefix(void *arg, char *key, void *value) { - if (strneq(key, list_prefix, strlen(list_prefix))) - addtolist(arg, key, value); -} - -extern List *primswithprefix(char *prefix) { - Ref(List *, primlist, NULL); - list_prefix = prefix; - dictforall(prims, listwithprefix, &primlist); - RefReturn(primlist); -} - PRIM(primitives) { static List *primlist = NULL; if (primlist == NULL) { @@ -48,6 +34,10 @@ extern void initprims(void) { prims = initprims_proc(prims); prims = initprims_access(prims); +#if HAVE_READLINE + prims = initprims_readline(prims); +#endif + #define primdict prims X(primitives); } diff --git a/prim.h b/prim.h index a6a0db05..8fca613a 100644 --- a/prim.h +++ b/prim.h @@ -19,3 +19,7 @@ extern Dict *initprims_etc(Dict *primdict); /* prim-etc.c */ extern Dict *initprims_sys(Dict *primdict); /* prim-sys.c */ extern Dict *initprims_proc(Dict *primdict); /* proc.c */ extern Dict *initprims_access(Dict *primdict); /* access.c */ + +#if HAVE_READLINE +extern Dict *initprims_readline(Dict *primdict); /* readline.c */ +#endif diff --git a/readline.c b/readline.c new file mode 100644 index 00000000..7d3f0cdb --- /dev/null +++ b/readline.c @@ -0,0 +1,526 @@ +/* prim-readline.c -- readline primitives */ + +#include "es.h" +#include "prim.h" + +#if HAVE_READLINE + +#include +#include +#include + +static Boolean reloadhistory = FALSE; +static Boolean resetterminal = FALSE; +static char *history; + +#if 0 +/* These split history file entries by timestamp, which allows readline to pick up + * multi-line commands correctly across process boundaries. Disabled by default, + * because it leaves the history file itself kind of ugly. */ +static int history_write_timestamps = 1; +static char history_comment_char = '#'; +#endif + + +/* + * history + */ + +extern void setmaxhistorylength(int len) { + static int currenthistlen = -1; /* unlimited */ + if (len != currenthistlen) { + switch (len) { + case -1: + unstifle_history(); + break; + case 0: + clear_history(); + FALLTHROUGH; + default: + stifle_history(len); + } + currenthistlen = len; + } +} + +extern void loghistory(char *cmd) { + int err; + if (cmd == NULL) + return; + add_history(cmd); + if (history == NULL) + return; + + if ((err = append_history(1, history))) { + eprint("history(%s): %s\n", history, esstrerror(err)); + vardef("history", NULL, NULL); + } +} + +static void reload_history(void) { + /* Attempt to populate readline history with new history file. */ + if (history != NULL) + read_history(history); + using_history(); + + reloadhistory = FALSE; +} + +extern void sethistory(char *file) { + if (reloadhistory) + reload_history(); + reloadhistory = TRUE; + history = file; +} + + +/* + * readey liney + */ + +/* quote -- teach readline how to quote a word in es during completion */ +static char *quote(char *text, int type, char *qp) { + char *p, *r; + + /* worst-case size: string is 100% quote characters which will all be + * doubled, plus initial and final quotes and \0 */ + p = r = ealloc(strlen(text) * 2 + 3); + /* supply opening quote if not already present */ + if (*qp != '\'') + *p++ = '\''; + while (*text) { + /* double any quotes for es quote-escaping rules */ + if (*text == '\'') + *p++ = '\''; + *p++ = *text++; + } + if (type == SINGLE_MATCH) + *p++ = '\''; + *p = '\0'; + return r; +} + +/* dequote -- teach es how to dequote a word */ +static char *dequote(char *text, int quote_char) { + char *p, *r; + + p = r = ealloc(strlen(text) + 1); + while (*text) { + *p++ = *text++; + if (quote_char && *(text - 1) == '\'' && *text == '\'') + ++text; + } + *p = '\0'; + return r; +} + +typedef enum { + NORMAL, + SYNTAX_ERROR, + FDBRACES +} CompletionType; + +/* hmm. */ +extern const char nw[]; + +/* Scan line back to its start. */ +/* This is a lot of code, and a poor reimplementation of the parser. :( */ +CompletionType boundcmd(char **start) { + char *line = rl_line_buffer; + char syntax[128] = { 0 }; + int lp, sp = 0; + Boolean quote = FALSE, first_word = TRUE; + + for (lp = rl_point; lp > 0; lp--) { + if (quote) + continue; + + switch (line[lp]) { + /* quotes. pretty easy */ + case '\'': + quote = !quote; + continue; + + /* "stackable" syntax. remember, we're moving backwards */ + case '}': + syntax[sp++] = '{'; + break; + case '{': + if (sp == 0) { + *start = rl_line_buffer + lp + 1; + return NORMAL; + } + if (syntax[--sp] != '{') { + *start = rl_line_buffer; + return SYNTAX_ERROR; + } + break; + case ')': + syntax[sp++] = '('; + break; + case '(': + if (sp > 0) { + if (syntax[--sp] != '(') { + *start = rl_line_buffer; + return SYNTAX_ERROR; + } + } else { + /* TODO: make `<=(a b` work */ + first_word = TRUE; + } + break; + + /* command separator chars */ + case ';': + if (sp == 0) { + *start = rl_line_buffer + lp + 1; + return NORMAL; + } + break; + case '&': + if (sp == 0) { + *start = rl_line_buffer + lp + 1; + return NORMAL; + } + break; + case '|': + if (sp == 0) { + int pp = lp+1; + Boolean inbraces = FALSE; + if (pp < rl_point && line[pp] == '[') { + inbraces = TRUE; + while (pp < rl_point) { + if (line[pp++] == ']') { + inbraces = FALSE; + break; + } + } + } + *start = rl_line_buffer + pp; + return inbraces ? FDBRACES : NORMAL; + } + break; + case '`': + if (first_word) { + *start = rl_line_buffer + lp + 1; + return NORMAL; + } + break; + case '<': + if (first_word && lp < rl_point - 1 && line[lp+1] == '=') { + *start = rl_line_buffer + lp + 2; + return NORMAL; + } + break; + } + if (nw[(unsigned char)line[lp]]) + first_word = FALSE; + } + /* TODO: fetch previous lines if sp > 0 */ + *start = rl_line_buffer; + return NORMAL; +} + + +/* calls `%complete prefix word` to get a list of candidates for how to complete + * `word`. + * + * TODO: improve argv for %complete + * - special dispatch for special syntax + * - split up args in a syntax-aware way + * - dequote args before and requote after (already done, just do it better) + * - skip/handle "command-irrelevant" syntax + * ! redirections binders + * - MAYBE: provide raw command/point? + * + * all the new behaviors above should ideally be done "manually", so that %complete + * can be used the same way without worrying about the line editing library. + * + * Handle the following properly, though maybe not in this function + * `let (a =` + * `let (a = b)` + * `a =` + * `a > ` + * `!` + * `$(f` + */ + +static List *callcomplete(char *word) { + int len; + char *start; + CompletionType type; + + Ref(List *, result, NULL); + Ref(List *, fn, NULL); + if ((fn = varlookup("fn-%complete", NULL)) == NULL) { + RefPop(fn); + return NULL; + } + type = boundcmd(&start); + + if (type == FDBRACES) { + /* TODO: fd completion */ + RefPop2(result, fn); + return NULL; + } + + len = rl_point - (start - rl_line_buffer) - strlen(word); + if (len < 0) { /* TODO: fix `word` for `|[2]` and delete this hack */ + len = 0; + word = ""; + } + Ref(char *, line, gcndup(start, len)); + gcdisable(); + fn = append(fn, mklist(mkstr(line), + mklist(mkstr(str("%s", word)), NULL))); + gcenable(); + result = eval(fn, NULL, 0); + RefEnd2(line, fn); + RefReturn(result); +} + +List *completion_to_file; + +static int callcompletiontofile(char **filep) { + List *result; + if (completion_to_file == NULL) + return 0; + Ref(List *, call, NULL); + gcdisable(); + call = append(completion_to_file, mklist(mkstr(*filep), NULL)); + gcenable(); + result = eval(call, NULL, 0); + RefEnd(call); + switch (length(result)) { + case 0: + return 0; + case 1: + if (streq(*filep, getstr(result->term))) + return 0; + /* move into ealloc-space */ + *filep = mprint("%E", result->term); + return 1; + default: + fail("%completion-to-file", "completion-filename mapping must return one value"); + } +} + +/* calls 'callcomplete' to produce candidates, and then returns them in a way + * readline likes. */ +static char *completion_matches(const char *text, int state) { + static char **matches = NULL; + static int matches_idx, matches_len; + int rlen; + char *result; + + if (!state) { + Vector *vm = vectorize(callcomplete((char *)text)); + matches = vm->vector; + matches_len = vm->count; + matches_idx = 0; + } + + if (!matches || matches_idx >= matches_len) + return NULL; + + rlen = strlen(matches[matches_idx]); + result = ealloc(rlen + 1); + strcpy(result, matches[matches_idx]); + result[rlen] = '\0'; + + matches_idx++; + return result; +} + +/* calls out to get candidates, and manages the tools to present those candidates + * correctly. + * TODO: + * - Hook function so completers can not only say "are these candidates files?" + * but also "how to get to the file from these candidates?" (for e.g., + * pathsearch-y commands) + */ +char **es_completion(UNUSED const char *text, UNUSED int start, UNUSED int end) { + char **matches; + Push ctf; + varpush(&ctf, "fn-%completion-to-file", NULL); + + matches = rl_completion_matches(text, completion_matches); + + /* mechanisms to control how the results are presented */ + completion_to_file = varlookup("fn-%completion-to-file", NULL); + if (completion_to_file != NULL) + rl_filename_completion_desired = TRUE; + varpop(&ctf); + rl_attempted_completion_over = 1; /* suppress "default" completions */ + + return matches; +} + +static void initreadline(void) { + rl_readline_name = "es"; + + /* these two word_break_characters exclude '&' due to primitive completion */ + rl_completer_word_break_characters = " \t\n\\'`$><=;|{()}"; + rl_basic_word_break_characters = " \t\n\\'`$><=;|{()}"; + rl_completer_quote_characters = "'"; + rl_special_prefixes = "$"; + + rl_filename_quote_characters = " \t\n\\`'$><=;|&{()}"; + rl_filename_quoting_function = quote; + rl_filename_dequoting_function = dequote; + + /* are these the right hooks? god there are a lot */ + rl_directory_rewrite_hook = callcompletiontofile; + rl_filename_stat_hook = callcompletiontofile; + + globalroot(&completion_to_file); +} + +/* set up readline for the next call */ +extern void rlsetup(void) { + static Boolean initialized = FALSE; + if (!initialized) { + initreadline(); + initialized = TRUE; + } + + rl_attempted_completion_function = es_completion; + + if (reloadhistory) + reload_history(); + if (resetterminal) { + rl_reset_terminal(NULL); + resetterminal = FALSE; + } + if (RL_ISSTATE(RL_STATE_INITIALIZED)) + rl_reset_screen_size(); +} + +static char *callreadline(char *prompt) { + char *r, *volatile line = NULL; + /* should this be called after each interruption, or? */ + rlsetup(); + if (!setjmp(slowlabel)) { + slow = TRUE; + r = readline(prompt); + } else { + r = NULL; + errno = EINTR; + } + slow = FALSE; + if (r != NULL) { + line = str("%s", r); + efree(r); + } + SIGCHK(); + return line; +} + + +/* + * primitives + */ + +PRIM(sethistory) { + if (list == NULL) { + sethistory(NULL); + return NULL; + } + Ref(List *, lp, list); + sethistory(getstr(lp->term)); + RefReturn(lp); +} + +PRIM(writehistory) { + if (list == NULL || list->next != NULL) + fail("$&writehistory", "usage: $&writehistory command"); + loghistory(getstr(list->term)); + return ltrue; +} + +PRIM(setmaxhistorylength) { + char *s; + int n; + if (list == NULL) { + setmaxhistorylength(-1); /* unlimited */ + return NULL; + } + if (list->next != NULL) + fail("$&setmaxhistorylength", "usage: $&setmaxhistorylength [limit]"); + Ref(List *, lp, list); + n = (int)strtol(getstr(lp->term), &s, 0); + if (n < 0 || (s != NULL && *s != '\0')) + fail("$&setmaxhistorylength", "max-history-length must be set to a positive integer"); + setmaxhistorylength(n); + RefReturn(lp); +} + +PRIM(resetterminal) { + resetterminal = TRUE; + return ltrue; +} + +static char *emptyprompt = ""; + +PRIM(readline) { + char *line; + char *pr0 = list == NULL ? "" : getstr(list->term); + char *prompt = emptyprompt; + int old = dup(0), in = fdmap(0); + + if (list != NULL) { + size_t psize = strlen(pr0) * sizeof(char) + 1; + prompt = ealloc(psize); + memcpy(prompt, pr0, psize); + } + if (dup2(in, 0) == -1) { + if (prompt != emptyprompt) + efree(prompt); + fail("$&readline", "dup2: %s", esstrerror(errno)); + } + + ExceptionHandler + + do { + line = callreadline(prompt); + } while (line == NULL && errno == EINTR); + + CatchException (e) + + if (prompt != emptyprompt) + efree(prompt); + mvfd(old, 0); + throw(e); + + EndExceptionHandler + + if (prompt != emptyprompt) + efree(prompt); + mvfd(old, 0); + + if (line == NULL) + return NULL; + list = mklist(mkstr(line), NULL); + return list; +} + +/* + * initialization + */ + +extern Dict *initprims_readline(Dict *primdict) { + X(sethistory); + X(writehistory); + X(resetterminal); + X(setmaxhistorylength); + X(readline); + return primdict; +} + +/* inithistory -- called at dawn of time from main() */ +extern void inithistory(void) { + /* declare the global roots */ + globalroot(&history); /* history file */ +} + +#endif diff --git a/stdenv.h b/stdenv.h index aa2780ea..214f06b4 100644 --- a/stdenv.h +++ b/stdenv.h @@ -123,10 +123,6 @@ extern void *qsort( ); #endif /* !STDC_HEADERS */ -#if HAVE_READLINE -# include -#endif - #include #include diff --git a/str.c b/str.c index 17e85747..1c218a7a 100644 --- a/str.c +++ b/str.c @@ -14,7 +14,6 @@ static int str_grow(Format *f, size_t more) { return 0; } -/* strv -- print a formatted string into gc space */ static char *sstrv(char *(*seal)(Buffer *), const char *fmt, va_list args) { Buffer *buf; Format format; @@ -40,6 +39,7 @@ static char *sstrv(char *(*seal)(Buffer *), const char *fmt, va_list args) { return seal(format.u.p); } +/* strv -- print a formatted string into gc space */ extern char *strv(const char *fmt, va_list args) { return sstrv(sealbuffer, fmt, args); } diff --git a/test/tests/parse.es b/test/tests/parse.es new file mode 100644 index 00000000..8f612a98 --- /dev/null +++ b/test/tests/parse.es @@ -0,0 +1,77 @@ +# tests/parse.es -- test that $&parse works with the chaos of various reader commands + +test 'parser' { + let (ex = ()) { + catch @ e { + ex = $e + } { + $&parse {throw test-exception} + } + assert {~ $ex test-exception} + } + + let ((e type msg) = ()) { + catch @ exc { + (e type msg) = $exc + } { + $&parse {result ')'} + } + assert {~ $e error && ~ $msg *'syntax error'*} \ + 'parser handles syntax error' + } + + # run these two in subshells, they cause their inputs to go "eof" + let (msg = `` \n {catch @ exc { + echo $exc + } { + $&parse {result 'aaaa ( bbbbb'} + } + }) { + assert {~ $msg 'error'*'memory exhausted'* || ~ $msg 'error'*'stack overflow'*} \ + 'parser handles infinite recursion' + } + + let (msg = `` \n { + catch @ exc { + echo 'caught' $exc + } { + let (line = 'aaaa ( bbbbb') + echo 'parsed' <={$&parse {let (l = $line) {line = (); result $l}}} + } + }) { + assert {~ $msg 'caught'*'syntax error'*} + } + + # normal 'nested parsing' exception + let ((e type msg) = ()) { + catch @ exc { + (e type msg) = $exc + } { + $&parse $&parse + } + assert {~ $e error && ~ $msg *'nested parsing'*} + } + + # test parsing from string while parsing from input + let (e = ()) { + catch @ exc { + e = $exc + } { + $&parse {eval result true} + } + assert {~ $e ()} + } + + # do normal cases last to see if previous ones broke anything + assert {~ <={$&parse {result 'echo >[1=2]'}} '{%dup 1 2 {echo}}'} + + # force GCs during parsing + assert {~ <={$&parse {$&collect; $&read}} '{fn-^zoom=@ *{%seq {this is one} {let(z=a a a){this is three}}}}'} << EOF +fn zoom { + this is one + let (z = a a a) { + this is three + } +} +EOF +} diff --git a/test/tests/trip.es b/test/tests/trip.es index 26c4401e..c3d69a99 100644 --- a/test/tests/trip.es +++ b/test/tests/trip.es @@ -1,16 +1,16 @@ # tests/trip.es -- migration of the classic trip.es to the new test framework. test 'lexical analysis' { - let (tmp = `{mktemp trip-nul.XXXXXX}) - unwind-protect { - ./testrun 0 > $tmp - let ((status output) = <={$&backquote \n {$es $tmp >[2=1]}}) { - assert {~ $output *'null character ignored'*} 'null character produces warning' - assert {~ $status 6} 'null character does not disturb behavior' - } - } { - rm -f $tmp - } +# let (tmp = `{mktemp trip-nul.XXXX}) +# unwind-protect { +# ./testrun 0 > $tmp +# let ((status output) = <={$&backquote \n {$es $tmp >[2=1]}}) { +# assert {~ $output *'null character ignored'*} 'null character produces warning' +# assert {~ $status 6} 'null character does not disturb behavior' +# } +# } { +# rm -f $tmp +# } let (wtmp = `{mktemp long-word.XXXXXX}; qtmp = `{mktemp long-qword.XXXXXX}) unwind-protect { diff --git a/token.c b/token.c index b4c0cf8d..af0613b9 100644 --- a/token.c +++ b/token.c @@ -10,15 +10,7 @@ #define BUFSIZE ((size_t) 2048) #define BUFMAX (8 * BUFSIZE) -typedef enum { NW, RW, KW } State; /* "nonword", "realword", "keyword" */ - -static State w = NW; -static Boolean newline = FALSE; -static Boolean goterror = FALSE; -static size_t bufsize = 0; -static char *tokenbuf = NULL; - -#define InsertFreeCaret() STMT(if (w != NW) { w = NW; UNGETC(c); return '^'; }) +#define InsertFreeCaret() STMT(if (input->ws != NW) { input->ws = NW; UNGETC(c); return '^'; }) /* @@ -65,22 +57,16 @@ const char dnw[] = { }; -/* print_prompt2 -- called before all continuation lines */ -extern void print_prompt2(void) { +/* increment_line -- called before all continuation lines */ +extern void increment_line(void) { input->lineno++; -#if HAVE_READLINE - prompt = prompt2; -#else - if ((input->runflags & run_interactive) && prompt2 != NULL) - eprint("%s", prompt2); -#endif } /* scanerror -- called for lexical errors */ static void scanerror(int c, char *s) { while (c != '\n' && c != EOF) c = GETC(); - goterror = TRUE; + input->goterror = TRUE; yyerror(s); } @@ -142,41 +128,35 @@ static Boolean getfds(int fd[2], int c, int default0, int default1) { } extern int yylex(YYSTYPE *y) { - static Boolean dollar = FALSE; int c; size_t i; /* The purpose of all these local assignments is to */ const char *meta; /* allow optimizing compilers like gcc to load these */ - char *buf = tokenbuf; /* values into registers. On a sparc this is a */ + char *buf = input->tokenbuf; /* values into registers. On a sparc this is a */ - if (goterror) { - goterror = FALSE; + if (input->goterror) { + input->goterror = FALSE; return NL; } /* rc variable-names may contain only alnum, '*' and '_', so use dnw if we are scanning one. */ - meta = (dollar ? dnw : nw); - dollar = FALSE; - if (newline) { - --input->lineno; /* slight space optimization; print_prompt2() always increments lineno */ - print_prompt2(); - newline = FALSE; - } -top: while ((c = GETC()) == ' ' || c == '\t') - w = NW; + meta = (input->dollar ? dnw : nw); + input->dollar = FALSE; + top: while ((c = GETC()) == ' ' || c == '\t') + input->ws = NW; if (c == EOF) return ENDFILE; if (!meta[(unsigned char) c]) { /* it's a word or keyword. */ InsertFreeCaret(); - w = RW; + input->ws = RW; i = 0; do { buf[i++] = c; - if (i >= bufsize) - buf = tokenbuf = erealloc(buf, bufsize *= 2); + if (i >= input->bufsize) + buf = input->tokenbuf = erealloc(buf, input->bufsize *= 2); } while ((c = GETC()) != EOF && !meta[(unsigned char) c]); UNGETC(c); buf[i] = '\0'; - w = KW; + input->ws = KW; if (buf[1] == '\0') { int k = *buf; if (k == '@' || k == '~') @@ -193,14 +173,14 @@ top: while ((c = GETC()) == ' ' || c == '\t') return CLOSURE; else if (streq(buf, "match")) return MATCH; - w = RW; + input->ws = RW; y->str = pdup(buf); return WORD; } if (c == '`' || c == '!' || c == '$' || c == '\'' || c == '=') { InsertFreeCaret(); if (c == '!' || c == '=') - w = KW; + input->ws = KW; } switch (c) { case '!': @@ -219,7 +199,7 @@ top: while ((c = GETC()) == ' ' || c == '\t') UNGETC(c); return '`'; case '$': - dollar = TRUE; + input->dollar = TRUE; switch (c = GETC()) { case '#': return COUNT; case '^': return FLAT; @@ -227,19 +207,19 @@ top: while ((c = GETC()) == ' ' || c == '\t') default: UNGETC(c); return '$'; } case '\'': - w = RW; + input->ws = RW; i = 0; while ((c = GETC()) != '\'' || (c = GETC()) == '\'') { buf[i++] = c; if (c == '\n') - print_prompt2(); + increment_line(); if (c == EOF) { - w = NW; + input->ws = NW; scanerror(c, "eof in quoted string"); return ERROR; } - if (i >= bufsize) - buf = tokenbuf = erealloc(buf, bufsize *= 2); + if (i >= input->bufsize) + buf = input->tokenbuf = erealloc(buf, input->bufsize *= 2); } UNGETC(c); buf[i] = '\0'; @@ -247,7 +227,7 @@ top: while ((c = GETC()) == ' ' || c == '\t') return QWORD; case '\\': if ((c = GETC()) == '\n') { - print_prompt2(); + increment_line(); UNGETC(' '); goto top; /* Pretend it was just another space. */ } @@ -258,7 +238,7 @@ top: while ((c = GETC()) == ' ' || c == '\t') UNGETC(c); c = '\\'; InsertFreeCaret(); - w = RW; + input->ws = RW; c = GETC(); switch (c) { case 'a': *buf = '\a'; break; @@ -314,21 +294,20 @@ top: while ((c = GETC()) == ' ' || c == '\t') FALLTHROUGH; case '\n': input->lineno++; - newline = TRUE; - w = NW; + input->ws = NW; return NL; case '(': - if (w == RW) /* not keywords, so let & friends work */ + if (input->ws == RW) /* not keywords, so let & friends work */ c = SUB; FALLTHROUGH; case ';': case '^': case ')': case '{': case '}': - w = NW; + input->ws = NW; return c; case '&': - w = NW; + input->ws = NW; c = GETC(); if (c == '&') return ANDAND; @@ -337,7 +316,7 @@ top: while ((c = GETC()) == ' ' || c == '\t') case '|': { int p[2]; - w = NW; + input->ws = NW; c = GETC(); if (c == '|') return OROR; @@ -369,7 +348,7 @@ top: while ((c = GETC()) == ' ' || c == '\t') } else cmd = "%heredoc"; else if (c == '=') { - w = NW; + input->ws = NW; return CALL; } else cmd = "%open"; @@ -389,7 +368,7 @@ top: while ((c = GETC()) == ' ' || c == '\t') cmd = "%create"; goto redirection; redirection: - w = NW; + input->ws = NW; if (!getfds(fd, c, fd[0], DEFAULT)) return ERROR; if (fd[1] != DEFAULT) { @@ -404,20 +383,20 @@ top: while ((c = GETC()) == ' ' || c == '\t') default: assert(c != '\0'); - w = NW; + input->ws = NW; return c; /* don't know what it is, let yacc barf on it */ } } -extern void inityy(void) { - newline = FALSE; - w = NW; - if (bufsize > BUFMAX) { /* return memory to the system if the buffer got too large */ - efree(tokenbuf); - tokenbuf = NULL; +extern void inityy(Input *in) { + in->ws = NW; + in->dollar = FALSE; + if (in->bufsize > BUFMAX) { /* return memory to the system if the buffer got too large */ + efree(in->tokenbuf); + in->tokenbuf = NULL; } - if (tokenbuf == NULL) { - bufsize = BUFSIZE; - tokenbuf = ealloc(bufsize); + if (in->tokenbuf == NULL) { + in->bufsize = BUFSIZE; + in->tokenbuf = ealloc(in->bufsize); } } diff --git a/tree.c b/tree.c index 442328f6..8e0b77f0 100644 --- a/tree.c +++ b/tree.c @@ -95,7 +95,7 @@ static size_t Tree1Scan(void *p) { case nCall: case nThunk: case nVar: n->u[0].p = forward(n->u[0].p); break; - } + } return offsetof(Tree, u[1]); } @@ -104,12 +104,12 @@ static size_t Tree2Scan(void *p) { switch (n->kind) { case nAssign: case nConcat: case nClosure: case nFor: case nLambda: case nLet: case nList: case nLocal: - case nVarsub: case nMatch: case nExtract: + case nVarsub: case nMatch: case nExtract: case nRedir: n->u[0].p = forward(n->u[0].p); n->u[1].p = forward(n->u[1].p); break; default: panic("Tree2Scan: bad node kind %d", n->kind); - } + } return offsetof(Tree, u[2]); } diff --git a/var.c b/var.c index bacc7523..b6189a24 100644 --- a/var.c +++ b/var.c @@ -350,13 +350,6 @@ static void listinternal(void *arg, char *key, void *value) { addtolist(arg, key, value); } -static char *list_prefix; - -static void listwithprefix(void *arg, char *key, void *value) { - if (strneq(key, list_prefix, strlen(list_prefix))) - addtolist(arg, key, value); -} - /* listvars -- return a list of all the (dynamic) variables */ extern List *listvars(Boolean internal) { Ref(List *, varlist, NULL); @@ -365,15 +358,6 @@ extern List *listvars(Boolean internal) { RefReturn(varlist); } -/* varswithprefix -- return a list of all the (dynamic) variables - * matching the given prefix */ -extern List *varswithprefix(char *prefix) { - Ref(List *, varlist, NULL); - list_prefix = prefix; - dictforall(vars, listwithprefix, &varlist); - RefReturn(varlist); -} - /* hide -- worker function for dictforall to hide initial state */ static void hide(void UNUSED *dummy, char UNUSED *key, void *value) { ((Var *) value)->flags |= var_isinternal; @@ -507,6 +491,10 @@ char *getenv(const char *name) { return realgetenv(name); } +extern void initgetenv(void) { + realgetenv = esgetenv; +} + extern int setenv(const char *name, const char *value, int overwrite) { assert(vars != NULL); if (name == NULL || name[0] == '\0' || strchr(name, '=') != NULL) { diff --git a/y.tab.c b/y.tab.c index 94601989..222f477c 100644 --- a/y.tab.c +++ b/y.tab.c @@ -1401,13 +1401,13 @@ YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); { case 2: /* es: line end */ #line 43 "./parse.y" - { parsetree = (yyvsp[-1].tree); YYACCEPT; } + { input->parsetree = (yyvsp[-1].tree); YYACCEPT; } #line 1406 "y.tab.c" break; case 3: /* es: error end */ #line 44 "./parse.y" - { yyerrok; parsetree = NULL; YYABORT; } + { yyerrok; input->parsetree = NULL; YYABORT; } #line 1412 "y.tab.c" break;