diff --git a/sys/man/1/pc b/sys/man/1/pc new file mode 100644 index 000000000..38b0314eb --- /dev/null +++ b/sys/man/1/pc @@ -0,0 +1,132 @@ +.TH PC 1 +.SH NAME +pc \- programmer's calculator +.SH SYNOPSYS +.B pc +[ +.B -n +] +.SH DESCRIPTION +.I Pc +is an arbitrary precision calculator with a special emphasis on supporting two's complement bit operations and working with different number bases. +.PP +.I Pc +reads input statements which are either expressions or control statements. +Multiple statements in one line can be separated by semicolons. +.I Pc +prints the value of all expressions that are not terminated by a semicolon. +.PP +Expressions can use the C-like operators +.TP +.B + - * ** \fR(exponentiation\fR) +.TP +.B / % \fR(Euclidean division, by default\fR) +.TP +.B "& | ^ ~ ! << >>" +.TP +.B "&& || \fR(returning the second argument, if appropriate)" +.TP +.B < >= < <= == != +.PP +Variables can be defined using +.BR = . +The builtin variable +.B @ +always refers to the last printed result. +.PP +Numbers can use the prefixes +.B 0b +(binary), +.B 0 +(octal), +.B 0d +(decimal) and +.B 0x +(hexadecimal). +.B _ +in numbers can be added for readability and is ignored. +.SS Builtin functions +.TF xtend(n,m) +.TP +.I bin(n) +Display \fIn\fR in binary. +.TP +.I oct(n) +Display \fIn\fR in octal. +.TP +.I dec(n) +Display \fIn\fR in decimal. +.TP +.I hex(n) +Display \fIn\fR in hexadecimal. +.TP +.I abs(n) +Absolute value of \fIn\fR. +.TP +.I round(n,m) +\fIn\fR rounded to the nearest multiple of \fIm\fR. +Numbers exactly halfway between are rounded to the next even multiple. +.TP +.I floor(n,m) +\fIn\fR rounded down to the next multiple of \fIm\fR. +.TP +.I ceil(n,m) +\fIn\fR rounded up to the next multiple of \fIm\fR. +.TP +.I trunc(n,m) +\fIn\fR truncated to \fIm\fR bits. +.TP +.I xtend(n,m) +\fIn\fR truncated to \fIm\fR bits, with the highest bit interpreted as a sign bit. +.TP +.I ubits(n) +The minimum number of bits required to represent \fIn\fR as an unsigned number. +.TP +.I sbits(n) +The minimum number of bits required to represent \fIn\fR as an signed number. +.SS Control statements +.PP +Control statements are always evaluated with default input base 10. +.TP +\fL_\fR \fIn\fR +If \fIn\fR ≠ 0, insert +.B _ +in all printed numbers, every +.I n +digits. +.TP +\fL<\fR \fIn\fR +Set the default input base to \fIn\fR (default 10). +The input base can always be overriden by the base prefixes defined above. +.TP +\fL>\fR \fIn\fR +Set the output base to \fIn\fR. +If \fIn\fR = 0 (default), print each number in the base it was input in. +.TP +\fL/\fR 0 +Use Euclidean division (default). +\fIa\fR / \fIb\fR is rounded towards ±∞ (opposite sign as \fIb\fR). +\fIa\fR % \fIb\fR is always non-negative. +.TP +\fL/\fR 1 +Use truncating division (same as C). +\fIa\fR / \fIb\fR is rounded towards zero. +\fIa\fR % \fIb\fR can be negative. +.SH SOURCE +.B /sys/src/cmd/pc.y +.SH "SEE ALSO" +.IR bc (1), +.IR hoc (1) +.SH BUGS +With the input base set to 16, terms such as +.B ABC +are ambiguous. +They are interpreted as numbers only if there is no function or variable of the same name. +To force interpretation as a number, use the \fL0x\fR prefix. +.PP +Arbitrary bases should be supported, but are not supported by the +.IR mp (2) +string functions. +.SH HISTORY +.I Pc +first appeared in 9front (August, 2016). diff --git a/sys/src/cmd/mkfile b/sys/src/cmd/mkfile index b315e50f5..052bf4f5e 100644 --- a/sys/src/cmd/mkfile +++ b/sys/src/cmd/mkfile @@ -48,7 +48,7 @@ all:V: $PROGS dirs &:n: $O.& mv $O.$stem $stem -%.tab.h %.tab.c: %.y +%.tab.h %.tab.c:D: %.y $YACC $YFLAGS -s $stem $prereq %.install:V: $BIN/% @@ -108,7 +108,7 @@ installall:V: %.acid: %.$O $HFILES $CC $CFLAGS -a $stem.c >$target -(bc|units|mpc).c:R: \1.tab.c +(bc|units|mpc|pc).c:R: \1.tab.c mv $stem1.tab.c $stem1.c $BIN/init: $O.init diff --git a/sys/src/cmd/pc.y b/sys/src/cmd/pc.y new file mode 100644 index 000000000..4549634de --- /dev/null +++ b/sys/src/cmd/pc.y @@ -0,0 +1,804 @@ +%{ +#include +#include +#include +#include +#include +#include +#include + +int inbase = 10, outbase, divmode, sep, fail, prompt; +enum { MAXARGS = 16 }; + +typedef struct Num Num; +struct Num { + mpint; + int b; + Ref; +}; +enum { STRONG = 0x100 }; + +void * +emalloc(int n) +{ + void *v; + + v = malloc(n); + if(v == nil) + sysfatal("malloc: %r"); + memset(v, 0, n); + setmalloctag(v, getcallerpc(&n)); + return v; +} + +void * +error(char *fmt, ...) +{ + va_list va; + Fmt f; + char buf[256]; + + fmtfdinit(&f, 2, buf, sizeof(buf)); + va_start(va, fmt); + fmtvprint(&f, fmt, va); + fmtrune(&f, '\n'); + fmtfdflush(&f); + va_end(va); + fail++; + return nil; +} + +Num * +numalloc(void) +{ + Num *r; + + r = emalloc(sizeof(Num)); + r->ref = 1; + r->p = emalloc(0); + mpassign(mpzero, r); + return r; +} + +Num * +numincref(Num *n) +{ + incref(n); + return n; +} + +Num * +numdecref(Num *n) +{ + if(n == nil) return nil; + if(decref(n) == 0){ + free(n->p); + free(n); + return nil; + } + return n; +} + +Num * +nummod(Num *n) +{ + Num *m; + + if(n == nil) return nil; + if(n->ref == 1) return n; + m = numalloc(); + mpassign(n, m); + m->b = n->b; + numdecref(n); + return m; +} + +int +basemax(int a, int b) +{ + if(a == STRONG+10 && b >= STRONG) return b; + if(b == STRONG+10 && a >= STRONG) return a; + if(a == 10) return b; + if(b == 10) return a; + if(a < b) return b; + return a; +} + +%} +%token LOEXP LOLSH LORSH LOEQ LONE LOLE LOGE LOLAND LOLOR +%{ + +Num * +numbin(int op, Num *a, Num *b) +{ + mpint *r; + + if(fail || a == nil || b == nil) return nil; + a = nummod(a); + a->b = basemax(a->b, b->b); + switch(op){ + case '+': mpadd(a, b, a); break; + case '-': mpsub(a, b, a); break; + case '*': mpmul(a, b, a); break; + case '/': + if(mpcmp(b, mpzero) == 0){ + numdecref(a); + numdecref(b); + return error("division by zero"); + } + r = mpnew(0); + mpdiv(a, b, a, r); + if(!divmode && r->sign < 0) + if(b->sign > 0) + mpsub(a, mpone, a); + else + mpadd(a, mpone, a); + mpfree(r); + break; + case '%': + if(mpcmp(b, mpzero) == 0){ + numdecref(a); + numdecref(b); + return error("division by zero"); + } + mpdiv(a, b, nil, a); + if(!divmode && a->sign < 0) + if(b->sign > 0) + mpadd(a, b, a); + else + mpsub(a, b, a); + break; + case '&': mpand(a, b, a); break; + case '|': mpor(a, b, a); break; + case '^': mpxor(a, b, a); break; + case LOEXP: + if(mpcmp(b, mpzero) < 0){ + numdecref(a); + numdecref(b); + return error("negative exponent"); + } + mpexp(a, b, nil, a); + break; + case LOLSH: + if(mpsignif(b) >= 31){ + if(b->sign > 0) + error("left shift overflow"); + itomp(-(mpcmp(a, mpzero) < 0), a); + }else + mpasr(a, -mptoi(b), a); + break; + case LORSH: + if(mpsignif(b) >= 31){ + if(b->sign < 0) + error("right shift overflow"); + itomp(-(mpcmp(a, mpzero) < 0), a); + }else + mpasr(a, mptoi(b), a); + break; + case '<': itomp(mpcmp(a, b) < 0, a); break; + case '>': itomp(mpcmp(a, b) > 0, a); break; + case LOLE: itomp(mpcmp(a, b) <= 0, a); break; + case LOGE: itomp(mpcmp(a, b) >= 0, a); break; + case LOEQ: itomp(mpcmp(a, b) == 0, a); break; + case LONE: itomp(mpcmp(a, b) != 0, a); break; + case LOLAND: + a->b = b->b; + if(mpcmp(a, mpzero) == 0) + mpassign(mpzero, a); + else + mpassign(b, a); + break; + case LOLOR: + a->b = b->b; + if(mpcmp(a, mpzero) != 0) + mpassign(mpone, a); + else + mpassign(b, a); + break; + } + numdecref(b); + return a; +} + +typedef struct Symbol Symbol; +struct Symbol { + enum { + SYMNONE, + SYMVAR, + SYMFUNC, + } t; + Num *val; + int nargs; + Num *(*func)(int, Num **); + char *name; + Symbol *next; +}; +Symbol *symtab[64]; + +Symbol * +getsym(char *n, int mk) +{ + Symbol **p; + for(p = &symtab[*n&63]; *p != nil; p = &(*p)->next) + if(strcmp((*p)->name, n) == 0) + return *p; + if(!mk) return nil; + *p = emalloc(sizeof(Symbol)); + (*p)->name = strdup(n); + return *p; +} + +void +numprint(Num *n) +{ + int b; + int l, i; + char *s, *t, *p, *q; + + if(n == nil) return; + if(n->b >= STRONG || n->b != 0 && outbase == 0) + b = n->b & ~STRONG; + else if(outbase == 0) + b = 10; + else + b = outbase; + s = mptoa(n, b, nil, 0); + l = strlen(s); + t = emalloc(l * 2 + 4); + q = t + l * 2 + 4; + *--q = 0; + for(p = s + l - 1, i = 0; p >= s && *p != '-'; p--, i++){ + if(sep != 0 && i == sep){ + *--q = '_'; + i = 0; + } + if(*p >= 'A') + *--q = *p + ('a' - 'A'); + else + *--q = *p; + } + if(mpcmp(n, mpzero) != 0) + switch(b){ + case 16: *--q = 'x'; *--q = '0'; break; + case 10: if(outbase != 0 && outbase != 10) {*--q = 'd'; *--q = '0';} break; + case 8: *--q = '0'; break; + case 2: *--q = 'b'; *--q = '0'; break; + } + if(p >= s) + *--q = '-'; + print("%s\n", q); + free(s); + free(t); +} + +Num * +fncall(Symbol *s, int n, Num **x) +{ + int i; + + if(s->t != SYMFUNC) + return error("%s: not a function", s->name); + else if(s->nargs >= 0 && s->nargs != n) + return error("%s: wrong number of arguments", s->name); + for(i = 0; i < n; i++) + if(x[i] == nil) + return nil; + return s->func(n, x); +} + +Num * +hexfix(Symbol *s) +{ + char *b, *p, *q; + + if(inbase != 16) return nil; + if(s->val != nil) return numincref(s->val); + if(strspn(s->name, "0123456789ABCDEFabcdef_") != strlen(s->name)) return nil; + b = strdup(s->name); + for(p = b, q = b; *p != 0; p++) + if(*p != '_') + *q++ = *p; + *q = 0; + s->val = numalloc(); + strtomp(b, nil, 16, s->val); + s->val->b = 16; + free(b); + return numincref(s->val); +} + +%} + +%union { + Num *n; + Symbol *sym; + struct { + Num *x[MAXARGS]; + int n; + } args; +} + +%token LNUM +%token LSYMB + +%type expr +%type elist elist1 + +%right '=' +%right '?' +%left LOLOR +%left LOLAND +%left '|' +%left '^' +%left '&' +%left LOEQ LONE +%left '<' '>' LOLE LOGE +%left LOLSH LORSH +%left '+' '-' +%left unary +%left '*' '/' '%' +%right LOEXP + +%{ + int save; + Num *last; + Num *lastp; +%} + +%% + +input: | input line '\n' { + if(!fail && last != nil) { + numprint(last); + numdecref(lastp); + lastp = last; + } + fail = 0; + last = nil; + } + +line: stat + | line ';' stat + +stat: { last = nil; } + | expr { last = $1; } + | '_' { save = inbase; inbase = 10; } expr { + inbase = save; + if(mpcmp($3, mpzero) < 0) + error("no."); + if(!fail) + sep = mptoi($3); + numdecref($3); + numdecref(last); + last = nil; + } + | '<' { save = inbase; inbase = 10; } expr { + inbase = save; + if(!fail) + inbase = mptoi($3); + if(inbase != 2 && inbase != 8 && inbase != 10 && inbase != 16){ + error("no."); + inbase = save; + } + numdecref($3); + numdecref(last); + last = nil; + } + | '>' { save = inbase; inbase = 10; } expr { + inbase = save; + save = outbase; + if(!fail) + outbase = mptoi($3); + if(outbase != 2 && outbase != 8 && outbase != 10 && outbase != 16){ + error("no."); + outbase = save; + } + numdecref($3); + numdecref(last); + last = nil; + } + | '/' { save = inbase; inbase = 10; } expr { + inbase = save; + save = divmode; + if(!fail) + divmode = mptoi($3); + if(divmode != 0 && divmode != 1){ + error("no."); + divmode = save; + } + numdecref($3); + numdecref(last); + last = nil; + } + | error + +expr: LNUM + | '(' expr ')' { $$ = $2; } + | expr '+' expr { $$ = numbin('+', $1, $3); } + | expr '-' expr { $$ = numbin('-', $1, $3); } + | expr '*' expr { $$ = numbin('*', $1, $3); } + | expr '/' expr { $$ = numbin('/', $1, $3); } + | expr '%' expr { $$ = numbin('%', $1, $3); } + | expr '&' expr { $$ = numbin('&', $1, $3); } + | expr '|' expr { $$ = numbin('|', $1, $3); } + | expr '^' expr { $$ = numbin('^', $1, $3); } + | expr LOEXP expr { $$ = numbin(LOEXP, $1, $3); } + | expr LOLSH expr { $$ = numbin(LOLSH, $1, $3); } + | expr LORSH expr { $$ = numbin(LORSH, $1, $3); } + | expr LOEQ expr { $$ = numbin(LOEQ, $1, $3); } + | expr LONE expr { $$ = numbin(LONE, $1, $3); } + | expr '<' expr { $$ = numbin('<', $1, $3); } + | expr '>' expr { $$ = numbin('>', $1, $3); } + | expr LOLE expr { $$ = numbin(LOLE, $1, $3); } + | expr LOGE expr { $$ = numbin(LOGE, $1, $3); } + | expr LOLAND expr { $$ = numbin(LOLAND, $1, $3); } + | expr LOLOR expr { $$ = numbin(LOLOR, $1, $3); } + | '+' expr %prec unary { $$ = $2; } + | '-' expr %prec unary { $$ = nummod($2); if($$ != nil) mpsub(mpzero, $$, $$); } + | '~' expr %prec unary { $$ = nummod($2); if($$ != nil) mpnot($$, $$); } + | '!' expr %prec unary { $$ = nummod($2); if($$ != nil) itomp(mpcmp($$, mpzero) == 0, $$); } + | expr '?' expr ':' expr %prec '?' { + if($1 == nil || mpcmp($1, mpzero) != 0){ + $$ = $3; + numdecref($5); + }else{ + $$ = $5; + numdecref($3); + } + numdecref($1); + } + | LSYMB '(' elist ')' { $$ = fncall($1, $3.n, $3.x); } + | LSYMB { + Num *n; + $$ = nil; + switch($1->t){ + case SYMVAR: $$ = numincref($1->val); break; + case SYMNONE: + n = hexfix($1); + if(n != nil) $$ = n; + else error("%s undefined", $1->name); + break; + case SYMFUNC: error("%s is a function", $1->name); break; + default: error("%s invalid here", $1->name); + } + } + | LSYMB '=' expr { + if($1->t != SYMNONE && $1->t != SYMVAR) + error("%s redefined", $1->name); + else if(!fail){ + $1->t = SYMVAR; + numdecref($1->val); + $1->val = numincref($3); + } + $$ = $3; + } + | '@' { + $$ = lastp; + if($$ == nil) error("no last result"); + else numincref($$); + } + +elist: { $$.n = 0; } | elist1 +elist1: expr { $$.x[0] = $1; $$.n = 1; } + | elist1 ',' expr { + $$ = $1; + if($$.n >= MAXARGS) + error("too many arguments"); + else + $$.x[$$.n++] = $3; + } + +%% + +typedef struct Keyword Keyword; +struct Keyword { + char *name; + int tok; +}; + +Keyword ops[] = { + "**", LOEXP, + "<<", LOLSH, + "<=", LOLE, + ">>", LORSH, + ">=", LOGE, + "==", LOEQ, + "&&", LOLAND, + "||", LOLOR, + "", 0, +}; + +Keyword *optab[128]; + + +Biobuf *in; +int prompted; + +int +yylex(void) +{ + int c, b; + char buf[512], *p; + Keyword *kw; + + if(prompt && !prompted) {print("; "); prompted = 1;} + do + c = Bgetc(in); + while(c != '\n' && isspace(c)); + if(c == '\n') prompted = 0; + if(isdigit(c)){ + for(p = buf, *p++ = c; c = Bgetc(in), isalnum(c) || c == '_'; ) + if(p < buf + sizeof(buf) - 1) + *p++ = c; + *p = 0; + Bungetc(in); + b = inbase; + p = buf; + if(*p == '0'){ + p++; + switch(*p++){ + case 0: p -= 2; break; + case 'b': case 'B': b = 2; break; + case 'd': case 'D': b = 10; break; + case 'x': case 'X': b = 16; break; + default: p--; b = 8; break; + } + } + yylval.n = numalloc(); + strtomp(p, &p, b, yylval.n); + if(*p != 0) error("not a number: %s", buf); + yylval.n->b = b; + return LNUM; + } + if(isalpha(c) || c >= 0x80 || c == '_'){ + for(p = buf, *p++ = c; c = Bgetc(in), isalnum(c) || c >= 0x80 || c == '_'; ) + if(p < buf + sizeof(buf) - 1) + *p++ = c; + *p = 0; + Bungetc(in); + if(buf[0] == '_' && buf[1] == 0) return '_'; + yylval.sym = getsym(buf, 1); + return LSYMB; + } + if(c < 128 && (kw = optab[c], kw != nil)){ + b = Bgetc(in); + for(; kw->name[0] == c; kw++) + if(kw->name[0] == b) + return kw->tok; + Bungetc(in); + } + return c; +} + +void +yyerror(char *msg) +{ + error("%s", msg); +} + +void +regfunc(char *n, Num *(*f)(int, Num **), int nargs) +{ + Symbol *s; + + s = getsym(n, 1); + s->t = SYMFUNC; + s->func = f; + s->nargs = nargs; +} + +int +toint(Num *n, int *p, int mustpos) +{ + if(mpsignif(n) > 31 || mustpos && mpcmp(n, mpzero) < 0){ + error("invalid argument"); + return -1; + } + if(p != nil) + *p = mptoi(n); + return 0; +} + +Num * +fnhex(int, Num **a) +{ + Num *r; + + r = nummod(a[0]); + r->b = STRONG | 16; + return r; +} + +Num * +fndec(int, Num **a) +{ + Num *r; + + r = nummod(a[0]); + r->b = STRONG | 10; + return r; +} + +Num * +fnoct(int, Num **a) +{ + Num *r; + + r = nummod(a[0]); + r->b = STRONG | 8; + return r; +} + +Num * +fnbin(int, Num **a) +{ + Num *r; + + r = nummod(a[0]); + r->b = STRONG | 2; + return r; +} + +Num * +fnabs(int, Num **a) +{ + Num *r; + + r = nummod(a[0]); + r->sign = 1; + return r; +} + +Num * +fnround(int, Num **a) +{ + mpint *q, *r; + int i; + + if(mpcmp(a[1], mpzero) <= 0){ + numdecref(a[0]); + numdecref(a[1]); + return error("invalid argument"); + } + q = mpnew(0); + r = mpnew(0); + a[0] = nummod(a[0]); + mpdiv(a[0], a[1], q, r); + if(r->sign < 0) mpadd(r, a[1], r); + mpleft(r, 1, r); + i = mpcmp(r, a[1]); + mpright(r, 1, r); + if(i > 0 || i == 0 && (a[0]->sign < 0) ^ (q->top != 0 && (q->p[0] & 1) != 0)) + mpsub(r, a[1], r); + mpsub(a[0], r, a[0]); + mpfree(q); + mpfree(r); + numdecref(a[1]); + return a[0]; +} + +Num * +fnfloor(int, Num **a) +{ + mpint *r; + + if(mpcmp(a[1], mpzero) <= 0){ + numdecref(a[0]); + numdecref(a[1]); + return error("invalid argument"); + } + r = mpnew(0); + a[0] = nummod(a[0]); + mpdiv(a[0], a[1], nil, r); + if(r->sign < 0) mpadd(r, a[1], r); + mpsub(a[0], r, a[0]); + mpfree(r); + numdecref(a[1]); + return a[0]; +} + +Num * +fnceil(int, Num **a) +{ + mpint *r; + + if(mpcmp(a[1], mpzero) <= 0){ + numdecref(a[0]); + numdecref(a[1]); + return error("invalid argument"); + } + r = mpnew(0); + a[0] = nummod(a[0]); + mpdiv(a[0], a[1], nil, r); + if(r->sign < 0) mpadd(r, a[1], r); + if(mpcmp(r, mpzero) != 0){ + mpsub(a[0], r, a[0]); + mpadd(a[0], a[1], a[0]); + } + mpfree(r); + numdecref(a[1]); + return a[0]; +} + +Num * +fntrunc(int, Num **a) +{ + int i; + + if(toint(a[1], &i, 1)){ + numdecref(a[0]); + numdecref(a[1]); + return nil; + } + mptrunc(a[0], i, a[0]); + return a[0]; +} + +Num * +fnxtend(int, Num **a) +{ + int i; + + if(toint(a[1], &i, 1)) return nil; + mpxtend(a[0], i, a[0]); + return a[0]; +} + +Num * +fnubits(int, Num **a) +{ + if(a[0]->sign < 0){ + numdecref(a[0]); + return error("invalid argument"); + } + a[0] = nummod(a[0]); + itomp(mpsignif(a[0]), a[0]); + a[0]->b = 10; + return a[0]; +} + +Num * +fnsbits(int, Num **a) +{ + a[0] = nummod(a[0]); + if(a[0]->sign < 0) mpadd(a[0], mpone, a[0]); + itomp(mpsignif(a[0]) + 1, a[0]); + a[0]->b = 10; + return a[0]; +} + + + +void +main(int argc, char **argv) +{ + Keyword *kw; + + fmtinstall('B', mpfmt); + + for(kw = ops; kw->name[0] != 0; kw++) + if(optab[kw->name[0]] == nil) + optab[kw->name[0]] = kw; + + regfunc("hex", fnhex, 1); + regfunc("dec", fndec, 1); + regfunc("oct", fnoct, 1); + regfunc("bin", fnbin, 1); + regfunc("abs", fnabs, 1); + regfunc("round", fnround, 2); + regfunc("floor", fnfloor, 2); + regfunc("ceil", fnceil, 2); + regfunc("trunc", fntrunc, 2); + regfunc("xtend", fnxtend, 2); + regfunc("ubits", fnubits, 1); + regfunc("sbits", fnsbits, 1); + + prompt = 1; + ARGBEGIN{ + case 'n': prompt = 0; break; + }ARGEND; + + in = Bfdopen(0, OREAD); + if(in == nil) sysfatal("Bfdopen: %r"); + extern void yyparse(void); + yyparse(); +}