/*
 * calc.c - ANSI terminal calculator (C89, old-gcc friendly)
 *
 * Uses ANSI escape codes (colors, clear screen, cursor home) only,
 * no ncurses and no termios. ASCII only, so no font problems.
 *
 * On x86 with gcc it calls the x87 FPU instructions directly
 * (fsqrt, fsin, fcos, fpatan, fyl2x), so it needs NO libm and
 * doubles as an FPU test.
 *
 * Build (no -lm needed):
 *   gcc -O2 -o calc calc.c
 *   gcc -O2 -m386 -o calc calc.c          (very old gcc)
 *   gcc -O2 -DUSE_LIBM -o calc calc.c -lm (use libm instead of raw x87)
 *
 * Usage examples at the prompt:
 *   2+3*4          (2^10)-1        sqrt(2)
 *   sin(pi/6)      4*atan(1)       ln(e)   exp(1)
 *   ans*2          10 % 3          -2^2
 * Commands: help, clear, q (or quit / exit)
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#if defined(__GNUC__) && defined(__i386__) && !defined(USE_LIBM)
#define USE_X87 1
#else
#include <math.h>
#endif

#define ESC      "\033"
#define HIST     10
#define PI       3.14159265358979323846
#define E_CONST  2.71828182845904523536

/* ---------- math layer ---------- */

static double ab(double x) { return x < 0 ? -x : x; }

#ifdef USE_X87
static double m_sqrt(double x)
{ double r; __asm__ __volatile__("fsqrt" : "=t"(r) : "0"(x)); return r; }

static double m_sin(double x)
{ double r; __asm__ __volatile__("fsin" : "=t"(r) : "0"(x)); return r; }

static double m_cos(double x)
{ double r; __asm__ __volatile__("fcos" : "=t"(r) : "0"(x)); return r; }

static double m_atan(double x)
{ double r; __asm__ __volatile__("fld1; fpatan" : "=t"(r) : "0"(x)); return r; }

static double m_ln(double x)
{ double r; __asm__ __volatile__("fldln2; fxch; fyl2x" : "=t"(r) : "0"(x)); return r; }

/* exp via argument halving + Taylor series (plain C, FPU does the work) */
static double m_exp(double x)
{
    int k = 0, i;
    double t = 1.0, s = 1.0;
    while (ab(x) > 0.5) { x /= 2.0; k++; }
    for (i = 1; i < 20; i++) { t *= x / i; s += t; }
    while (k-- > 0) s *= s;
    return s;
}

/* keep fsin/fcos inside their valid range */
static double reduce(double x)
{
    long n;
    if (ab(x) > 1e9) return x;
    n = (long)(x / (2.0 * PI));
    return x - n * (2.0 * PI);
}
#define F_SIN(x)  m_sin(reduce(x))
#define F_COS(x)  m_cos(reduce(x))
#else
#define m_sqrt sqrt
#define m_atan atan
#define m_ln   log
#define m_exp  exp
#define F_SIN(x) sin(x)
#define F_COS(x) cos(x)
#endif

/* ---------- expression parser (recursive descent) ---------- */

static const char *p;
static const char *perr;
static double last = 0.0;

static void fail(const char *m) { if (!perr) perr = m; }
static void skip(void) { while (*p == ' ' || *p == '\t') p++; }

static double expr(void);
static double unary(void);

static double ipow(double x, long n)
{
    double r = 1.0;
    int neg = 0;
    if (n < 0) { neg = 1; n = -n; }
    while (n) { if (n & 1) r *= x; x *= x; n >>= 1; }
    if (neg) {
        if (r == 0.0) { fail("division by zero"); return 0.0; }
        return 1.0 / r;
    }
    return r;
}

static double power(double x, double y)
{
    long n = (long)y;
    if ((double)n == y && n > -1024 && n < 1024)
        return ipow(x, n);
    if (x <= 0.0) { fail("bad base for fractional power"); return 0.0; }
    return m_exp(y * m_ln(x));
}

static double call(const char *n, double v)
{
    if (!strcmp(n, "sqrt")) {
        if (v < 0) { fail("sqrt of negative"); return 0.0; }
        return m_sqrt(v);
    }
    if (!strcmp(n, "sin"))  return F_SIN(v);
    if (!strcmp(n, "cos"))  return F_COS(v);
    if (!strcmp(n, "tan")) {
        double c = F_COS(v);
        if (c == 0.0) { fail("tan undefined"); return 0.0; }
        return F_SIN(v) / c;
    }
    if (!strcmp(n, "atan")) return m_atan(v);
    if (!strcmp(n, "exp"))  return m_exp(v);
    if (!strcmp(n, "abs"))  return ab(v);
    if (!strcmp(n, "ln") || !strcmp(n, "log")) {
        if (v <= 0) { fail("log of non-positive"); return 0.0; }
        if (!strcmp(n, "ln")) return m_ln(v);
        return m_ln(v) / m_ln(10.0);
    }
    fail("unknown function");
    return 0.0;
}

static double primary(void)
{
    double v;
    skip();
    if (*p == '(') {
        p++;
        v = expr();
        skip();
        if (*p != ')') { fail("missing )"); return 0.0; }
        p++;
        return v;
    }
    if (isdigit((unsigned char)*p) || *p == '.') {
        char *e;
        v = strtod(p, &e);
        p = e;
        return v;
    }
    if (isalpha((unsigned char)*p)) {
        char name[16];
        int i = 0;
        while (isalpha((unsigned char)*p) && i < 15) name[i++] = *p++;
        name[i] = 0;
        skip();
        if (*p == '(') {
            p++;
            v = expr();
            skip();
            if (*p != ')') { fail("missing )"); return 0.0; }
            p++;
            return call(name, v);
        }
        if (!strcmp(name, "pi"))  return PI;
        if (!strcmp(name, "e"))   return E_CONST;
        if (!strcmp(name, "ans")) return last;
        fail("unknown name");
        return 0.0;
    }
    fail("syntax error");
    return 0.0;
}

static double powterm(void)
{
    double v = primary();
    skip();
    if (*p == '^') {
        p++;
        v = power(v, unary());   /* right associative */
    }
    return v;
}

static double unary(void)
{
    skip();
    if (*p == '-') { p++; return -unary(); }
    if (*p == '+') { p++; return unary(); }
    return powterm();
}

static double term(void)
{
    double v = unary(), r;
    for (;;) {
        skip();
        if (*p == '*') { p++; v *= unary(); }
        else if (*p == '/') {
            p++;
            r = unary();
            if (r == 0.0) { fail("division by zero"); return 0.0; }
            v /= r;
        } else if (*p == '%') {
            long a, b;
            p++;
            r = unary();
            a = (long)v; b = (long)r;
            if (b == 0) { fail("modulo by zero"); return 0.0; }
            v = (double)(a % b);
        } else break;
    }
    return v;
}

static double expr(void)
{
    double v = term();
    for (;;) {
        skip();
        if (*p == '+') { p++; v += term(); }
        else if (*p == '-') { p++; v -= term(); }
        else break;
    }
    return v;
}

static int evaluate(const char *s, double *out)
{
    p = s;
    perr = NULL;
    *out = expr();
    skip();
    if (!perr && *p) fail("unexpected text");
    return perr == NULL;
}

/* ---------- ANSI user interface ---------- */

static char he[HIST][80];   /* history: expression   */
static char hr[HIST][64];   /* history: result/error */
static int  hok[HIST];
static int  hn = 0;

static void push(const char *e, const char *r, int ok)
{
    int i;
    if (hn == HIST) {
        for (i = 1; i < HIST; i++) {
            strcpy(he[i - 1], he[i]);
            strcpy(hr[i - 1], hr[i]);
            hok[i - 1] = hok[i];
        }
        hn--;
    }
    strncpy(he[hn], e, 79); he[hn][79] = 0;
    strncpy(hr[hn], r, 63); hr[hn][63] = 0;
    hok[hn] = ok;
    hn++;
}

static void draw(void)
{
    int i;
    printf(ESC "[2J" ESC "[H");                              /* clear + home */
    printf(ESC "[1;37;44m%-64s" ESC "[0m\n",
           "  FPU CALCULATOR   (help = commands, q = quit)");
    printf(ESC "[2m%s" ESC "[0m\n",
           "  + - * / % ^  ( )   sqrt sin cos tan atan ln log exp abs   pi e ans");
    printf("\n");
    for (i = 0; i < HIST - hn; i++) printf("\n\n");          /* keep prompt low */
    for (i = 0; i < hn; i++) {
        printf(ESC "[36m  %s" ESC "[0m\n", he[i]);
        if (hok[i]) printf(ESC "[1;32m    = %s" ESC "[0m\n", hr[i]);
        else        printf(ESC "[1;31m    ! %s" ESC "[0m\n", hr[i]);
    }
    printf("\n" ESC "[1;33mcalc>" ESC "[0m ");
    fflush(stdout);
}

static void help(void)
{
    printf(ESC "[2J" ESC "[H");
    printf(ESC "[1;37;44m%-64s" ESC "[0m\n\n", "  HELP");
    printf("  Operators : + - * / %% ^ (power, right assoc)  ( )\n");
    printf("  Functions : sqrt sin cos tan atan ln log exp abs\n");
    printf("  Constants : pi  e  ans (previous result)\n");
    printf("  Examples  : 2+3*4     sqrt(2)     sin(pi/6)\n");
    printf("              4*atan(1) 2^0.5       ans*10\n");
    printf("  Commands  : help, clear, q\n\n");
#ifdef USE_X87
    printf("  Math mode : raw x87 instructions (fsqrt/fsin/fcos/fpatan/fyl2x)\n\n");
#else
    printf("  Math mode : libm\n\n");
#endif
    printf("  Press Enter to continue...");
    fflush(stdout);
}

int main(void)
{
    char line[128];
    char res[64];
    double v;
    size_t n;

    for (;;) {
        draw();
        if (!fgets(line, sizeof(line), stdin)) break;        /* EOF / Ctrl-D */
        n = strlen(line);
        while (n > 0 && (line[n - 1] == '\n' || line[n - 1] == '\r'))
            line[--n] = 0;

        if (n == 0) continue;
        if (!strcmp(line, "q") || !strcmp(line, "quit") || !strcmp(line, "exit"))
            break;
        if (!strcmp(line, "clear")) { hn = 0; continue; }
        if (!strcmp(line, "help")) { help(); fgets(line, sizeof(line), stdin); continue; }

        if (evaluate(line, &v)) {
            sprintf(res, "%.12g", v);
            last = v;
            push(line, res, 1);
        } else {
            push(line, perr, 0);
        }
    }
    printf(ESC "[0m\n");
    return 0;
}
