add pc(1)

This commit is contained in:
aiju 2016-08-28 13:40:01 +02:00
parent 43bb71c8cc
commit a931ad737a
3 changed files with 938 additions and 2 deletions

132
sys/man/1/pc Normal file
View file

@ -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).

View file

@ -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

804
sys/src/cmd/pc.y Normal file
View file

@ -0,0 +1,804 @@
%{
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <ctype.h>
#include <mp.h>
#include <pool.h>
#include <thread.h>
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 <n> LNUM
%token <sym> LSYMB
%type <n> expr
%type <args> 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();
}