xref: /inferno-os/appl/cmd/sh/expr.b (revision 37da2899f40661e3e9631e497da8dc59b971cbd0)
1implement Shellbuiltin;
2
3include "sys.m";
4	sys: Sys;
5include "draw.m";
6include "sh.m";
7	sh: Sh;
8	Listnode, Context: import sh;
9	myself: Shellbuiltin;
10
11initbuiltin(ctxt: ref Context, shmod: Sh): string
12{
13	sys = load Sys Sys->PATH;
14	sh = shmod;
15	myself = load Shellbuiltin "$self";
16	if (myself == nil)
17		ctxt.fail("bad module", sys->sprint("expr: cannot load self: %r"));
18
19	ctxt.addsbuiltin("expr", myself);
20	ctxt.addbuiltin("ntest", myself);
21	return nil;
22}
23
24whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string
25{
26	return nil;
27}
28
29getself(): Shellbuiltin
30{
31	return myself;
32}
33
34EQ, GT, LT, GE, LE, PLUS, MINUS, DIVIDE, AND, TIMES, MOD,
35OR, XOR, UMINUS, SHL, SHR, NOT, BNOT, NEQ, REP, SEQ: con iota;
36
37runbuiltin(ctxt: ref Sh->Context, nil: Sh,
38			cmd: list of ref Sh->Listnode, nil: int): string
39{
40	case (hd cmd).word {
41	"ntest" =>
42		if (len cmd != 2)
43			ctxt.fail("usage", "usage: ntest n");
44		if (big (hd tl cmd).word == big 0)
45			return "false";
46	}
47	return nil;
48}
49
50runsbuiltin(ctxt: ref Sh->Context, nil: Sh,
51			cmd: list of ref Sh->Listnode): list of ref Listnode
52{
53	# only one sbuiltin: expr.
54	stk: list of big;
55	lastop := -1;
56	lastn := -1;
57	lastname := "";
58	radix: int;
59	(cmd, radix) = opts(ctxt, tl cmd);
60	for (; cmd != nil; cmd = tl cmd) {
61		w := (hd cmd).word;
62		op := -1;
63		nops := 2;
64		case w {
65		"+" =>
66			op = PLUS;
67		"-" =>
68			op = MINUS;
69		"x" or "*" or "×" =>
70			op = TIMES;
71		"/" =>
72			op = DIVIDE;
73		"%" =>
74			op = MOD;
75		"and" =>
76			op = AND;
77		"or" =>
78			op = OR;
79		"xor" =>
80			op = XOR;
81		"_"=>
82			(op, nops) = (UMINUS, 1);
83		"<<" or "shl" =>
84			op = SHL;
85		">>" or "shr" =>
86			op = SHR;
87		"=" or "==" or "eq" =>
88			op = EQ;
89		"!=" or "neq" =>
90			op = NEQ;
91		">" or "gt" =>
92			op = GT;
93		"<" or "lt" =>
94			op = LT;
95		">=" or "ge" =>
96			op = GE;
97		"<=" or "le" =>
98			op = LE;
99		"!" or "not" =>
100			(op, nops) = (NOT, 1);
101		"~" =>
102			(op, nops) = (BNOT, 1);
103		"rep" =>
104			(op, nops) = (REP, 0);
105		"seq" =>
106			(op, nops) = (SEQ, 2);
107		}
108		if (op == -1)
109			stk = makenum(ctxt, w) :: stk;
110		else
111			stk = operator(ctxt, stk, op, nops, lastop, lastn, w, lastname);
112		lastop = op;
113		lastn = nops;
114		lastname = w;
115	}
116	r: list of ref Listnode;
117	for (; stk != nil; stk = tl stk)
118		r = ref Listnode(nil, big2string(hd stk, radix)) :: r;
119	return r;
120}
121
122opts(ctxt: ref Context, cmd: list of ref Listnode): (list of ref Listnode, int)
123{
124	radix := 10;
125	if (cmd == nil)
126		return (nil, 10);
127	w := (hd cmd).word;
128	if (len w < 2)
129		return (cmd, 10);
130	if (w[0] != '-' || (w[1] >= '0' && w[1] <= '9'))
131		return (cmd, 10);
132	if (w[1] != 'r')
133		ctxt.fail("usage", "usage: expr [-r radix] [arg...]");
134	if (len w > 2)
135		w = w[2:];
136	else {
137		if (tl cmd == nil)
138			ctxt.fail("usage", "usage: expr [-r radix] [arg...]");
139		cmd = tl cmd;
140		w = (hd cmd).word;
141	}
142	r := int w;
143	if (r <= 0 || r > 36)
144		ctxt.fail("usage", "expr: invalid radix " + string r);
145	return (tl cmd, int w);
146}
147
148operator(ctxt: ref Context, stk: list of big, op, nops, lastop, lastn: int,
149		opname, lastopname: string): list of big
150{
151	al: list of big;
152	for (i := 0; i < nops; i++) {
153		if (stk == nil)
154			ctxt.fail("empty stack",
155				sys->sprint("expr: empty stack on op '%s'", opname));
156		al = hd stk :: al;
157		stk = tl stk;
158	}
159	return oper(ctxt, al, op, lastop, lastn, lastopname, stk);
160}
161
162# args are in reverse order
163oper(ctxt: ref Context, args: list of big, op, lastop, lastn: int,
164		lastopname: string, stk: list of big): list of big
165{
166	if (op == REP) {
167		if (lastop == -1 || lastop == SEQ || lastn != 2)
168			ctxt.fail("usage", "expr: bad operator for rep");
169		if (stk == nil || tl stk == nil)
170			return stk;
171		while (tl stk != nil)
172			stk = operator(ctxt, stk, lastop, 2, -1, -1, lastopname, nil);
173		return stk;
174	}
175	n2 := big 0;
176	n1 := hd args;
177	if (tl args != nil)
178		n2 = hd tl args;
179	r := big 0;
180	case op {
181	EQ =>	r = big(n1 == n2);
182	NEQ =>	r = big(n1 != n2);
183	GT =>	r = big(n1 > n2);
184	LT =>	r = big(n1 < n2);
185	GE =>	r = big(n1 >= n2);
186	LE =>	r = big(n1 <= n2);
187	PLUS =>	r = big(n1 + n2);
188	MINUS =>	r = big(n1 - n2);
189	NOT	 =>	r = big(n1 != big 0);
190	DIVIDE =>
191			if (n2 == big 0)
192				ctxt.fail("divide by zero", "expr: division by zero");
193			r = n1 / n2;
194	MOD =>
195			if (n2 == big 0)
196				ctxt.fail("divide by zero", "expr: division by zero");
197			r = n1 % n2;
198	TIMES =>	r = n1 * n2;
199	AND =>	r = n1 & n2;
200	OR =>	r = n1 | n2;
201	XOR =>	r = n1 ^ n2;
202	UMINUS => r = -n1;
203	BNOT =>	r = ~n1;
204	SHL =>	r = n1 << int n2;
205	SHR =>	r = n1 >> int n2;
206	SEQ =>	return seq(n1, n2, stk);
207	}
208	return r :: stk;
209}
210
211seq(n1, n2: big, stk: list of big): list of big
212{
213	incr := big 1;
214	if (n2 < n1)
215		incr = big -1;
216	for (; n1 != n2; n1 += incr)
217		stk = n1 :: stk;
218	return n1 :: stk;
219}
220
221makenum(ctxt: ref Context, s: string): big
222{
223	if (s == nil || (s[0] != '-' && (s[0] < '0' || s[0] > '9')))
224		ctxt.fail("usage", sys->sprint("expr: unknown operator '%s'", s));
225
226	t := s;
227	if (neg := s[0] == '-')
228		s = s[1:];
229	radix := 10;
230	for (i := 0; i < len s && i < 3; i++) {
231		if (s[i] == 'r') {
232			radix = int s;
233			s = s[i+1:];
234			break;
235		}
236	}
237	if (radix == 10)
238		return big t;
239	if (radix == 0 || radix > 36)
240		ctxt.fail("usage", "expr: bad number " + t);
241	n := big 0;
242	for (i = 0; i < len s; i++) {
243		if ('0' <= s[i] && s[i] <= '9')
244			n = (n * big radix) + big(s[i] - '0');
245		else if ('a' <= s[i] && s[i] < 'a' + radix - 10)
246			n = (n * big radix) + big(s[i] - 'a' + 10);
247		else if ('A' <= s[i] && s[i]  < 'A' + radix - 10)
248			n = (n * big radix) + big(s[i] - 'A' + 10);
249		else
250			break;
251	}
252	if (neg)
253		return -n;
254	return n;
255}
256
257big2string(n: big, radix: int): string
258{
259	if (neg := n < big 0) {
260		n = -n;
261	}
262	s := "";
263	do {
264		c: int;
265		d := int (n % big radix);
266		if (d < 10)
267			c = '0' + d;
268		else
269			c = 'a' + d - 10;
270		s[len s] = c;
271		n /= big radix;
272	} while (n > big 0);
273	t := s;
274	for (i := len s - 1; i >= 0; i--)
275		t[len s - 1 - i] = s[i];
276	if (radix != 10)
277		t = string radix + "r" + t;
278	if (neg)
279		return "-" + t;
280	return t;
281}
282