Mon, 05 Oct 2015 21:55:53 +0300
Update to the calculator
.hgignore | file | annotate | diff | comparison | revisions | |
calc.py | file | annotate | diff | comparison | revisions | |
calculator.py | file | annotate | diff | comparison | revisions | |
mod_idgames.py | file | annotate | diff | comparison | revisions | |
mod_util.py | file | annotate | diff | comparison | revisions |
--- a/.hgignore Sat Sep 05 05:25:36 2015 +0300 +++ b/.hgignore Mon Oct 05 21:55:53 2015 +0300 @@ -4,6 +4,8 @@ commits.db *.pyc zandronum* +doomseeker cobalt.cfg *.orig *.rej +.*~
--- a/calc.py Sat Sep 05 05:25:36 2015 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,656 +0,0 @@ -''' - Copyright 2015 Teemu Piippo - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - 3. The name of the author may not be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -''' - -from __future__ import print_function -import re -import cmath -import math -import random -import time -import operator -import string -import enum -from copy import deepcopy - -epsilon = 1e-10 - -class Operator (object): - def __init__ (self, name, symbol, operands, priority, function): - self.name = name - self.symbol = symbol - self.operands = operands - self.priority = priority - self.function = function - - def __str__ (self): - return '''<operator %s>''' % self.name - - def __repr__ (self): - return self.__str__() - -class FunctionCall (object): - def __init__ (self, funcname, args): - assert (type(args) is list) - self.funcname = funcname - self.args = args - - def __str__ (self): - return '''<function %s (%s)>''' % (self.funcname, self.args) - - def __repr__ (self): - return self.__str__() - -class Value (object): - def __init__ (self, value): - self.value = value - - def __str__ (self): - return '''<value %s>''' % (self.value) - - def __repr__ (self): - return self.__str__() - -def do_realf (func, *args): - for x in args: - if x.imag: - raise ValueError ('%s called with a complex number' % func.__name__) - - return func (*[x.real for x in args]) - -def do_intf (func, *args): - for x in args: - if x.imag: - raise ValueError ('%s called with a complex number' % func.__name__) - - if x.real - math.floor (x.real): - raise ValueError ('%s called with a floating point number' % func.__name__) - - return func (*[int (x.real) for x in args]) - -def realf (func): - return lambda *args: do_realf (func, *args) - -def intf (func): - return lambda *args: do_intf (func, *args) - -def dice (numRolls, maxValue): - sumValue = 0 - - for i in range (0, numRolls): - sumValue += random.randint (1, maxValue) - - return sumValue - -def scientific (mantissa, exp): - return mantissa * 10 ** exp - -Operators = [] -OperatorData = { - 'sci': { 'symbol': 'e', 'operands': 2, 'priority': 1, 'function': intf (scientific) }, - 'dice': { 'symbol': 'd', 'operands': 2, 'priority': 2, 'function': intf (dice) }, - 'not': { 'symbol': '!', 'operands': 1, 'priority': 5, 'function': lambda x: not x }, - 'compl': { 'symbol': '~', 'operands': 1, 'priority': 5, 'function': intf (operator.inv) }, - 'neg': { 'symbol': '-', 'operands': 1, 'priority': 5, 'function': lambda x: -x }, - 'pow': { 'symbol': '**', 'operands': 2, 'priority': 10, 'function': lambda x, y: x ** y }, - 'mul': { 'symbol': '*', 'operands': 2, 'priority': 50, 'function': lambda x, y: x * y }, - 'div': { 'symbol': '/', 'operands': 2, 'priority': 50, 'function': lambda x, y: x / y }, - 'mod': { 'symbol': '%', 'operands': 2, 'priority': 50, 'function': realf (math.fmod) }, - 'add': { 'symbol': '+', 'operands': 2, 'priority': 100, 'function': lambda x, y: x + y }, - 'sub': { 'symbol': '-', 'operands': 2, 'priority': 100, 'function': lambda x, y: x - y }, - 'eq': { 'symbol': '==', 'operands': 2, 'priority': 500, 'function': lambda x, y: x == y }, - 'neq': { 'symbol': '!=', 'operands': 2, 'priority': 500, 'function': lambda x, y: x != y }, - 'lt': { 'symbol': '<', 'operands': 2, 'priority': 500, 'function': lambda x, y: x < y }, - 'lteq': { 'symbol': '<=', 'operands': 2, 'priority': 500, 'function': lambda x, y: x <= y }, - 'gt': { 'symbol': '>', 'operands': 2, 'priority': 500, 'function': lambda x, y: x > y }, - 'gteq': { 'symbol': '>=', 'operands': 2, 'priority': 500, 'function': lambda x, y: x >= y }, - 'btand': { 'symbol': '&', 'operands': 2, 'priority': 600, 'function': intf (operator.and_) }, - 'btxor': { 'symbol': '^', 'operands': 2, 'priority': 601, 'function': intf (operator.xor) }, - 'btor': { 'symbol': '|', 'operands': 2, 'priority': 602, 'function': intf (operator.or_) }, - 'and': { 'symbol': '&&', 'operands': 2, 'priority': 603, 'function': lambda x, y: x and y }, - 'or': { 'symbol': '||', 'operands': 2, 'priority': 604, 'function': lambda x, y: x or y }, -} - -for name, data in OperatorData.items(): - Operators.append (Operator (name=name, symbol=data['symbol'], operands=data['operands'], - priority=data['priority'], function=data['function'])) - -OperatorSymbols={} -for op in Operators: - if op.symbol in OperatorSymbols: - OperatorSymbols[op.symbol].append (op) - else: - OperatorSymbols[op.symbol] = [op] - -def sgn (x): - return cmp (x, 0) - -def integerclamp (x, bits, signed): - x = x % (2 ** bits) - - if signed and x >= (2 ** (bits - 1)): - x = -(2 ** bits) + x - - return x - -Constants = { - 'pi': cmath.pi, - 'e': cmath.e, - 'phi': 1.6180339887498948482, - 'epsilon': epsilon, - 'potato': cmath.sqrt(8) / 3, -} - -random.seed (time.time()) - -Functions = { - 'round': { 'function': realf (round) }, - 'floor': { 'function': realf (math.floor) }, - 'ceil': { 'function': realf (math.ceil) }, - 'fact': { 'function': intf (math.factorial) }, - 'abs': { 'function': realf (math.fabs) }, - 'degrees': { 'function': realf (math.degrees) }, - 'radians': { 'function': realf (math.radians) }, - 'erf': { 'function': realf (math.erf) }, - 'erfc': { 'function': realf (math.erfc) }, - 'gamma': { 'function': realf (math.gamma) }, - 'lgamma': { 'function': realf (math.lgamma) }, - 'sqrt': { 'function': cmath.sqrt }, - 'ln': { 'function': cmath.log }, - 'log': { 'function': cmath.log10 }, - 'sin': { 'function': cmath.sin }, - 'sinh': { 'function': cmath.sinh }, - 'asin': { 'function': cmath.asin }, - 'asinh': { 'function': cmath.asinh }, - 'cos': { 'function': cmath.cos }, - 'cosh': { 'function': cmath.cosh }, - 'acos': { 'function': cmath.acos }, - 'acosh': { 'function': cmath.acosh }, - 'tan': { 'function': cmath.tan }, - 'tanh': { 'function': cmath.tanh }, - 'atan': { 'function': cmath.atan }, - 'atanh': { 'function': cmath.atanh }, - 'exp': { 'function': cmath.exp }, - 'phase': { 'function': cmath.phase }, - 'lg': { 'function': lambda x: cmath.log (x, 2) }, - 're': { 'function': lambda x: x.real }, - 'im': { 'function': lambda x: x.imag }, - 'conjugate':{ 'function': lambda x: x.conjugate() }, - 'rand': { 'function': random.random }, - 'sgn': { 'function': realf (sgn) }, - 'byte': { 'function': intf (lambda x: integerclamp (x, bits=8, signed=False)) }, - 'sbyte': { 'function': intf (lambda x: integerclamp (x, bits=8, signed=True)) }, - 'word': { 'function': intf (lambda x: integerclamp (x, bits=16, signed=False)) }, - 'sword': { 'function': intf (lambda x: integerclamp (x, bits=16, signed=True)) }, - 'dword': { 'function': intf (lambda x: integerclamp (x, bits=32, signed=False)) }, - 'sdword': { 'function': intf (lambda x: integerclamp (x, bits=32, signed=True)) }, - 'qword': { 'function': intf (lambda x: integerclamp (x, bits=64, signed=False)) }, - 'sqword': { 'function': intf (lambda x: integerclamp (x, bits=64, signed=True)) }, -} - -Tokens = ['(', ')'] - -# Symbol table -SymbolType = enum.Enum ('SymbolType', ('constant', 'function', 'operator', 'token')) -SymbolTypes = {} -Symbols = [] - -for name, value in Constants.items(): - Symbols.append (name) - SymbolTypes[name] = SymbolType.constant - -for name, data in Functions.items(): - Symbols.append (name) - SymbolTypes[name] = SymbolType.function - -for op in Operators: - if op.symbol not in Symbols: - Symbols.append (op.symbol) - SymbolTypes[op.symbol] = SymbolType.operator - -for name in Tokens: - SymbolTypes[name] = SymbolType.token - -Symbols += Tokens -Symbols = sorted (Symbols, key=lambda x: len (x), reverse=True) -PreferredBase = 10 - -def set_preferred_base(x): - global PreferredBase - PreferredBase = x - -def rindex (li, value): - return (len(li) - 1) - li[::-1].index(value) - -def realPrint (x): - print (x) - -Attributes = { - 'hex': lambda self: self.set_preferred_base (0x10), - 'binary': lambda self: self.set_preferred_base (0b10), - 'decimal': lambda self: self.set_preferred_base (10), -} - -Attributes['bin'] = Attributes['binary'] -Attributes['dec'] = Attributes['decimal'] -AttributeNames = sorted ([key for key in Attributes], key=lambda x:len(x), reverse=True) - -def is_int (x): - return math.fabs (x - math.floor(x)) < epsilon - -class Calculator (object): - def __init__ (self): - self.preferred_base = None - - def set_preferred_base (self, x): - self.preferred_base = x - - def trim_spaces (self, expr): - return re.sub ('\s+', '', expr.strip()) - - def parse_attributes (self, expr): - if expr[0] != '<': - return expr - - i = 1 - while expr[i] != '>': - if expr[i] == '|': - i += 1 - - for name in AttributeNames: - if expr[i:i + len(name)] == name: - Attributes[name] (self) - i += len(name) - - if expr[i] == '>': - break - - if expr[i] != '|': - raise ValueError ('malformed attributes') - break - else: - raise ValueError ('bad attributes: %s' % expr[i:]) - - return expr[i + 1:] - - def parse_number (self, expr): - """Tries to parse a number from the expression. Returns (value, length) on success.""" - i = 0 - value = None - base = 10 - - # Possibly it's hexadecimal - if expr[0:2].lower() == '0x': - base = 0x10 - digits = string.hexdigits - digitname = 'hexit' - i = 2 - elif expr[0:2].lower() == '0b': - base = 0b10 - digits = ['0', '1'] - digitname = 'bit' - i = 2 - - if base != 10: - if not self.preferred_base: - self.preferred_base = base - - # Skip leading zeroes - while i < len (expr) and expr[i] == '0': - i += 1 - - startingIndex = i - while i < len (expr) and expr[i] in digits: - i += 1 - - if i < len(expr) and expr[i] == '.': - raise ValueError ('non-decimal floating point numbers are not supported') - - if i == startingIndex: - if i < len (expr): - raise ValueError ('not a valid %s "%s" in %s' % (digitname, expr[i], expr[0:i+1])) - else: - raise ValueError ('end of expression encountered while parsing ' - 'base-%d literal' % base) - - return (Value (int (expr[startingIndex:i], base)), i) - - if expr[0] == 'i': - # Special case, we just have 'i' -- need special handling here because otherwise this would - # call float('') in the complex number routine, which throws an error. - # TODO: maybe 'i' can be a symbol instead? - return (1j, 1) - - # Try parse increasingly long substrings until we are unable to create a float or complex number - # from it. - try: - while i < len (expr): - if expr[i] == 'i': - value = Value (float (expr[:i]) * 1j) - else: - value = Value (float (expr [:i + 1])) - i += 1 - - return (value, i) - except ValueError: - if i != 0: - # Got a number (the error happened when we tried to parse past the number) - return (value, i) - else: - # The error happened on the first character. So this is not a number. - return None - - def parse_symbol (self, expr): - for sym in Symbols: - if expr[:len (sym)] == sym: - return sym - - return None - - def tokenize (self, expr): - i=0 - tokens = [] - - while i < len(expr): - sym = self.parse_symbol (expr[i:]) - - if sym: - symtype = SymbolTypes[sym] - if symtype == SymbolType.constant: - tokens.append (Constants[sym]) - else: - tokens.append (sym) - - i += len(sym) - continue - - result = self.parse_number (expr[i:]) - if result: - num, length = result - tokens.append (num) - i += length - continue - - raise ValueError ("""bad expression, couldn't parse: %s""" % expr[i:]) - - return tokens - - def process_parens (self, expr): - """Processes parentheses of expr into sublists in-place. - [1.0, '*', '(', 3.5, '+', 1j, ')'] - -> [1.0, '*', [3.5, '+', 1j]]""" - if '(' not in expr and ')' not in expr: - return - - try: - parenStart = rindex (expr, '(') - parenEnd = expr.index (')', parenStart) - except ValueError: - raise ValueError ("""mismatched parentheses in expression: %s""" % expr) - - subexpr = expr[parenStart + 1:parenEnd] - del expr[parenStart + 1:parenEnd + 1] - expr[parenStart] = subexpr - self.process_parens (expr) - - def process_functions (self, expr): - """Processes functions in-place""" - i = 0 - while i < len (expr): - if type (expr[i]) is list: - self.process_functions (expr[i]) - - if (type(expr[i]) is not str) or (expr[i] not in Functions): - i += 1 - continue - - # Ensure that arguments follow - if (i + 1 >= len (expr)) or (type (expr[i + 1]) is not list): - raise ValueError ("""function %s used without arguments""" % expr[i]) - - args = expr[i + 1] - del expr[i + 1] - expr[i] = FunctionCall (expr[i], args) - i += 1 - - def is_operand (self, x): - # Operands can be either lists (which also mean parens, thus can be single-expressions) - # or complex numbers - return type(x) in [complex, list, FunctionCall] - - def find_fitting_operator (self, sym, numOperands): - # Pass 1: exact numOperands match - for op in Operators: - if op.symbol != sym: - continue - - if op.operands == numOperands: - return op - - # Pass 2: by symbol - for op in Operators: - if op.symbol == sym: - return op - - raise ValueError ('''unknown operator %s!''' % sym) - - def process_operators (self, expr): - """Processes operators""" - i = 0 - - # Find all operators in this expression - while i < len (expr): - if type (expr[i]) is list: - self.process_functions (expr[i]) - self.process_operators (expr[i]) - - if type (expr[i]) is FunctionCall: - self.process_functions (expr[i].args) - self.process_operators (expr[i].args) - - if (type(expr[i]) is not str) or (expr[i] not in OperatorSymbols): - i += 1 - continue - - args = [] - argIndices = [] - if i > 0 and self.is_operand (expr[i - 1]): - args.append (expr[i - 1]) - argIndices.append (i - 1) - - if i - 1 < len(expr) and self.is_operand (expr[i + 1]): - args.append (expr[i + 1]) - argIndices.append (i + 1) - - # Resolve operators with the same symbol based on operand count - numOperands = 0 - for arg in args: - if self.is_operand (arg): - numOperands += 1 - - expr[i] = self.find_fitting_operator (expr[i], numOperands) - i += 1 - - def find_priority_operator (self, expr): - """Finds the operator with most priority in the expression""" - bestOp = None - bestOpIndex = -1 - - for i in range (0, len (expr)): - sym = expr[i] - - if type (sym) is not Operator: - continue - - if not bestOp or sym.priority < bestOp.priority: - bestOp = sym - bestOpIndex = i - - return (bestOp, bestOpIndex) - - def evaluate (self, expr, verbose=False): - printFunc = realPrint if verbose else lambda x:None - printFunc (self.tabs + 'Preprocess: %s' % expr) - - # If there are sub-expressions in here, those need to be evaluated first - i = 0 - while i < len (expr): - sym = expr[i] - - if type (sym) is list and sym: - printFunc (self.tabs + 'Evaluating sub-expression: %s' % (sym)) - self.tabs += '\t' - expr[i] = self.evaluate (list (sym), verbose) - self.tabs = self.tabs[:-1] - printFunc (self.tabs + '-> %s' % expr[i]) - - # If there are function calls, evaluate those - if type (sym) is FunctionCall: - self.tabs += '\t' - if sym.args: - sym.args = [self.evaluate (sym.args, verbose)] - self.tabs = self.tabs[:-1] - - printFunc (self.tabs + 'Evaluating function call: %s' % (sym)) - expr[i] = Value (Functions[sym.funcname]['function'] (*[x.value for x in sym.args])) - printFunc (self.tabs + '-> %s' % expr[i]) - - i += 1 - - printFunc (self.tabs + 'Evaluate: %s' % expr) - runaway = 0 - while True: - runaway += 1 - if runaway > 1000: - raise ValueError ('infinite loop detected') - - op, i = self.find_priority_operator (expr) - if not op: - break - - if op.operands == 2: - argIndices = [i - 1, i + 1] - else: - argIndices = [i + 1] - - args = [expr[x] for x in argIndices] - argIndices = sorted (argIndices, reverse=True) - printFunc (self.tabs + 'Processing: (%s, %d) with args %s' % (op, i, args)) - expr[i] = Value (op.function (*[x.value for x in args])) - printFunc (self.tabs + '-> %s' % expr[i]) - - for i2 in argIndices: - del expr[i2] - - printFunc (self.tabs + 'Result: %s' % expr[0]) - - if len(expr) != 1: - printFunc (self.tabs + 'Bad expression detected, tokens: %s' % expr) - raise ValueError ('malformed expression') - - return expr[0] - - def repr_number (self, x): - """Returns a string representation for a real number""" - base = self.preferred_base if self.preferred_base else 10 - if is_int (x) and base != 10: - digits='0123456789abcdef' - assert base <= len (digits), '''base %d is too large''' % base - - divisor = base - rep = '' - x = int (x) - runaway = 0 - - if not x: - return '0x0' - - if x < 0: - return '-' + self.repr_number (abs (x)) - - while x > 0: - runaway += 1 - if runaway > 1000: - raise ValueError('runaway triggered') - - i = (x % divisor) / (divisor / base) - x -= i * (divisor / base) - rep += digits[i] - divisor *= base - - rep += 'x' if base == 16 else 'b' - rep += '0' - return rep[::-1] - - rep = '%.10f' % x - - if '.' in rep: - while rep[-1] == '0': - rep = rep[:-1] - - if rep[-1] == '.': - rep = rep[:-1] - - return rep - - def repr_imaginary (self, x): - rep = self.repr_number (x) - - if rep == '1': - return 'i' - - if rep == '-1': - return '-i' - - return rep + 'i' - - def represent (self, x): - """Returns a string representation of a float or complex number""" - if math.fabs (x.imag) > epsilon: - if math.fabs (x.real) > epsilon: - # Complex number - return '%s %s %s' % (self.repr_number (x.real), - '+' if x.imag >= 0 else '-', - self.repr_imaginary (math.fabs (x.imag))) - else: - # Pure imaginary number - return self.repr_imaginary (x.imag) - else: - # Real number - return self.repr_number (x.real) - - def calc (self, expr, verbose=False): - self.state = {} - self.tabs = '' - expr = self.trim_spaces (expr) - expr = self.parse_attributes (expr) - expr = self.tokenize (expr) - self.process_parens (expr) - self.process_functions (expr) - self.process_operators (expr) - result = self.evaluate (expr, verbose).value - return self.represent (result)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/calculator.py Mon Oct 05 21:55:53 2015 +0300 @@ -0,0 +1,720 @@ +''' + Copyright 2015 Teemu Piippo + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +''' + +from __future__ import print_function +import re +import cmath +import math +import random +import time +import operator +import string +import enum +from copy import deepcopy +from math import pi as π + +ε = 1e-10 + +class Operator (object): + def __init__ (self, name, symbol, operands, priority, function): + self.name = name + self.symbol = symbol + self.operands = operands + self.priority = priority + self.function = function + + def __repr__ (self): + return '<operator %s>' % (self.name) + """return ('Operator(name={name}, symbol={symbol}, operands={operands}, ' + 'priority={priority}, function={function})'.format(**self.__dict__))""" + +class FunctionCall (object): + def __init__ (self, funcname, args): + assert (type(args) is list) + self.funcname = funcname + self.args = args + + def __repr__ (self): + return '''FunctionCall(%r, %r)''' % (self.funcname, self.args) + +class Value (object): + def __init__ (self, value): + self.value = value + + def __repr__ (self): + return '''Value(%r)''' % (self.value) + +def do_realf (func, *args): + for x in args: + if x.imag: + raise ValueError ('%s called with a complex number' % func.__name__) + + return func (*[x.real for x in args]) + +def do_intf (func, *args): + for x in args: + if x.imag: + raise ValueError ('%s called with a complex number' % func.__name__) + + if x.real - math.floor (x.real): + raise ValueError ('%s called with a floating point number' % func.__name__) + + return func (*[int (x.real) for x in args]) + +def realf (func): + return lambda *args: do_realf (func, *args) + +def intf (func): + return lambda *args: do_intf (func, *args) + +def dice (numRolls, maxValue): + sumValue = 0 + + for i in range (0, numRolls): + sumValue += random.randint (1, maxValue) + + return sumValue + +def scientific (mantissa, exp): + return mantissa * 10 ** exp + +def cbrt (x): + return x ** (1/3) + +def factorial (x): + return math.gamma (x + 1) + +Operators = {} +OperatorData = { + 'sci': { 'symbol': 'e', 'operands': 2, 'priority': 1, 'function': intf (scientific) }, + 'dice': { 'symbol': 'd', 'operands': 2, 'priority': 2, 'function': intf (dice) }, + 'not': { 'symbol': '!', 'operands': 1, 'priority': 5, 'function': lambda x: not x }, + 'compl': { 'symbol': '~', 'operands': 1, 'priority': 5, 'function': intf (operator.inv) }, + 'neg': { 'symbol': '-', 'operands': 1, 'priority': 5, 'function': lambda x: -x }, + 'sqrt': { 'symbol': '√', 'operands': 1, 'priority': 7, 'function': cmath.sqrt }, + 'cbrt': { 'symbol': '∛', 'operands': 1, 'priority': 7, 'function': cbrt }, + 'fact': { 'symbol': '!', 'operands': -1, 'priority': 8, 'function': realf(factorial) }, + 'pow': { 'symbol': '**', 'operands': 2, 'priority': 10, 'function': lambda x, y: x ** y }, + 'mul': { 'symbol': '*', 'operands': 2, 'priority': 50, 'function': lambda x, y: x * y }, + 'div': { 'symbol': '/', 'operands': 2, 'priority': 50, 'function': lambda x, y: x / y }, + 'mod': { 'symbol': '%', 'operands': 2, 'priority': 50, 'function': realf (math.fmod) }, + 'add': { 'symbol': '+', 'operands': 2, 'priority': 100, 'function': lambda x, y: x + y }, + 'sub': { 'symbol': '-', 'operands': 2, 'priority': 100, 'function': lambda x, y: x - y }, + 'eq': { 'symbol': '==', 'operands': 2, 'priority': 500, 'function': lambda x, y: x == y }, + 'neq': { 'symbol': '!=', 'operands': 2, 'priority': 500, 'function': lambda x, y: x != y }, + 'lt': { 'symbol': '<', 'operands': 2, 'priority': 500, 'function': lambda x, y: x < y }, + 'lteq': { 'symbol': '<=', 'operands': 2, 'priority': 500, 'function': lambda x, y: x <= y }, + 'gt': { 'symbol': '>', 'operands': 2, 'priority': 500, 'function': lambda x, y: x > y }, + 'gteq': { 'symbol': '>=', 'operands': 2, 'priority': 500, 'function': lambda x, y: x >= y }, + 'btand': { 'symbol': '&', 'operands': 2, 'priority': 600, 'function': intf (operator.and_) }, + 'btxor': { 'symbol': '^', 'operands': 2, 'priority': 601, 'function': intf (operator.xor) }, + 'btor': { 'symbol': '|', 'operands': 2, 'priority': 602, 'function': intf (operator.or_) }, + 'and': { 'symbol': '&&', 'operands': 2, 'priority': 603, 'function': lambda x, y: x and y }, + 'or': { 'symbol': '||', 'operands': 2, 'priority': 604, 'function': lambda x, y: x or y }, +} + +def powerfunction (n): + return lambda x: x**n + +superscripts='⁰¹²³⁴⁵⁶⁷⁸⁹' +for i, sup in enumerate(superscripts): + print (i, sup) + func = powerfunction(i) + func.__name__ = 'x' + sup + OperatorData['sup' + str(i)] = { 'symbol': sup, 'operands': -1, 'priority': 3, 'function': func } + +for name, data in OperatorData.items(): + Operators[name] = Operator (name=name, symbol=data['symbol'], operands=data['operands'], + priority=data['priority'], function=data['function']) + +OperatorSymbols={} +for op in Operators.values(): + if op.symbol in OperatorSymbols: + OperatorSymbols[op.symbol].append (op) + else: + OperatorSymbols[op.symbol] = [op] + +def sgn (x): + return cmp (x, 0) + +def integerclamp (x, bits, signed): + x = x % (2 ** bits) + + if signed and x >= (2 ** (bits - 1)): + x = -(2 ** bits) + x + + return x + +Constants = { + 'pi': π, + 'e': math.ℯ, + 'phi': 1.6180339887498948482, + 'epsilon': ε, +} + +random.seed (time.time()) + +Functions = { + 'round': { 'function': realf (round) }, + 'floor': { 'function': realf (math.floor) }, + 'ceil': { 'function': realf (math.ceil) }, + 'factorial': { 'function': intf (math.factorial) }, + 'abs': { 'function': realf (math.fabs) }, + 'degrees': { 'function': realf (math.degrees) }, + 'radians': { 'function': realf (math.radians) }, + 'erf': { 'function': realf (math.erf) }, + 'erfc': { 'function': realf (math.erfc) }, + 'gamma': { 'function': realf (math.gamma) }, + 'lgamma': { 'function': realf (math.lgamma) }, + 'sqrt': { 'function': cmath.sqrt }, + 'cbrt': { 'function': cbrt }, + 'ln': { 'function': cmath.log }, + 'log': { 'function': cmath.log10 }, + 'sin': { 'function': cmath.sin }, + 'sinh': { 'function': cmath.sinh }, + 'asin': { 'function': cmath.asin }, + 'asinh': { 'function': cmath.asinh }, + 'cos': { 'function': cmath.cos }, + 'cosh': { 'function': cmath.cosh }, + 'acos': { 'function': cmath.acos }, + 'acosh': { 'function': cmath.acosh }, + 'tan': { 'function': cmath.tan }, + 'tanh': { 'function': cmath.tanh }, + 'atan': { 'function': cmath.atan }, + 'atanh': { 'function': cmath.atanh }, + 'exp': { 'function': cmath.exp }, + 'phase': { 'function': cmath.phase }, + 'lg': { 'function': lambda x: cmath.log (x, 2) }, + 're': { 'function': lambda x: x.real }, + 'im': { 'function': lambda x: x.imag }, + 'conjugate': { 'function': lambda x: x.conjugate() }, + 'rand': { 'function': random.random }, + 'sgn': { 'function': realf (sgn) }, + 'byte': { 'function': intf (lambda x: integerclamp (x, bits=8, signed=False)) }, + 'sbyte': { 'function': intf (lambda x: integerclamp (x, bits=8, signed=True)) }, + 'word': { 'function': intf (lambda x: integerclamp (x, bits=16, signed=False)) }, + 'sword': { 'function': intf (lambda x: integerclamp (x, bits=16, signed=True)) }, + 'dword': { 'function': intf (lambda x: integerclamp (x, bits=32, signed=False)) }, + 'sdword': { 'function': intf (lambda x: integerclamp (x, bits=32, signed=True)) }, + 'qword': { 'function': intf (lambda x: integerclamp (x, bits=64, signed=False)) }, + 'sqword': { 'function': intf (lambda x: integerclamp (x, bits=64, signed=True)) }, +} + +Tokens = {'(', ')'} + +# Symbol table +SymbolType = enum.Enum ('SymbolType', ('constant', 'function', 'operator', 'token')) +SymbolTypes = {} +Symbols = {'ans'} +SymbolTypes['ans'] = SymbolType.constant + +Aliases = { + 'π': 'pi', + 'ℯ': 'e', + 'φ': 'phi', + 'ε': 'epsilon', + '∨': '||', + '∧': '&&', + '⊻': '^', + 'ℜ': 're', + 'ℑ': 'im', + 'fact': 'factorial', +} + +for name, value in Constants.items(): + Symbols.add (name) + SymbolTypes[name] = SymbolType.constant + +for name, data in Functions.items(): + Symbols.add (name) + SymbolTypes[name] = SymbolType.function + +for op in Operators.values(): + if op.symbol not in Symbols: + Symbols.add (op.symbol) + SymbolTypes[op.symbol] = SymbolType.operator + +for name in Tokens: + SymbolTypes[name] = SymbolType.token + +Symbols |= Tokens +Symbols |= set(Aliases.keys()) +Symbols = sorted (Symbols, key=lambda x: len (x), reverse=True) +PreferredBase = 10 + +def set_preferred_base(x): + global PreferredBase + PreferredBase = x + +def rindex (li, value): + return (len(li) - 1) - li[::-1].index(value) + +Attributes = { + 'hex': lambda self: self.set_preferred_base (0x10), + 'binary': lambda self: self.set_preferred_base (0b10), + 'decimal': lambda self: self.set_preferred_base (10), +} + +Attributes['bin'] = Attributes['binary'] +Attributes['dec'] = Attributes['decimal'] +AttributeNames = sorted ([key for key in Attributes], key=lambda x:len(x), reverse=True) + +def is_int (x): + return math.fabs (x - math.floor(x)) < ε + +class Calculator (object): + def __init__ (self, verbose=False): + self.previous_result = None + self.preferred_base = None + self.verbose = verbose + + def set_preferred_base (self, x): + self.preferred_base = x + + def trim_spaces (self, expr): + return re.sub ('\s+', '', expr.strip()) + + def parse_attributes (self, expr): + if expr[0] != '<': + return expr + + i = 1 + while expr[i] != '>': + if expr[i] == '|': + i += 1 + + for name in AttributeNames: + if expr[i:i + len(name)] == name: + Attributes[name] (self) + i += len(name) + + if expr[i] == '>': + break + + if expr[i] != '|': + raise ValueError ('malformed attributes') + break + else: + raise ValueError ('bad attributes: %s' % expr[i:]) + + return expr[i + 1:] + + def parse_number (self, expr): + """Tries to parse a number from the expression. Returns (value, length) on success.""" + i = 0 + value = None + base = 10 + + # Possibly it's hexadecimal + if expr[0:2].lower() == '0x': + base = 0x10 + digits = string.hexdigits + digitname = 'hexit' + i = 2 + elif expr[0:2].lower() == '0b': + base = 0b10 + digits = ['0', '1'] + digitname = 'bit' + i = 2 + + if base != 10: + if not self.preferred_base: + self.preferred_base = base + + # Skip leading zeroes + while i < len (expr) and expr[i] == '0': + i += 1 + + startingIndex = i + while i < len (expr) and expr[i] in digits: + i += 1 + + if i < len(expr) and expr[i] == '.': + raise ValueError ('non-decimal floating point numbers are not supported') + + if i == startingIndex: + if i < len (expr): + raise ValueError ('not a valid %s "%s" in %s' % (digitname, expr[i], expr[0:i+1])) + else: + raise ValueError ('end of expression encountered while parsing ' + 'base-%d literal' % base) + + return (Value (int (expr[startingIndex:i], base)), i) + + if expr[0] == 'i' or expr[0] == 'ι': + # Special case, we just have 'i' -- need special handling here because otherwise this would + # call float('') in the complex number routine, which throws an error. + return (Value(1j), 1) + + # Try parse increasingly long substrings until we are unable to create a float or complex number + # from it. + try: + while i < len (expr): + if expr[i] == 'i' or expr[i] == 'ι': + value = Value (float (expr[:i]) * 1j) + else: + value = Value (float (expr [:i + 1])) + i += 1 + + return (value, i) + except ValueError: + if i != 0: + # Got a number (the error happened when we tried to parse past the number) + return (value, i) + else: + # The error happened on the first character. So this is not a number. + return None + + def parse_symbol (self, expr): + for sym in Symbols: + if expr[:len (sym)].lower() == sym.lower(): + return sym.lower() + + return None + + def tokenize (self, expr): + i=0 + tokens = [] + + while i < len(expr): + sym = self.parse_symbol (expr[i:]) + + if sym: + i += len(sym) + if sym in Aliases: + sym = Aliases[sym] + + if sym == 'ans': + if self.previous_result is None: + raise ValueError ('Cannot use "ans" because there is no prior result') + tokens.append (Value (self.previous_result)) + else: + symtype = SymbolTypes[sym] + if symtype == SymbolType.constant: + tokens.append (Value(Constants[sym])) + else: + tokens.append (sym) + continue + + result = self.parse_number (expr[i:]) + if result: + num, length = result + tokens.append (num) + i += length + continue + + raise ValueError ("""bad expression, couldn't parse: %s""" % expr[i:]) + + return tokens + + def process_parens (self, expr): + """Processes parentheses of expr into sublists in-place. + [1.0, '*', '(', 3.5, '+', 1j, ')'] + -> [1.0, '*', [3.5, '+', 1j]]""" + if '(' not in expr and ')' not in expr: + return + + try: + parenStart = rindex (expr, '(') + parenEnd = expr.index (')', parenStart) + except ValueError: + raise ValueError ("""mismatched parentheses in expression: %s""" % expr) + + subexpr = expr[parenStart + 1:parenEnd] + del expr[parenStart + 1:parenEnd + 1] + expr[parenStart] = subexpr + self.process_parens (expr) + + def process_functions (self, expr): + """Processes functions in-place""" + i = 0 + while i < len (expr): + if type (expr[i]) is list: + self.process_functions (expr[i]) + + if (type(expr[i]) is not str) or (expr[i] not in Functions): + i += 1 + continue + + # Ensure that arguments follow + if (i + 1 >= len (expr)) or (type (expr[i + 1]) is not list): + raise ValueError ("""function %s used without arguments""" % expr[i]) + + args = expr[i + 1] + del expr[i + 1] + expr[i] = FunctionCall (expr[i], args) + i += 1 + + def is_operand (self, x): + # Operands can be either lists (which also mean parens, thus can be single-expressions) + # or complex numbers + return type(x) in [list, FunctionCall, Value] + + def find_fitting_operator (self, sym, numOperands): + # Pass 1: exact numOperands match + for op in Operators.values(): + if op.symbol != sym: + continue + + if op.operands == numOperands: + return op + + # Pass 2: by symbol + for op in Operators.values(): + if op.symbol == sym: + return op + + raise ValueError ('''unknown operator %s!''' % sym) + + def process_operators (self, expr): + """Processes operators""" + i = 0 + + # Find all operators in this expression + while i < len (expr): + if type (expr[i]) is list: + self.process_functions (expr[i]) + self.process_operators (expr[i]) + + if type (expr[i]) is FunctionCall: + self.process_functions (expr[i].args) + self.process_operators (expr[i].args) + + if (type(expr[i]) is not str) or (expr[i] not in OperatorSymbols): + i += 1 + continue + + # Test for suffix operators + if i > 0 and self.is_operand (expr[i - 1]): + try: + op = self.find_fitting_operator (expr[i], -1) + except: + pass + else: + expr[i] = op + continue + + args = [] + argIndices = [] + if i > 0 and self.is_operand (expr[i - 1]): + args.append (expr[i - 1]) + argIndices.append (i - 1) + + if i - 1 < len(expr) and self.is_operand (expr[i + 1]): + args.append (expr[i + 1]) + argIndices.append (i + 1) + + # Resolve operators with the same symbol based on operand count + numOperands = 0 + for arg in args: + if self.is_operand (arg): + numOperands += 1 + + expr[i] = self.find_fitting_operator (expr[i], numOperands) + i += 1 + + def amend_multiplication (self, expr): + i = 0 + while i + 1 < len(expr): + if self.is_operand (expr[i]) and self.is_operand (expr[i + 1]): + expr.insert(i + 1, Operators['mul']) + i += 2 + else: + i += 1 + + def find_priority_operator (self, expr): + """Finds the operator with most priority in the expression""" + bestOp = None + bestOpIndex = -1 + + for i in range (0, len (expr)): + sym = expr[i] + + if type (sym) is not Operator: + continue + + if not bestOp or sym.priority < bestOp.priority: + bestOp = sym + bestOpIndex = i + + return (bestOp, bestOpIndex) + + def evaluate (self, expr, verbose=False): + printFunc = print if verbose else lambda x:None + printFunc (self.tabs + 'Preprocess: %s' % expr) + + # If there are sub-expressions in here, those need to be evaluated first + i = 0 + while i < len (expr): + sym = expr[i] + + if type (sym) is list and sym: + printFunc (self.tabs + 'Evaluating sub-expression: %s' % (sym)) + self.tabs += '\t' + expr[i] = self.evaluate (list (sym), verbose) + self.tabs = self.tabs[:-1] + printFunc (self.tabs + '-> %s' % expr[i]) + + # If there are function calls, evaluate those + if type (sym) is FunctionCall: + self.tabs += '\t' + if sym.args: + sym.args = [self.evaluate (sym.args, verbose)] + self.tabs = self.tabs[:-1] + + printFunc (self.tabs + 'Evaluating function call: %s' % (sym)) + expr[i] = Value (Functions[sym.funcname]['function'] (*[x.value for x in sym.args])) + printFunc (self.tabs + '-> %s' % expr[i]) + + i += 1 + + printFunc (self.tabs + 'Evaluate: %s' % expr) + runaway = 0 + while True: + runaway += 1 + if runaway > 1000: + raise ValueError ('infinite loop detected') + + op, i = self.find_priority_operator (expr) + if not op: + break + + if op.operands == 2: + argIndices = [i - 1, i + 1] + elif op.operands == -1: + argIndices = [i - 1] + else: + argIndices = [i + 1] + + args = [expr[x] for x in argIndices] + argIndices = sorted (argIndices, reverse=True) + printFunc (self.tabs + 'Processing: (%s, %d) with args %s' % (op, i, args)) + expr[i] = Value (op.function (*[x.value for x in args])) + printFunc (self.tabs + '-> %s' % expr[i]) + + for i2 in argIndices: + del expr[i2] + + printFunc (self.tabs + 'Result: %s' % expr[0]) + + if len(expr) != 1: + printFunc (self.tabs + 'Bad expression detected, tokens: %s' % expr) + raise ValueError ('malformed expression') + + return expr[0] + + def repr_number (self, x): + """Returns a string representation for a real number""" + base = self.preferred_base if self.preferred_base else 10 + if is_int (x) and base != 10: + digits='0123456789abcdef' + assert base <= len (digits), '''base %d is too large''' % base + + divisor = base + rep = '' + x = int (x) + runaway = 0 + + if not x: + return '0x0' + + if x < 0: + return '-' + self.repr_number (abs (x)) + + while x > 0: + runaway += 1 + if runaway > 1000: + raise ValueError('runaway triggered') + + i = (x % divisor) / (divisor / base) + x -= i * (divisor / base) + rep += digits[i] + divisor *= base + + rep += 'x' if base == 16 else 'b' + rep += '0' + return rep[::-1] + + rep = '%.10f' % x + + if '.' in rep: + while rep[-1] == '0': + rep = rep[:-1] + + if rep[-1] == '.': + rep = rep[:-1] + + return rep + + def repr_imaginary (self, x): + rep = self.repr_number (x) + + if rep == '1': + return 'i' + + if rep == '-1': + return '-i' + + return rep + 'i' + + def represent (self, x): + """Returns a string representation of a float or complex number""" + if math.fabs (x.imag) > ε: + if math.fabs (x.real) > ε: + # Complex number + return '%s %s %s' % (self.repr_number (x.real), + '+' if x.imag >= 0 else '-', + self.repr_imaginary (math.fabs (x.imag))) + else: + # Pure imaginary number + return self.repr_imaginary (x.imag) + else: + # Real number + return self.repr_number (x.real) + + def calc (self, expr, verbose=None): + if verbose is None: + verbose = self.verbose + self.state = {} + self.tabs = '' + expr = self.trim_spaces (expr) + expr = self.parse_attributes (expr) + expr = self.tokenize (expr) + self.process_parens (expr) + self.process_functions (expr) + self.amend_multiplication (expr) + self.process_operators (expr) + result = self.evaluate (expr, verbose).value + self.previous_result = result + return self.represent (result) + + def __call__ (self, expr): + return self.calc (expr) + +calc = Calculator() +dcalc = Calculator(verbose=True) \ No newline at end of file
--- a/mod_idgames.py Sat Sep 05 05:25:36 2015 +0300 +++ b/mod_idgames.py Mon Oct 05 21:55:53 2015 +0300 @@ -28,7 +28,7 @@ from __future__ import print_function from modulecore import command_error -import urllib +import urllib.parse import json import utility @@ -48,7 +48,7 @@ def cmd_idgames (bot, args, reply, **rest): try: - url = g_idgamesSearchURL % urllib.quote (args['wad']) + url = g_idgamesSearchURL % urllib.parse.quote (args['wad']) response = utility.read_url (url) data = json.loads (response)
--- a/mod_util.py Sat Sep 05 05:25:36 2015 +0300 +++ b/mod_util.py Mon Oct 05 21:55:53 2015 +0300 @@ -32,10 +32,11 @@ import re import modulecore import utility -import calc +import calculator import urllib.parse import datetime from munge import munge +from math import pi as π @modulecore.irc_command (args='<value> as <valuetype>') def convert (bot, args, reply, error, **rest): @@ -46,18 +47,18 @@ error (str (e)) valuetype = args['valuetype'] - + if valuetype in ['radians', 'degrees']: if valuetype == 'radians': radvalue = value - degvalue = (value * 180.) / math.pi + degvalue = (value * 180.) / π else: - radvalue = (value * math.pi) / 180. + radvalue = (value * π) / 180. degvalue = value - + reply ('%s radians, %s degrees (%s)' % (radvalue, degvalue, degvalue % 360.)) return - + if valuetype in ['celsius', 'fahrenheit']: if valuetype == 'celsius': celvalue = value @@ -65,10 +66,10 @@ else: celvalue = (value - 32) / 1.8 fahrvalue = value - + reply ('%s degrees celsius, %s degrees fahrenheit' % (celvalue, fahrvalue)) return - + error ('unknown valuetype %s, expected one of: degrees, radians (angle conversion), ' + 'celsius, fahrenheit (temperature conversion)' % valuetype) @@ -129,7 +130,7 @@ @modulecore.irc_command (args='<expression...>') def calc (bot, reply, args, **rest): '''Calculates a mathematical expression.''' - reply (calc.Calculator().calc (args['expression'])) + reply (calculator.calc (args['expression'])) @modulecore.irc_command() def more (commandObject, **rest): @@ -171,10 +172,4 @@ @modulecore.irc_command (args='<format...>') def strftime (reply, args, **rest): - '''Formats current time.''' reply ('\x0f' + datetime.datetime.utcnow().strftime (args['format'])) - -@modulecore.irc_command (name='munge', args='<string...>') -def mungecmd (reply, args, **rest): - '''Tests text munging.''' - reply ('\x0f' + munge (args['string'])) \ No newline at end of file