diff options
Diffstat (limited to 'dc/eval.c')
-rw-r--r-- | dc/eval.c | 237 |
1 files changed, 174 insertions, 63 deletions
@@ -1,7 +1,8 @@ -/* +/* * 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 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 @@ -17,8 +18,8 @@ * 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 + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA */ /* This is the only module which knows about the dc input language */ @@ -37,6 +38,7 @@ # endif #endif #endif +#include <signal.h> #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; @@ -103,7 +106,7 @@ 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++; } @@ -116,17 +119,40 @@ input_str DC_DECLVOID() */ 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); + 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 +166,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 +227,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 +236,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 +245,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 +296,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 */ @@ -341,7 +358,7 @@ between 2 and %d (inclusive)\n", return DC_QUIT; case 'r': /* rotate (swap) the top two elements on the stack */ - if (dc_pop(&datum) == DC_SUCCESS) { + if (dc_pop(&datum) == DC_SUCCESS){ dc_data datum2; int two_status; two_status = dc_pop(&datum2); @@ -374,17 +391,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; @@ -441,8 +448,7 @@ between 2 and %d (inclusive)\n", 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). */ @@ -520,7 +526,7 @@ between 2 and %d (inclusive)\n", /* takes a string and evals it */ int dc_evalstr DC_DECLARG((string)) - dc_data string DC_DECLEND + dc_data *string DC_DECLEND { const char *s; const char *end; @@ -531,16 +537,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,9 +563,44 @@ 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); + return DC_OKAY; + }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; @@ -575,24 +619,26 @@ dc_evalstr DC_DECLARG((string)) --count; else if (*p == '[') ++count; - len = p - s; + len = (size_t) (p - s); dc_push(dc_makestring(s, len-1)); s = p; break; case DC_SYSTEM: s = dc_system(s); + /*@fallthrough@*/ 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; } @@ -615,6 +661,7 @@ dc_evalfile DC_DECLARG((fp)) int next_negcmp = 0; dc_data datum; + signal(SIGINT, dc_trap_interrupt); stdin_lookahead = EOF; for (c=getc(fp); c!=EOF; c=peekc){ peekc = getc(fp); @@ -624,6 +671,20 @@ 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; switch (dc_func(c, peekc, negcmp)){ @@ -634,6 +695,33 @@ 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) + return DC_SUCCESS; + 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; @@ -649,16 +737,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'); + if (ferror(stdin)) + goto error_fail; (void)dc_system(dc_str2charp(datum.v.string)); dc_free_str(&datum.v.string); peekc = getc(fp); @@ -674,9 +768,26 @@ 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; } } - return DC_SUCCESS; + if (!ferror(fp)) + return DC_SUCCESS; + +error_fail: + fprintf(stderr, "%s: ", progname); + perror("error reading input"); + return DC_FAIL; } + + +/* + * Local Variables: + * mode: C + * tab-width: 4 + * End: + * vi: set ts=4 : + */ |