diff options
Diffstat (limited to 'gnu/usr.bin/dc/dc.c')
-rw-r--r-- | gnu/usr.bin/dc/dc.c | 909 |
1 files changed, 909 insertions, 0 deletions
diff --git a/gnu/usr.bin/dc/dc.c b/gnu/usr.bin/dc/dc.c new file mode 100644 index 000000000000..933b24b916cd --- /dev/null +++ b/gnu/usr.bin/dc/dc.c @@ -0,0 +1,909 @@ +/* + * `dc' desk calculator utility. + * + * Copyright (C) 1984, 1993 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) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * 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.; 675 Mass Ave. Cambridge, MA 02139, USA. + */ + +#include <stdio.h> +#include "decimal.h" /* definitions for our decimal arithmetic package */ + +FILE *open_file; /* input file now open */ +int file_count; /* Number of input files not yet opened */ +char **next_file; /* Pointer to vector of names of input files left */ + +struct regstack + { + decimal value; /* Saved value of register */ + struct regstack *rest; /* Tail of list */ + }; + +typedef struct regstack *regstack; + +regstack freeregstacks; /* Chain of free regstack structures for fast realloc */ + +decimal regs[128]; /* "registers", with single-character names */ +regstack regstacks[128]; /* For each register, a stack of previous values */ + +int stacktop; /* index of last used element in stack */ +int stacksize; /* Current allocates size of stack */ +decimal *stack; /* Pointer to computation stack */ + +/* A decimal number can be regarded as a string by + treating its contents as characters and ignoring the + position of its decimal point. + Decimal numbers are marked as strings by having an `after' field of -1 + One use of strings is to execute them as macros. +*/ + +#define STRING -1 + +int macrolevel; /* Current macro nesting; 0 if taking keyboard input */ +int macrostacksize; /* Current allocated size of macrostack and macroindex */ +decimal *macrostack; /* Pointer to macro stack array */ +int *macroindex; /* Pointer to index-within-macro stack array */ + /* Note that an empty macro is popped from the stack + only when an trying to read a character from it + or trying to push another macro. */ + +int ibase; /* Radix for numeric input. */ +int obase; /* Radix for numeric output. */ +int precision; /* Number of digits to keep in multiply and divide. */ + +char *buffer; /* Address of buffer used for reading numbers */ +int bufsize; /* Current size of buffer (made bigger when nec) */ + +decimal dec_read (); +regstack get_regstack (); +int fetch (); +int fgetchar (); +char *concat (); +void pushsqrt (); +void condop (); +void setibase (); +void setobase (); +void setprecision (); +void pushmacro (); +decimal read_string (); +void pushlength (); +void pushscale (); +void unfetch (); +void popmacros (); +void popmacro (); +void popstack (); +void print_obj (); +void print_string (); +void free_regstack (); +void pushreg (); +void execute (); +void fputchar (); +void push (); +void incref (); +void decref (); +void binop (); + +main (argc, argv, env) + int argc; + char **argv, **env; +{ + + ibase = 10; + obase = 10; + precision = 0; + + freeregstacks = 0; + + bzero (regs, sizeof regs); + bzero (regstacks, sizeof regstacks); + + bufsize = 40; + buffer = (char *) xmalloc (40); + + stacksize = 40; + stack = (decimal *) xmalloc (stacksize * sizeof (decimal)); + stacktop = -1; + + macrostacksize = 40; + macrostack = (decimal *) xmalloc (macrostacksize * sizeof (decimal)); + macroindex = (int *) xmalloc (macrostacksize * sizeof (int)); + macrolevel = 0; + /* Initialize for reading input files if any */ + + open_file = 0; + + file_count = argc - 1; + next_file = argv + 1; + + + while (1) + { + execute (); + } +} + +/* Read and execute one command from the current source of input */ + +void +execute () +{ + int c = fetch (); + + if (c < 0) exit (0); + + { + switch (c) + { + case '+': /* Arithmetic operators... */ + binop (decimal_add); + break; + + case '-': + binop (decimal_sub); + break; + + case '*': + binop (decimal_mul_dc); /* Like decimal_mul but hairy + way of deciding precision to keep */ + break; + + case '/': + binop (decimal_div); + break; + + case '%': + binop (decimal_rem); + break; + + case '^': + binop (decimal_expt); + break; + + case '_': /* Begin a negative decimal constant */ + { + decimal tem = dec_read (stdin); + tem->sign = !tem->sign; + push (tem); + } + break; + + case '.': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': /* All these begin decimal constants */ + unfetch (c); + push (dec_read (stdin)); + break; + + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + unfetch (c); + push (dec_read (stdin)); + break; + + case 'c': /* Clear the stack */ + while (stacktop >= 0) + decref (stack[stacktop--]); + break; + + case 'd': /* Duplicate top of stack */ + if (stacktop < 0) + error ("stack empty", 0); + else push (stack[stacktop]); + break; + + case 'f': /* Describe all registers and stack contents */ + { + int regno; + int somereg = 0; /* set to 1 if we print any registers */ + for (regno = 0; regno < 128; regno++) + { + if (regs[regno]) + { + printf ("register %c: ", regno); + print_obj (regs[regno]); + somereg = 1; + printf ("\n"); + } + } + if (somereg) + printf ("\n"); + if (stacktop < 0) + printf ("stack empty\n"); + else + { + int i; + printf ("stack:\n"); + for (i = 0; i <= stacktop; i++) + { + print_obj (stack[stacktop - i]); + printf ("\n"); + } + } + } + break; + + case 'i': /* ibase <- top of stack */ + popstack (setibase); + break; + + case 'I': /* Push current ibase */ + push (decimal_from_int (ibase)); + break; + + case 'k': /* like i, I but for precision instead of ibase */ + popstack (setprecision); + break; + + case 'K': + push (decimal_from_int (precision)); + break; + + case 'l': /* l<x> load register <x> onto stack */ + { + char c1 = fetch (); + if (c1 < 0) exit (0); + if (!regs[c1]) + error ("register %c empty", c1); + else + push (regs[c1]); + } + break; + + case 'L': /* L<x> load register <x> to stack, pop <x>'s own stack */ + { + char c1 = fetch (); + if (c1 < 0) exit (0); + if (!regstacks[c1]) + error ("nothing pushed on register %c", c1); + else + { + regstack r = regstacks[c1]; + if (!regs[c1]) + error ("register %c empty after pop", c1); + else + push (regs[c1]); + regs[c1] = r->value; + regstacks[c1] = r->rest; + free_regstack (r); + } + } + break; + + case 'o': /* o, O like i, I but for obase instead of ibase */ + popstack (setobase); + break; + + case 'O': + push (decimal_from_int (obase)); + break; + + case 'p': /* Print tos, don't pop, do print newline afterward */ + if (stacktop < 0) + error ("stack empty", 0); + else + { + print_obj (stack[stacktop]); + printf ("\n"); + } + break; + + case 'P': /* Print tos, do pop, no newline afterward */ + popstack (print_obj); + break; + + case 'q': /* Exit */ + if (macrolevel) + { popmacro (); popmacro (); } /* decrease recursion level by 2 */ + else + exit (0); /* If not in a macro, exit the program. */ + + break; + + case 'Q': /* Tos says how many levels to exit */ + popstack (popmacros); + break; + + case 's': /* s<x> -- Pop stack and set register <x> */ + if (stacktop < 0) + empty (); + else + { + int c1 = fetch (); + if (c1 < 0) exit (0); + if (regs[c1]) decref (regs[c1]); + regs[c1] = stack[stacktop--]; + } + break; + + case 'S': /* S<x> -- pop stack and push as new value of register <x> */ + if (stacktop < 0) + empty (); + else + { + int c1 = fetch (); + if (c1 < 0) exit (0); + pushreg (c1); + regs[c1] = stack[stacktop--]; + } + break; + + case 'v': /* tos gets square root of tos */ + popstack (pushsqrt); + break; + + case 'x': /* pop stack , call as macro */ + popstack (pushmacro); + break; + + case 'X': /* Pop stack, get # fraction digits, push that */ + popstack (pushscale); + break; + + case 'z': /* Compute depth of stack, push that */ + push (decimal_from_int (stacktop + 1)); + break; + + case 'Z': /* Pop stack, get # digits, push that */ + popstack (pushlength); + break; + + case '<': /* Conditional: pop two numbers, compare, maybe execute register */ + /* Note: for no obvious reason, the standard Unix `dc' + considers < to be true if the top of stack is less + than the next-to-top of stack, + and vice versa for >. + This seems backwards to me, but I am preserving compatibility. */ + condop (1); + break; + + case '>': + condop (-1); + break; + + case '=': + condop (0); + break; + + case '?': /* Read expression from terminal and execute it */ + /* First ignore any leading newlines */ + { + int c1; + while ((c1 = getchar ()) == '\n'); + ungetc (c1, stdin); + } + /* Read a line from the terminal and execute it. */ + pushmacro (read_string ('\n', fgetchar, 0)); + break; + + case '[': /* Begin string constant */ + push (read_string (']', fetch, '[')); + break; + + case ' ': + case '\n': + break; + + default: + error ("undefined command %c", c); + } + } +} + +/* Functionals for performing arithmetic, etc */ + +/* Call the function `op', with the top of stack value as argument, + and then pop the stack. + If the stack is empty, print a message and do not call `op'. */ + +void +popstack (op) + void (*op) (); +{ + if (stacktop < 0) + empty (); + else + { + decimal value = stack[stacktop--]; + op (value); + decref (value); + } +} + +/* Call the function `op' with two arguments taken from the stack top, + then pop those arguments and push the value returned by `op'. + `op' is assumed to return a decimal number. + If there are not two values on the stack, print a message + and do not call `op'. */ + +void +binop (op) + decimal (*op) (); +{ + if (stacktop < 1) + error ("stack empty", 0); + else if (stack[stacktop]->after == STRING || stack[stacktop - 1]->after == STRING) + error ("operands not both numeric"); + else + { + decimal arg2 = stack [stacktop--]; + decimal arg1 = stack [stacktop--]; + + push (op (arg1, arg2, precision)); + + decref (arg1); + decref (arg2); + } +} + +void +condop (cond) + int cond; +{ + int regno = fetch (); + if (!regs[regno]) + error ("register %c is empty", regno); + else if (stacktop < 1) + empty (); + else + { + decimal arg2 = stack[stacktop--]; + decimal arg1 = stack[stacktop--]; + int relation = decimal_compare (arg1, arg2); + decref (arg1); + decref (arg2); + if (cond == relation + || (cond < 0 && relation < 0) + || (cond > 0 && relation > 0)) + pushmacro (regs[regno]); + } +} + +/* Handle the command input source */ + +/* Fetch the next command character from a macro or from the terminal */ + +int +fetch() +{ + int c = -1; + + while (macrolevel && + LENGTH (macrostack[macrolevel-1]) == macroindex[macrolevel-1]) + popmacro(); + if (macrolevel) + return macrostack[macrolevel - 1]->contents[macroindex[macrolevel-1]++]; + while (1) + { + if (open_file) + { + c = getc (open_file); + if (c >= 0) break; + fclose (open_file); + open_file = 0; + } + else if (file_count) + { + open_file = fopen (*next_file++, "r"); + file_count--; + if (!open_file) + perror_with_name (*(next_file - 1)); + } + else break; + } + if (c >= 0) return c; + return getc (stdin); +} + +/* Unread character c on command input stream, whatever it is */ + +void +unfetch (c) + char c; +{ + if (macrolevel) + macroindex[macrolevel-1]--; + else if (open_file) + ungetc (c, open_file); + else + ungetc (c, stdin); +} + +/* Begin execution of macro m. */ + +void +pushmacro (m) + decimal m; +{ + while (macrolevel && + LENGTH (macrostack[macrolevel-1]) == macroindex[macrolevel-1]) + popmacro(); + if (m->after == STRING) + { + if (macrolevel == macrostacksize) + { + macrostacksize *= 2; + macrostack = (decimal *) xrealloc (macrostack, macrostacksize * sizeof (decimal)); + macroindex = (int *) xrealloc (macroindex, macrostacksize * sizeof (int)); + } + macroindex[macrolevel] = 0; + macrostack[macrolevel++] = m; + incref (m); + } + else + { /* Number supplied as a macro! */ + push (m); /* Its effect wouyld be to push the number. */ + } +} + +/* Pop a specified number of levels of macro execution. + The number of levels is specified by a decimal number d. */ + +void +popmacros (d) + decimal d; +{ + int num_pops = decimal_to_int (d); + int i; + for (i = 0; i < num_pops; i++) + popmacro (); +} +/* Exit one level of macro execution. */ + +void +popmacro () +{ + if (!macrolevel) + exit (0); + else + { + decref (macrostack[--macrolevel]); + } +} + +void +push (d) + decimal d; +{ + if (stacktop == stacksize - 1) + stack = (decimal *) xrealloc (stack, (stacksize *= 2) * sizeof (decimal)); + + incref (d); + + stack[++stacktop] = d; +} + +/* Reference counting and storage freeing */ + +void +decref (d) + decimal d; +{ + if (!--d->refcnt) + free (d); +} + +void +incref (d) + decimal d; +{ + d->refcnt++; +} + +empty () +{ + error ("stack empty", 0); +} + +regstack +get_regstack () +{ + if (freeregstacks) + { + regstack r = freeregstacks; + freeregstacks = r ->rest; + return r; + } + else + return (regstack) xmalloc (sizeof (struct regstack)); +} + +void +free_regstack (r) + regstack r; +{ + r->rest = freeregstacks; + freeregstacks = r; +} + +void +pushreg (c) + char c; +{ + regstack r = get_regstack (); + + r->rest = regstacks[c]; + r->value = regs[c]; + regstacks[c] = r; + regs[c] = 0; +} + +/* Input of numbers and strings */ + +/* Return a character read from the terminal. */ + +fgetchar () +{ + return getchar (); +} + +void +fputchar (c) + char (c); +{ + putchar (c); +} + +/* Read text from command input source up to a close-bracket, + make a string out of it, and return it. + If STARTC is nonzero, then it and STOPC must balance when nested. */ + +decimal +read_string (stopc, inputfn, startc) + char stopc; + int (*inputfn) (); + int startc; +{ + int c; + decimal result; + int i = 0; + int count = 0; + + while (1) + { + c = inputfn (); + if (c < 0 || (c == stopc && count == 0)) + { + if (count != 0) + error ("Unmatched `%c'", startc); + break; + } + if (c == stopc) + count--; + if (c == startc) + count++; + if (i + 1 >= bufsize) + buffer = (char *) xrealloc (buffer, bufsize *= 2); + buffer[i++] = c; + } + result = make_decimal (i, 0); + result->after = -1; /* Mark it as a string */ + result->before++; /* but keep the length unchanged */ + bcopy (buffer, result->contents, i); + return result; +} + +/* Read a number from the current input source */ + +decimal +dec_read () +{ + int c; + int i = 0; + + while (1) + { + c = fetch (); + if (! ((c >= '0' && c <= '9') + || (c >= 'A' && c <= 'F') + || c == '.')) + break; + if (i + 1 >= bufsize) + buffer = (char *) xrealloc (buffer, bufsize *= 2); + buffer[i++] = c; + } + buffer[i++] = 0; + unfetch (c); + + return decimal_parse (buffer, ibase); +} + +/* Output of numbers and strings */ + +/* Print the contents of obj, either numerically or as a string, + according to what obj says it is. */ + +void +print_obj (obj) + decimal obj; +{ + if (obj->after == STRING) + print_string (obj); + else + decimal_print (obj, fputchar, obase); +} + +/* Print the contents of the decimal number `string', treated as a string. */ + +void +print_string (string) + decimal string; +{ + char *p = string->contents; + int len = LENGTH (string); + int i; + + for (i = 0; i < len; i++) + { + putchar (*p++); + } +} + +/* Set the input radix from the value of the decimal number d, if valid. */ + +void +setibase (d) + decimal d; +{ + int value = decimal_to_int (d); + if (value < 2 || value > 36) + error ("input radix must be from 2 to 36", 0); + else + ibase = value; +} + +/* Set the output radix from the value of the decimal number d, if valid. */ + +void +setobase (d) + decimal d; +{ + int value = decimal_to_int (d); + if (value < 2 || value > 36) + error ("output radix must be from 2 to 36", 0); + else + obase = value; +} + +/* Set the precision for mul and div from the value of the decimal number d, if valid. */ + +void +setprecision (d) + decimal d; +{ + int value = decimal_to_int (d); + if (value < 0 || value > 30000) + error ("precision must be nonnegative and < 30000", 0); + else + precision = value; +} + +/* Push the number of digits in decimal number d, as a decimal number. */ + +void +pushlength (d) + decimal d; +{ + push (decimal_from_int (LENGTH (d))); +} + +/* Push the number of fraction digits in d. */ + +void +pushscale (d) + decimal d; +{ + push (decimal_from_int (d->after)); +} + +/* Push the square root of decimal number d. */ + +void +pushsqrt (d) + decimal d; +{ + push (decimal_sqrt (d, precision)); +} + +/* Print error message and exit. */ + +fatal (s1, s2) + char *s1, *s2; +{ + error (s1, s2); + exit (1); +} + +/* Print error message. `s1' is printf control string, `s2' is arg for it. */ + +error (s1, s2) + char *s1, *s2; +{ + printf ("dc: "); + printf (s1, s2); + printf ("\n"); +} + +decimal_error (s1, s2) + char *s1, *s2; +{ + error (s1, s2); +} + +perror_with_name (name) + char *name; +{ + extern int errno, sys_nerr; + extern char *sys_errlist[]; + char *s; + + if (errno < sys_nerr) + s = concat ("", sys_errlist[errno], " for %s"); + else + s = "cannot open %s"; + error (s, name); +} + +/* Return a newly-allocated string whose contents concatenate those of s1, s2, s3. */ + +char * +concat (s1, s2, s3) + char *s1, *s2, *s3; +{ + int len1 = strlen (s1), len2 = strlen (s2), len3 = strlen (s3); + char *result = (char *) xmalloc (len1 + len2 + len3 + 1); + + strcpy (result, s1); + strcpy (result + len1, s2); + strcpy (result + len1 + len2, s3); + *(result + len1 + len2 + len3) = 0; + + return result; +} + +/* Like malloc but get fatal error if memory is exhausted. */ + +int +xmalloc (size) + int size; +{ + int result = malloc (size); + if (!result) + fatal ("virtual memory exhausted", 0); + return result; +} + +int +xrealloc (ptr, size) + char *ptr; + int size; +{ + int result = realloc (ptr, size); + if (!result) + fatal ("virtual memory exhausted"); + return result; +} |