#!/usr/bin/env python3 import sys, math, traceback, ast, operator from fractions import * from modgrammar import * args = [] file = "" code = "" flags = {"--sympy": 0, "--parse": 0, "c": 0, "--subparser": 1} i = 1 while i < len(sys.argv): arg = sys.argv[i] if arg and arg[0] == "-" and arg[1:] and arg[1] != "-": for a in arg[1:]: if a in flags: num = flags[a] args.append((a, sys.argv[i + 1:][:num])) i += num elif arg in flags: num = flags[arg] args.append((arg, sys.argv[i + 1:][:num])) i += num else: file = arg i += 1 sympy = False parse = False parser = "Program" for flag in args: if flag[0] == "--sympy": sympy = True elif flag[0] == "--parse": parse = True elif flag[0] == "--subparser": parser = flag[1][0] elif flag[0] == "c": code, file = file, "" if sympy: import sympy else: import mpmath if sympy: to_float = sympy.Rational to_complex = lambda a, b: sympy.Rational(a) + sympy.Rational(b) * sympy.I else: to_float = mpmath.mpf to_complex = mpmath.mpc class NZD(Grammar): grammar = L("1") | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" class HexDigit(Grammar): grammar = L("0") | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "a" | "A" | "b" | "B" | "c" | "C" | "d" | "D" | "e" | "E" | "f" | "F" def number_format(digits): g = L(digits[0]) for i in digits[1:]: g |= i g = REPEAT(g) return OR((g, ".", g), (OPTIONAL("."), g)) class DecimalNumber(Grammar): grammar = OR(("0", ".", WORD("0-9")), (NZD, OPTIONAL(number_format("0123456789"))), "0") class OctalNumber(Grammar): grammar = ("0", number_format("01234567")) class BinaryNumber(Grammar): grammar = (L("0") | (NZD, OPTIONAL(WORD("0-9"))), "b", number_format("01")) class ExtOctalNumber(Grammar): grammar = (L("0") | (NZD, OPTIONAL(WORD("0-9"))), "o", number_format("01234567")) class HexNumber(Grammar): grammar = (L("0") | (NZD, OPTIONAL(WORD("0-9"))), "x", number_format("0123456789abcdefABCDEF")) class SingleString(Grammar): grammar = ("\'", ZERO_OR_MORE(OR(ANY_EXCEPT("\\\'"), ("\\", ANY))), "\'") class DoubleString(Grammar): grammar = ("\"", ZERO_OR_MORE(OR(ANY_EXCEPT("\\\""), ("\\", ANY))), "\"") class String(Grammar): grammar = SingleString | DoubleString class Number(Grammar): grammar = (OPTIONAL("-"), DecimalNumber | BinaryNumber | HexNumber | ExtOctalNumber | OctalNumber, OPTIONAL("j")) class Literal(Grammar): grammar = String | Number class Identifier(Grammar): grammar = WORD("A-Za-z_", "A-Za-z_0-9") class Bracketed(Grammar): grammar = ("(", REF("_expr"), ")") class Value(Grammar): grammar = (ZERO_OR_MORE(OR("**", "*", "-", "~", "!")), OPTIONAL(WHITESPACE), Identifier | Literal | Bracketed | REF("List") | REF("Set") | REF("Dict")) class Slice(Grammar): grammar = (OPTIONAL(REF("Expression")), ":", OPTIONAL(REF("Expression")), OPTIONAL(":", OPTIONAL(REF("Expression")))) class IndexAccessor(Grammar): grammar = Slice | REF("Expression") class IndexAccess(Grammar): grammar = (Value, ZERO_OR_MORE("[", OPTIONAL(LIST_OF(IndexAccessor, sep = ",", grammar_whitespace_mode = "optional")), "]")) class KeywordArgument(Grammar): grammar = (Identifier, ":", REF("_notuple")) grammar_whitespace_mode = "optional" # TODO class FunctionCall(Grammar): grammar = (Bracketed | Identifier, ZERO_OR_MORE(("(", LIST_OF(OR(REF("_notuple")), min = 0, sep = ",", grammar_whitespace_mode = "optional"), ")"))) class FunctionAtCall(Grammar): grammar = LIST_OF(FunctionCall | IndexAccess, sep = "@", grammar_whitespace_mode = "optional") class IndexHashAccess(Grammar): grammar = LIST_OF(FunctionAtCall, sep = "#", grammar_whitespace_mode = "optional") class Exponent(Grammar): grammar = LIST_OF(IndexHashAccess, sep = "**", grammar_whitespace_mode = "optional") class Product(Grammar): grammar = LIST_OF(Exponent, sep = OR("*", "/", "/,", "%", "/@"), grammar_whitespace_mode = "optional") class Sum(Grammar): grammar = LIST_OF(Product, sep = OR("+", "-"), grammar_whitespace_mode = "optional") class FunctionTildeCall(Grammar): grammar = LIST_OF(Sum, sep = "~", grammar_whitespace_mode = "optional") class FunctionPipeCall(Grammar): grammar = LIST_OF(FunctionTildeCall, sep = "|>", grammar_whitespace_mode = "optional") class ComparisonChain(Grammar): grammar = LIST_OF(FunctionPipeCall, sep = OR(">", "<", ">=", "<=", "==", "!="), grammar_whitespace_mode = "optional") class List(Grammar): grammar = OR(("[", OR(REF("Tuple"), REF("Expression")), "]"), ("[", "]")) class Set(Grammar): grammar = ("{", OR(REF("Tuple"), REF("Expression")), "}") class DictEntry(Grammar): grammar = (REF("Expression"), ":", REF("Expression")) class Dict(Grammar): grammar = ("{", ZERO_OR_MORE(DictEntry, sep = ",", grammar_whitespace_mode = "optional"), "}") class Tuple(Grammar): grammar = LIST_OF(OPTIONAL(ComparisonChain | List | Set | Dict), sep = ",", grammar_whitespace_mode = "optional") class Assignable(Grammar): grammar = Identifier class Assignment(Grammar): grammar = (LIST_OF(List | Bracketed | Tuple, sep = "=", grammar_whitespace_mode = "optional"), "=", REF("Expression")) grammar_whitespace_mode = "optional" class If(Grammar): grammar = ("if", REF("Expression"), REF("Expression"), OPTIONAL("else", REF("Expression"))) grammar_whitespace_mode = "optional" class For(Grammar): grammar = ("for", REF("Expression"), ":", REF("Expression"), REF("Expression"), OPTIONAL("else", REF("Expression"))) grammar_whitespace_mode = "optional" class _notuple(Grammar): grammar = If | For | List | Dict | Set | ComparisonChain class _expr(Grammar): grammar = If | For | List | Dict | Set | Tuple class Expr(Grammar): grammar = ("(", Assignment | _expr, ")") class Expression(Grammar): grammar = Expr | Assignment | _expr class Statement(Grammar): grammar = (Expression) class StatementDelimiter(Grammar): grammar = (ZERO_OR_MORE(SPACE), L(";") | L("\n") | (L(";"), ZERO_OR_MORE(SPACE), L("\n")), ZERO_OR_MORE(SPACE)) class Program(Grammar): grammar = (LIST_OF(Statement, sep = StatementDelimiter, grammar_whitespace_mode = "explicit"), OPTIONAL(StatementDelimiter)) grammar_whitespace_mode = "optional" def prettyprint(node, indent = 0): print(indent * " " + repr(node)) try: for n in node: prettyprint(n, indent + 1) except: pass def _print(val): if isinstance(val, list) and val and val[-1].parent.object is not None: print(val[-1]) def parse_float(string, base): if "." in string: left, right = string.split(".") else: left, right = string, "0" total = 0 for i, e in enumerate(left[::-1]): total += to_float(base.find(e)) * len(base) ** i for i, e in enumerate(right): total += to_float(base.find(e)) * len(base) ** ~i return total def neval(string): if string.startswith("-"): return -neval(string[1:]) if string.endswith("j"): return to_complex(0, neval(string[:-1])) if "x" in string: exp, base = string.split("x") return 16 ** int(exp) * parse_float(base.lower(), "0123456789abcdef") if "b" in string: exp, base = string.split("b") return 2 ** int(exp) * parse_float(base.lower(), "01") if string.startswith("0") and not string.startswith("0."): return parse_float(string.lower(), "01234567") return parse_float(string.lower(), "0123456789") def prep_index(val): if isinstance(val, slice): return val return int(val) def proton_range(*a): return list(map(to_float, range(*map(int, a)))) class splat: def __init__(self, item): self.item = item class dsplat: def __init__(self, item): self.item = item class kwarg: def __init__(self, key, val): self.key = key self.val = val global_scope = { "print": print, "input": input, "range": proton_range, } def global_eval(node, depth = 0, scope = [global_scope], debug = False): def ieval(*a, **k): if "depth" not in k: k["depth"] = depth + 1 if "scope" not in k: k["scope"] = scope return global_eval(*a, **k) if node == None: return None name = node.grammar_name if debug: print(" " * depth + name, node) if name == "Program": a = None for sub in list(node[0])[::2]: a = ieval(sub) return a elif name == "Statement": a = None for sub in node: a = ieval(sub) return a elif name == "Expression": return ieval(node[0]) elif name == "Expr": return ieval(node[1]) elif name == "_expr": return ieval(node[0]) elif name == "_notuple": return ieval(node[0]) elif name == "Tuple": nodes = list(node[0])[::2] if len(nodes) == 1: return ieval(nodes[0]) if nodes[-1] == None: nodes.pop() return tuple(map(ieval, nodes)) elif name == "List": return list(ieval(node[0][1])) elif name == "Set": return set(ieval(node[1])) elif name == "Dict": return {ieval(k[0][0]): ieval(k[2][0]) for k in node[1]} elif name == "ComparisonChain": node = node[0] root = ieval(node[0]) if list(node)[1:]: for i in range(1, len(list(node)), 2): comparator = {">": operator.gt, "<": operator.lt, ">=": operator.ge, "<=": operator.le, "==": operator.eq, "!=": operator.ne}[str(node[i])] next = ieval(node[i + 1]) if not comparator(root, next): return False root = next return True return root elif name == "FunctionPipeCall": node = node[0] val = ieval(node[0]) for i in range(2, len(list(node)), 2): val = ieval(node[i])(val) return val elif name == "FunctionTildeCall": node = list(node[0]) if len(node) // 2 % 2 == 1: raise RuntimeError("Function Tilde Call cannot be interpreted with an odd number of tildes. Try again.") vals = list(map(ieval, node[::2])) while len(vals) > 1: vals[:3] = [vals[1](vals[0], vals[2])] return vals[0] elif name == "Sum": node = node[0] val = ieval(node[0]) for i in range(1, len(list(node)), 2): adder = {"+": lambda a, b: a + str(b) if isinstance(a, str) else str(a) + b if isinstance(b, str) else a + b, "-": operator.sub}[str(node[i])] val = adder(val, ieval(node[i + 1])) return val elif name == "Product": node = node[0] val = ieval(node[0]) itermul = lambda a, b: a * int(b) + a[:int(len(a) * (b % 1))] for i in range(1, len(list(node)), 2): combiner = {"*": lambda a, b: itermul(a, b) if hasattr(a, "__iter__") else itermul(b, a) if hasattr(b, "__iter__") else a * b, "/": operator.truediv, "/,": operator.floordiv, "%": operator.mod, "/@": lambda a, b: list(map(a, b))}[str(node[i])] val = combiner(val, ieval(node[i + 1])) return val elif name == "Exponent": node = node[0] val = ieval(node[-1]) for i in range(len(list(node)) - 2, -1, -2): combiner = {"**": operator.pow}[str(node[i])] val = combiner(ieval(node[i - 1]), val) return val elif name == "IndexHashAccess": node = node[0] val = ieval(node[-1]) for j in list(node)[2::2]: val = val[prep_index(ieval(j))] return val elif name == "FunctionAtCall": node = node[0] val = ieval(node[-1]) for j in list(node)[2::2]: arg = ieval(j) val = val(*(arg if type(arg) == tuple else [arg])) return val elif name == "IndexAccess": val = ieval(node[0]) for j in list(node[1])[::2]: arg = ieval(j[1][0]) val = val.__getitem__(*(prep_index(val) for val in (arg if type(arg) == tuple else [arg]))) return val elif name == "IndexAccessor": return ieval(node[0]) elif name == "FunctionCall": val = ieval(node[0]) for j in list(node[1]): args = map(ieval, list(j[1])[::2]) res = [] dres = {} for arg in args: if isinstance(arg, splat): res.extend(list(arg.item)) elif isinstance(arg, dsplat): dres = {**dres, **arg.item} elif isinstance(arg, kwarg): dres[arg.key] = arg.val else: res.append(arg) val = val(*res, **dres) return val elif name == "Value": unary = {"-": operator.neg, "~": operator.invert, "!": operator.not_, "*": splat, "**": dsplat} val = ieval(node[2]) for oper in list(node[0])[::-1]: val = unary[str(oper)](val) return val elif name == "KeywordArgument": return kwarg(str(node[0]), ieval(node[2])) elif name == "Literal": return ieval(node[0]) elif name == "Identifier": return eval(str(node)) # TODO wtf elif name == "Number": return neval(str(node)) elif name == "Bracketed": return ieval(node[1]) elif name == "String": return ast.literal_eval(str(node)) else: print(name, "not evaluated:", list(node)) return None mode = prettyprint if parse else global_eval parser = globals()[parser] def remove_comments(string): output = "" i = 0 while i < len(string): if string[i] == "\'" or string[i] == "\"": close = string[i] output += close i += 1 while string[i] != close: if string[i] == "\\": output += "\\" + string[i + 1] i += 2 else: output += string[i] i += 1 output += close i += 1 elif string[i:i + 2] == "//": while i < len(string) and string[i] != "\n": i += 1 elif string[i:i + 2] == "/*": nesting = 1 if i >= len(string): raise RuntimeError("Unclosed multiline comment") while nesting: i += 1 if string[i:i + 2] == "/*": nesting += 1 elif string[i:i + 2] == "*/": nesting -= 1 i += 2 else: output += string[i] i += 1 return output if file: try: mode(parser.parser().parse_string(remove_comments(open(file, "r").read().strip()))) except RecursionError: sys.stderr.write("RecursionError - Likely caused by incomplete parsing - check your statements\n") elif code: try: mode(parser.parser().parse_string(remove_comments(code.strip()))) except RecursionError: sys.stderr.write("RecursionError - Likely caused by incomplete parsing - check your statements\n") else: while True: try: mode = prettyprint if parse else lambda k: _print(global_eval(k)) mode(parser.parser().parse_string(remove_comments(input(">>> ").strip()))) except KeyboardInterrupt: print("\b\b \nKeyboardInterrupt") except EOFError: break except RecursionError: print("RecursionError - Likely caused by incomplete parsing - check your statements") except Exception as e: traceback.print_exc()