diff --git a/sh.c b/sh.c index 2d5565fc..1b73a600 100644 --- a/sh.c +++ b/sh.c @@ -112,34 +112,6 @@ static time_t chktim; /* Time mail last checked */ char *progname; int tcsh; -/* - * This preserves the input state of the shell. It is used by - * st_save and st_restore to manupulate shell state. - */ -struct saved_state { - int insource; - int OLDSTD; - int SHIN; - int SHOUT; - int SHDIAG; - int intty; - struct whyle *whyles; - Char *gointr; - Char *arginp; - Char *evalp; - Char **evalvec; - Char *alvecp; - Char **alvec; - int onelflg; - int enterhist; - Char **argv; - Char **av; - Char HIST; - int cantell; - struct Bin B; - int justpr; -}; - static int srccat (Char *, Char *); #ifndef WINNT_NATIVE static int srcfile (const char *, int, int, Char **); @@ -152,10 +124,6 @@ static void mailchk (void); static Char **defaultpath (void); #endif static void record (void); -static void st_save (struct saved_state *, int, int, - Char **, Char **); -static void st_restore (void *); - int main (int, char **); #ifndef LOCALEDIR @@ -1569,7 +1537,7 @@ srcfile(const char *f, int onlyown, int flag, Char **av) * Save the shell state, and establish new argument vector, and new input * fd. */ -static void +void st_save(struct saved_state *st, int unit, int hflg, Char **al, Char **av) { st->insource = insource; @@ -1659,19 +1627,28 @@ st_save(struct saved_state *st, int unit, int hflg, Char **al, Char **av) gointr = 0; evalvec = 0; evalp = 0; - alvec = al; alvecp = 0; enterhist = hflg; if (enterhist) HIST = '\0'; - insource = 1; + if (al == &fdecl) { + alvec = NULL; + insource = 2; + st->fpipe = fpipe; + st->fdecl = *al; + flvl++; + } + else { + alvec = al; + insource = 1; + } } /* * Restore the shell to a saved state */ -static void +void st_restore(void *xst) { struct saved_state *st; @@ -1696,6 +1673,12 @@ st_restore(void *xst) xclose(SHIN); + if (insource == 2) { + xclose(fpipe); + fpipe = st->fpipe; + fdecl = st->fdecl; + flvl--; + } insource = st->insource; SHIN = st->SHIN; if (st->OLDSTD != -1) diff --git a/sh.decls.h b/sh.decls.h index a863fede..5a18f99e 100644 --- a/sh.decls.h +++ b/sh.decls.h @@ -52,6 +52,8 @@ extern void done (int) __attribute__((__noreturn__)); extern void xexit (int) __attribute__((__noreturn__)); #endif extern int grabpgrp (int, pid_t); +extern void st_save (struct saved_state *, int, int, Char **, Char **); +extern void st_restore (void *); /* * sh.dir.c @@ -150,6 +152,7 @@ extern void doend (Char **, struct command *); extern void doeval (Char **, struct command *); extern void doexit (Char **, struct command *); extern void doforeach (Char **, struct command *); +extern void dofunction (Char **, struct command *); extern void doglob (Char **, struct command *); extern void dogoto (Char **, struct command *); extern void doif (Char **, struct command *); @@ -164,6 +167,7 @@ extern void dohup (Char **, struct command *); extern void doonintr (Char **, struct command *); extern void doprintenv (Char **, struct command *); extern void dorepeat (Char **, struct command *); +extern void doreturn (Char **, struct command *); extern void dofiletest (Char **, struct command *); extern void dosetenv (Char **, struct command *); extern void dosuspend (Char **, struct command *); diff --git a/sh.err.c b/sh.err.c index 9d9600ae..6b95f465 100644 --- a/sh.err.c +++ b/sh.err.c @@ -188,7 +188,11 @@ extern int enterhist; #define ERR_BADCOLORVAR 134 #define ERR_EOF 135 #define ERR_UNAVAILABLE 136 -#define NO_ERRORS 137 +#define ERR_UNDFUNC 137 +#define ERR_FUNCBEGIN 138 +#define ERR_FUNCALNUM 139 +#define ERR_RECURSION 140 +#define NO_ERRORS 141 static const char *elst[NO_ERRORS] INIT_ZERO_STRUCT; @@ -367,7 +371,10 @@ errinit(void) elst[ERR_BADCOLORVAR] = CSAVS(1, 137, "Unknown %s color variable '%c%c'"); elst[ERR_EOF] = CSAVS(1, 138, "Unexpected end of file"); elst[ERR_UNAVAILABLE] = CSAVS(1, 139, "%s: Feature is not available for this platform"); - + elst[ERR_UNDFUNC] = CSAVS(1, 140, "%S: Undeclared function"); + elst[ERR_FUNCBEGIN] = CSAVS(1, 142, "Function name must begin with a letter"); + elst[ERR_FUNCALNUM] = CSAVS(1, 143, "Function name must contain alphanumeric characters"); + elst[ERR_RECURSION] = CSAVS(1, 144, "Too deep a recursion or nest"); } /* Cleanup data. */ diff --git a/sh.func.c b/sh.func.c index a9c0dd6f..a9862024 100644 --- a/sh.func.c +++ b/sh.func.c @@ -498,11 +498,18 @@ doexit(Char **v, struct command *c) stderror(ERR_NAME | ERR_EXPRESSION); } btoeof(); -#if 0 - if (intty) -#endif - /* Always close, why only on ttys? */ + + /* Always close, except in the context of + * dosource or dofunction. + * st_restore will handle. */ + switch (insource) { + case 0: xclose(SHIN); + SHIN = -1; + break; + case 2: + fdecl += Strlen(fdecl); + } } /*ARGSUSED*/ @@ -1096,6 +1103,11 @@ getword(struct Strbuf *wp) stderror(ERR_NAME | ERR_NOTFOUND, "label"); break; + case TC_EXIT: + setname(short2str(Sgoal)); + stderror(ERR_NAME | ERR_NOTFOUND, "return"); + break; + default: break; } @@ -2719,3 +2731,126 @@ getYN(const char *prompt) continue; return doit; } + +int flvl, + fpipe = -1; +Char *fdecl; + +void +dofunction(Char **v, struct command *c) +{ + if (*++v == NULL) { + plist(&functions, VAR_READONLY); + + return; + } + Sgoal = *v++; + Stype = TC_EXIT; + { + int pv[2]; + struct saved_state st; + struct varent *varp; + Char *p; + + if (!letter(*(p = Sgoal))) + stderror(ERR_NAME | ERR_FUNCBEGIN); + while (*++p) + if (!alnum(*p)) + stderror(ERR_NAME | ERR_FUNCALNUM); + if ((varp = adrof1(Sgoal, &functions))) { + if (flvl == 100) + stderror(ERR_RECURSION); + mypipe(pv); + cleanup_push(&st, st_restore); + st_save(&st, pv[0], 0, &fdecl, v); + fpipe = pv[1]; + fdecl = *varp->vec; + process(0); + cleanup_until(&st); + + return; + } + if (*v || c->t_dlef || c->t_drit || + c->t_dflg & (F_PIPEIN | F_PIPEOUT)) + stderror(ERR_UNDFUNC, Sgoal); + { + Char funcexit[] = { 'r', 'e', 't', 'u', 'r', 'n', '\0' }, + alarg[] = { '!', '*', '\0' }, + *(*varvec)[4]; + struct Strbuf aword = Strbuf_INIT, + func = Strbuf_INIT; + struct wordent *histent = NULL, + *ohistent = NULL; + + cleanup_push(&aword, Strbuf_cleanup); + while (1) { + if (intty) { + histent = xmalloc(sizeof(*histent)); + ohistent = xmalloc(sizeof(*histent)); + ohistent->word = STRNULL; + ohistent->next = histent; + histent->prev = ohistent; + } + if (intty && fseekp == feobp && aret == TCSH_F_SEEK) + printprompt(1, bname); + (void) getword(&aword); + Strbuf_terminate(&aword); + if (intty && Strlen(aword.s) > 0) { + histent->word = Strsave(aword.s); + histent->next = xmalloc(sizeof(*histent)); + histent->next->prev = histent; + histent = histent->next; + } + + if (eq(aword.s, funcexit)) + break; + Strbuf_append(&func, aword.s); + Strbuf_append1(&func, ' '); + while (getword(&aword)) { + Strbuf_terminate(&aword); + if (intty && Strlen(aword.s) > 0) { + histent->word = Strsave(aword.s); + histent->next = xmalloc(sizeof(*histent)); + histent->next->prev = histent; + histent = histent->next; + } + Strbuf_append(&func, aword.s); + Strbuf_append1(&func, ' '); + } + func.s[func.len - 1] = '\n'; + + if (intty) { + ohistent->prev = histgetword(histent); + ohistent->prev->next = ohistent; + savehist(ohistent, 0); + freelex(ohistent); + xfree(ohistent); + } else + (void) getword(NULL); + } + + if (intty) { + ohistent->prev = histgetword(histent); + ohistent->prev->next = ohistent; + savehist(ohistent, 0); + freelex(ohistent); + xfree(ohistent); + } + cleanup_until(&aword); + if (!func.len) + return; + Strbuf_terminate(&func); + **(varvec = xcalloc(1, sizeof *varvec - + (sizeof **varvec * 2))) = + func.s; + setq(Sgoal, *varvec, &functions, VAR_READONLY); + **(varvec = xcalloc(1, sizeof *varvec)) = + Strsave(str2short(bname)); + (*varvec)[1] = Strsave(Sgoal); + (*varvec)[2] = Strsave(alarg); + setq(Sgoal, *varvec, &aliases, VAR_READWRITE); + + tw_cmd_free(); + } + } +} diff --git a/sh.h b/sh.h index 19bf10d6..721e5192 100644 --- a/sh.h +++ b/sh.h @@ -1019,7 +1019,9 @@ EXTERN struct varent { #define VAR_LAST 64 struct varent *v_link[3]; /* The links, see below */ int v_bal; /* Balance factor */ -} shvhed IZERO_STRUCT, aliases IZERO_STRUCT; +} shvhed IZERO_STRUCT, + aliases IZERO_STRUCT, + functions IZERO_STRUCT; #define v_left v_link[0] #define v_right v_link[1] @@ -1266,6 +1268,36 @@ EXTERN nl_catd catd; extern int filec; #endif /* FILEC */ +/* + * This preserves the input state of the shell. It is used by + * st_save and st_restore to manupulate shell state. + */ +struct saved_state { + int insource; + int OLDSTD; + int SHIN; + int SHOUT; + int SHDIAG; + int intty; + struct whyle *whyles; + Char *gointr; + Char *arginp; + Char *evalp; + Char **evalvec; + Char *alvecp; + Char **alvec; + int onelflg; + int enterhist; + Char **argv; + Char **av; + Char HIST; + int cantell; + struct Bin B; + int justpr; + int fpipe; + Char *fdecl; +}; + #include "sh.decls.h" /* * Since on some machines characters are unsigned, and the signed @@ -1305,5 +1337,10 @@ extern int filec; #define TEXP_IGNORE 1 /* in ignore, it means to ignore value, just parse */ #define TEXP_NOGLOB 2 /* in ignore, it means not to globone */ +extern int flvl, /* Level of nesting or recursion used + * used by dofunction. */ + fpipe; /* Write end of a pipe used by dofunction. */ +extern Char *fdecl; /* Pointer to function declaration + * used by dofunction. */ #endif /* _h_sh */ diff --git a/sh.init.c b/sh.init.c index 737afbc7..d751b754 100644 --- a/sh.init.c +++ b/sh.init.c @@ -80,6 +80,7 @@ const struct biltins bfunc[] = { { "fg", dofg, 0, INF }, { "filetest", dofiletest, 2, INF }, { "foreach", doforeach, 3, INF }, + { "function", dofunction, 0, INF }, #ifdef TCF { "getspath", dogetspath, 0, 0 }, { "getxvers", dogetxvers, 0, 0 }, @@ -122,6 +123,7 @@ const struct biltins bfunc[] = { { "pushd", dopushd, 0, INF }, { "rehash", dohash, 0, 3 }, { "repeat", dorepeat, 2, INF }, + { "return", dozip, 0, 0 }, #ifdef apollo { "rootnode", dorootnode, 1, 1 }, #endif /* apollo */ diff --git a/sh.lex.c b/sh.lex.c index 6651e55e..0c682dcc 100644 --- a/sh.lex.c +++ b/sh.lex.c @@ -1717,6 +1717,11 @@ bgetc(void) buf = (int) feobp / BUFSIZE; balloc(buf); roomleft = BUFSIZE - off; + if (insource == 2) { + if (!*fdecl) + return CHAR_ERR; + (void) xwrite(fpipe, fdecl++, (size_t) 1); + } c = wide_read(SHIN, fbuf[buf] + off, roomleft, 0); if (c > 0) feobp += c; diff --git a/tcsh.man.in b/tcsh.man.in index bde78ca3..fd8709f4 100644 --- a/tcsh.man.in +++ b/tcsh.man.in @@ -5424,12 +5424,14 @@ messages are verbose. .It Ic end .It Ic endif .It Ic endsw +.It Ic return See the description of the .Ic foreach , .Ic if , .Ic switch , +.Ic while , and -.Ic while +.Ic return statements below. . .El @@ -5521,6 +5523,43 @@ the loop are executed. If you make a mistake typing in a loop at the terminal you can rub it out. . +.It Ic function No (+) +.It Ic function Ar name No (+) +.It Ic \&... +.It Ic return +.It Ic function Ar name Xo +.Op Ar arg No ... +(+) +.It Ar name Xo +.Op Ar arg No ... +(+) +.Xc +The first form of the command prints the value of all shell functions. +.Pp +The second form declares a function +.Ar name Ns No . +A declaration ends when a +.Ic return +is matched. (Both +.Ic function +and +.Ic return +must appear alone on separate lines.) +May not be declared otherwise, and declared +functions may not be redeclared or undeclared. +.Pp +The third form calls a function +.Ar name Ns No , +optionally, preceded by +.Ar arg Ns No , +which is a list of arguments to be passed. +Function calls may be nested or recursive, +but too deep a nest or recursion will raise an error. +.Pp +The fourth form is an +.Ic alias +for the third form. +. .El .Bl -tag -width 6n . @@ -10642,6 +10681,12 @@ and message catalog code to interface to Windows. .br Color ls additions. . +.It Matheus Garcia , +2023. +.br +.Ic function +built-in. +. .El . .Sh THANKS TO diff --git a/tests/commands.at b/tests/commands.at index 48418cca..99049565 100644 --- a/tests/commands.at +++ b/tests/commands.at @@ -636,6 +636,39 @@ c AT_CLEANUP() +AT_SETUP([function]) +AT_KEYWORDS([commands]) + +AT_DATA([function.csh], +[[ +if ( ! { function test test } ) then + echo 'FAIL: '\''function'\'' is not passing arguments!' + exit 1 +else if >& /dev/null ( { function test2 } ) then + echo 'FAIL: '\''function'\'' is not seeking for an ending exit!' + exit 1 +else if >& /dev/null ( ! { function test3 } ) then + echo 'FAIL: '\''function'\'' is not seeking for an ending exit on EOF!' + exit 1 +endif +exit + +test: +if ( "$1" == ) exit 1 +exit + +test2: +exit +echo test + +test3: +exit +]]) +AT_CHECK([tcsh -f function.csh]) + +AT_CLEANUP() + + dnl dnl getspath dnl @@ -1870,3 +1903,22 @@ endif AT_CHECK([tcsh -f time_output.csh], 0, [ignore]) AT_CLEANUP() + +AT_SETUP([main function]) +AT_KEYWORDS([commands]) + +AT_DATA([main.csh], +[[ +if >& /dev/null ( { function test } ) then + echo 'FAIL: '\''function'\'' is not seeking for an ending first exit!' + exit 1 +endif +exit +echo test + +test: +exit +]]) +AT_CHECK([tcsh -f main.csh]) + +AT_CLEANUP()