#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.internal.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/fmt/conv.h" #include "libc/fmt/fmt.h" #include "libc/fmt/itoa.h" #include "libc/limits.h" #include "libc/log/color.internal.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/gdtoa/gdtoa.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 <\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_xfmt_p(buf, &x.f, 16, sizeof(buf), 0); } 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; }