summaryrefslogtreecommitdiff
path: root/dc/eval.c
diff options
context:
space:
mode:
Diffstat (limited to 'dc/eval.c')
-rw-r--r--dc/eval.c327
1 files changed, 239 insertions, 88 deletions
diff --git a/dc/eval.c b/dc/eval.c
index 21592d9..05a3d9e 100644
--- a/dc/eval.c
+++ b/dc/eval.c
@@ -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 :
+ */