diff options
Diffstat (limited to 'mwlib/expr.py')
-rwxr-xr-x | mwlib/expr.py | 222 |
1 files changed, 222 insertions, 0 deletions
diff --git a/mwlib/expr.py b/mwlib/expr.py new file mode 100755 index 0000000..fa11ce9 --- /dev/null +++ b/mwlib/expr.py @@ -0,0 +1,222 @@ +#! /usr/bin/env python + +# Copyright (c) 2007-2008 PediaPress GmbH +# See README.txt for additional licensing information. +# based on pyparsing example code (SimpleCalc.py) + +"""Implementation of mediawiki's #expr template. +http://meta.wikimedia.org/wiki/ParserFunctions#.23expr: +""" + +from __future__ import division + +import re +import inspect +import math + +class ExprError(Exception): + pass + +def _myround(a,b): + r=round(a, int(b)) + if int(r)==r: + return int(r) + return r + + +pattern = """ +(?:\s+) +|((?:(?:\d+)(?:\.\d+)? + |(?:\.\d+)) (?:e(?:\+|-)?\d+)?) +|(\+|-|\*|/|>=|<=|<>|!=|[a-zA-Z]+|.) +""" + +rxpattern = re.compile(pattern, re.VERBOSE | re.DOTALL | re.IGNORECASE) +def tokenize(s): + res = [] + for (v1,v2) in rxpattern.findall(s): + if not (v1 or v2): + continue + v2=v2.lower() + if v2 in Expr.constants: + res.append((v2,"")) + else: + res.append((v1,v2)) + return res + + return [(v1,v2.lower()) for (v1,v2) in rxpattern.findall(s) if v1 or v2] + +class uminus: pass +class uplus: pass + +precedence = {"(":-1, ")":-1} +functions = {} + +def addop(op, prec, fun, numargs=None): + precedence[op] = prec + if numargs is None: + numargs = len(inspect.getargspec(fun)[0]) + + + def wrap(stack): + assert len(stack)>=numargs + args = tuple(stack[-numargs:]) + del stack[-numargs:] + stack.append(fun(*args)) + + functions[op] = wrap + +a=addop +a(uminus, 10, lambda x: -x) +a(uplus, 10, lambda x: x) +a("^", 10, math.pow, 2) +a("not", 9, lambda x:int(not(bool(x)))) +a("abs", 9, abs, 1) +a("sin", 9, math.sin, 1) +a("cos", 9, math.cos, 1) +a("asin", 9, math.asin, 1) +a("acos", 9, math.acos, 1) +a("tan", 9, math.tan, 1) +a("atan", 9, math.atan, 1) +a("exp", 9, math.exp, 1) +a("ln", 9, math.log, 1) +a("ceil", 9, lambda x: int(math.ceil(x))) +a("floor", 9, lambda x: int(math.floor(x))) +a("trunc", 9, long, 1) + +a("*", 8, lambda x,y: x*y) +a("/", 8, lambda x,y: x/y) +a("div", 8, lambda x,y: x/y) +a("mod", 8, lambda x,y: int(x)%int(y)) + + +a("+", 6, lambda x,y: x+y) +a("-", 6, lambda x,y: x-y) + +a("round", 5, _myround) + +a("<", 4, lambda x,y: int(x<y)) +a(">", 4, lambda x,y: int(x>y)) +a("<=", 4, lambda x,y: int(x<=y)) +a(">=", 4, lambda x,y: int(x>=y)) +a("!=", 4, lambda x,y: int(x!=y)) +a("<>", 4, lambda x,y: int(x!=y)) +a("=", 4, lambda x,y: int(x==y)) + +a("and", 3, lambda x,y: int(bool(x) and bool(y))) +a("or", 2, lambda x,y: int(bool(x) or bool(y))) +del a + +class Expr(object): + constants = dict( + e=math.e, + pi=math.pi) + + def as_float_or_int(self, s): + try: + return self.constants[s] + except KeyError: + pass + + if "." in s or "e" in s.lower(): + return float(s) + return long(s) + + def output_operator(self, op): + return functions[op](self.operand_stack) + + def output_operand(self, operand): + self.operand_stack.append(operand) + + def parse_expr(self, s): + tokens = tokenize(s) + if not tokens: + return "" + + self.operand_stack = [] + operator_stack = [] + + seen_operand=False + + last_operand, last_operator = False, True + + for operand, operator in tokens: + if operand: + if last_operand: + raise ExprError("expected operator") + self.output_operand(self.as_float_or_int(operand)) + elif operator=="(": + operator_stack.append("(") + elif operator==")": + while 1: + if not operator_stack: + raise ExprError("unbalanced parenthesis") + t = operator_stack.pop() + if t=="(": + break + self.output_operator(t) + elif operator in precedence: + if last_operator and last_operator!=")": + if operator=='-': + operator = uminus + elif operator=='+': + operator = uplus + + is_unary = operator in (uplus, uminus) + prec = precedence[operator] + while not is_unary and operator_stack and prec<=precedence[operator_stack[-1]]: + p = operator_stack.pop() + self.output_operator(p) + operator_stack.append(operator) + else: + raise ExprError("unknown operator: %r" % (operator,)) + + last_operand, last_operator = operand, operator + + + while operator_stack: + p=operator_stack.pop() + if p=="(": + raise ExprError("unbalanced parenthesis") + self.output_operator(p) + + if len(self.operand_stack)!=1: + raise ExprError("bad stack: %s" % (self.operand_stack,)) + + return self.operand_stack[-1] + +def expr(s): + return Expr().parse_expr(s) + +def main(): + ParseException = ExprError + import time + try: + import readline # do not remove. makes raw_input use readline + readline + except ImportError: + pass + + ep = expr + + while 1: + input_string = raw_input("> ") + if not input_string: + continue + + stime = time.time() + try: + res=expr(input_string) + except Exception, err: + print "ERROR:", err + import traceback + traceback.print_exc() + + continue + print res + print time.time()-stime, "s" + +if __name__=='__main__': + main() + + |