dc.py

download raw

import sys
import inspect
import re
import getopt
from fractions import Fraction as F

parser = re.compile(r"(?P<number>_?[1-9A-F][0-9A-F]*([.][0-9A-F]*)?|_?0?[.][0-9A-F]*|0?)"
                    r"|(?:\s*)"
                    r"|(?P<op>.)")

ibase = obase = 10
scale = 0
stack = []
ops = {}

def make_op(name):
    def inner(f):
        in_arity = len(inspect.getfullargspec(f).args)
        def wrapper():
            args = reversed([stack.pop() for _ in range(in_arity)])
            result = f(*args)
            if result is None:
                return
            if isinstance(result, tuple):
                stack.extend(result)
            else:
                stack.append(result)
        ops[name] = wrapper
        return f
    return inner

def push(x):
    stack.append(x)

def pop():
    return stack.pop()

def powbgex(b, x):
    n = 1
    bn = b
    while (bn2 := bn*bn) < x:
        n *= 2
        bn = bn2
    while bn < x:
        bn *= b
        n += 1
    return n, bn

basechars = "0123456789ABCDEF"
def as_base(n: int, b: int, m: int = 0):
    if n == 0:
        return "0"

    assert n > 0
    assert 2 <= b <= 16
    result = []
    while n or m:
        if m: m -= 1
        n, d = divmod(n, b)
        result.append(basechars[d])

    return "".join(reversed(result))

def format_num_in_base(x, b, sc):
    if x < 0:
        s = "-"
        x = -x
    else:
        s = ""
    whole, frac = divmod(x, 1)
    w = as_base(whole, b)
    if not frac:
        return s + w
    nplaces, bn = powbgex(b, frac.denominator)
    #print(f"{nplaces} {bn} {frac.denominator} {frac.denominator % bn}")
    if bn % frac.denominator:
        nplaces = max(nplaces, sc)
    f = as_base(int(frac * b ** nplaces), b, nplaces)
    return s + w + "." + f

def print_num_in_base(x, b, sc):
    print(format_num_in_base(x, b, sc))

@make_op("c")
def clear_stack():
    del stack[:]

@make_op("f")
def print_stack_nondestructive():
    for a in reversed(stack):
        print_num(a)

@make_op("n")
def print_num_nonl(x):
    print(format_num_in_base(x, obase, scale), end="")

@make_op("P")
def print_num(x):
    print_num_in_base(x, obase, scale)
    return x

@make_op("p")
def ppop(x):
    print_num(x)

@make_op("+")
def add(x, y):
    return x + y

@make_op("-")
def sub(x, y):
    return x - y

@make_op("*")
def mul(x, y):
    return x * y

@make_op("/")
def div(x, y):
    return x / y

@make_op("d")
def dup(x):
    return (x, x)

@make_op("i")
def set_ibase(x):
    global ibase
    ibase = int(x)

@make_op("I")
def get_ibase():
    return ibase

@make_op("o")
def set_obase(x):
    global obase
    obase = int(x)

@make_op("O")
def get_obase():
    return obase

@make_op("k")
def set_scale(x):
    global scale
    scale = int(x)

@make_op("K")
def get_scale(x):
    return scale

def parse_number(x):
    if x.startswith("_"):
        result = -parse_number(x[1:])
        return result
    whole, dot, frac = x.partition('.')
    result = 0
    if whole:
        result = F(int(whole, ibase))
    if frac:
        result += F(int(frac, ibase), ibase ** len(frac))
    return result

def invalid_fun(op):
    print(f"{op!r} ({ord(op)}) unimplemented")

def parse_execute(line: str):
    for m in parser.finditer(line):
        if number := m.group("number"):
            push(parse_number(number))
        elif op := m.group("op"):
            fun = ops.get(op, None)
            if fun is None:
                 invalid_fun(op)
            else:
                fun()

def main():
    opts, args = getopt.getopt(sys.argv[1:], "e:i")
    interact = True
    if opts:
        interact = False
        for k, v in opts:
            if k == '-e':
                try:
                    parse_execute(v)
                except Exception as e:
                    print(e)
            if k == '-i':
                interact = True
    while interact:
        try:
            line = input("pydc> ")
        except EOFError:
            break
        try:
            parse_execute(line)
        except Exception as e:
            print(e)
    if stack:
        print("On stack at exit:")
        print_stack_nondestructive()

if __name__ == '__main__':
    main()