722 lines
15 KiB
C
722 lines
15 KiB
C
#if 0
|
|
/*─────────────────────────────────────────────────────────────────╗
|
|
│ To the extent possible under law, Justine Tunney has waived │
|
|
│ all copyright and related or neighboring rights to this file, │
|
|
│ as it is written in the following disclaimers: │
|
|
│ • http://unlicense.org/ │
|
|
│ • http://creativecommons.org/publicdomain/zero/1.0/ │
|
|
╚─────────────────────────────────────────────────────────────────*/
|
|
#endif
|
|
#include "dsp/tty/tty.h"
|
|
#include "libc/alg/arraylist2.h"
|
|
#include "libc/assert.h"
|
|
#include "libc/bits/bits.h"
|
|
#include "libc/bits/morton.h"
|
|
#include "libc/bits/popcnt.h"
|
|
#include "libc/calls/calls.h"
|
|
#include "libc/conv/conv.h"
|
|
#include "libc/conv/itoa.h"
|
|
#include "libc/dce.h"
|
|
#include "libc/fmt/fmt.h"
|
|
#include "libc/limits.h"
|
|
#include "libc/log/log.h"
|
|
#include "libc/macros.h"
|
|
#include "libc/math.h"
|
|
#include "libc/mem/mem.h"
|
|
#include "libc/rand/rand.h"
|
|
#include "libc/runtime/runtime.h"
|
|
#include "libc/stdio/stdio.h"
|
|
#include "libc/str/str.h"
|
|
#include "libc/sysv/consts/ex.h"
|
|
#include "libc/sysv/consts/exit.h"
|
|
#include "libc/sysv/consts/sig.h"
|
|
#include "libc/tinymath/emodl.h"
|
|
#include "libc/x/x.h"
|
|
#include "third_party/dtoa/dtoa.h"
|
|
#include "third_party/getopt/getopt.h"
|
|
|
|
#define INT intmax_t
|
|
#define FLOAT long double
|
|
#define EPSILON 1e-16l
|
|
|
|
#define BANNER \
|
|
"\
|
|
Reverse Polish Notation Calculator\n\
|
|
Copyright 2020 Justine Alexandra Roberts Tunney\n\
|
|
This is fast portable lightweight software. You have the freedom to build\n\
|
|
software just like it painlessly. See http://github.com/jart/cosmopolitan\n\
|
|
"
|
|
|
|
#define USAGE1 \
|
|
"SYNOPSIS\n\
|
|
\n\
|
|
Reverse Polish Notation Calculator\n\
|
|
\n\
|
|
USAGE\n\
|
|
\n"
|
|
|
|
#define USAGE2 \
|
|
" [FLAGS] [FILE...]\n\
|
|
\n\
|
|
FLAGS\n\
|
|
\n\
|
|
-h\n\
|
|
-? shows this information\n\
|
|
-i force interactive mode\n\
|
|
\n\
|
|
KEYBOARD SHORTCUTS\n\
|
|
\n\
|
|
CTRL-D Closes standard input\n\
|
|
CTRL-C Sends SIGINT to program\n\
|
|
CTRL-U Redraw line\n\
|
|
CTRL-L Redraw display\n\
|
|
\n\
|
|
FUNCTIONS\n\
|
|
\n"
|
|
|
|
#define USAGE3 \
|
|
"\n\
|
|
EXAMPLES\n\
|
|
\n\
|
|
calculator.com\n\
|
|
40 2 +\n\
|
|
42\n\
|
|
\n\
|
|
echo '2 2 + . cr' | calculator.com\n\
|
|
4\n\
|
|
\n\
|
|
calculator.com <<EOF\n\
|
|
true assert\n\
|
|
-1 ~ 0 = assert\n\
|
|
3 2 / 1.5 = assert\n\
|
|
81 3 // 27 = assert\n\
|
|
2 8 ** 256 = assert\n\
|
|
pi cos -1 = assert\n\
|
|
pi sqrt pi sqrt * pi - abs epsilon < assert\n\
|
|
nan isnormal ! assert\n\
|
|
0b1010101 0b0110101 ^ 0b1100000 = assert\n\
|
|
0b1010101 popcnt 4 = assert\n\
|
|
EOF\n\
|
|
\n\
|
|
CONTACT\n\
|
|
\n\
|
|
Justine Tunney <jtunney@gmail.com>\n\
|
|
https://github.com/jart/cosmooplitan\n\
|
|
\n\
|
|
"
|
|
|
|
#define CTRL(C) ((C) ^ 0100)
|
|
#define Fatal(...) Log(kFatal, __VA_ARGS__)
|
|
#define Warning(...) Log(kWarning, __VA_ARGS__)
|
|
|
|
enum Severity {
|
|
kFatal,
|
|
kWarning,
|
|
};
|
|
|
|
enum Exception {
|
|
kUnderflow = 1,
|
|
kDivideError,
|
|
};
|
|
|
|
struct Bytes {
|
|
size_t i, n;
|
|
char *p;
|
|
};
|
|
|
|
struct History {
|
|
size_t i, n;
|
|
struct Bytes *p;
|
|
};
|
|
|
|
struct Value {
|
|
enum Type {
|
|
kInt,
|
|
kFloat,
|
|
} t;
|
|
union {
|
|
INT i;
|
|
FLOAT f;
|
|
};
|
|
};
|
|
|
|
struct Function {
|
|
const char sym[16];
|
|
void (*fun)(void);
|
|
const char *doc;
|
|
};
|
|
|
|
jmp_buf thrower;
|
|
const char *file;
|
|
struct Bytes token;
|
|
struct History history;
|
|
struct Value stack[128];
|
|
int sp, comment, line, column, interactive;
|
|
|
|
INT Popcnt(INT x) {
|
|
uintmax_t word = x;
|
|
return popcnt(word >> 64) + popcnt(word);
|
|
}
|
|
|
|
char *Repr(struct Value x) {
|
|
static char buf[64];
|
|
if (x.t == kFloat) {
|
|
g_fmt(buf, x.f);
|
|
} else {
|
|
sprintf(buf, "%jd", x.i);
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
char *ReprStack(void) {
|
|
int i, j, l;
|
|
char *s, *p;
|
|
static char buf[80];
|
|
p = memset(buf, 0, sizeof(buf));
|
|
for (i = 0; i < sp; ++i) {
|
|
s = Repr(stack[i]);
|
|
l = strlen(s);
|
|
if (p + l + 2 > buf + sizeof(buf)) break;
|
|
p = mempcpy(p, s, l);
|
|
*p++ = ' ';
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
void ShowStack(void) {
|
|
if (interactive) {
|
|
printf("\r\e[K");
|
|
fputs(ReprStack(), stdout);
|
|
}
|
|
}
|
|
|
|
void Cr(FILE *f) {
|
|
fputs(interactive || IsWindows() ? "\r\n" : "\n", f);
|
|
}
|
|
|
|
void Log(enum Severity l, const char *fmt, ...) {
|
|
va_list va;
|
|
const char *severity[] = {"FATAL", "WARNING"};
|
|
if (interactive) fflush(/* key triggering throw not echo'd yet */ stdout);
|
|
fprintf(stderr, "%s:%d:%d:%s: ", file, line + 1, column, severity[l & 1]);
|
|
va_start(va, fmt);
|
|
vfprintf(stderr, fmt, va);
|
|
va_end(va);
|
|
Cr(stderr);
|
|
if (l == kFatal) exit(EXIT_FAILURE);
|
|
if (interactive) ShowStack();
|
|
}
|
|
|
|
void __on_arithmetic_overflow(void) {
|
|
Warning("arithmetic overflow");
|
|
}
|
|
|
|
void OnDivideError(void) {
|
|
longjmp(thrower, kDivideError);
|
|
}
|
|
|
|
struct Value Push(struct Value x) {
|
|
if (sp >= ARRAYLEN(stack)) Fatal("stack overflow");
|
|
return (stack[sp++] = x);
|
|
}
|
|
|
|
struct Value Pop(void) {
|
|
if (sp) {
|
|
return stack[--sp];
|
|
} else {
|
|
longjmp(thrower, kUnderflow);
|
|
}
|
|
}
|
|
|
|
INT Popi(void) {
|
|
struct Value x = Pop();
|
|
return x.t == kInt ? x.i : x.f;
|
|
}
|
|
|
|
void Pushi(INT i) {
|
|
struct Value x;
|
|
x.t = kInt;
|
|
x.i = i;
|
|
Push(x);
|
|
}
|
|
|
|
FLOAT Popf(void) {
|
|
struct Value x = Pop();
|
|
return x.t == kInt ? x.i : x.f;
|
|
}
|
|
|
|
void Pushf(FLOAT f) {
|
|
struct Value x;
|
|
x.t = kFloat;
|
|
x.f = f;
|
|
Push(x);
|
|
}
|
|
|
|
void OpDrop(void) {
|
|
Pop();
|
|
}
|
|
void OpDup(void) {
|
|
Push(Push(Pop()));
|
|
}
|
|
void OpExit(void) {
|
|
exit(Popi());
|
|
}
|
|
void OpSrand(void) {
|
|
srand(Popi());
|
|
}
|
|
void OpEmit(void) {
|
|
fputwc(Popi(), stdout);
|
|
}
|
|
void OpCr(void) {
|
|
Cr(stdout);
|
|
}
|
|
void OpPrint(void) {
|
|
printf("%s ", Repr(Pop()));
|
|
}
|
|
void OpComment(void) {
|
|
comment = true;
|
|
}
|
|
void Glue0f(FLOAT fn(void)) {
|
|
Pushf(fn());
|
|
}
|
|
void Glue0i(INT fn(void)) {
|
|
Pushi(fn());
|
|
}
|
|
void Glue1f(FLOAT fn(FLOAT)) {
|
|
Pushf(fn(Popf()));
|
|
}
|
|
void Glue1i(INT fn(INT)) {
|
|
Pushi(fn(Popi()));
|
|
}
|
|
|
|
void OpSwap(void) {
|
|
struct Value a, b;
|
|
b = Pop();
|
|
a = Pop();
|
|
Push(b);
|
|
Push(a);
|
|
}
|
|
|
|
void OpOver(void) {
|
|
struct Value a, b;
|
|
b = Pop();
|
|
a = Pop();
|
|
Push(a);
|
|
Push(b);
|
|
Push(a);
|
|
}
|
|
|
|
void OpKey(void) {
|
|
wint_t c;
|
|
ttyraw(kTtyCursor | kTtySigs | kTtyLfToCrLf);
|
|
c = fgetwc(stdin);
|
|
ttyraw(-1);
|
|
if (c != -1) Pushi(c);
|
|
}
|
|
|
|
void OpAssert(void) {
|
|
if (!Popi()) Fatal("assert failed");
|
|
}
|
|
|
|
void OpExpect(void) {
|
|
if (!Popi()) Warning("expect failed");
|
|
}
|
|
|
|
void OpMeminfo(void) {
|
|
OpCr();
|
|
OpCr();
|
|
fflush(stdout);
|
|
meminfo(fileno(stdout));
|
|
}
|
|
|
|
void Glue2f(FLOAT fn(FLOAT, FLOAT)) {
|
|
FLOAT x, y;
|
|
y = Popf();
|
|
x = Popf();
|
|
Pushf(fn(x, y));
|
|
}
|
|
|
|
void Glue2i(INT fn(INT, INT)) {
|
|
INT x, y;
|
|
y = Popi();
|
|
x = Popi();
|
|
Pushi(fn(x, y));
|
|
}
|
|
|
|
void Glue1g(FLOAT fnf(FLOAT), INT fni(INT)) {
|
|
struct Value x;
|
|
x = Pop();
|
|
switch (x.t) {
|
|
case kInt:
|
|
Pushi(fni(x.i));
|
|
break;
|
|
case kFloat:
|
|
Pushf(fnf(x.f));
|
|
break;
|
|
default:
|
|
Warning("type mismatch");
|
|
}
|
|
}
|
|
|
|
void Glue2g(FLOAT fnf(FLOAT, FLOAT), INT fni(INT, INT)) {
|
|
struct Value x, y;
|
|
y = Pop();
|
|
x = Pop();
|
|
if (x.t == kInt && y.t == kInt) {
|
|
Pushi(fni(x.i, y.i));
|
|
} else if (x.t == kFloat && y.t == kFloat) {
|
|
Pushf(fnf(x.f, y.f));
|
|
} else if (x.t == kInt && y.t == kFloat) {
|
|
Pushf(fnf(x.i, y.f));
|
|
} else if (x.t == kFloat && y.t == kInt) {
|
|
Pushf(fnf(x.f, y.i));
|
|
} else {
|
|
Warning("type mismatch");
|
|
}
|
|
}
|
|
|
|
#define LB {
|
|
#define RB }
|
|
#define SEMI ;
|
|
#define FNTYPEi INT
|
|
#define FNTYPEf FLOAT
|
|
#define FORM(F) LB F SEMI RB
|
|
#define FNPL0(T) void
|
|
#define FNPL1(T) FNTYPE##T x
|
|
#define FNPL2(T) FNTYPE##T x, FNTYPE##T y
|
|
#define FNDEF(A, T, S, C) FNTYPE##T Fn##S##T(FNPL##A(T)) FORM(return C)
|
|
#define FNDEFf(A, S, C) FNDEF(A, f, S, C)
|
|
#define FNDEFi(A, S, C) FNDEF(A, i, S, C)
|
|
#define FNDEFg(A, S, C) FNDEF(A, f, S, C) FNDEF(A, i, S, C)
|
|
#define OPDEF(A, T, S, C) void Op##S(void) FORM(Glue##A##T(Fn##S##T))
|
|
#define OPDEFf(A, S, C) OPDEF(A, f, S, C)
|
|
#define OPDEFi(A, S, C) OPDEF(A, i, S, C)
|
|
#define OPDEFg(A, S, C) void Op##S(void) FORM(Glue##A##g(Fn##S##f, Fn##S##i))
|
|
#define M(A, T, N, S, C, D) FNDEF##T(A, S, C) OPDEF##T(A, S, C)
|
|
#include "tool/build/calculator.inc"
|
|
#undef M
|
|
|
|
const struct Function kFunctions[] = {
|
|
{".", OpPrint, "pops prints value repr"},
|
|
{"#", OpComment, "line comment"},
|
|
#define M(A, T, N, S, C, D) {N, Op##S, D},
|
|
#include "tool/build/calculator.inc"
|
|
#undef M
|
|
{"dup", OpDup, "pushes copy of last item on stack"},
|
|
{"drop", OpDrop, "pops and discards"},
|
|
{"swap", OpSwap, "swaps last two items on stack"},
|
|
{"over", OpOver, "pushes second item on stack"},
|
|
{"cr", OpCr, "prints newline"},
|
|
{"key", OpKey, "reads and pushes unicode character from keyboard"},
|
|
{"emit", OpEmit, "pops and writes unicode character to output"},
|
|
{"assert", OpAssert, "crashes if top of stack isn't nonzero"},
|
|
{"expect", OpExpect, "prints warning if top of stack isn't nonzero"},
|
|
{"meminfo", OpMeminfo, "prints memory mappings"},
|
|
{"exit", OpExit, "exits program with status"},
|
|
};
|
|
|
|
bool CallFunction(const char *sym) {
|
|
int i;
|
|
char s[16];
|
|
strncpy(s, sym, sizeof(s));
|
|
for (i = 0; i < ARRAYLEN(kFunctions); ++i) {
|
|
if (memcmp(kFunctions[i].sym, s, sizeof(s)) == 0) {
|
|
kFunctions[i].fun();
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ConsumeLiteral(const char *literal) {
|
|
char *e;
|
|
struct Value x;
|
|
x.t = kInt;
|
|
x.i = strtoimax(literal, &e, 0);
|
|
if (!e || *e) {
|
|
x.t = kFloat;
|
|
x.f = strtod(literal, &e);
|
|
if (!e || *e) return false;
|
|
}
|
|
Push(x);
|
|
return true;
|
|
}
|
|
|
|
void ConsumeToken(void) {
|
|
enum Exception ex;
|
|
if (!token.i) return;
|
|
token.p[token.i] = 0;
|
|
token.i = 0;
|
|
if (history.i) history.p[history.i - 1].i = 0;
|
|
if (comment) return;
|
|
if (startswith(token.p, "#!")) return;
|
|
switch (setjmp(thrower)) {
|
|
default:
|
|
if (CallFunction(token.p)) return;
|
|
if (ConsumeLiteral(token.p)) return;
|
|
Warning("bad token: %s", token.p);
|
|
break;
|
|
case kUnderflow:
|
|
Warning("stack underflow");
|
|
break;
|
|
case kDivideError:
|
|
Warning("divide error");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CleanupRepl(void) {
|
|
if (sp && interactive) {
|
|
Cr(stdout);
|
|
}
|
|
}
|
|
|
|
void AppendByte(struct Bytes *a, char b) {
|
|
APPEND(&a->p, &a->i, &a->n, &b);
|
|
}
|
|
|
|
void AppendHistory(void) {
|
|
struct Bytes line;
|
|
if (interactive) {
|
|
memset(&line, 0, sizeof(line));
|
|
APPEND(&history.p, &history.i, &history.n, &line);
|
|
}
|
|
}
|
|
|
|
void AppendLine(char b) {
|
|
if (interactive) {
|
|
if (!history.i) AppendHistory();
|
|
AppendByte(&history.p[history.i - 1], b);
|
|
}
|
|
}
|
|
|
|
void Echo(int c) {
|
|
if (interactive) {
|
|
fputc(c, stdout);
|
|
}
|
|
}
|
|
|
|
void Backspace(int c) {
|
|
struct Bytes *line;
|
|
if (interactive) {
|
|
if (history.i) {
|
|
line = &history.p[history.i - 1];
|
|
if (line->i && token.i) {
|
|
do {
|
|
line->i--;
|
|
token.i--;
|
|
} while (line->i && token.i && (line->p[line->i] & 0x80));
|
|
fputs("\e[D \e[D", stdout);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void NewLine(void) {
|
|
line++;
|
|
column = 0;
|
|
comment = false;
|
|
if (interactive) {
|
|
Cr(stdout);
|
|
AppendHistory();
|
|
ShowStack();
|
|
}
|
|
}
|
|
|
|
void KillLine(void) {
|
|
if (interactive) {
|
|
if (history.i) {
|
|
history.p[history.i - 1].i--;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ClearLine(void) {
|
|
if (interactive) {
|
|
if (token.i) sp = 0;
|
|
ShowStack();
|
|
}
|
|
}
|
|
|
|
void RedrawDisplay(void) {
|
|
int i;
|
|
struct Bytes *line;
|
|
if (interactive) {
|
|
printf("\e[H\e[2J%s", BANNER);
|
|
ShowStack();
|
|
if (history.i) {
|
|
line = &history.p[history.i - 1];
|
|
for (i = 0; i < line->i; ++i) {
|
|
fputc(line->p[i], stdout);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void GotoStartOfLine(void) {
|
|
if (interactive) {
|
|
printf("\r");
|
|
}
|
|
}
|
|
|
|
void GotoEndOfLine(void) {
|
|
}
|
|
void GotoPrevLine(void) {
|
|
}
|
|
void GotoNextLine(void) {
|
|
}
|
|
void GotoPrevChar(void) {
|
|
}
|
|
void GotoNextChar(void) {
|
|
}
|
|
|
|
void Calculator(FILE *f) {
|
|
int c;
|
|
while (!feof(f)) {
|
|
switch ((c = getc(f))) {
|
|
case -1:
|
|
case CTRL('D'):
|
|
goto Done;
|
|
case CTRL('@'):
|
|
break;
|
|
case ' ':
|
|
column++;
|
|
Echo(c);
|
|
AppendLine(c);
|
|
ConsumeToken();
|
|
break;
|
|
case '\t':
|
|
column = ROUNDUP(column, 8);
|
|
Echo(c);
|
|
AppendLine(c);
|
|
ConsumeToken();
|
|
break;
|
|
case '\r':
|
|
case '\n':
|
|
ConsumeToken();
|
|
NewLine();
|
|
break;
|
|
case CTRL('A'):
|
|
GotoStartOfLine();
|
|
break;
|
|
case CTRL('E'):
|
|
GotoEndOfLine();
|
|
break;
|
|
case CTRL('P'):
|
|
GotoPrevLine();
|
|
break;
|
|
case CTRL('N'):
|
|
GotoNextLine();
|
|
break;
|
|
case CTRL('B'):
|
|
GotoPrevChar();
|
|
break;
|
|
case CTRL('F'):
|
|
GotoNextChar();
|
|
break;
|
|
case CTRL('L'):
|
|
RedrawDisplay();
|
|
break;
|
|
case CTRL('U'):
|
|
ClearLine();
|
|
break;
|
|
case CTRL('K'):
|
|
KillLine();
|
|
break;
|
|
case CTRL('?'):
|
|
case CTRL('H'):
|
|
Backspace(c);
|
|
break;
|
|
default:
|
|
column++;
|
|
/* fallthrough */
|
|
case 0x80 ... 0xff:
|
|
Echo(c);
|
|
AppendLine(c);
|
|
AppendByte(&token, c);
|
|
break;
|
|
}
|
|
fflush(/* needed b/c this is event loop */ stdout);
|
|
}
|
|
Done:
|
|
CleanupRepl();
|
|
}
|
|
|
|
int Calculate(const char *path) {
|
|
FILE *f;
|
|
file = path;
|
|
line = column = 0;
|
|
if (!(f = fopen(file, "r"))) Fatal("file not found: %s", file);
|
|
Calculator(f);
|
|
return fclose(f);
|
|
}
|
|
|
|
void CleanupTerminal(void) {
|
|
ttyraw(-1);
|
|
}
|
|
|
|
void StartInteractive(void) {
|
|
if (!interactive && !isterminalinarticulate() && isatty(fileno(stdin)) &&
|
|
isatty(fileno(stdout)) && cancolor()) {
|
|
interactive = true;
|
|
}
|
|
if (interactive) {
|
|
fputs(BANNER, stdout);
|
|
fflush(/* needed b/c entering tty mode */ stdout);
|
|
ttyraw(kTtyCursor | kTtySigs);
|
|
atexit(CleanupTerminal);
|
|
}
|
|
}
|
|
|
|
void PrintUsage(int rc, FILE *f) {
|
|
char c;
|
|
int i, j;
|
|
fprintf(f, "%s %s%s", USAGE1, program_invocation_name, USAGE2);
|
|
for (i = 0; i < ARRAYLEN(kFunctions); ++i) {
|
|
fputs(" ", f);
|
|
for (j = 0; j < ARRAYLEN(kFunctions[i].sym); ++j) {
|
|
c = kFunctions[i].sym[j];
|
|
fputc(c ? c : ' ', f);
|
|
}
|
|
fprintf(f, " %s\n", kFunctions[i].doc);
|
|
}
|
|
fputs(USAGE3, f);
|
|
exit(rc);
|
|
}
|
|
|
|
void GetOpts(int argc, char *argv[]) {
|
|
int opt;
|
|
while ((opt = getopt(argc, argv, "?hi")) != -1) {
|
|
switch (opt) {
|
|
case 'i':
|
|
interactive = true;
|
|
break;
|
|
case 'h':
|
|
case '?':
|
|
PrintUsage(EXIT_SUCCESS, stdout);
|
|
default:
|
|
PrintUsage(EX_USAGE, stderr);
|
|
}
|
|
}
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
int i, rc;
|
|
showcrashreports();
|
|
GetOpts(argc, argv);
|
|
xsigaction(SIGFPE, OnDivideError, 0, 0, 0);
|
|
if (optind == argc) {
|
|
file = "/dev/stdin";
|
|
StartInteractive();
|
|
Calculator(stdin);
|
|
return fclose(stdin);
|
|
} else {
|
|
for (i = optind; i < argc; ++i) {
|
|
if (Calculate(argv[i]) == -1) {
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|