dc: support for bases 2 and 8, by Nate Case (ncase AT xes-inc.com)
[oweals/busybox.git] / miscutils / dc.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
4  */
5
6 #include "libbb.h"
7 #include <math.h>
8
9 /* Tiny RPN calculator, because "expr" didn't give me bitwise operations. */
10
11
12 struct globals {
13         unsigned pointer;
14         unsigned base;
15         double stack[1];
16 };
17 enum { STACK_SIZE = (COMMON_BUFSIZE - offsetof(struct globals, stack)) / sizeof(double) };
18 #define G (*(struct globals*)&bb_common_bufsiz1)
19 #define pointer   (G.pointer   )
20 #define base      (G.base      )
21 #define stack     (G.stack     )
22 #define INIT_G() do { } while (0)
23
24
25 static void push(double a)
26 {
27         if (pointer >= STACK_SIZE)
28                 bb_error_msg_and_die("stack overflow");
29         stack[pointer++] = a;
30 }
31
32 static double pop(void)
33 {
34         if (pointer == 0)
35                 bb_error_msg_and_die("stack underflow");
36         return stack[--pointer];
37 }
38
39 static void add(void)
40 {
41         push(pop() + pop());
42 }
43
44 static void sub(void)
45 {
46         double subtrahend = pop();
47
48         push(pop() - subtrahend);
49 }
50
51 static void mul(void)
52 {
53         push(pop() * pop());
54 }
55
56 #if ENABLE_FEATURE_DC_LIBM
57 static void power(void)
58 {
59         double topower = pop();
60
61         push(pow(pop(), topower));
62 }
63 #endif
64
65 static void divide(void)
66 {
67         double divisor = pop();
68
69         push(pop() / divisor);
70 }
71
72 static void mod(void)
73 {
74         unsigned d = pop();
75
76         push((unsigned) pop() % d);
77 }
78
79 static void and(void)
80 {
81         push((unsigned) pop() & (unsigned) pop());
82 }
83
84 static void or(void)
85 {
86         push((unsigned) pop() | (unsigned) pop());
87 }
88
89 static void eor(void)
90 {
91         push((unsigned) pop() ^ (unsigned) pop());
92 }
93
94 static void not(void)
95 {
96         push(~(unsigned) pop());
97 }
98
99 static void set_output_base(void)
100 {
101         static const char bases[] ALIGN1 = { 2, 8, 10, 16, 0 };
102         unsigned b = (unsigned)pop();
103
104         base = *strchrnul(bases, b);
105         if (base == 0) {
106                 bb_error_msg("error, base %u is not supported", b);
107                 base = 10;
108         }
109 }
110
111 static void print_base(double print)
112 {
113         unsigned x, i;
114
115         if (base == 10) {
116                 printf("%g\n", print);
117                 return;
118         }
119
120         x = (unsigned)print;
121         switch (base) {
122         case 16:
123                 printf("%x\n", x);
124                 break;
125         case 8:
126                 printf("%o\n", x);
127                 break;
128         default: /* base 2 */
129                 i = (unsigned)INT_MAX + 1;
130                 do {
131                         if (x & i) break;
132                         i >>= 1;
133                 } while (i > 1);
134                 do {
135                         bb_putchar('1' - !(x & i));
136                         i >>= 1;
137                 } while (i);
138                 bb_putchar('\n');
139         }
140 }
141
142 static void print_stack_no_pop(void)
143 {
144         unsigned i = pointer;
145         while (i)
146                 print_base(stack[--i]);
147 }
148
149 static void print_no_pop(void)
150 {
151         print_base(stack[pointer-1]);
152 }
153
154 struct op {
155         const char name[4];
156         void (*function) (void);
157 };
158
159 static const struct op operators[] = {
160         {"+",   add},
161         {"add", add},
162         {"-",   sub},
163         {"sub", sub},
164         {"*",   mul},
165         {"mul", mul},
166         {"/",   divide},
167         {"div", divide},
168 #if ENABLE_FEATURE_DC_LIBM
169         {"**",  power},
170         {"exp", power},
171         {"pow", power},
172 #endif
173         {"%",   mod},
174         {"mod", mod},
175         {"and", and},
176         {"or",  or},
177         {"not", not},
178         {"eor", eor},
179         {"xor", eor},
180         {"p", print_no_pop},
181         {"f", print_stack_no_pop},
182         {"o", set_output_base},
183         { /* zero filled */ }
184 };
185
186 static void stack_machine(const char *argument)
187 {
188         char *endPointer;
189         double d;
190         const struct op *o = operators;
191
192         if (argument == 0)
193                 return;
194
195         d = strtod(argument, &endPointer);
196
197         if (endPointer != argument) {
198                 push(d);
199                 return;
200         }
201
202         while (o->name[0]) {
203                 if (strcmp(o->name, argument) == 0) {
204                         o->function();
205                         return;
206                 }
207                 o++;
208         }
209         bb_error_msg_and_die("%s: syntax error", argument);
210 }
211
212 /* return pointer to next token in buffer and set *buffer to one char
213  * past the end of the above mentioned token
214  */
215 static char *get_token(char **buffer)
216 {
217         char *current = skip_whitespace(*buffer);
218         if (*current != '\0') {
219                 *buffer = skip_non_whitespace(current);
220                 return current;
221         }
222         return NULL;
223 }
224
225 int dc_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
226 int dc_main(int argc UNUSED_PARAM, char **argv)
227 {
228         INIT_G();
229
230         argv++;
231         if (!argv[0]) {
232                 /* take stuff from stdin if no args are given */
233                 char *line;
234                 char *cursor;
235                 char *token;
236                 while ((line = xmalloc_fgetline(stdin)) != NULL) {
237                         cursor = line;
238                         while (1) {
239                                 token = get_token(&cursor);
240                                 if (!token) break;
241                                 *cursor++ = '\0';
242                                 stack_machine(token);
243                         }
244                         free(line);
245                 }
246         } else {
247                 if (argv[0][0] == '-')
248                         bb_show_usage();
249                 do {
250                         stack_machine(*argv);
251                 } while (*++argv);
252         }
253         return EXIT_SUCCESS;
254 }