diff options
Diffstat (limited to 'appl/cmd/sh/mpexpr.b')
| -rw-r--r-- | appl/cmd/sh/mpexpr.b | 435 |
1 files changed, 435 insertions, 0 deletions
diff --git a/appl/cmd/sh/mpexpr.b b/appl/cmd/sh/mpexpr.b new file mode 100644 index 00000000..682c01e8 --- /dev/null +++ b/appl/cmd/sh/mpexpr.b @@ -0,0 +1,435 @@ +implement Shellbuiltin; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "keyring.m"; + keyring: Keyring; + IPint: import keyring; +include "sh.m"; + sh: Sh; + Listnode, Context: import sh; + myself: Shellbuiltin; + +Big: type ref IPint; +Zero: Big; +One: Big; +initbuiltin(ctxt: ref Context, shmod: Sh): string +{ + sys = load Sys Sys->PATH; + keyring = load Keyring Keyring->PATH; + sh = shmod; + myself = load Shellbuiltin "$self"; + if (myself == nil) + ctxt.fail("bad module", sys->sprint("expr: cannot load self: %r")); + + Zero = IPint.inttoip(0); + One = IPint.inttoip(1); + 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, +BITS, EXPMOD, INVERT, RAND, EXP: 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(strtoip(ctxt, (hd tl cmd).word).eq(Zero)) + 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); + "bits" => + (op, nops) = (BITS, 1); + "expmod" => + (op, nops) = (EXPMOD, 3); + "invert" => + (op, nops) = (INVERT, 2); + "rand" => + (op, nops) = (RAND, 1); + "exp" or "xx" or "**" => + (op, nops) = (EXP, 2); + } + if (op == -1){ + if (w == nil || (w[0] != '-' && (w[0] < '0' || w[0] > '9'))) + ctxt.fail("usage", sys->sprint("expr: unknown operator '%s'", w)); + stk = strtoip(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, iptostr(hd stk, radix)) :: r; + return r; +} + +opts(ctxt: ref Context, cmd: list of ref Listnode): (list of ref Listnode, int) +{ + 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 && r != 64)) + 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; + } + n3 := Zero; + n2 := Zero; + n1 := hd args; + if (tl args != nil){ + n2 = hd tl args; + if(tl tl args != nil) + n3 = hd tl tl args; + } + r := Zero; + case op { + EQ => r = mki(n1.eq(n2)); + NEQ => r = mki(!n1.eq(n2)); + GT => r = mki(n1.cmp(n2) > 0); + LT => r = mki(n1.cmp(n2) < 0); + GE => r = mki(n1.cmp(n2) >= 0); + LE => r = mki(n1.cmp(n2) <= 0); + PLUS => r = n1.add(n2); + MINUS => r = n1.sub(n2); + NOT => r = mki(n1.eq(Zero)); + DIVIDE => + if (n2.eq(Zero)) + ctxt.fail("divide by zero", "expr: division by zero"); + (r, nil) = n1.div(n2); + MOD => + if (n2.eq(Zero)) + ctxt.fail("divide by zero", "expr: division by zero"); + (nil, r) = n1.div(n2); + TIMES => + r = n1.mul(n2); + AND => r = bitop(ipand, n1, n2); + OR => r = bitop(ipor, n1, n2); + XOR => r = bitop(ipxor, n1, n2); + UMINUS => r = n1.neg(); + BNOT => r = n1.neg().sub(One); + SHL => r = n1.shl(n2.iptoint()); + SHR => r = n1.shr(n2.iptoint()); + SEQ => return seq(n1, n2, stk); + BITS => r = mki(n1.bits()); + EXPMOD => r = n1.expmod(n2, n3); + EXP => r = n1.expmod(n2, nil); + RAND => r = IPint.random(0, n1.iptoint()); + INVERT => r = n1.invert(n2); + } + return r :: stk; +} + +# won't work if op(0, 0) != 0 +bitop(op: ref fn(n1, n2: Big): Big, n1, n2: Big): Big +{ + bits := max(n1.bits(), n2.bits()); + return signedmag(op(twoscomp(n1, bits), twoscomp(n2, bits)), bits); +} + +onebits(n: int): Big +{ + return One.shl(n).sub(One); +} + +# return a two's complement version of n, +# sign-extended to b bits if negative. +# sign bit is at 1<<b. +twoscomp(n: Big, b: int): Big +{ + if(n.cmp(Zero) >= 0) + return n; + return n.not().ori(onebits(b).xor(onebits(n.bits()))).add(One); +} + +# return conventional representation of n, +# where n is in two's complement form in b bits. +signedmag(n: Big, b: int): Big +{ + if(n.and(One.shl(b)).eq(Zero)) + return n; + return n.sub(One).not().and(onebits(b)).neg(); +} + +max(x, y: int): int +{ + if(x > y) + return x; + else + return y; +} + +seq(n1, n2: Big, stk: list of Big): list of Big +{ + incr := mki(1); + if (n2.cmp(n1) < 0) + incr = mki(-1); + for (; !n1.eq(n2); n1 = n1.add(incr)) + stk = n1 :: stk; + return n1 :: stk; +} + +strtoip(ctxt: ref Context, s: string): Big +{ + 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 IPint.strtoip(s, 10); + if (radix == 0 || (radix > 36 && radix != 64)) + ctxt.fail("usage", "expr: bad number " + t); + n := Zero; + case radix { + 10 or 16 or 64 => + n = IPint.strtoip(s, radix); + * => + r := mki(radix); + for (i = 0; i < len s; i++) { + if ('0' <= s[i] && s[i] <= '9') + n = n.mul(r).add(mki(s[i] - '0')); + else if ('a' <= s[i] && s[i] < 'a' + radix - 10) + n = n.mul(r).add(mki(s[i] - 'a' + 10)); + else if ('A' <= s[i] && s[i] < 'A' + radix - 10) + n = n.mul(r).add(mki(s[i] - 'A' + 10)); + else + break; + } + } + if(neg) + return n.neg(); + return n; +} + +iptostr(n: Big, radix: int): string +{ + neg := n.cmp(Zero) < 0; + t: string; + case radix { + 2 or 4 or 16 or 32 => + b := n.iptobebytes(); + rbits := log2(radix); + bits := roundup(n.bits(), rbits); + for(i := bits - rbits; i >= 0; i -= rbits){ + d := 0; + for(j := 0; j < rbits; j++) + d |= getbit(b, i+j) << j; + t[len t] = digit(d); + } + 10 => + return n.iptostr(radix); + 64 => + t = n.iptostr(radix); + if(neg) + t = t[1:]; + * => + if(neg) + n = n.neg(); + r := mki(radix); + s: string; + do{ + d: Big; + (n, d) = n.div(r); + s[len s] = digit(d.iptoint()); + }while(n.cmp(Zero) > 0); + t = s; + for (i := len s - 1; i >= 0; i--) + t[len s - 1 - i] = s[i]; + } + t = string radix + "r" + t; + if (neg) + return "-" + t; + return t; +} + +mki(i: int): Big +{ + return IPint.inttoip(i); +} + +b2s(b: array of byte): string +{ + s := ""; + for(i := 0; i < len b; i++) + s += sys->sprint("%.2x", int b[i]); + return s; +} + +# count from least significant bit. +getbit(b: array of byte, bit: int): int +{ + if((i := bit >> 3) >= len b){ + return 0; + }else{ + return (int b[len b - i -1] >> (bit&7)) & 1; + } +} + +digit(d: int): int +{ + if(d < 10) + return '0' + d; + else + return 'a' + d - 10; +} + +log2(x: int): int +{ + case x { + 2 => return 1; + 4 => return 2; + 8 => return 3; + 16 => return 4; + 32 => return 5; + } + return 0; +} + +roundup(n: int, m: int): int +{ + return m*((n+m-1)/m); +} + +# these functions are to get around the fact that the limbo compiler isn't +# currently considering ref fn(x: self X, ...) compatible with ref fn(x: X, ...). +ipand(n1, n2: Big): Big +{ + return n1.and(n2); +} + +ipor(n1, n2: Big): Big +{ + return n1.ori(n2); +} + + +ipxor(n1, n2: Big): Big +{ + return n1.xor(n2); +} |
