diff options
author | Miguel Angel Alvarez Bernardo <miguel@miguel-ubuntu.(none)> | 2007-07-11 19:06:44 (GMT) |
---|---|---|
committer | Miguel Angel Alvarez Bernardo <miguel@miguel-ubuntu.(none)> | 2007-07-11 19:06:44 (GMT) |
commit | 60134cc71bb5aeedecbfa9aa7499016a31da0bb6 (patch) | |
tree | 0dbb32af602f036b18121f37cf1c148c40f9d015 /eqnparser.py |
Initial Commit
Diffstat (limited to 'eqnparser.py')
-rw-r--r-- | eqnparser.py | 413 |
1 files changed, 413 insertions, 0 deletions
diff --git a/eqnparser.py b/eqnparser.py new file mode 100644 index 0000000..c2b9719 --- /dev/null +++ b/eqnparser.py @@ -0,0 +1,413 @@ +#eqnparser.py + +import logging +_logger = logging.getLogger('EqnParser') + +from mathlib import MathLib + +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 + + 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 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_code(self, c): + self.error_code = c + +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 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789 ' + DIGITS = '0123456789' + SPACE_CHARS = '\t \r\n' + +# These will be filled from register_operator + OP_START_CHARS = "" + OP_CHARS = "" + + TYPE_UNKNOWN = 1 + TYPE_INT = 2 + TYPE_FLOAT = 3 + TYPE_BOOL = 4 + TYPE_SYMBOLIC = 5 + + def __init__(self): + self.ml = MathLib() + + self.error_offset = 0 + + self.variables = {} + self.functions = {} + self.operators = [] + + self.register_function('exp', 1, lambda x: self.ml.exp(x[0])) + self.register_function('ln', 1, lambda x: self.ml.ln(x[0])) + self.register_function('log10', 1, lambda x: self.ml.log10(x[0])) + self.register_function('pow', 2, lambda x: self.ml.pow(x[0], x[1])) + + self.register_function('sqrt', 1, lambda x: self.ml.sqrt(x[0])) + + self.register_function('sin', 1, lambda x: self.ml.sin(x[0])) + self.register_function('cos', 1, lambda x: self.ml.cos(x[0])) + self.register_function('tan', 1, lambda x: self.ml.tan(x[0])) + + self.register_function('asin', 1, lambda x: self.ml.asin(x[0])) + self.register_function('acos', 1, lambda x: self.ml.acos(x[0])) + self.register_function('atan', 1, lambda x: self.ml.atan(x[0])) + + self.register_function('sinh', 1, lambda x: self.ml.sinh(x[0])) + self.register_function('cosh', 1, lambda x: self.ml.cosh(x[0])) + self.register_function('tanh', 1, lambda x: self.ml.tanh(x[0])) + + self.register_function('asinh', 1, lambda x: self.ml.asinh(x[0])) + self.register_function('acosh', 1, lambda x: self.ml.acosh(x[0])) + self.register_function('atanh', 1, lambda x: self.ml.atanh(x[0])) + + self.register_operator('+', self.OP_DIADIC, 0, lambda x: self.ml.add(x[0], x[1])) + self.register_operator('+', self.OP_PRE, 1, 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, 1, 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('/', 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_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_operator('|', self.OP_DIADIC, 0, lambda x: x[0] or 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: 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])) + + def register_function(self, name, nargs, f): + self.functions[name] = (nargs, f) + + 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 reset_variable_level(self, level): + return +# for i in self.variables.keys(): +# self.variables[i].highest_level = level + + def set_var(self, name, val): + self.variables[name] = val + + 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 + return self.parse(self.variables[name]) + else: + _logger.debug('variable %s not defined', name) + ps.set_type(self.TYPE_SYMBOLIC) + return None + + def eval_func(self, func, args, level): + if func not in self.functions: + _logger.error('Function \'%s\' not defined', func) + return None + + (nargs, f) = self.functions[func] + if len(args) != nargs: + _logger.error('Invalid number of arguments (%d instead of %d)', len(args), nargs) + return None + + pargs = [] + for i in range(len(args)): + pargs.append(self.parse(args[i])) + if pargs[i] is None: + _logger.error('Unable to parse argument %d: \'%s\'', i, args[i]) + return None + + res = f(pargs) + _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 == '.': + ps.next() + while ps.more() and ps.char in self.DIGITS: + ps.next() + +# exponent + if ps.char is not None and ps.char in 'eE': + ps.next() + if ps.char is not None and ps.char in '+-': + 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 + while ps.more() and ps.char in self.OP_CHARS: + ps.next() + op = self.valid_operator(ps.str[startofs:ps.ofs], left_val) + if op is not None: + _logger.debug('parse_operator(): %d - %d: %s', startofs, ps.ofs, ps.str[startofs:ps.ofs]) + return op + + 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 == ',': + args.append(ps.str[startofs:ps.ofs]) + startofs = ps.ofs + 1 + elif ps.char == '(': + pcount += 1 + elif ps.char == ')': + pcount -= 1 + if pcount == 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) + return self.eval_func(name, args, ps.level) + +# 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_ofs(startofs) + ps.set_error_code(ParserState.PARSE_ERROR) + 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) + + left_val = None + right_val = None + op = None + while ps.more(): +# _logger.debug('Looking at \'%c\', ofs %d in \'%s\'', 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 == '(': + 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_code(ParserState.PARSE_ERROR) + 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_code(ParserState.PARSE_ERROR) + 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_code(ParserState.PARSE_ERROR) + return None + +# Parse number + elif ps.char in '0123456789.': + if right_val is not None or left_val is not None: + _logger.error('Number not expected!') + ps.set_error_code(ParserState.PARSE_ERROR) + return None + + if op is not None and otype == self.OP_PRE: + left_val = of([self.parse_number(ps)]) + op = None + else: + left_val = self.parse_number(ps) + +# Parse operator + elif ps.char in self.OP_START_CHARS: + 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 == None: + return None + + res = of([left_val, right_val]) + _logger.debug('OP: %s, %s ==> %s', self.ml.format_number(left_val), self.ml.format_number(right_val), self.ml.format_number(res)) + left_val = res + right_val = None + + op = None + +# Operator that goes after value + elif otype == self.OP_POST: + left_val = of([left_val]) + op = None + +# Operator that goes before value + elif otype == self.OP_PRE: + pass + + elif otype == self.OP_INVALID: + _logger.debug('Invalid operator') + +# Parse variable or function + else: + left_val = self.parse_var_func(ps) + + if 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') + ps.set_error_code(ParserState.PARSE_ERROR) + return None + + def set_error_offset(self, o): + self.error_offset = o + + def get_error_offset(self): + return self.error_offset + + def parse(self, eqn): + """Construct ParserState object and call _parse""" + _logger.debug('parse(): %s', eqn) + self.reset_variable_level(0) + ps = ParserState(eqn) + res = self._parse(ps) + if res is None: + self.set_error_offset(ps.ofs) + return res + |