summary refs log tree commit diff
path: root/.bin/rpn.c
blob: 0fcf7933951e0587508fca23e797426364171db7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#if 0
exec cc -Wall -Wextra $@ -ledit -o $(dirname $0)/rpn $0
#endif

#include <assert.h>
#include <ctype.h>
#include <err.h>
#include <histedit.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sysexits.h>

static char *fmt(int radix, int64_t val) {
    static char buf[65];
    if (radix == 2) {
        uint64_t u = val;
        int i = sizeof(buf);
        do {
            buf[--i] = '0' + (u & 1);
        } while (u >>= 1);
        return &buf[i];
    } else if (radix == 8) {
        snprintf(buf, sizeof(buf), "%llo", val);
    } else if (radix == 10) {
        snprintf(buf, sizeof(buf), "%lld", val);
    } else if (radix == 16) {
        snprintf(buf, sizeof(buf), "%llx", val);
    } else abort();
    return buf;
}

static struct {
    int64_t data[1024];
    size_t len;
    int radix;
} stack = { .radix = 10 };

static void push(int64_t val) {
    stack.data[stack.len++] = val;
    assert(stack.len < sizeof(stack.data));
}

static int64_t pop(void) {
    if (stack.len == 0) return 0;
    return stack.data[--stack.len];
}

static bool stack_op(char op) {
    int64_t a, b;
    switch (op) {
        case '$': pop();
        break; case '\\': a = pop(); b = pop(); push(a); push(b);
        break; case ':': a = pop(); push(a); push(a);
        break; case '_': a = pop(); push(-a);
        break; case '+': a = pop(); push(pop() + a);
        break; case '-': a = pop(); push(pop() - a);
        break; case '*': a = pop(); push(pop() * a);
        break; case '/': a = pop(); push(pop() / a);
        break; case '%': a = pop(); push(pop() % a);
        break; case '~': a = pop(); push(~a);
        break; case '&': a = pop(); push(pop() & a);
        break; case '|': a = pop(); push(pop() | a);
        break; case '^': a = pop(); push(pop() ^ a);
        break; case '<': a = pop(); push(pop() << a);
        break; case '>': a = pop(); push(pop() >> a);
        break; case '.': a = pop(); printf("%s\n", fmt(stack.radix, a));
        break; case ',': a = pop(); printf("%c\n", (char) a);
        break; case 'b': stack.radix = 2;
        break; case 'o': stack.radix = 8;
        break; case 'd': stack.radix = 10;
        break; case 'x': stack.radix = 16;
        break; default: return false;
    }
    return true;
}

static char *prompt(EditLine *el __attribute((unused))) {
    static char p[4096];
    if (stack.len == 0) return "[] ";

    size_t q = 0;
    for (size_t i = 0; i < stack.len; ++i) {
        q += (size_t) snprintf(&p[q], sizeof(p) - 2 - q, " %s", fmt(stack.radix, stack.data[i]));
    }
    p[0] = '[';
    p[q] = ']';
    p[++q] = ' ';
    p[++q] = 0;

    return p;
}

int main(int argc __attribute((unused)), char *argv[]) {
    EditLine *el = el_init(argv[0], stdin, stdout, stderr);
    el_set(el, EL_PROMPT, prompt);
    el_set(el, EL_SIGNAL, true);

    for (;;) {
        int count;
        const char *line = el_gets(el, &count);
        if (count < 0) err(EX_IOERR, "el_gets");
        if (!line) break;

        while (*line) {
            if (stack_op(*line)) {
                line++;
            } else {
                char *rest;
                int64_t val = strtoll(line, &rest, stack.radix);
                if (rest != line) {
                    line = rest;
                    push(val);
                } else line++;
            }
        }
    }

    el_end(el);
    return EX_OK;
}