Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorReinier Heeres <reinier@heeres.eu>2008-12-23 21:50:32 (GMT)
committer Reinier Heeres <reinier@heeres.eu>2008-12-23 21:50:32 (GMT)
commit6807696129436dbf071b606300ce6dda2adb1c50 (patch)
tree984328276f12f6d18f89ea5ea4e940a379c3993f
parentf0b95df2389c5686979b489fa5f2084fd3738edf (diff)
Move to more flexible and faster parser based on python AST
-rw-r--r--astparser.py484
-rw-r--r--constants.py42
-rw-r--r--eqnparser.py663
-rw-r--r--functions.py401
-rw-r--r--mathlib.py238
-rw-r--r--plotlib.py70
6 files changed, 973 insertions, 925 deletions
diff --git a/astparser.py b/astparser.py
new file mode 100644
index 0000000..8acb145
--- /dev/null
+++ b/astparser.py
@@ -0,0 +1,484 @@
+# -*- coding: UTF-8 -*-
+
+import types
+import parser
+import re
+import inspect
+import math
+import logging
+
+from gettext import gettext as _
+
+# Python 2.6 has a 'public' ast module
+try:
+ import ast
+except ImportError:
+ import _ast as ast
+
+from mathlib import MathLib
+from plotlib import PlotLib
+
+class ParseError(Exception):
+ def __init__(self, msg, start, end=None):
+ self._msg = msg
+
+ if end is None:
+ end = start + 1
+ self._range = (start, end)
+
+ def get_range(self):
+ return self._range
+
+ def __str__(self):
+ msg = _("Error at %d") % (self._range[0] + 1)
+ if self._msg is not None and len(self._msg) > 0:
+ msg += ": %s" % (self._msg)
+ return msg
+
+class Helper:
+
+ def __init__(self, parent):
+ self._parent = parent
+ self._topics = {}
+ self.add_help('test',
+ _('This is just a test topic, use help(index) for the index'))
+
+ def add_help(self, topic, text):
+ self._topics[unicode(topic)] = _(text)
+ self._topics[unicode(_(topic))] = _(text)
+
+ def get_help(self, topic=None):
+ if isinstance(topic, ast.Name):
+ topic = topic.id
+ elif isinstance(topic, ast.Str):
+ topic = topic.s
+ elif type(topic) not in (types.StringType, types.UnicodeType) or len(topic) == 0:
+ return _("Use help(test) for help about 'test', or help(index) for the index")
+
+ # TRANS: This command is descriptive, so can be translated
+ if topic in ('index', _('index'), 'topics', _('topics')):
+ ret = _('Topics') + ': '
+ topics = self._topics.keys()
+ topics.append('index')
+ topics.sort()
+ ret += ', '.join(topics)
+ return ret
+
+ # TRANS: This command is descriptive, so can be translated
+ if topic in ('variables', _('variables')):
+ ret = _('Variables') + ': '
+ variables = self._parent.get_variable_names()
+ ret += ', '.join(variables)
+ return ret
+
+ # TRANS: This command is descriptive, so can be translated
+ if topic in ('functions', _('functions')):
+ ret = _('Functions') + ': '
+ functions = self._parent.get_function_names()
+ ret += ', '.join(functions)
+ return ret
+
+ for (key, val) in self._topics.iteritems():
+ if topic == key or _(topic) == key:
+ return val
+
+ return _("No help about '%s' available, use help(index) for the index") % (topic)
+
+class AstParser:
+ '''
+ Equation parser based on python's ast (abstract syntax tree) module.
+ In 2.5 this is a private module, but in 2.6 it is public.
+ '''
+
+ OPERATOR_MAP = {
+ u'⨯': '*',
+ u'×': '*',
+ u'÷': '/',
+ '^': '**',
+ }
+
+ DIADIC_OPS = (
+ '+', '-', '*', u'⨯', u'×', u'÷' , '/', '^', '**',
+ '&', '|', '=', '!=', '<', '>', '<<', '>>', '%',
+ )
+
+ PRE_OPS = (
+ '-', '+', '~',
+ )
+
+ POST_OPS = (
+ )
+
+ FLOAT_REGEXP_STR = '([+-]?[0-9]*\.?[0-9]+([eE][+-]?[0-9]+)?)'
+ FLOAT_REGEXP = re.compile(FLOAT_REGEXP_STR)
+ RANGE_REGEXP = re.compile(FLOAT_REGEXP_STR + '\.\.' + FLOAT_REGEXP_STR)
+
+ # Unary and binary operator maps.
+ # Mappings to a string will be replaced by calls to MathLib functions
+ # with the same name.
+
+ UNARYOP_MAP = {
+ ast.UAdd: lambda x: x,
+ ast.USub: lambda x: -x,
+ ast.Not: lambda x: not x,
+ }
+
+ BINOP_MAP = {
+ ast.Add: 'add',
+ ast.And: lambda x, y: x and y,
+ ast.BitAnd: lambda x, y: x & y,
+ ast.BitOr: lambda x, y: x | y,
+ ast.BitXor: lambda x, y: x ^ y,
+ ast.Div: 'div',
+ ast.FloorDiv: 'div',
+ ast.LShift: 'shift_left',
+ ast.Mod: 'mod',
+ ast.Mult: 'mul',
+ ast.NotEq: lambda x, y: x != y,
+ ast.Or: lambda x, y: x or y,
+ ast.Pow: 'pow',
+ ast.RShift: 'shift_right',
+ ast.Sub: 'sub',
+ }
+
+ CMPOP_MAP = {
+ ast.Gt: lambda x, y: x > y,
+ ast.GtE: lambda x, y: x >= y,
+ ast.Is: lambda x, y: x == y,
+ ast.IsNot: lambda x, y: x != y,
+ ast.Lt: lambda x, y: x < y,
+ ast.LtE: lambda x, y: x <= y,
+ }
+
+ _ARG_STRING = 0
+ _ARG_NODE = 1
+
+ def __init__(self, ml=None, pl=None):
+ self._namespace = {}
+
+ if ml is None:
+ self.ml = MathLib()
+ else:
+ self.ml = ml
+
+ if pl is None:
+ self.pl = PlotLib(self)
+ else:
+ self.pl = pl
+
+ # Plug-in plot function
+ self.set_var('plot', self.pl.plot)
+
+ # Help manager
+ self._helper = Helper(self)
+ self.set_var('help', self._helper.get_help)
+ self._special_func_args = {
+ (self._helper.get_help, 0): self._ARG_STRING,
+ (self.pl.plot, 0): self._ARG_NODE,
+ }
+
+ self._load_plugins()
+
+ # Redirect operations to registered functions
+ for key, val in self.UNARYOP_MAP.iteritems():
+ if type(val) is types.StringType:
+ self.UNARYOP_MAP[key] = self.get_var(val)
+ for key, val in self.BINOP_MAP.iteritems():
+ if type(val) is types.StringType:
+ self.BINOP_MAP[key] = self.get_var(val)
+
+ def _load_plugin_items(self, items):
+ for name, item in items:
+ if name.startswith('_') or type(item) is types.ModuleType:
+ continue
+
+ self.set_var(name, item)
+ if type(item) in (types.FunctionType, types.ClassType):
+ if item.__doc__ is not None:
+ self._helper.add_help(name, item.__doc__)
+
+ def _load_plugins(self):
+ plugins = ('functions', 'constants')
+ for plugin in plugins:
+ try:
+ exec('import %s' % plugin)
+ exec('_mod = %s' % plugin)
+ items = inspect.getmembers(_mod)
+ self._load_plugin_items(items)
+
+ except Exception, e:
+ logging.error('Error loading plugin: %s', e)
+
+ def log_debug_info(self):
+ logging.debug('Variables:')
+ for name in self.get_variable_names():
+ logging.debug(' %s', name)
+ logging.debug('Functions:')
+ for name in self.get_function_names():
+ logging.debug(' %s', name)
+ logging.debug('Unary ops:')
+ for op in self.UNARYOP_MAP.keys():
+ logging.debug(' %s', op)
+ logging.debug('Binary ops:')
+ for op in self.BINOP_MAP.keys():
+ logging.debug(' %s', op)
+
+ def set_var(self, name, value):
+ '''Set variable <name> to <value>, which could be a function too.'''
+ self._namespace[unicode(name)] = value
+
+ def get_var(self, name):
+ '''Return variable value, or None if non-existent.'''
+ return self._namespace.get(unicode(name), None)
+
+ def _get_names(self, start='', include_vars=True):
+ ret = []
+ for key, val in self._namespace.iteritems():
+ if type(val) is types.ClassType:
+ for key2, val2 in inspect.getmembers(val):
+ if key2.startswith('_'):
+ continue
+
+ b = type(val2) not in (types.FunctionType, types.MethodType)
+ if not include_vars:
+ b = not b
+ if b and key2.startswith(start):
+ ret.append(key2)
+
+ else:
+ b = type(val) not in (types.FunctionType, types.MethodType)
+ if not include_vars:
+ b = not b
+ if b and key.startswith(start):
+ ret.append(key)
+
+ ret.sort()
+ return ret
+
+ def get_names(self, start=''):
+ '''Return a list with names of all defined variables/functions.'''
+ ret = []
+ for key, val in self._namespace.iteritems():
+ if key.startswith(start):
+ ret.append(key)
+
+ return ret
+
+ def get_variable_names(self, start=''):
+ '''Return a list with names of all defined variables.'''
+ return self._get_names(start, include_vars=True)
+
+ def get_function_names(self, start=''):
+ '''Return a list with names of all defined function.'''
+ return self._get_names(start, include_vars=False)
+
+ def add_help(self, topic, text):
+ self._help_topics[topic] = text
+
+ def get_diadic_operators(self):
+ return self.DIADIC_OPS
+
+ def get_post_operators(self):
+ return self.POST_OPS
+
+ def get_pre_operators(self):
+ return self.PRE_OPS
+
+ def _resolve_arg(self, func, index, arg, level):
+ funcarg = (func, index)
+ if funcarg in self._special_func_args:
+ val = self._special_func_args[funcarg]
+ if val == self._ARG_NODE:
+ return arg
+ if val == self._ARG_STRING:
+ if isinstance(arg, ast.Name):
+ return arg.id
+ elif isinstance(arg, ast.Str):
+ return arg.s
+ else:
+ logging.error('Unable to resolve special arg %r', arg)
+ else:
+ return self._process_node(arg, level)
+
+ def _process_node(self, node, level=0, isfunc=False):
+ ofs = node.col_offset
+
+ if node is None:
+ return None
+
+ elif isinstance(node, ast.Expression):
+ return self._process_node(node.body)
+
+ elif isinstance(node, ast.BinOp):
+ left = self._process_node(node.left, level + 1)
+ right = self._process_node(node.right, level + 1)
+ if left is None or right is None:
+ return None
+ func = self.BINOP_MAP[type(node.op)]
+ return func(left, right)
+
+ elif isinstance(node, ast.UnaryOp):
+ operand = self._process_node(node.operand, level + 1)
+ if operand is None:
+ return None
+ func = self.UNARYOP_MAP[type(node.op)]
+ return func(operand)
+
+ elif isinstance(node, ast.Compare):
+ left = self._process_node(node.left)
+ right = self._process_node(node.comparators[0])
+ func = self.CMPOP_MAP[type(node.ops[0])]
+ return func(left, right)
+
+ elif isinstance(node, ast.Call):
+ func = self._process_node(node.func, level + 1, isfunc=True)
+ if func is None:
+ return None
+
+ for i in range(len(node.args)):
+ node.args[i] = self._resolve_arg(func, i, node.args[i], level + 1)
+ if node.args[i] is None:
+ return None
+ kwargs = {}
+ for i in range(len(node.keywords)):
+ key = node.keywords[i].arg
+ val = self._process_node(node.keywords[i].value, level + 1)
+ if key is None or val is None:
+ return None
+ kwargs[key] = val
+
+ try:
+ ret = func(*node.args, **kwargs)
+ return ret
+ except Exception, e:
+ msg = str(e)
+ raise ParseError(msg, ofs)
+
+ elif isinstance(node, ast.Num):
+ return node.n
+
+ elif isinstance(node, ast.Str):
+ return node.s
+
+ elif isinstance(node, ast.Tuple):
+ list = [self._process_node(i, level + 1) for i in node.elts]
+ return tuple(list)
+
+ elif isinstance(node, ast.Name):
+ if not isfunc and node.id in ('help', _('help')):
+ return self._helper.get_help()
+
+ elif node.id in self._namespace:
+ var = self.get_var(node.id)
+ if type(var) is ast.Expression:
+ return self._process_node(var.body)
+ else:
+ return var
+ else:
+ if isfunc:
+ msg = _("Function '%s' not defined") % (node.id)
+ else:
+ msg = _("Variable '%s' not defined") % (node.id)
+ raise ParseError(msg, ofs, ofs + len(node.id))
+
+ elif isinstance(node, ast.Attribute):
+ parent = self._process_node(node.value)
+ if parent:
+ try:
+ val = parent.__dict__[node.attr]
+ return val
+ except Exception, e:
+ msg = _("Attribute '%s' does not exist)") % node.value
+ raise ParseError(msg, ofs, ofs + len(node.value))
+
+ return None
+
+ else:
+ logging.debug('Unknown node: %r', repr(node))
+
+ return None
+
+ def _preprocess_eqn(self, eqn):
+ eqn = unicode(eqn)
+ for key, val in self.OPERATOR_MAP.iteritems():
+ eqn = eqn.replace(key, val)
+
+ # Replace a..b ranges with (a,b)
+ eqn = self.RANGE_REGEXP.sub(r'(\1,\3)', eqn)
+
+ return eqn
+
+ def parse(self, eqn):
+ '''
+ Parse an equation and return a parse tree.
+ '''
+
+ eqn = self._preprocess_eqn(eqn)
+ logging.debug('Parsing preprocessed equation: %r', eqn)
+
+ try:
+ tree = compile(eqn, '<string>', 'eval', ast.PyCF_ONLY_AST)
+ except SyntaxError, e:
+ msg = _('Parse error')
+ raise ParseError(msg, e.offset - 1)
+
+ return tree
+
+ def evaluate(self, eqn):
+ '''
+ Evaluate an equation or parse tree.
+ '''
+
+ if type(eqn) in (types.StringType, types.UnicodeType):
+ eqn = self.parse(eqn)
+
+ if isinstance(eqn, ast.Expression):
+ ret = self._process_node(eqn.body)
+ else:
+ ret = self._process_node(eqn)
+
+ if type(ret) is types.FunctionType:
+ return ret()
+ else:
+ return ret
+
+ def parse_and_eval(self, eqn):
+ '''
+ Parse and evaluate an equation.
+ '''
+
+ tree = self.parse(eqn)
+ if tree is not None:
+ return self.evaluate(tree)
+ else:
+ return None
+
+if __name__ == '__main__':
+ logging.basicConfig(level=logging.DEBUG)
+
+ p = AstParser()
+ eqn = 'sin(45)'
+ ret = p.evaluate(eqn)
+ print 'Eqn: %s, ret: %s' % (eqn, ret)
+
+ eqn = '2<=physics.c'
+ ret = p.evaluate(eqn)
+ print 'Eqn: %s, ret: %s' % (eqn, ret)
+
+ eqn = 'help(functions)'
+ ret = p.evaluate(eqn)
+ print 'Eqn: %s, ret: %s' % (eqn, ret)
+
+ eqn = 'factorize(105)'
+ ret = p.evaluate(eqn)
+ print 'Eqn: %s, ret: %s' % (eqn, ret)
+
+ eqn = 'plot(x**2,x=-2..2)'
+ ret = p.evaluate(eqn)
+ print 'Eqn: %s, ret: %s' % (eqn, ret)
+
+ p.set_var('a', 123)
+ eqn = 'a * 5'
+ ret = p.evaluate(eqn)
+ print 'Eqn: %s, ret: %s' % (eqn, ret)
+
diff --git a/constants.py b/constants.py
new file mode 100644
index 0000000..f071ab3
--- /dev/null
+++ b/constants.py
@@ -0,0 +1,42 @@
+# constants.py, constants available in Calculate,
+# by Reinier Heeres <reinier@heeres.eu>
+# Most of these come from Wikipedia.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import math as _math
+
+pi = _math.pi # 3.1415926535
+e = _math.exp(1) # 2.7182818284590451
+
+class math:
+ golden_ratio = 1.61803398874989484820458683436563811
+
+class physics:
+ c = 299792458 # Speed of light (in vacuum)
+ h = 6.6260689633e-34 # Planck's constant
+ hbar = 1.05457162853e-34 # Dirac's constant
+ mu0 = 4e-7 * pi # Magnetic permeability of vacuum
+ e0 = 8.854187817e-12 # Electric permeability of vacuum
+
+ Na = 6.022141510e23 # Avogadro's number
+ kb = 1.380650524e-23 # Boltmann's constant
+ R = 8.31447215 # Gas constant
+
+ c_e = -1.60217648740e-19 # Electron charge
+ m_e = 9.109382616e-31 # Electron mass
+ m_p = 1.6726217129e-27 # Proton mass
+ m_n = 1.6749272928e-27 # Neutron mass
+
diff --git a/eqnparser.py b/eqnparser.py
deleted file mode 100644
index b09a7c3..0000000
--- a/eqnparser.py
+++ /dev/null
@@ -1,663 +0,0 @@
-# -*- coding: UTF-8 -*-
-# eqnparser.py, generic equation parser by Reinier Heeres <reinier@heeres.eu>
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-#
-# Change log:
-# 2007-07-03: rwh, first version
-
-import logging
-_logger = logging.getLogger('EqnParser')
-
-import types
-from mathlib import MathLib
-from plotlib import PlotLib
-from eqnparserhelp import EqnParserHelp
-
-from rational import Rational
-
-from gettext import gettext as _
-
-_generic_error = _('Parse error')
-
-class Equation:
- def __init__(self, eqn):
- self.equation = eqn
-
-class ParserState:
- OK = 1
- PARSE_ERROR = 2
-
- def __init__(self, str):
- self.str = str
- self.strlen = len(str)
- if self.strlen > 0:
- self.char = str[0]
- else:
- self.char = None
- self.ofs = 0
- self.level = -1
- self.result_type = EqnParser.TYPE_UNKNOWN
- self.error_code = self.OK
- self.error_msg = ""
- self.error_range = (0, 0)
-
- def state_string(self):
- return _('level: %d, ofs %d') % (self.level, self.ofs)
-
- def more(self):
- return self.error_code == self.OK and self.ofs < self.strlen
-
- def next(self):
- self.ofs += 1
- if self.ofs < self.strlen:
- self.char = self.str[self.ofs]
- else:
- self.char = None
- return self.char
-
- def prev(self):
- self.ofs -= 1
- if self.ofs < self.strlen and self.ofs >= 0:
- self.char = self.str[self.ofs]
- else:
- self.char = None
- return self.char
-
- def set_ofs(self, o):
- self.ofs = o
- self.char = self.str[o]
-
- def inc_level(self):
- self.level += 1
-
- def dec_level(self):
- self.level -= 1
-
- def set_type(self, t):
- if self.result_type == EqnParser.TYPE_UNKNOWN or t is EqnParser.TYPE_SYMBOLIC:
- self.result_type = t
- return True
- elif self.result_type != t:
- _logger.debug('Type error')
- return False
- else:
- return True
-
- def set_error(self, c, msg=None, range=None):
- self.error_code = c
- if msg is not None:
- self.error_msg = msg
- if range is not None:
- _logger.debug('Setting range: %r', range)
- self.error_range = range
- else:
- _logger.debug('Setting offset: %d', self.ofs)
- self.error_range = (self.ofs, self.ofs + 1)
-
- def get_error(self):
- return self.error_code
-
- def set_error_range(self, r):
- self.error_range = r
-
- def format_error(self):
- msg = _("Error at %d") % (self.error_range[0])
- if self.error_msg is not None and len(self.error_msg) > 0:
- msg += ": %s" % (self.error_msg)
- return msg
-
- def copy_error(self, ps):
- """Copy error state from recursively created ParserState object"""
- self.error_code = ps.error_code
- self.error_msg = ps.error_msg
- self.error_range = (ps.error_range[0] + ps.ofs, ps.error_range[1] + ps.ofs)
-
-class EqnParser:
- OP_INVALID = -1
- OP_PRE = 1
- OP_POST = 2
- OP_DIADIC = 3
- OP_ASSIGN = 4
-
- INVALID_OP = ('err', OP_INVALID, 0, lambda x: False)
-
- NAME_CHARS = u'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789 '
- DIGITS = u'0123456789'
- SPACE_CHARS = u'\t \r\n'
-
-# These will be filled from register_operator
- OP_START_CHARS = u""
- OP_CHARS = u""
-
- TYPE_UNKNOWN = 1
- TYPE_INT = 2
- TYPE_FLOAT = 3
- TYPE_BOOL = 4
- TYPE_SYMBOLIC = 5
-
- def __init__(self, ml=None):
- if ml is None:
- self.ml = MathLib()
- else:
- self.ml = ml
-
- self.ps = None
-
- self.pl = PlotLib(self)
-
- self.variables = {}
- self.parse_var = {} # Whether or not to parse this variable recursively
- self.functions = {}
- self.operators = []
-
- self.cached_diadic_ops = None
- self.cached_pre_ops = None
- self.cached_post_ops = None
-
- self.register_function('exp', lambda x: self.ml.exp(x[0]), {"nargs": 1})
- self.register_function('ln', lambda x: self.ml.ln(x[0]), {"nargs": 1})
- self.register_function('log', lambda x: self.ml.log10(x[0]), {"nargs": 1})
- self.register_function('log10', lambda x: self.ml.log10(x[0]), {"nargs": 1})
- self.register_function('pow', lambda x: self.ml.pow(x[0], x[1]), {"nargs": 2})
-
- self.register_function('sqrt', lambda x: self.ml.sqrt(x[0]), {"nargs": 1})
-
- self.register_function('sin', lambda x: self.ml.sin(x[0]), {"nargs": 1})
- self.register_function('cos', lambda x: self.ml.cos(x[0]), {"nargs": 1})
- self.register_function('tan', lambda x: self.ml.tan(x[0]), {"nargs": 1})
-
- self.register_function('asin', lambda x: self.ml.asin(x[0]), {"nargs": 1})
- self.register_function('acos', lambda x: self.ml.acos(x[0]), {"nargs": 1})
- self.register_function('atan', lambda x: self.ml.atan(x[0]), {"nargs": 1})
-
- self.register_function('sinh', lambda x: self.ml.sinh(x[0]), {"nargs": 1})
- self.register_function('cosh', lambda x: self.ml.cosh(x[0]), {"nargs": 1})
- self.register_function('tanh', lambda x: self.ml.tanh(x[0]), {"nargs": 1})
-
- self.register_function('asinh', lambda x: self.ml.asinh(x[0]), {"nargs": 1})
- self.register_function('acosh', lambda x: self.ml.acosh(x[0]), {"nargs": 1})
- self.register_function('atanh', lambda x: self.ml.atanh(x[0]), {"nargs": 1})
-
- self.register_function('round', lambda x: self.ml.round(x[0]), {"nargs": 1})
- self.register_function('floor', lambda x: self.ml.floor(x[0]), {"nargs": 1})
- self.register_function('ceil', lambda x: self.ml.ceil(x[0]), {"nargs": 1})
-
- self.register_function('rand', lambda x: self.ml.rand_float(), {"nargs": 0})
- self.register_function('randint', lambda x: self.ml.rand_int(), {"nargs": 0})
-
- self.register_function('mod', lambda x: self.ml.mod(x[0], x[1]), {"nargs": 2})
-
- self.register_function('factorize', lambda x: self.ml.factorize(x[0]), {"nargs": 1})
-
- self.register_function('plot', lambda x: self.pl.plot(x[0], x[1]), {"nargs": 2, 'parse_options': False})
-
- self.register_function('help', lambda x: EqnParserHelp.help(x[0]), {"nargs": 1, 'parse_options': False})
-
- self.register_function('functions', lambda x: self.functions_string(), {"nargs": 0})
- self.register_function('variables', lambda x: self.variables_string(), {"nargs": 0})
- self.register_function('operators', lambda x: self.operators_string(), {"nargs": 0})
-
- self.register_operator('+', self.OP_DIADIC, 0, lambda x: self.ml.add(x[0], x[1]))
- self.register_operator('+', self.OP_PRE, 2, lambda x: x[0])
- self.register_operator('-', self.OP_DIADIC, 0, lambda x: self.ml.sub(x[0], x[1]))
- self.register_operator('-', self.OP_PRE, 2, lambda x: self.ml.negate(x[0]))
- self.register_operator('*', self.OP_DIADIC, 1, lambda x: self.ml.mul(x[0], x[1]))
- self.register_operator(u'⨯', self.OP_DIADIC, 1, lambda x: self.ml.mul(x[0], x[1]))
- self.register_operator(u'×', self.OP_DIADIC, 1, lambda x: self.ml.mul(x[0], x[1]))
- self.register_operator('/', self.OP_DIADIC, 1, lambda x: self.ml.div(x[0], x[1]))
- self.register_operator(u'÷', self.OP_DIADIC, 1, lambda x: self.ml.div(x[0], x[1]))
-
- self.register_operator('^', self.OP_DIADIC, 2, lambda x: self.ml.pow(x[0], x[1]))
- self.register_operator('**', self.OP_DIADIC, 2, lambda x: self.ml.pow(x[0], x[1]))
-
- self.register_operator('!', self.OP_POST, 0, lambda x: self.ml.factorial(x[0]))
-
- self.register_operator('&', self.OP_DIADIC, 0, lambda x: x[0] and x[1])
- self.register_function('and', lambda x: x[0] and x[1], {"nargs": 2})
- self.register_operator('|', self.OP_DIADIC, 0, lambda x: x[0] or x[1])
- self.register_function('or', lambda x: x[0] or x[1], {"nargs": 2})
- self.register_operator('!', self.OP_PRE, 0, lambda x: not x[0])
- self.register_function('not', lambda x: not x[0], {"nargs": 1})
-
-# self.register_operator('^', self.OP_PRE, 0, lambda x: not x[0])
- self.register_function('xor', lambda x: (x[0] and not x[1]) or (x[1] and not x[0]), {"nargs": 2})
-
- self.register_operator('=', self.OP_DIADIC, 0, lambda x: x[0] == x[1])
- self.register_operator('!=', self.OP_DIADIC, 0, lambda x: x[0] != x[1])
- self.register_operator('<', self.OP_DIADIC, 0, lambda x: x[0] < x[1])
- self.register_operator('>', self.OP_DIADIC, 0, lambda x: x[0] > x[1])
-
- self.register_operator('<<', self.OP_DIADIC, 0, lambda x: self.ml.shift_left(x[0], x[1]))
- self.register_operator('>>', self.OP_DIADIC, 0, lambda x: self.ml.shift_right(x[0], x[1]))
-
- self.register_operator('%', self.OP_DIADIC, 2, lambda x: self.ml.mod(x[0], x[1]))
-
- self.set_var('help',
- _("Use help(test) for help about 'test', or help(index) for the index"),
- parse=False)
-
- def register_function(self, name, f, opts):
- self.functions[name] = (f, opts)
- self.functions[_(name)] = (f, opts)
-
- def register_operator(self, op, type, presedence, f):
- self.operators.append((op, type, presedence, f))
-
- if op[0] not in self.OP_START_CHARS:
- self.OP_START_CHARS += op[0]
- for c in op:
- if c not in self.OP_CHARS:
- self.OP_CHARS += c
-
- def get_diadic_operators(self):
- if self.cached_diadic_ops == None:
- self.cached_diadic_ops = []
- for (op, type, presedence, f) in self.operators:
- if type == self.OP_DIADIC:
- self.cached_diadic_ops.append(op)
- return self.cached_diadic_ops
-
- def get_pre_operators(self):
- if self.cached_pre_ops == None:
- self.cached_pre_ops = []
- for (op, type, presedence, f) in self.operators:
- if type == self.OP_PRE:
- self.cached_pre_ops.append(op)
- return self.cached_pre_ops
-
- def get_post_operators(self):
- if self.cached_post_ops == None:
- self.cached_post_ops = []
- for (op, type, presedence, f) in self.operators:
- if type == self.OP_POST:
- self.cached_post_ops.append(op)
- return self.cached_post_ops
-
- def reset_variable_level(self, level):
- return
-# for i in self.variables.keys():
-# self.variables[i].highest_level = level
-
- def set_var(self, name, val, parse=True):
- if type(val) is types.FloatType:
- self.variables[name] = self.ml.d(val)
- else:
- self.variables[name] = val
- self.parse_var[name] = parse
-
- def get_var(self, name):
- return self.variables.get(name, None)
-
- def lookup_var(self, name, ps):
- c = self.ml.get_constant(name)
- if c is not None:
- return c
-
- if name in self.variables.keys():
-# if self.variables[name].highest_level > 0 and self.variables[name].highest_level != ps.level:
-# _logger.error('EqnParser.lookup_var(): recursion detected')
-# return None
-# self.variables[name].highest_level = level
- if (type(self.variables[name]) is types.UnicodeType or type(self.variables[name]) is types.StringType) \
- and self.parse_var[name]:
- return self.parse(self.variables[name], reset=False)
- else:
- return self.variables[name]
- else:
- _logger.debug('variable %s not defined', name)
- ps.set_type(self.TYPE_SYMBOLIC)
- return None
-
- def get_vars(self):
- list = []
- for name in self.variables:
- list.append((name, self.variables[name]))
- return list
-
- def get_var_names(self, start=None):
- names = self.variables.keys()
- names.sort()
-
- if start is None:
- return names
-
- retnames = []
- for name in names:
- if name[:len(start)] == start:
- retnames.append(name)
- return retnames
-
- def get_function_names(self):
- names = self.functions.keys()
- names.sort()
- return names
-
- def eval_func(self, func, args, level):
- if func not in self.functions:
- _logger.error(_("Function '%s' not defined"), func)
- self.ps.set_error(ParserState.PARSE_ERROR, msg=_("Function '%s' not defined") % (func))
- return None
-
- (f, opts) = self.functions[func]
- if len(args) != opts['nargs']:
- _logger.error(_('Invalid number of arguments (%d instead of %d)'), len(args), opts['nargs'])
- self.ps.set_error(ParserState.PARSE_ERROR, msg=_('function takes %d args') % (opts['nargs']))
- return None
-
- if 'parse_options' in opts and opts['parse_options'] == False:
- pargs = args
- else:
- pargs = []
- for i in range(len(args)):
- pargs.append(self.parse(args[i], reset=False))
- if pargs[i] is None:
- _logger.error(_('Unable to parse argument %d: \'%s\''), i, args[i])
- self.ps.set_error(ParserState.PARSE_ERROR, msg=_('Unable to parse argument %d: \'%s\'') % (i, args[i]))
- return None
-
- try:
- res = f(pargs)
-
-# Maybe we should map exceptions to more obvious error messages
- except Exception, inst:
- res = None
- self.ps.set_error(ParserState.PARSE_ERROR, msg=_("Function error: %s") % (str(inst)))
-
- _logger.debug('Function \'%s\' returned %s', func, self.ml.format_number(res))
- return res
-
- def parse_number(self, ps):
- startofs = ps.ofs
-
-# integer part
- while ps.more() and ps.char in self.DIGITS:
- ps.next()
-
-# part after dot
- if ps.char == '.' or ps.char == self.ml.fraction_sep:
- ps.next()
- while ps.more() and ps.char in self.DIGITS:
- ps.next()
-
-# exponent
- if ps.char is not None and ps.char in u'eE':
- ps.next()
- if ps.char is not None and ps.char in u'+-':
- ps.next()
- while ps.more() and ps.char in self.DIGITS:
- ps.next()
-
- _logger.debug('parse_number(): %d - %d: %s', startofs, ps.ofs, ps.str[startofs:ps.ofs])
- n = self.ml.parse_number(ps.str[startofs:ps.ofs])
- return n
-
- def valid_operator(self, opstr, left_val):
- for op_tuple in self.operators:
- (op, type, presedence, f) = op_tuple
- if op == opstr:
- if type == self.OP_DIADIC and left_val is not None:
- return op_tuple
- elif type == self.OP_POST and left_val is not None:
- return op_tuple
- elif type == self.OP_PRE and left_val is None:
- return op_tuple
- return None
-
- def parse_operator(self, ps, left_val):
- startofs = ps.ofs
- op = None
- while ps.more() and ps.char in self.OP_CHARS:
- ps.next()
- op2 = self.valid_operator(ps.str[startofs:ps.ofs], left_val)
- if op2 is not None:
- op = op2
- elif op is not None:
- ps.prev()
- break
-
- if op is not None:
- _logger.debug('parse_operator(): %d - %d: %s', startofs, ps.ofs, ps.str[startofs:ps.ofs])
- return op
- else:
- return self.INVALID_OP
-
- def parse_func_args(self, ps):
- startofs = ps.ofs
- args = []
- pcount = 1
- while ps.more() and pcount > 0:
- if ps.char == ',' and pcount == 1:
- args.append(ps.str[startofs:ps.ofs])
- startofs = ps.ofs + 1
- elif ps.char == '(':
- pcount += 1
- elif ps.char == ')':
- pcount -= 1
- if pcount == 0 and (ps.ofs - startofs) > 0:
- args.append(ps.str[startofs:ps.ofs])
- ps.next()
- _logger.debug('parse_func_args(): %d - %d: %r', startofs, ps.ofs, args)
- return args
-
- def parse_var_func(self, ps):
- startofs = ps.ofs
- while ps.more() and ps.char in self.NAME_CHARS:
- ps.next()
- name = ps.str[startofs:ps.ofs]
- name.strip(self.SPACE_CHARS)
- name.rstrip(self.SPACE_CHARS)
-
-# handle function
- if ps.char == '(':
- ps.next()
- _logger.debug('parse_var_func(): function %d - %d: %s', startofs, ps.ofs, name)
- args = self.parse_func_args(ps)
- ret = self.eval_func(name, args, ps.level)
- if ret is None:
- ps.set_error_range((startofs, ps.ofs))
- return ret
-
-# handle var
- else:
- _logger.debug('parse_var_func(): variable %d - %d: %s', startofs, ps.ofs, name)
- res = self.lookup_var(name, ps)
- if res is None:
- ps.set_error(ParserState.PARSE_ERROR, msg=_("Variable '%s' not defined") % (name), range=(startofs, ps.ofs))
- return res
-
- def _parse(self, ps, presedence=None):
- if presedence is None:
- ps.inc_level()
- _logger.debug('_parse(): %s, presedence: %r', ps.state_string(), presedence)
-
- op = None
- left_val = None
- right_val = None
-
- while ps.more():
-# _logger.debug('Looking at %r, ofs %d in %r', ps.char, ps.ofs, ps.str)
-
-# Skip spaces
- if ps.char in self.SPACE_CHARS:
- ps.next()
-
-# Left parenthesis: parse sub-expression
- elif ps.char == '(':
- if not (left_val is None or left_val is not None and op is not None):
- _logger.error('Parse error (left parenthesis)')
- ps.set_error(ParserState.PARSE_ERROR, msg=_("Left parenthesis unexpected"))
- return None
-
- ps.next()
- left_val = self._parse(ps)
-
-# Right parenthesis: return from parsing sub-expression
-# -If we are looking ahead because of operator presedence return value
-# -Else move to next character, decrease level and return value
- elif ps.char == ')':
- if presedence is not None:
- if left_val is None:
- _logger.error(_('Parse error (right parenthesis)'))
- ps.set_error(ParserState.PARSE_ERROR, msg=_("Right parenthesis unexpected"))
- return None
- else:
- _logger.debug('returning %s', self.ml.format_number(left_val))
- return left_val
-
- if ps.level > 0:
- ps.next()
- ps.dec_level()
- if left_val is None:
- _logger.error(_('Parse error (right parenthesis, no left_val)'))
- ps.set_error(ParserState.PARSE_ERROR, msg=_("Right parenthesis unexpected"))
- return None
- else:
- _logger.debug('returning %s', self.ml.format_number(left_val))
- return left_val
- else:
- _logger.error(_('Parse error (right parenthesis, no level to close)'))
- ps.set_error(ParserState.PARSE_ERROR, msg=_("Right parenthesis unexpected"))
- return None
-
-# Parse number
- elif ps.char == '.' or ps.char in self.DIGITS:
- if right_val is not None or left_val is not None:
- _logger.error(_('Number not expected'))
- ps.set_error(ParserState.PARSE_ERROR, msg=_("Number not expected"))
- return None
-
- left_val = self.parse_number(ps)
-
-# Parse operator
- elif ps.char in self.OP_START_CHARS:
- if op is not None:
- ps.set_error(ParserState.PARSE_ERROR, msg=_("Operator not expected"))
- return None
-
- startofs = ps.ofs
- op = self.parse_operator(ps, left_val)
- (opstr, otype, opres, of) = op
-
-# Diadic operators
- if otype == self.OP_DIADIC:
- if presedence is not None and opres <= presedence:
- ps.set_ofs(startofs)
- _logger.debug('returning %s (by presedence, %d)', self.ml.format_number(left_val), ps.ofs)
- return left_val
- else:
- right_val = self._parse(ps, presedence=opres)
- if right_val is None and self.ps.get_error() == ParserState.OK:
- _logger.error(_('Parse error: number or variable expected'))
- ps.set_error(ParserState.PARSE_ERROR, msg=_("Number or variable expected"))
- return None
- elif right_val is None:
- return None
-
- res = of([left_val, right_val])
- _logger.debug('OP: %s (%r), %s (%r) ==> %s (%r)', self.ml.format_number(left_val), left_val, self.ml.format_number(right_val), right_val, self.ml.format_number(res), res)
- left_val = res
- right_val = None
- op = None
-
-# Operator that goes after value
- elif otype == self.OP_POST:
- res = of([left_val])
- _logger.debug('OP POST: %s ==> %s', self.ml.format_number(left_val), self.ml.format_number(res))
- left_val = res
- op = None
-
-# Operator that goes before value
- elif otype == self.OP_PRE:
- right_val = self._parse(ps, presedence=opres)
- if right_val is None:
- return None
- left_val = of([right_val])
- _logger.debug('OP PRE: %s ==> %s', self.ml.format_number(right_val), self.ml.format_number(left_val))
- op = None
-
- elif otype == self.OP_INVALID:
- _logger.debug('Invalid operator')
- ps.set_error(ParserState.PARSE_ERROR, msg=_("Invalid operator"), range=(startofs, ps.ofs))
- return None
-
-# Parse variable or function
- else:
- if left_val is not None:
- _logger.debug('Operator expected: %r', self.OP_START_CHARS)
- ps.set_error(ParserState.PARSE_ERROR, msg=_("Operator expected"))
- return None
-
- left_val = self.parse_var_func(ps)
-
- if not ps.more() and ps.level > 0:
- _logger.debug('Parse error: \')\' expected')
- ps.set_error(ParserState.PARSE_ERROR, msg=_("Right parenthesis unexpected"))
- return None
- elif op is None and left_val is not None:
- _logger.debug('returning %s', self.ml.format_number(left_val))
- return left_val
- else:
- _logger.error(_('_parse(): returning None'))
-# Should be set somewhere already
-# ps.set_error(ParserState.PARSE_ERROR)
- return None
-
- def get_error_offset(self):
- return self.ps.error_range[0]
-
- def get_error_range(self):
- return self.ps.error_range
-
- def parse(self, eqn, reset=True):
- """Construct ParserState object and call _parse"""
-
- # Parse everything in unicode
- if type(eqn) is types.StringType:
- eqn = unicode(eqn)
-
- _logger.debug('parse(): %r', eqn)
- self.reset_variable_level(0)
-
- if reset:
- self.ps = None
-
- oldps = self.ps
- self.ps = ParserState(eqn)
- ret = self._parse(self.ps)
- if oldps is not None:
- oldps.copy_error(self.ps)
- self.ps = oldps
-
- return ret
-
- def functions_string(self):
- ret = ""
- for key in self.functions.keys():
- ret += key + " "
- return ret
-
- def variables_string(self):
- ret = ""
- for key in self.variables.keys():
- ret += key + " "
- return ret
-
- def operators_string(self):
- ret = ""
- for (op, type, p, f) in self.operators:
- ret += op + " "
- return ret
-
diff --git a/functions.py b/functions.py
new file mode 100644
index 0000000..b069581
--- /dev/null
+++ b/functions.py
@@ -0,0 +1,401 @@
+# functions.py, functions available in Calculate,
+# by Reinier Heeres <reinier@heeres.eu>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+# Any variable or function in this module that does not start with an
+# underscore ('_') will be available in Calculate through astparser.py.
+# Docstrings will automatically be added to the help index for that function.
+# However, instead of setting the docstring on a function in the simple way we
+# add it after the function definition so that they can more easily be
+# localized through gettext.
+
+import types
+import math
+import random
+from decimal import Decimal as _Decimal
+from rational import Rational as _Rational
+
+from gettext import gettext as _
+
+# List of functions to allow translating the function names.
+_FUNCTIONS = [
+ _('add'),
+ _('abs'),
+ _('acos'),
+ _('acosh'),
+ _('asin'),
+ _('asinh'),
+ _('atan'),
+ _('atanh'),
+ _('and'),
+ _('ceil'),
+ _('cos'),
+ _('cosh'),
+ _('div'),
+ _('gcd'),
+ _('exp'),
+ _('factorial'),
+ _('fac'),
+ _('factorize'),
+ _('floor'),
+ _('is_int'),
+ _('ln'),
+ _('log10'),
+ _('mul'),
+ _('or'),
+ _('rand_float'),
+ _('rand_int'),
+ _('round'),
+ _('sin'),
+ _('sinh'),
+ _('sinc'),
+ _('sqrt'),
+ _('sub'),
+ _('tan'),
+ _('tanh'),
+ _('xor'),
+ ]
+
+def _d(val):
+ '''Return a _Decimal object.'''
+
+ if isinstance(val, _Decimal):
+ return val
+ elif type(val) in (types.IntType, types.LongType):
+ return _Decimal(val)
+ elif type(val) == types.StringType:
+ d = _Decimal(val)
+ return d.normalize()
+ elif type(val) is types.FloatType or hasattr(val, '__float__'):
+ s = '%.10e' % float(val)
+ d = _Decimal(s)
+ return d.normalize()
+ else:
+ return None
+
+_angle_scaling = 1
+def _scale_angle(x):
+ return x * _angle_scaling
+
+def _inv_scale_angle(x):
+ return x / _angle_scaling
+
+def abs(x):
+ return math.fabs(x)
+abs.__doc__ = _(
+'abs(x), return absolute value of x, which means -x for x < 0')
+
+def acos(x):
+ return _inv_scale_angle(math.acos(x))
+acos.__doc__ = _(
+'acos(x), return the arc cosine of x. This is the angle for which the cosine \
+is x. Defined for -1 <= x < 1')
+
+def acosh(x):
+ return math.acosh(x)
+acosh.__doc__ = _(
+'acosh(x), return the arc hyperbolic cosine of x. This is the value y for \
+which the hyperbolic cosine equals x.')
+
+def And(x, y):
+ return x & y
+And.__doc__ = _(
+'And(x, y), logical and. Returns True if x and y are True, else returns False')
+
+def add(x, y):
+ if isinstance(x, _Decimal) or isinstance(y, _Decimal):
+ x = _d(x)
+ y = _d(y)
+ return x + y
+add.__doc__ = _('add(x, y), return x + y')
+
+def asin(x):
+ return _inv_scale_angle(math.asin(x))
+asin.__doc__ = _(
+'asin(x), return the arc sine of x. This is the angle for which the sine is ix. \
+Defined for -1 <= x <= 1')
+
+def asinh(x):
+ return math.asinh(x)
+asinh.__doc__ = _(
+'asinh(x), return the arc hyperbolic sine of x. This is the value y for \
+which the hyperbolic sine equals x.')
+
+def atan(x):
+ return _inv_scale_angle(math.atan(x))
+atan.__doc__ = _(
+'atan(x), return the arc tangent of x. This is the angle for which the tangent \
+is x. Defined for all x')
+
+def atanh(x):
+ return math.atanh(x)
+atanh.__doc__ = _(
+'atanh(x), return the arc hyperbolic tangent of x. This is the value y for \
+which the hyperbolic tangent equals x.')
+
+def ceil(x):
+ return math.ceil(float(x))
+ceil.__doc__ = _('ceil(x), return the smallest integer larger than x.')
+
+def cos(x):
+ return math.cos(_scale_angle(x))
+cos.__doc__ = _(
+'cos(x), return the cosine of x. This is the x-coordinate on the unit circle \
+at the angle x')
+
+def cosh(x):
+ return math.cosh(x)
+cosh.__doc__ = _(
+'cosh(x), return the hyperbolic cosine of x. Given by (exp(x) + exp(-x)) / 2')
+
+def div(x, y):
+ if y == 0 or y == 0.0:
+ raise ValueError(_('Can not divide by zero'))
+
+ if is_int(x) and float(abs(x)) < 1e12 and \
+ is_int(y) and float(abs(y)) < 1e12:
+ return _Rational(x, y)
+
+ if isinstance(x, _Decimal) or isinstance(y, _Decimal):
+ x = _d(x)
+ y = _d(y)
+
+ return x / y
+
+def _do_gcd(a, b):
+ if b == 0:
+ return a
+ else:
+ return self._do_gcd(b, a % b)
+
+def gcd(self, a, b):
+ TYPES = (types.IntType, types.LongType)
+ if type(a) not in TYPES or type(b) not in types:
+ raise ValueError(_('Invalid argument'))
+ return self._do_gcd(a, b)
+gcd.__doc__ = _(
+'gcd(a, b), determine the greatest common denominator of a and b. \
+For example, the biggest factor that is shared by the numbers 15 and 18 is 3.')
+
+def exp(x):
+ return math.exp(float(x))
+exp.__doc__ = _('exp(x), return the natural exponent of x. Given by e^x')
+
+def factorial(n):
+ if type(n) not in (types.IntType, types.LongType):
+ raise ValueError(_('Factorial only defined for integers'))
+
+ if n == 0:
+ return 1
+
+ n = long(n)
+ res = long(n)
+ while n > 2:
+ res *= n - 1
+ n -= 1
+
+ return res
+factorial.__doc__ = _(
+'factorial(n), return the factorial of n. \
+Given by n * (n - 1) * (n - 2) * ...')
+
+def fac(x):
+ return factorial(x)
+fac.__doc__ = _(
+'fac(x), return the factorial of x. Given by x * (x - 1) * (x - 2) * ...')
+
+def factorize(x):
+ if not is_int(x):
+ return 0
+
+ factors = []
+ num = x
+ i = 2
+ while i <= math.sqrt(num):
+ if num % i == 0:
+ factors.append(i)
+ num /= i
+ i = 2
+ elif i == 2:
+ i += 1
+ else:
+ i += 2
+ factors.append(num)
+
+ if len(factors) == 1:
+ return "1 * %d" % x
+ else:
+ ret = "%d" % factors[0]
+ for fac in factors[1:]:
+ ret += " * %d" % fac
+ return ret
+factorize.__doc__ = (
+'factorize(x), determine the prime factors that together form x. \
+For examples: 15 = 3 * 5.')
+
+def floor(x):
+ return math.floor(float(x))
+floor.__doc__ = _('floor(x), return the largest integer smaller than x.')
+
+def is_int(n):
+ if type(n) in (types.IntType, types.LongType):
+ return True
+
+ if not isinstance(n, _Decimal):
+ n = _d(n)
+ if n is None:
+ return False
+
+
+ (sign, d, e) = n.normalize().as_tuple()
+ return e >= 0
+is_int.__doc__ = ('is_int(n), determine whether n is an integer.')
+
+def ln(x):
+ if float(x) > 0:
+ return math.log(float(x))
+ else:
+ raise ValueError(_('Logarithm(x) only defined for x > 0'))
+ln.__doc__ = _(
+'ln(x), return the natural logarithm of x. This is the value for which the \
+exponent exp() equals x. Defined for x >= 0.')
+
+def log10(x):
+ if float(x) > 0:
+ return math.log(float(x))
+ else:
+ raise ValueError(_('Logarithm(x) only defined for x > 0'))
+log10.__doc__ = _(
+'log10(x), return the base 10 logarithm of x. This is the value y for which \
+10^y equals x. Defined for x >= 0.')
+
+def mod(x, y):
+ if self.is_int(y):
+ return x % y
+ else:
+ raise ValueError(_('Can only calculate x modulo <integer>'))
+
+def mul(x, y):
+ if isinstance(x, _Decimal) or isinstance(y, _Decimal):
+ x = _d(x)
+ y = _d(y)
+ return x * y
+mul.__doc__ = _('mul(x, y), return x * y')
+
+def negate(x):
+ return -x
+negate.__doc__ = _('negate(x), return -x')
+
+def Or(x, y):
+ return x | y
+Or.__doc__ = _(
+'Or(x, y), logical or. Returns True if x or y is True, else returns False')
+
+def pow(x, y):
+ if is_int(y):
+ if is_int(x):
+ return long(x) ** int(y)
+ elif hasattr(x, '__pow__'):
+ return x ** y
+ else:
+ return float(x) ** int(y)
+ else:
+ if isinstance(x, _Decimal) or isinstance(y, _Decimal):
+ x = _d(x)
+ y = _d(y)
+ return _d(math.pow(float(x), float(y)))
+pow.__doc__ = _('pow(x, y), return x to the power y (x**y)')
+
+def rand_float():
+ return random.random()
+rand_float.__doc__ = _(
+'rand_float(), return a random floating point number between 0.0 and 1.0')
+
+def rand_int(maxval=65535):
+ return random.randint(0, maxval)
+rand_int.__doc__ = _(
+'rand_int([<maxval>]), return a random integer between 0 and <maxval>. \
+<maxval> is an optional argument and is set to 65535 by default.')
+
+def round(x):
+ return math.round(float(x))
+round.__doc__ = _('round(x), return the integer nearest to x.')
+
+def shift_left(x, y):
+ if is_int(x) and is_int(y):
+ return d(int(x) << int(y))
+ else:
+ raise ValueError(_('Bitwise operations only apply to integers'))
+shift_left.__doc__ = _(
+'shift_left(x, y), shift x by y bits to the left (multiply by 2 per bit)')
+
+def shift_right(self, x, y):
+ if is_int(x) and is_int(y):
+ return d(int(x) >> int(y))
+ else:
+ raise ValueError(_('Bitwise operations only apply to integers'))
+shift_right.__doc__ = _(
+'shift_right(x, y), shift x by y bits to the right (divide by 2 per bit)')
+
+def sin(x):
+ return math.sin(_scale_angle(x))
+sin.__doc__ = _(
+'sin(x), return the sine of x. This is the y-coordinate on the unit circle at \
+the angle x')
+
+def sinh(x):
+ return math.sinh(x)
+sinh.__doc__ = _(
+'sinh(x), return the hyperbolic sine of x. Given by (exp(x) - exp(-x)) / 2')
+
+def sinc(x):
+ if float(x) == 0.0:
+ return 1
+ return sin(x) / x
+sinc.__doc__ = _(
+'sinc(x), return the sinc of x. This is given by sin(x) / x.')
+
+def sqrt(x):
+ return math.sqrt(float(x))
+sqrt.__doc__ = _(
+'sqrt(x), return the square root of x. This is the value for which the square \
+equals x. Defined for x >= 0.')
+
+def sub(x, y):
+ if isinstance(x, _Decimal) or isinstance(y, _Decimal):
+ x = _d(x)
+ y = _d(y)
+ return x - y
+add.__doc__ = _('sub(x, y), return x - y')
+
+def tan(x):
+ return math.tan(_scale_angle(x))
+tan.__doc__ = _(
+'tan(x), return the tangent of x. This is the slope of the line from the origin \
+of the unit circle to the point on the unit circle defined by the angle x. Given \
+by sin(x) / cos(x)')
+
+def tanh(x):
+ return math.tanh(x)
+tanh.__doc__ = _(
+'tanh(x), return the hyperbolic tangent of x. Given by sinh(x) / cosh(x)')
+
+def xor(x, y):
+ return x ^ y
+xor.__doc__ = _(
+'xor(x, y), logical xor. Returns True if either x is True (and y is False) \
+or y is True (and x is False), else returns False')
+
diff --git a/mathlib.py b/mathlib.py
index a0dad3e..4b81a4d 100644
--- a/mathlib.py
+++ b/mathlib.py
@@ -35,37 +35,12 @@ class MathLib:
ANGLE_RAD = 1
ANGLE_GRAD = 1
- MATH_FUNCTIONS = (
- 'is_int', 'is_bool', 'compare', 'negate', 'abs', 'add', 'sub',
- 'mul', 'div', 'pow', 'sqrt', 'mod', 'exp', 'ln', 'log10', 'factorial',
- 'sin', 'cos', 'tan', 'asin', 'acos', 'atan', 'sinh', 'cosh', 'tanh',
- 'asinh', 'acosh', 'atanh', 'round', 'floor', 'ceil', 'rand_float',
- 'rand_int', 'shift_left', 'shift_right', 'factorize',
- )
-
def __init__(self):
self.constants = {}
self.set_angle_type(self.ANGLE_DEG)
self._setup_i18n()
-#Constants should maybe become variables in eqnparser.py
- self.set_constant('true', True)
- self.set_constant('True', True)
- self.set_constant('false', False)
- self.set_constant('False', False)
- self.set_constant('pi', self.parse_number('3.1415926535'))
- self.set_constant('kb', self.parse_number('1.380650524e-23'))
- self.set_constant('Na', self.parse_number('6.02214e23'))
- self.set_constant('e', self.exp(1))
- self.set_constant('c', self.parse_number('2.99792458e8'))
- self.set_constant('c_e', self.parse_number('-1.60217648740e-19')) #electron properties
- self.set_constant('m_e', self.parse_number('9.109382616e-31'))
- self.set_constant('c_p', self.parse_number('1.6021765314e-19')) #proton properties
- self.set_constant('m_p', self.parse_number('1.6726217129e-27'))
- self.set_constant('c_n', self.parse_number('0')) #neutron properties
- self.set_constant('m_n', self.parse_number('1.6749272928e-27'))
-
def _setup_i18n(self):
loc = locale.localeconv()
@@ -93,27 +68,6 @@ class MathLib:
self.angle_scaling = self.d(type)
_logger.debug('Angle type set to:%s', self.angle_scaling)
- def set_constant(self, name, val):
- self.constants[name] = val
-
- def get_constant(self, name):
- if name in self.constants:
- return self.constants[name]
- else:
- return None
-
- def get_constants(self):
- return self.constants
-
- def get_math_functions(self):
- ret = {}
- items = inspect.getmembers(self)
- for key, val in items:
- if key in self.MATH_FUNCTIONS:
- ret[key] = val
-
- return ret
-
def d(self, val):
if isinstance(val, Decimal):
return val
@@ -227,195 +181,3 @@ class MathLib:
(sign, d, e) = n.normalize().as_tuple()
return e >= 0
-
- def is_float(self, n):
- if isinstance(n, Decimal):
- return not self.is_int(n)
- else:
- return False
-
- def is_bool(self, n):
- return type(n) is types.BoolType
-
- def compare(self, x, y):
- return x == y
-
- def negate(self, x):
- return -x
-
- def abs(self, x):
- return self.d(math.fabs(x))
-
- def add(self, x, y):
- if isinstance(x, Decimal) or isinstance(y, Decimal):
- x = self.d(x)
- y = self.d(y)
- return x + y
-
- def sub(self, x, y):
- if isinstance(x, Decimal) or isinstance(y, Decimal):
- x = self.d(x)
- y = self.d(y)
- return x - y
-
- def mul(self, x, y):
- if isinstance(x, Decimal) or isinstance(y, Decimal):
- x = self.d(x)
- y = self.d(y)
- return x * y
-
- def div(self, x, y):
- if y == 0 or y == 0.0:
- return _('Undefined')
-
- if self.is_int(x) and float(self.abs(y)) < 1e12 and \
- self.is_int(x) and float(self.abs(y)) < 1e12:
- return Rational(x, y)
-
- if isinstance(x, Decimal) or isinstance(y, Decimal):
- x = self.d(x)
- y = self.d(y)
-
- return x / y
-
- def pow(self, x, y):
- if self.is_int(y):
- if self.is_int(x):
- return long(x) ** int(y)
- elif hasattr(x, '__pow__'):
- return x ** y
- else:
- return float(x) ** int(y)
- else:
- if isinstance(x, Decimal) or isinstance(y, Decimal):
- x = self.d(x)
- y = self.d(y)
- return self.d(math.pow(float(x), float(y)))
-
- def sqrt(self, x):
- return self.d(math.sqrt(float(x)))
-
- def mod(self, x, y):
- if self.is_int(y):
- return x % y
- else:
- return self.d(0)
-
- def exp(self, x):
- return self.d(math.exp(float(x)))
-
- def ln(self, x):
- if float(x) > 0:
- return self.d(math.log(float(x)))
- else:
- return 0
-
- def log10(self, x):
- if float(x) > 0:
- return self.d(math.log10(float(x)))
- else:
- return 0
-
- def factorial(self, n):
- if not self.is_int(n):
- return self.d(0)
-
- if n == 0:
- return 1
-
- n = long(n)
- res = long(n)
- while n > 2:
- res *= n - 1
- n -= 1
-
- return res
-
- def sin(self, x):
- return self.d(math.sin(float(x * self.angle_scaling)))
-
- def cos(self, x):
- return self.d(math.cos(float(x * self.angle_scaling)))
-
- def tan(self, x):
- return self.d(math.tan(float(x * self.angle_scaling)))
-
- def asin(self, x):
- return self.d(math.asin(float(x))) / self.angle_scaling
-
- def acos(self, x):
- return self.d(math.acos(float(x))) / self.angle_scaling
-
- def atan(self, x):
- return self.d(math.atan(float(x))) / self.angle_scaling
-
- def sinh(self, x):
- return self.d(math.sinh(float(x)))
-
- def cosh(self, x):
- return self.d(math.cosh(float(x)))
-
- def tanh(self, x):
- return self.d(math.tanh(float(x)))
-
- def asinh(self, x):
- return self.d(math.asinh(float(x)))
-
- def acosh(self, x):
- return self.d(math.acosh(float(x)))
-
- def atanh(self, x):
- return self.d(math.atanh(float(x)))
-
- def round(self, x):
- return self.d(round(float(x)))
-
- def floor(self, x):
- return self.d(math.floor(float(x)))
-
- def ceil(self, x):
- return self.d(math.ceil(float(x)))
-
- def rand_float(self):
- return self.d(random.random())
-
- def rand_int(self):
- return self.d(random.randint(0, 65535))
-
- def shift_left(self, x, y):
- if self.is_int(x) and self.is_int(y):
- return self.d(int(x) << int(y))
- else:
- return 0
-
- def shift_right(self, x, y):
- if self.is_int(x) and self.is_int(y):
- return self.d(int(x) >> int(y))
- else:
- return 0
-
- def factorize(self, x):
- if not self.is_int(x):
- return 0
-
- factors = []
- num = x
- i = 2
- while i <= math.sqrt(num):
- if num % i == 0:
- factors.append(i)
- num /= i
- i = 2
- elif i == 2:
- i += 1
- else:
- i += 2
- factors.append(num)
-
- if len(factors) == 1:
- return "1 * %d" % x
- else:
- str = "%d" % factors[0]
- for fac in factors[1:]:
- str += " * %d" % fac
- return str
diff --git a/plotlib.py b/plotlib.py
index 746cd50..2b5802e 100644
--- a/plotlib.py
+++ b/plotlib.py
@@ -39,27 +39,25 @@ class PlotLib:
def get_svg(self):
return self.svg_data
- def parse_range(self, range):
- p1 = range.split('=')
- if len(p1) != 2:
- return None
- p2 = p1[1].split('..')
- if len(p2) != 2:
- return None
- return (p1[0], (float(p2[0]), float(p2[1])))
-
- def evaluate(self, eqn, var, range, n=100):
+ def evaluate(self, eqn, var, range, points=100):
x_old = self.parser.get_var(var)
+ if type(eqn) in (types.StringType, types.UnicodeType):
+ eqn = self.parser.parse(eqn)
+
res = []
- d = float((range[1] - range[0])) / (n - 1)
+ d = float((range[1] - range[0])) / (points - 1)
x = range[0]
- while n > 0:
+ while points > 0:
self.parser.set_var(var, x)
- v = float(self.parser.parse(eqn))
+ ret = self.parser.evaluate(eqn)
+ if ret is not None:
+ v = float(ret)
+ else:
+ v = 0
res.append((x, v))
x += d
- n -= 1
+ points -= 1
self.parser.set_var(var, x_old)
return res
@@ -104,11 +102,17 @@ class PlotLib:
self.maxx = max(float(x), self.maxx)
self.maxy = max(float(y), self.maxy)
- x_space = 0.02 * (self.maxx - self.minx)
+ if self.minx == self.maxx:
+ xYspace = 0.5
+ else:
+ x_space = 0.02 * (self.maxx - self.minx)
self.minx -= x_space
self.maxx += x_space
- y_space = 0.02 * (self.maxy - self.miny)
+ if self.miny == self.maxy:
+ y_space = 0.5
+ else:
+ y_space = 0.02 * (self.maxy - self.miny)
self.miny -= y_space
self.maxy += y_space
@@ -150,21 +154,39 @@ class PlotLib:
f.write(self.svg_data)
f.close()
- def plot(self, eqn, range_spec):
- _logger.debug('plot(): %r, %r', eqn, range_spec)
+ def plot(self, eqn, **kwargs):
+ '''
+ Plot function <eqn>.
+
+ kwargs can contain: 'points'
+
+ The last item in kwargs is interpreted as the variable that should
+ be varied.
+ '''
+
+ _logger.debug('plot(): %r, %r', eqn, kwargs)
+
+ if 'points' in kwargs:
+ points = kwargs['points']
+ del kwargs['points']
+ else:
+ points = 100
+
+ if len(kwargs) > 1:
+ _logger.error('Too many variables specified')
+ return None
- (var, range) = self.parse_range(range_spec)
- if range is None:
- _logger.error('Unable to parse range')
- return False
+ for var, range in kwargs.iteritems():
+ pass
_logger.info('Plot range for var %s: %r', var, range)
self.set_size(250, 250)
self.create_image()
- self.draw_axes(var, eqn)
+ # FIXME: should use equation as label
+ self.draw_axes(var, 'f(x)')
- vals = self.evaluate(eqn, var, range)
+ vals = self.evaluate(eqn, var, range, points=points)
# print 'vals: %r' % vals
self.add_curve(vals)