shithub: purgatorio

ref: d1540c7f666e3c5d636b48c956b444205b50502d
dir: /appl/cmd/sh/expr.b/

View raw version
implement Shellbuiltin;

include "sys.m";
	sys: Sys;
include "draw.m";
include "sh.m";
	sh: Sh;
	Listnode, Context: import sh;
	myself: Shellbuiltin;

initbuiltin(ctxt: ref Context, shmod: Sh): string
{
	sys = load Sys Sys->PATH;
	sh = shmod;
	myself = load Shellbuiltin "$self";
	if (myself == nil)
		ctxt.fail("bad module", sys->sprint("expr: cannot load self: %r"));

	ctxt.addsbuiltin("expr", myself);
	ctxt.addbuiltin("ntest", myself);
	return nil;
}

whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string
{
	return nil;
}

getself(): Shellbuiltin
{
	return myself;
}

EQ, GT, LT, GE, LE, PLUS, MINUS, DIVIDE, AND, TIMES, MOD,
OR, XOR, UMINUS, SHL, SHR, NOT, BNOT, NEQ, REP, SEQ: con iota;

runbuiltin(ctxt: ref Sh->Context, nil: Sh,
			cmd: list of ref Sh->Listnode, nil: int): string
{
	case (hd cmd).word {
	"ntest" =>
		if (len cmd != 2)
			ctxt.fail("usage", "usage: ntest n");
		if (big (hd tl cmd).word == big 0)
			return "false";
	}
	return nil;
}

runsbuiltin(ctxt: ref Sh->Context, nil: Sh,
			cmd: list of ref Sh->Listnode): list of ref Listnode
{
	# only one sbuiltin: expr.
	stk: list of big;
	lastop := -1;
	lastn := -1;
	lastname := "";
	radix: int;
	(cmd, radix) = opts(ctxt, tl cmd);
	for (; cmd != nil; cmd = tl cmd) {
		w := (hd cmd).word;
		op := -1;
		nops := 2;
		case w {
		"+" =>
			op = PLUS; 
		"-" =>
			op = MINUS;
		"x" or "*" or "×" =>
			op = TIMES;
		"/" =>
			op = DIVIDE;
		"%" =>
			op = MOD;
		"and" =>
			op = AND;
		"or" =>
			op = OR;
		"xor" =>
			op = XOR;
		"_"=>
			(op, nops) = (UMINUS, 1);
		"<<" or "shl" =>
			op = SHL;
		">>" or "shr" =>
			op = SHR;
		"=" or "==" or "eq" =>
			op = EQ;
		"!=" or "neq" =>
			op = NEQ;
		">" or "gt" =>
			op = GT;
		"<" or "lt" =>
			op = LT;
		">=" or "ge" =>
			op = GE;
		"<=" or "le" =>
			op = LE;
		"!" or "not" =>
			(op, nops) = (NOT, 1);
		"~" =>
			(op, nops) = (BNOT, 1);
		"rep" =>
			(op, nops) = (REP, 0);
		"seq" =>
			(op, nops) = (SEQ, 2);
		}
		if (op == -1)
			stk = makenum(ctxt, w) :: stk;
		else 
			stk = operator(ctxt, stk, op, nops, lastop, lastn, w, lastname);
		lastop = op;
		lastn = nops;
		lastname = w;
	}
	r: list of ref Listnode;
	for (; stk != nil; stk = tl stk)
		r = ref Listnode(nil, big2string(hd stk, radix)) :: r;
	return r;
}

opts(ctxt: ref Context, cmd: list of ref Listnode): (list of ref Listnode, int)
{
	radix := 10;
	if (cmd == nil)
		return (nil, 10);
	w := (hd cmd).word;
	if (len w < 2)
		return (cmd, 10);
	if (w[0] != '-' || (w[1] >= '0' && w[1] <= '9'))
		return (cmd, 10);
	if (w[1] != 'r')
		ctxt.fail("usage", "usage: expr [-r radix] [arg...]");
	if (len w > 2)
		w = w[2:];
	else {
		if (tl cmd == nil)
			ctxt.fail("usage", "usage: expr [-r radix] [arg...]");
		cmd = tl cmd;
		w = (hd cmd).word;
	}
	r := int w;
	if (r <= 0 || r > 36)
		ctxt.fail("usage", "expr: invalid radix " + string r);
	return (tl cmd, int w);
}

operator(ctxt: ref Context, stk: list of big, op, nops, lastop, lastn: int,
		opname, lastopname: string): list of big
{
	al: list of big;
	for (i := 0; i < nops; i++) {
		if (stk == nil)
			ctxt.fail("empty stack",
				sys->sprint("expr: empty stack on op '%s'", opname));
		al = hd stk :: al;
		stk = tl stk;
	}
	return oper(ctxt, al, op, lastop, lastn, lastopname, stk);
}

# args are in reverse order
oper(ctxt: ref Context, args: list of big, op, lastop, lastn: int,
		lastopname: string, stk: list of big): list of big
{
	if (op == REP) {
		if (lastop == -1 || lastop == SEQ || lastn != 2)
			ctxt.fail("usage", "expr: bad operator for rep");
		if (stk == nil || tl stk == nil)
			return stk;
		while (tl stk != nil)
			stk = operator(ctxt, stk, lastop, 2, -1, -1, lastopname, nil);
		return stk;
	}
	n2 := big 0;
	n1 := hd args;
	if (tl args != nil)
		n2 = hd tl args;
	r := big 0;
	case op {
	EQ =>	r = big(n1 == n2);
	NEQ =>	r = big(n1 != n2);
	GT =>	r = big(n1 > n2);
	LT =>	r = big(n1 < n2);
	GE =>	r = big(n1 >= n2);
	LE =>	r = big(n1 <= n2);
	PLUS =>	r = big(n1 + n2);
	MINUS =>	r = big(n1 - n2);
	NOT	 =>	r = big(n1 != big 0);
	DIVIDE =>
			if (n2 == big 0)
				ctxt.fail("divide by zero", "expr: division by zero");
			r = n1 / n2;
	MOD =>
			if (n2 == big 0)
				ctxt.fail("divide by zero", "expr: division by zero");
			r = n1 % n2;
	TIMES =>	r = n1 * n2;
	AND =>	r = n1 & n2;
	OR =>	r = n1 | n2;
	XOR =>	r = n1 ^ n2;
	UMINUS => r = -n1;
	BNOT =>	r = ~n1;
	SHL =>	r = n1 << int n2;
	SHR =>	r = n1 >> int n2;
	SEQ =>	return seq(n1, n2, stk);
	}
	return r :: stk;
}

seq(n1, n2: big, stk: list of big): list of big
{
	incr := big 1;
	if (n2 < n1)
		incr = big -1;
	for (; n1 != n2; n1 += incr)
		stk = n1 :: stk;
	return n1 :: stk;
}

makenum(ctxt: ref Context, s: string): big
{
	if (s == nil || (s[0] != '-' && (s[0] < '0' || s[0] > '9')))
		ctxt.fail("usage", sys->sprint("expr: unknown operator '%s'", s));

	t := s;	
	if (neg := s[0] == '-')
		s = s[1:];
	radix := 10;
	for (i := 0; i < len s && i < 3; i++) {
		if (s[i] == 'r') {
			radix = int s;
			s = s[i+1:];
			break;
		}
	}
	if (radix == 10)
		return big t;
	if (radix == 0 || radix > 36)
		ctxt.fail("usage", "expr: bad number " + t);
	n := big 0;
	for (i = 0; i < len s; i++) {
		if ('0' <= s[i] && s[i] <= '9')
			n = (n * big radix) + big(s[i] - '0');
		else if ('a' <= s[i] && s[i] < 'a' + radix - 10)
			n = (n * big radix) + big(s[i] - 'a' + 10);
		else if ('A' <= s[i] && s[i]  < 'A' + radix - 10)
			n = (n * big radix) + big(s[i] - 'A' + 10);
		else
			break;
	}
	if (neg)
		return -n;
	return n;
}

big2string(n: big, radix: int): string
{
	if (neg := n < big 0) {
		n = -n;
	}
	s := "";
	do {
		c: int;
		d := int (n % big radix);
		if (d < 10)
			c = '0' + d;
		else
			c = 'a' + d - 10;
		s[len s] = c;
		n /= big radix;
	} while (n > big 0);
	t := s;
	for (i := len s - 1; i >= 0; i--)
		t[len s - 1 - i] = s[i];
	if (radix != 10)
		t = string radix + "r" + t;
	if (neg)
		return "-" + t;
	return t;
}