diff options
Diffstat (limited to 'dc/eval.c')
-rw-r--r-- | dc/eval.c | 327 |
1 files changed, 239 insertions, 88 deletions
@@ -1,11 +1,12 @@ -/* +/* * evaluate the dc language, from a FILE* or a string * - * Copyright (C) 1994, 1997, 1998, 2000 Free Software Foundation, Inc. + * Copyright (C) 1994, 1997, 1998, 2000, 2003, 2005, 2006, 2008, 2010, 2012-2017 + * Free Software Foundation, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) + * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, @@ -14,11 +15,8 @@ * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with this program; if not, you can either send email to this - * program's author (see below) or write to: - * The Free Software Foundation, Inc. - * 59 Temple Place, Suite 330 - * Boston, MA 02111 USA + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * */ /* This is the only module which knows about the dc input language */ @@ -37,6 +35,10 @@ # endif #endif #endif +#include <signal.h> +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif #include "dc.h" #include "dc-proto.h" @@ -45,11 +47,9 @@ typedef enum {DC_FALSE, DC_TRUE} dc_boolean; typedef enum { DC_OKAY = DC_SUCCESS, /* no further intervention needed for this command */ DC_EATONE, /* caller needs to eat the lookahead char */ + DC_EVALREG, /* caller needs to eval the string named by `peekc' */ + DC_EVALTOS, /* caller needs to eval the string on top of the stack */ DC_QUIT, /* quit out of unwind_depth levels of evaluation */ - - /* with the following return values, the caller does not have to - * fret about stdin_lookahead's value - */ DC_INT, /* caller needs to parse a dc_num from input stream */ DC_STR, /* caller needs to parse a dc_str from input stream */ DC_SYSTEM, /* caller needs to run a system() on next input line */ @@ -66,6 +66,9 @@ static int dc_scale=0; /* scale (see user documentaton) */ /* for Quitting evaluations */ static int unwind_depth=0; +/* for handling SIGINT properly */ +static volatile sig_atomic_t interrupt_seen=0; + /* if true, active Quit will not exit program */ static dc_boolean unwind_noexit=DC_FALSE; @@ -75,6 +78,9 @@ static dc_boolean unwind_noexit=DC_FALSE; */ static int stdin_lookahead=EOF; +/* forward reference */ +static int evalstr(dc_data *string); + /* input_fil and input_str are passed as arguments to dc_getnum */ @@ -103,30 +109,53 @@ input_fil DC_DECLVOID() static int input_str DC_DECLVOID() { - if (!*input_str_string) + if (*input_str_string == '\0') return EOF; - return *input_str_string++; + return *(const unsigned char *)input_str_string++; } /* takes a string and evals it; frees the string when done */ -/* Wrapper around dc_evalstr to avoid duplicating the free call +/* Wrapper around evalstr to avoid duplicating the free call * at all possible return points. */ static int dc_eval_and_free_str DC_DECLARG((string)) - dc_data string DC_DECLEND + dc_data *string DC_DECLEND { dc_status status; - status = dc_evalstr(string); - if (string.dc_type == DC_STRING) - dc_free_str(&string.v.string); + status = evalstr(string); + if (string->dc_type == DC_STRING) + dc_free_str(&string->v.string); return status; } +/* notice when an interrupt event happens */ +static void +dc_trap_interrupt DC_DECLARG((signo)) + int signo DC_DECLEND +{ + signal(signo, dc_trap_interrupt); + interrupt_seen = 1; +} + + +/* step pointer past next end-of-line (or to end-of-string) */ +static const char * +skip_past_eol DC_DECLARG((strptr, strend)) + const char *strptr DC_DECLSEP + const char *strend DC_DECLEND +{ + const char *p = memchr(strptr, '\n', (size_t)(strend-strptr)); + if (p != NULL) + return p+1; + return strend; +} + + /* dc_func does the grunt work of figuring out what each input * character means; used by both dc_evalstr and dc_evalfile * @@ -140,11 +169,6 @@ dc_func DC_DECLARG((c, peekc, negcmp)) int peekc DC_DECLSEP int negcmp DC_DECLEND { - /* we occasionally need these for temporary data */ - /* Despite the GNU coding standards, it is much easier - * to have these declared once here, since this function - * is just one big switch statement. - */ dc_data datum; int tmpint; @@ -206,10 +230,8 @@ dc_func DC_DECLARG((c, peekc, negcmp)) */ if (peekc == EOF) return DC_EOF_ERROR; - if ( (dc_cmpop() < 0) == !negcmp ) - if (dc_register_get(peekc, &datum) == DC_SUCCESS) - if (dc_eval_and_free_str(datum) == DC_QUIT) - return DC_QUIT; + if ( (dc_cmpop() < 0) == (negcmp==0) ) + return DC_EVALREG; return DC_EATONE; case '=': /* eval register named by peekc if @@ -217,10 +239,8 @@ dc_func DC_DECLARG((c, peekc, negcmp)) */ if (peekc == EOF) return DC_EOF_ERROR; - if ( (dc_cmpop() == 0) == !negcmp ) - if (dc_register_get(peekc, &datum) == DC_SUCCESS) - if (dc_eval_and_free_str(datum) == DC_QUIT) - return DC_QUIT; + if ( (dc_cmpop() == 0) == (negcmp==0) ) + return DC_EVALREG; return DC_EATONE; case '>': /* eval register named by peekc if @@ -228,19 +248,19 @@ dc_func DC_DECLARG((c, peekc, negcmp)) */ if (peekc == EOF) return DC_EOF_ERROR; - if ( (dc_cmpop() > 0) == !negcmp ) - if (dc_register_get(peekc, &datum) == DC_SUCCESS) - if (dc_eval_and_free_str(datum) == DC_QUIT) - return DC_QUIT; + if ( (dc_cmpop() > 0) == (negcmp==0) ) + return DC_EVALREG; return DC_EATONE; case '?': /* read a line from standard-input and eval it */ if (stdin_lookahead != EOF){ ungetc(stdin_lookahead, stdin); stdin_lookahead = EOF; } - if (dc_eval_and_free_str(dc_readstring(stdin, '\n', '\n')) == DC_QUIT) - return DC_QUIT; - return DC_OKAY; + datum = dc_readstring(stdin, '\n', '\n'); + if (ferror(stdin)) + return DC_EOF_ERROR; + dc_push(datum); + return DC_EVALTOS; case '[': /* read to balancing ']' into a dc_str */ return DC_STR; case '!': /* read to newline and call system() on resulting string */ @@ -279,13 +299,13 @@ dc_func DC_DECLARG((c, peekc, negcmp)) tmpint = 0; if (datum.dc_type == DC_NUMBER) tmpint = dc_num2int(datum.v.number, DC_TOSS); - if ( ! (2 <= tmpint && tmpint <= DC_IBASE_MAX) ) + if (2 <= tmpint && tmpint <= DC_IBASE_MAX) + dc_ibase = tmpint; + else fprintf(stderr, "%s: input base must be a number \ between 2 and %d (inclusive)\n", progname, DC_IBASE_MAX); - else - dc_ibase = tmpint; } break; case 'k': /* set scale to value on top of stack */ @@ -339,16 +359,8 @@ between 2 and %d (inclusive)\n", unwind_depth = 1; /* the return below is the first level of returns */ unwind_noexit = DC_FALSE; return DC_QUIT; - case 'r': /* rotate (swap) the top two elements on the stack - */ - if (dc_pop(&datum) == DC_SUCCESS) { - dc_data datum2; - int two_status; - two_status = dc_pop(&datum2); - dc_push(datum); - if (two_status == DC_SUCCESS) - dc_push(datum2); - } + case 'r': /* rotate (swap) the top two elements on the stack */ + dc_stack_rotate(2); break; case 's': /* "store" -- replace top of register stack named * by peekc with the value popped from the top @@ -374,17 +386,7 @@ between 2 and %d (inclusive)\n", } break; case 'x': /* eval the datum popped from top of stack */ - if (dc_pop(&datum) == DC_SUCCESS){ - if (datum.dc_type == DC_STRING){ - if (dc_eval_and_free_str(datum) == DC_QUIT) - return DC_QUIT; - }else if (datum.dc_type == DC_NUMBER){ - dc_push(datum); - }else{ - dc_garbage("at top of stack", -1); - } - } - break; + return DC_EVALTOS; case 'z': /* push the current stack depth onto the top of stack */ dc_push(dc_int2data(dc_tell_stackdepth())); break; @@ -417,10 +419,11 @@ between 2 and %d (inclusive)\n", if (datum.dc_type == DC_NUMBER) dc_dump_num(datum.v.number, DC_TOSS); else if (datum.dc_type == DC_STRING) - dc_out_str(datum.v.string, DC_NONL, DC_TOSS); + dc_out_str(datum.v.string, DC_TOSS); else dc_garbage("at top of stack", -1); } + fflush(stdout); break; case 'Q': /* quit out of top-of-stack nested evals; * pops value from stack; @@ -439,10 +442,8 @@ between 2 and %d (inclusive)\n", progname); } break; -#if 0 case 'R': /* pop a value off of the evaluation stack,; - * rotate the top - remaining stack elements that many + * rotate the top remaining stack elements that many * places forward (negative numbers mean rotate * backward). */ @@ -453,7 +454,6 @@ between 2 and %d (inclusive)\n", dc_stack_rotate(tmpint); } break; -#endif case 'S': /* pop a value off of the evaluation stack * and push it onto the register stack named by peekc */ @@ -518,9 +518,9 @@ between 2 and %d (inclusive)\n", /* takes a string and evals it */ -int -dc_evalstr DC_DECLARG((string)) - dc_data string DC_DECLEND +static int +evalstr DC_DECLARG((string)) + dc_data *string DC_DECLEND { const char *s; const char *end; @@ -531,16 +531,19 @@ dc_evalstr DC_DECLARG((string)) int count; int negcmp; int next_negcmp = 0; + int tail_depth = 1; /* how much tail recursion is active */ + dc_data evalstr; - if (string.dc_type != DC_STRING){ + if (string->dc_type != DC_STRING){ fprintf(stderr, "%s: eval called with non-string argument\n", progname); return DC_OKAY; } - s = dc_str2charp(string.v.string); - end = s + dc_strlen(string.v.string); - while (s < end){ + interrupt_seen = 0; + s = dc_str2charp(string->v.string); + end = s + dc_strlen(string->v.string); + while (s < end && interrupt_seen==0){ c = *(const unsigned char *)s++; peekc = EOF; if (s < end) @@ -554,12 +557,48 @@ dc_evalstr DC_DECLARG((string)) if (peekc != EOF) ++s; break; + case DC_EVALREG: + /*commands which return this guarantee that peekc!=EOF*/ + ++s; + if (dc_register_get(peekc, &evalstr) != DC_SUCCESS) + break; + dc_push(evalstr); + /*@fallthrough@*/ + case DC_EVALTOS: + /*skip trailing whitespace to assist tail-recursion detection*/ + while (s<end && (*s==' '||*s=='\t'||*s=='\n'||*s=='#')){ + if (*s++ == '#') + s = skip_past_eol(s, end); + } + if (dc_pop(&evalstr) == DC_SUCCESS){ + if (evalstr.dc_type == DC_NUMBER){ + dc_push(evalstr); + }else if (evalstr.dc_type != DC_STRING){ + dc_garbage("at top of stack", -1); + }else if (s == end){ + /*handle tail recursion*/ + dc_free_str(&string->v.string); + *string = evalstr; + s = dc_str2charp(string->v.string); + end = s + dc_strlen(string->v.string); + ++tail_depth; + }else if (dc_eval_and_free_str(&evalstr) == DC_QUIT){ + if (unwind_depth > 0){ + --unwind_depth; + return DC_QUIT; + } + return DC_OKAY; + } + } + break; case DC_QUIT: - if (unwind_depth > 0){ - --unwind_depth; + if (unwind_depth >= tail_depth){ + unwind_depth -= tail_depth; return DC_QUIT; } - return DC_OKAY; + /*adjust tail recursion accounting and continue*/ + tail_depth -= unwind_depth; + break; case DC_INT: input_str_string = s - 1; @@ -575,24 +614,26 @@ dc_evalstr DC_DECLARG((string)) --count; else if (*p == '[') ++count; - len = p - s; - dc_push(dc_makestring(s, len-1)); + len = (size_t) (p - s); + dc_push(dc_makestring(s, (count==0 ? len-1 : len))); s = p; break; case DC_SYSTEM: s = dc_system(s); + break; case DC_COMMENT: - s = memchr(s, '\n', (size_t)(end-s)); - if (!s) - s = end; - else - ++s; + s = skip_past_eol(s, end); break; case DC_NEGCMP: next_negcmp = 1; break; case DC_EOF_ERROR: + if (ferror(stdin)) { + fprintf(stderr, "%s: ", progname); + perror("error reading stdin"); + return DC_FAIL; + } fprintf(stderr, "%s: unexpected EOS\n", progname); return DC_OKAY; } @@ -600,6 +641,23 @@ dc_evalstr DC_DECLARG((string)) return DC_OKAY; } +/* wrapper around evalstr, to handle top-level QUIT requests correctly*/ +int +dc_evalstr(dc_data *string) +{ + switch (evalstr(string)) { + case DC_OKAY: + return DC_SUCCESS; + case DC_QUIT: + if (unwind_noexit != DC_TRUE) + return DC_FAIL; + return DC_SUCCESS; + default: + return DC_FAIL; + } +} + + /* This is the main function of the whole DC program. * Reads the file described by fp, calls dc_func to do @@ -613,8 +671,24 @@ dc_evalfile DC_DECLARG((fp)) int peekc; int negcmp; int next_negcmp = 0; + typedef void (*handler_t)(int); + handler_t sigint_handler = dc_trap_interrupt; + handler_t sigint_default = signal(SIGINT, SIG_IGN); dc_data datum; + /* Signals are awkward: we want to allow interactive users + * to break out of long running macros, but otherwise we + * prefer that SIGINT not be given any special treatment. + * Sometimes "no special treatment" means to continue to + * *ignore* the signal, but usually it means to kill the program. + */ + signal(SIGINT, sigint_default); +#ifdef HAVE_UNISTD_H + /* don't trap SIGINT if we can tell that we are not reading from a tty */ + if ( ! isatty(fileno(fp)) ) + sigint_handler = sigint_default; +#endif + stdin_lookahead = EOF; for (c=getc(fp); c!=EOF; c=peekc){ peekc = getc(fp); @@ -624,8 +698,23 @@ dc_evalfile DC_DECLARG((fp)) */ if (fp == stdin) stdin_lookahead = peekc; + /* + * In the switch(), cases which naturally update peekc + * (unconditionally) do not have to update or reference + * stdin_lookahead; other functions use the predicate: + * stdin_lookahead != peekc && fp == stdin + * to recognize the case where: + * a) stdin_lookahead == EOF (stdin and peekc are not in sync) + * b) peekc != EOF (resync is possible) + * c) fp == stdin (resync is relevant) + * The whole stdin_lookahead complication arises because the + * '?' command may be invoked from an arbritrarily deeply + * nested dc_evalstr(), '?' reads exclusively from stdin, + * and this winds up making peekc invalid when fp==stdin. + */ negcmp = next_negcmp; next_negcmp = 0; + signal(SIGINT, sigint_handler); switch (dc_func(c, peekc, negcmp)){ case DC_OKAY: if (stdin_lookahead != peekc && fp == stdin) @@ -634,9 +723,36 @@ dc_evalfile DC_DECLARG((fp)) case DC_EATONE: peekc = getc(fp); break; + case DC_EVALREG: + /*commands which send us here shall guarantee that peekc!=EOF*/ + c = peekc; + peekc = getc(fp); + stdin_lookahead = peekc; + if (dc_register_get(c, &datum) != DC_SUCCESS) + break; + dc_push(datum); + /*@fallthrough@*/ + case DC_EVALTOS: + if (stdin_lookahead != peekc && fp == stdin) + peekc = getc(fp); + if (dc_pop(&datum) == DC_SUCCESS){ + if (datum.dc_type == DC_NUMBER){ + dc_push(datum); + }else if (datum.dc_type == DC_STRING){ + if (dc_eval_and_free_str(&datum) == DC_QUIT){ + if (unwind_noexit != DC_TRUE) + goto reset_and_exit_quit; + fprintf(stderr, "%s: Q command argument exceeded \ +string execution depth\n", progname); + } + }else{ + dc_garbage("at top of stack", -1); + } + } + break; case DC_QUIT: if (unwind_noexit != DC_TRUE) - return DC_SUCCESS; + goto reset_and_exit_quit; fprintf(stderr, "%s: Q command argument exceeded string execution depth\n", progname); @@ -649,16 +765,22 @@ dc_evalfile DC_DECLARG((fp)) input_pushback = c; ungetc(peekc, fp); dc_push(dc_getnum(input_fil, dc_ibase, &peekc)); + if (ferror(fp)) + goto error_fail; break; case DC_STR: ungetc(peekc, fp); datum = dc_readstring(fp, '[', ']'); + if (ferror(fp)) + goto error_fail; dc_push(datum); peekc = getc(fp); break; case DC_SYSTEM: ungetc(peekc, fp); - datum = dc_readstring(stdin, '\n', '\n'); + datum = dc_readstring(fp, '\n', '\n'); + if (ferror(fp)) + goto error_fail; (void)dc_system(dc_str2charp(datum.v.string)); dc_free_str(&datum.v.string); peekc = getc(fp); @@ -674,9 +796,38 @@ dc_evalfile DC_DECLARG((fp)) break; case DC_EOF_ERROR: + if (ferror(fp)) + goto error_fail; fprintf(stderr, "%s: unexpected EOF\n", progname); - return DC_FAIL; + goto reset_and_exit_fail; } + + if (interrupt_seen) + fprintf(stderr, "\nInterrupt!\n"); + interrupt_seen = 0; + signal(SIGINT, sigint_default); } + if (!ferror(fp)) + goto reset_and_exit_success; + +error_fail: + fprintf(stderr, "%s: ", progname); + perror("error reading input"); + return DC_FAIL; +reset_and_exit_quit: +reset_and_exit_fail: + signal(SIGINT, sigint_default); + return DC_FAIL; +reset_and_exit_success: + signal(SIGINT, sigint_default); return DC_SUCCESS; } + + +/* + * Local Variables: + * mode: C + * tab-width: 4 + * End: + * vi: set ts=4 : + */ |