shithub: riscv

Download patch

ref: a931ad737a1ad2598850014e7297f2d0edfd9dc1
parent: 43bb71c8cca061a705689be27906207329c3b5f7
author: aiju <devnull@localhost>
date: Sun Aug 28 09:40:01 EDT 2016

add pc(1)

--- /dev/null
+++ b/sys/man/1/pc
@@ -1,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).
--- a/sys/src/cmd/mkfile
+++ b/sys/src/cmd/mkfile
@@ -48,7 +48,7 @@
 &: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 @@
 %.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
--- /dev/null
+++ b/sys/src/cmd/pc.y
@@ -1,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();
+}