Files
calc/calc.c
Landon Curt Noll db77e29a23 convert ASCII TABs to ASCII SPACEs
Converted all ASCII tabs to ASCII spaces using a 8 character
tab stop, for all files, except for all Makefiles (plus rpm.mk).
The `git diff -w` reports no changes.
2024-07-11 22:03:52 -07:00

891 lines
38 KiB
C

/*
* calc - arbitrary precision calculator
*
* Copyright (C) 1999-2013,2021-2023 David I. Bell, Landon Curt Noll and Ernest Bowen
*
* Calc is open software; you can redistribute it and/or modify it under
* the terms of the version 2.1 of the GNU Lesser General Public License
* as published by the Free Software Foundation.
*
* Calc 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 Lesser General
* Public License for more details.
*
* A copy of version 2.1 of the GNU Lesser General Public License is
* distributed with calc under the filename COPYING-LGPL. You should have
* received a copy with calc; if not, write to Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Under source code control: 1990/02/15 01:48:11
* File existed as early as: before 1990
*
* Share and enjoy! :-) http://www.isthe.com/chongo/tech/comp/calc/
*/
#include <stdio.h>
#include <signal.h>
#if !defined(_WIN32) && !defined(_WIN64)
# include <pwd.h>
#endif
#include <sys/types.h>
#include <ctype.h>
#if defined(_WIN32) || defined(_WIN64)
# include <io.h>
# if !defined(NOTCYGWIN)
/*
* getopt.h file is from the Cygwin GNU library
*
* See:
* http://sources.redhat.com/cygwin/
*/
# include "../getopt/getopt.h"
# endif
# define strdup _strdup
# define isatty _isatty
#endif /* Windows */
#define CALC_C
#include "calc.h"
#include "hist.h"
#include "func.h"
#include "opcodes.h"
#include "conf.h"
#include "token.h"
#include "symbol.h"
#include "have_uid_t.h"
#include "have_const.h"
#include "custom.h"
#include "lib_calc.h"
#include "args.h"
#include "zmath.h"
#include "strl.h"
#include "have_unistd.h"
#if defined(HAVE_UNISTD_H)
#include <unistd.h>
#endif
#include "have_stdlib.h"
#if defined(HAVE_STDLIB_H)
#include <stdlib.h>
#endif
#include "have_strdup.h"
#if !defined(HAVE_STRDUP)
# define strdup(x) calc_strdup((CONST char *)(x))
#endif /* HAVE_STRDUP */
#include "have_unused.h"
#include "errtbl.h"
#include "banned.h" /* include after system header <> includes */
/*
* S_FUNC definitions and functions
*/
S_FUNC void intint(int arg); /* interrupt routine */
S_FUNC void calc_interrupt(char *fmt, ...);
S_FUNC int nextcp(char **cpp, int *ip, int argc, char **argv, bool haveendstr);
S_FUNC void set_run_state(run state);
/*
* Top level calculator routine.
*/
int
main(int argc, char **argv)
{
int want_defhelp = 0; /* 1=> we only want the default help */
int cmdlen; /* length of the command string */
int newcmdlen;
int c; /* option */
int index;
int maxindex;
char *cp;
char *endcp;
char *bp;
bool done = false;
bool havearg;
bool haveendstr;
bool stdin_closed = false;
size_t len;
/*
* parse args
*/
program = argv[0];
script_name = strdup(argv[0]);
if (script_name == NULL) {
fprintf(stderr, "%s: failed to strdup(argv[0])\n", program);
exit(1);
}
cmdbuf[0] = '\0';
cmdlen = 0;
/* process command line options */
index = 1;
cp = endcp = NULL;
maxindex = argc;
havecommands = false;
while (index < maxindex && !done) {
cp = argv[index];
if (*cp == '\0') {
index++;
continue;
}
for (;;) {
havearg = false;
if (*cp != '-') {
done = true;
break;
}
++cp;
if (*cp == '-') {
cp++;
while (*cp == ' ')
++cp;
done = true;
break;
}
for (;;) {
c = *cp;
if (c == '\0' || c == ' ')
break;
switch (c) {
case 'C':
#if defined(CUSTOM)
/*
* error if libcustcalc was compiled with CUSTOM undefined
*/
if (custom_compiled() != true) {
math_error("%s: calc was built with custom functions enabled, "
"custom_compiled() returned: %d != %d",
program, custom_compiled(), true);
exit(1);
}
/*
* indicate that custom functions are now allowed
*/
allow_custom = true;
break;
#else /* CUSTOM */
/*
* error if libcustcalc was compiled with CUSTOM defined
*/
if (custom_compiled() != false) {
math_error("%s: calc was built with custom functions disabled, "
"custom_compiled() returned: %d != %d",
program, custom_compiled(), false);
}
/*
* we are too early in processing to
* call libcalc_call_me_last() -
* nothing to cleanup
*/
fprintf(stderr,
"%s: calc was built with custom"
" functions disabled, -C usage is"
" disallowed\n", program);
exit(1);
#endif /* CUSTOM */
case 'e':
no_env = true;
break;
case 'h':
want_defhelp = 1;
break;
case 'i':
i_flag = true;
break;
case 'm':
cp++;
while (*cp == ' ' || *cp == '\t')
cp++;
if (*cp == '\0') {
index++;
if (index >= argc) {
fprintf(stderr,
"-m expects"
" argument");
exit(2);
}
cp = argv[index];
}
if (*cp < '0' || *cp > '7') {
/*
* we are too early in
* processing to call
* libcalc_call_me_last()
* nothing to cleanup
*/
fprintf(stderr,
"%s: unknown -m arg\n",
program);
exit(3);
}
allow_read = (((*cp-'0') & 04) > 0);
allow_write = (((*cp-'0') & 02) > 0);
allow_exec = (((*cp-'0') & 01) > 0);
cp++;
if (*cp != ' ' && *cp != '\0') {
fprintf(stderr, "??? m-arg");
exit(4);
}
havearg = true;
break;
case 'n':
/*
* -n is deprecated and may be reused
* for another purpose in the future
*/
break;
case 'O':
use_old_std = true;
break;
case 'p':
p_flag = true;
break;
case 'q':
q_flag = true;
break;
case 'u':
u_flag = true;
break;
case 'c':
c_flag = true;
break;
case 'd':
d_flag = true;
break;
case 'v':
/*
* we are too early in processing to
* call libcalc_call_me_last() -
* nothing to cleanup
*/
fputs(CALC_TITLE, stdout);
#if defined(CUSTOM)
fputs(" w/custom functions", stdout);
#else
fputs(" w/o custom functions", stdout);
#endif /* CUSTOM */
printf(" (version %s)\n", version());
exit(0);
case 'D':
/*
* parse the -D arg
*
* Could be:
*
* calc_debug
* calc_debug:resource_debug
* calc_debug:resource_debug:user_debug
*/
if (nextcp(&cp, &index, argc, argv,
false)) {
fprintf(stderr,
"-D expects argument\n");
exit(5);
}
havearg = true;
if (*cp != ':') {
if (*cp < '0' || *cp > '9') {
fprintf(stderr,
"-D expects"
" integer\n");
exit(6);
}
calc_debug = cp;
(void) strtol(cp, &endcp, 10);
cp = endcp;
if (*cp != '\0' &&
*cp != ' ' && *cp != ':') {
fprintf(stderr,
"Bad syntax im -D"
" arg\n");
exit(7);
}
if (*cp != ':') {
if (nextcp(&cp, &index,
argc, argv,
false)
|| *cp != ':')
break;
}
}
if (nextcp(&cp, &index, argc, argv,
false)) {
fprintf(stderr,
"-D : expects"
" argument\n");
exit(8);
}
if (*cp != ':') {
if (*cp < '0' || *cp > '9') {
fprintf(stderr,
"-D : expects"
" integer\n");
exit(9);
}
resource_debug = cp;
(void) strtol(cp, &endcp, 10);
cp = endcp;
if (*cp != '\0' &&
*cp != ' ' && *cp != ':') {
fprintf(stderr,
"Bad syntax im -D"
" : arg\n");
exit(10);
}
if (*cp != ':') {
if (nextcp(&cp, &index,
argc, argv,
false)
|| *cp != ':') {
break;
}
}
}
if (nextcp(&cp, &index, argc, argv,
false)) {
fprintf(stderr, "-D : : expects"
" argument\n");
exit(11);
}
if (*cp < '0' || *cp > '9') {
fprintf(stderr, "-D :: expects"
" integer\n");
exit(12);
}
user_debug = cp;
(void) strtol(cp, &endcp, 10);
cp = endcp;
if (*cp != '\0' && *cp != ' ') {
fprintf(stderr, "Bad syntax in"
" -D : : arg\n");
exit(13);
}
break;
case 'f':
haveendstr = (cp[1] == '\0');
if (nextcp(&cp, &index, argc, argv,
haveendstr)) {
fprintf(stderr, "-f expects"
" filename\n");
exit(14);
}
if (*cp == ';') {
fprintf(stderr,
"-f expects"
" filename\n");
exit(15);
}
havearg = true;
if (cmdlen > 0)
cmdbuf[cmdlen++] = ' ';
strlcpy(cmdbuf + cmdlen, "read ",
sizeof("read "));
cmdlen += sizeof("read ")-1;
cmdbuf[cmdlen] = '\0';
if (strncmp(cp, "-once",
sizeof("-once")) == 0 &&
(cp[sizeof("-once")-1] == '\0' ||
cp[sizeof("-once")-1] == ' ')) {
cp += sizeof("-once")-1;
haveendstr = (*cp == '\0');
strlcpy(cmdbuf+cmdlen, "-once ",
sizeof("-once "));
cmdlen += sizeof("-once ")-1;
cmdbuf[cmdlen] = '\0';
if (nextcp(&cp, &index, argc,
argv, haveendstr)) {
fprintf(stderr, "-f -once"
" expects"
" filename\n");
exit(16);
}
}
bp = cmdbuf + cmdlen;
/*
* duplicate -f filename arg
* as a new script_name value
*/
if (script_name != NULL) {
free(script_name);
}
script_name = NULL;
script_name = strdup(cp);
if (script_name == NULL) {
fprintf(stderr,
"strdup(-f argument)"
"failed\n");
exit(17);
}
/* process -f filename arg */
if (haveendstr) {
len = strlen(cp);
if (len == 0) {
fprintf(stderr,
"Null"
" filename!\n");
exit(18);
}
if (cmdlen + len + 2 > MAXCMD) {
fprintf(stderr,
"Commands too"
" long\n");
exit(19);
}
/* XXX - what if *cp = '\''? */
*bp++ = '\'';
strlcpy(bp, cp, len+1);
bp += len;
*bp++ = '\'';
cp += len;
cmdlen += len + 2;
} else {
do {
if (cmdlen > MAXCMD) {
fprintf(stderr,
"Commands"
" too long\n");
exit(20);
}
*bp++ = *cp++;
cmdlen++;
} while (*cp != '\0' &&
*cp != ';' &&
*cp != ' ');
}
if (*cp == ';')
cp++;
*bp++ = ';';
*bp = '\0';
cmdlen++;
/* -f implies -s */
s_flag = true;
maxindex = index + 1;
break;
case 's':
s_flag = true;
maxindex = index + 1;
break;
default:
/*
* we are too early in processing to
* call libcalc_call_me_last() -
* nothing to cleanup
*/
fprintf(stderr, "Illegal option -%c\n",
c);
fprintf(stderr,
"usage: %s [-c] [-C] [-d] [-e] [-h] [-i] [-m mode]\n"
"\t[-D calc_debug[:resource_debug[:user_debug]]]\n"
"\t[-O] [-p] [-q] [-s] [-u] [-v] "
"[--] [calc_cmd ...]\n"
"usage: %s ... -f filename\n"
"1st cscript line: #/path/to/calc ... -s -f\n",
program, program);
exit(21);
}
if (havearg)
break;
cp++;
}
while (*cp == ' ')
cp++;
if (*cp == '\0') {
index++;
break;
}
}
}
while (index < maxindex) {
size_t cplen;
if (cmdlen > 0)
cmdbuf[cmdlen++] = ' ';
cplen = strlen(cp);
newcmdlen = cmdlen + cplen;
if (newcmdlen > MAXCMD) {
fprintf(stderr,
"%s: commands too long\n",
program);
exit(22);
}
strlcpy(cmdbuf + cmdlen, cp, cplen+1);
cmdbuf[newcmdlen] = '\0';
cmdlen = newcmdlen;
index++;
if (index < maxindex)
cp = argv[index];
}
havecommands = (cmdlen > 0);
if (havecommands) {
cmdbuf[cmdlen++] = '\n';
cmdbuf[cmdlen] = '\0';
if (p_flag != true) {
if (freopen("/dev/null", "r", stdin) == NULL) {
perror("in main: freopen(\"/dev/null\", \"r\", stdin) failed:");
}
stdin_closed = true;
}
}
argc_value = argc - maxindex + 1;
argv_value = argv + maxindex;
/*
* unbuffered mode
*/
if (u_flag) {
if (stdin_closed == false) {
setbuf(stdin, NULL);
}
setbuf(stdout, NULL);
}
/*
* initialize
*/
libcalc_call_me_first();
if (u_flag) {
if (conf->calc_debug & CALCDBG_TTY) {
if (stdin_closed == false) {
printf("main: stdin set to unbuffered before "
"calling libcalc_call_me_first()\n");
} else {
printf("main: stdin closed before "
"calling libcalc_call_me_first()\n");
}
}
}
stdin_tty = isatty(0); /* assume stdin is on fd 0 */
if (conf->calc_debug & CALCDBG_TTY)
printf("main: stdin_tty is %d\n", stdin_tty);
if (want_defhelp) {
givehelp(DEFAULTCALCHELP);
libcalc_call_me_last();
exit(0);
}
/*
* if allowed or needed, print version and setup bindings
*/
if (!havecommands && stdin_tty) {
if (!d_flag) {
printf("%s (version %s)\n", CALC_TITLE, version());
printf("Calc is open software. For license details "
"type: help copyright\n");
printf("[%s]\n\n",
"Type \"exit\" to exit, or \"help\" for help.");
}
switch (hist_init(calcbindings)) {
case HIST_NOFILE:
fprintf(stderr,
"%s: Cannot open bindings file \"%s\", "
"fancy editing disabled.\n",
program, calcbindings);
break;
case HIST_NOTTY:
fprintf(stderr,
"%s: Cannot set terminal modes, "
"fancy editing disabled\n", program);
break;
}
}
/*
* establish error longjump point with initial conditions
*/
if (setjmp(calc_scanerr_jmpbuf) == 0) {
/*
* reset/initialize the computing environment
*/
initialize();
calc_use_scanerr_jmpbuf = 1;
}
/*
* (re)establish the interrupt handler
*/
(void) signal(SIGINT, intint);
/*
* execute calc code based on the run state
*/
if (run_state == RUN_BEGIN) {
if (!q_flag && allow_read) {
set_run_state(RUN_RCFILES);
runrcfiles();
}
set_run_state(RUN_PRE_CMD_ARGS);
}
while (run_state == RUN_RCFILES) {
fprintf(stderr, "Error in rcfiles\n");
if ((c_flag && !stoponerror) || stoponerror < 0) {
getcommands(false);
if (inputlevel() == 0) {
closeinput();
runrcfiles();
set_run_state(RUN_PRE_CMD_ARGS);
} else {
closeinput();
}
} else {
if ((havecommands && !i_flag) || !stdin_tty) {
set_run_state(RUN_EXIT_WITH_ERROR);
} else {
set_run_state(RUN_PRE_CMD_ARGS);
}
}
}
if (run_state == RUN_PRE_CMD_ARGS) {
if (havecommands) {
set_run_state(RUN_CMD_ARGS);
(void) openstring(cmdbuf, strlen(cmdbuf));
getcommands(false);
closeinput();
}
set_run_state(RUN_PRE_TOP_LEVEL);
}
while (run_state == RUN_CMD_ARGS) {
fprintf(stderr, "Error in commands\n");
if ((c_flag && !stoponerror) || stoponerror < 0) {
getcommands(false);
if (inputlevel() == 0)
set_run_state(RUN_PRE_TOP_LEVEL);
closeinput();
} else {
closeinput();
if (!stdin_tty || !i_flag || p_flag) {
set_run_state(RUN_EXIT_WITH_ERROR);
} else {
set_run_state(RUN_PRE_TOP_LEVEL);
}
}
}
if (run_state == RUN_PRE_TOP_LEVEL) {
if (stdin_tty &&
(((havecommands) && !i_flag) || p_flag)) {
set_run_state(RUN_EXIT);
} else {
if (stdin_tty) {
reinitialize();
} else {
resetinput();
openterminal();
}
set_run_state(RUN_TOP_LEVEL);
getcommands(true);
}
if (p_flag || (!i_flag && havecommands))
set_run_state(RUN_EXIT);
}
while (run_state == RUN_TOP_LEVEL) {
if (conf->calc_debug & CALCDBG_RUNSTATE)
printf("main: run_state = TOP_LEVEL\n");
if ((c_flag && !stoponerror) || stoponerror < 0) {
getcommands(true);
if (!inputisterminal()) {
closeinput();
continue;
}
if (!p_flag && i_flag && !stdin_tty) {
closeinput();
if(!freopen("/dev/tty", "r", stdin)) {
#if defined(_WIN32) || defined(_WIN64)
fprintf(stderr,
"/dev/tty does not exist on "
"this operating system. "
"Change operating systems\n"
"or don't use this calc mode "
"in the future, sorry!\n");
#else /* Windows free systems */
fprintf(stderr,
"Unable to associate stdin"
" with /dev/tty");
#endif /* Windows free systems */
set_run_state(RUN_EXIT_WITH_ERROR);
break;
}
stdin_tty = true;
if (conf->calc_debug & CALCDBG_TTY)
printf("main: stdin_tty is %d\n",
stdin_tty);
reinitialize();
}
} else {
if (stdin_tty) {
reinitialize();
getcommands(true);
} else if (inputisterminal() &&
!p_flag && (!havecommands||i_flag)) {
closeinput();
if(!freopen("/dev/tty", "r", stdin)) {
#if defined(_WIN32) || defined(_WIN64)
fprintf(stderr,
"/dev/tty does not exist on "
"this operating system. "
"Change operating systems\n"
"or don't use this calc mode "
"in the future, sorry!\n");
#else /* Windows free systems */
fprintf(stderr,
"Unable to associate stdin"
" with /dev/tty");
#endif /* Windows free systems */
set_run_state(RUN_EXIT_WITH_ERROR);
break;
}
stdin_tty = true;
if (conf->calc_debug & CALCDBG_TTY)
printf("main: stdin_tty is %d\n",
stdin_tty);
reinitialize();
} else {
set_run_state(RUN_EXIT_WITH_ERROR);
}
}
}
if (conf->calc_debug & CALCDBG_RUNSTATE)
printf("main: run_state = %s\n", run_state_name(run_state));
/*
* All done! - Jessica Noll, Age 2
*/
libcalc_call_me_last();
return (run_state == RUN_EXIT_WITH_ERROR ||
run_state == RUN_ZERO) ? 1 : 0;
}
/*
* Interrupt routine.
*
* given:
* arg to keep ANSI C happy
*/
/*ARGSUSED*/
S_FUNC void
intint(int UNUSED(arg))
{
(void) signal(SIGINT, intint);
if (inputwait || (++abortlevel >= ABORT_NOW)) {
calc_interrupt("\nABORT");
not_reached();
}
if (abortlevel >= ABORT_MATH)
_math_abort_ = true;
printf("\n[Abort level %d]\n", abortlevel);
}
/*
* report an interrupt
*/
static void
calc_interrupt(char *fmt, ...)
{
va_list ap;
if (funcname && (*funcname != '*'))
fprintf(stderr, "\"%s\": ", funcname);
if (funcline && ((funcname && (*funcname != '*')) ||
!inputisterminal()))
fprintf(stderr, "line %ld: ", funcline);
va_start(ap, fmt);
vsnprintf(calc_err_msg, MAXERROR, fmt, ap);
va_end(ap);
calc_err_msg[MAXERROR] = '\0'; /* paranoia */
fprintf(stderr, "%s\n\n", calc_err_msg);
funcname = NULL;
if (calc_use_scanerr_jmpbuf != 0) {
longjmp(calc_scanerr_jmpbuf, 22);
} else {
fprintf(stderr, "It is too early provide a command line prompt "
"so we must simply exit. Sorry!\n");
/*
* don't call libcalc_call_me_last() -- we might loop
* and besides ... this is an unusual internal error case
*/
exit(24);
}
}
S_FUNC int
nextcp(char **cpp, int *ip, int argc, char **argv, bool haveendstr)
{
char *cp;
int index;
cp = *cpp;
index = *ip;
if (haveendstr) {
index++;
*ip = index;
if (index >= argc)
return 1;
*cpp = argv[index];
return 0;
}
if (*cp != '\0')
cp++;
for (;;) {
if (*cp == '\0') {
index++;
*ip = index;
if (index >= argc)
return 1;
cp = argv[index];
}
while (*cp == ' ')
cp++;
if (*cp != '\0')
break;
}
*cpp = cp;
return 0;
}
S_FUNC void
set_run_state(run state)
{
if (conf->calc_debug & CALCDBG_RUNSTATE)
printf("main: run_state from %s to %s\n",
run_state_name(run_state),
run_state_name(state));
run_state = state;
}