| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259 |
- # vim: set fileencoding=utf-8 :
- import os.path
- import sys
- sys.path.insert(0, os.path.realpath('external'))
- from graph_drawing.graph import generate_graph
- from graph_drawing.line import generate_line
- from graph_drawing.node import Node, Leaf
- TYPE_OPERATOR = 1
- TYPE_IDENTIFIER = 2
- TYPE_INTEGER = 4
- TYPE_FLOAT = 8
- # Unary
- OP_NEG = 1
- # Binary
- OP_ADD = 2
- OP_SUB = 3
- OP_MUL = 4
- OP_DIV = 5
- OP_POW = 6
- OP_MOD = 7
- # N-ary (functions)
- OP_INT = 8
- OP_EXPAND = 9
- OP_COMMA = 10
- TYPE_MAP = {
- int: TYPE_INTEGER,
- float: TYPE_FLOAT,
- str: TYPE_IDENTIFIER,
- }
- OP_MAP = {
- '+': OP_ADD,
- # Either substraction or negation. Skip the operator sign in 'x' (= 2).
- '-': lambda x: OP_SUB if len(x) > 2 else OP_NEG,
- '*': OP_MUL,
- '/': OP_DIV,
- '^': OP_POW,
- 'mod': OP_MOD,
- 'int': OP_INT,
- 'expand': OP_EXPAND,
- ',': OP_COMMA,
- }
- def to_expression(obj):
- return obj if isinstance(obj, ExpressionBase) else ExpressionLeaf(obj)
- class ExpressionBase(object):
- def __lt__(self, other):
- """
- Comparison between this expression{node,leaf} and another
- expression{node,leaf}. This comparison will return True if this
- instance has less value than the other expression{node,leaf}.
- Otherwise, False is returned.
- The comparison is based on the following conditions:
- 1. Both are leafs. String comparison of the value is used.
- 2. This is a leaf and other is a node. This leaf has less value, thus
- True is returned.
- 3. This is a node and other is a leaf. This leaf has more value, thus
- False is returned.
- 4. Both are nodes. Compare the polynome properties of the nodes. True
- is returned if this node's root property is less than other's root
- property, or this node's exponent property is less than other's
- exponent property, or this node's coefficient property is less than
- other's coefficient property. Otherwise, False is returned.
- """
- if self.is_leaf():
- if other.is_leaf():
- # Both are leafs, string compare the value.
- return str(self.value) < str(other.value)
- # Self is a leaf, thus has less value than an expression node.
- return True
- if other.is_leaf():
- # Self is an expression node, and the other is a leaf. Thus, other
- # is greater than self.
- return False
- # Both are nodes, compare the polynome properties.
- s_coeff, s_root, s_exp = self.extract_polynome_properties()
- o_coeff, o_root, o_exp = other.extract_polynome_properties()
- return s_root < o_root or s_exp < o_exp or s_coeff < o_coeff
- def is_op(self, op):
- return not self.is_leaf() and self.op == op
- def is_leaf(self):
- return self.type != TYPE_OPERATOR
- def is_power(self):
- return not self.is_leaf() and self.op == OP_POW
- def is_nary(self):
- return not self.is_leaf() and self.op in [OP_ADD, OP_SUB, OP_MUL]
- def is_identifier(self):
- return self.is_leaf() and self.type & TYPE_IDENTIFIER
- def is_int(self):
- return self.is_leaf() and self.type & TYPE_INTEGER
- def is_float(self):
- return self.is_leaf() and self.type & TYPE_FLOAT
- def is_numeric(self):
- return self.is_leaf() and self.type & (TYPE_FLOAT | TYPE_INTEGER)
- def __add__(self, other):
- return ExpressionNode('+', self, to_expression(other))
- def __sub__(self, other):
- return ExpressionNode('-', self, to_expression(other))
- def __mul__(self, other):
- return ExpressionNode('*', self, to_expression(other))
- def __div__(self, other):
- return ExpressionNode('/', self, to_expression(other))
- def __pow__(self, other):
- return ExpressionNode('^', self, to_expression(other))
- def __neg__(self):
- return ExpressionNode('-', to_expression(self))
- class ExpressionNode(Node, ExpressionBase):
- def __init__(self, *args, **kwargs):
- super(ExpressionNode, self).__init__(*args, **kwargs)
- self.type = TYPE_OPERATOR
- self.op = OP_MAP[args[0]]
- if hasattr(self.op, '__call__'):
- self.op = self.op(args)
- def __str__(self): # pragma: nocover
- return generate_line(self)
- def __eq__(self, other):
- """
- Check strict equivalence.
- """
- if isinstance(other, ExpressionNode):
- return self.op == other.op and self.nodes == other.nodes
- return False
- def graph(self): # pragma: nocover
- return generate_graph(self)
- def extract_polynome_properties(self):
- """
- Extract polynome properties into tuple format: (coefficient, root,
- exponent). Thus: c * r ^ e will be extracted into the tuple (c, r, e).
- This function will normalize the expression before extracting the
- properties. Therefore, the expression r ^ e * c results the same tuple
- (c, r, e) as the expression c * r ^ e.
- >>> from src.node import ExpressionNode as N, ExpressionLeaf as L
- >>> c, r, e = L('c'), L('r'), L('e')
- >>> n1 = N('*', c, N('^', r, e))
- >>> n1.extract_polynome()
- (c, r, e)
- >>> n2 = N('*', N('^', r, e), c)
- >>> n2.extract_polynome()
- (c, r, e)
- """
- # TODO: change "get_polynome" -> "extract_polynome".
- # TODO: change retval of c * r ^ e to (c, r, e).
- # was: (root, exponent, coefficient, literal_exponent)
- # rule: r ^ e -> (1, r, e)
- if self.is_power():
- return (ExpressionLeaf(1), self[0], self[1])
- if self.op != OP_MUL:
- return
- # rule: 3 * 7 ^ e | 'a' * 'b' ^ e
- # expression: c * r ^ e ; tree:
- #
- # *
- # ╭┴───╮
- # c ^
- # ╭─┴╮
- # r e
- #
- # rule: c * r ^ e | (r ^ e) * c
- for i, j in [(0, 1), (1, 0)]:
- if self[j].is_power():
- return (self[i], self[j][0], self[j][1])
- # Normalize c * r and r * c -> c * r. Otherwise, the tuple will not
- # match if the order of the expression is different. Example:
- # r ^ e * c == c * r ^ e
- # without normalization, those expressions will not match.
- #
- # rule: c * r | r * c
- if self[0] < self[1]:
- return (self[0], self[1], ExpressionLeaf(1))
- return (self[1], self[0], ExpressionLeaf(1))
- def get_scope(self):
- """"""
- scope = []
- #op = OP_ADD | OP_SUB if self.op & (OP_ADD | OP_SUB) else self.op
- # TODO: what to do with OP_SUB and OP_ADD in get_scope?
- for child in self:
- if not child.is_leaf() and child.op == self.op:
- scope += child.get_scope()
- else:
- scope.append(child)
- return scope
- class ExpressionLeaf(Leaf, ExpressionBase):
- def __init__(self, *args, **kwargs):
- super(ExpressionLeaf, self).__init__(*args, **kwargs)
- self.type = TYPE_MAP[type(args[0])]
- def __eq__(self, other):
- if isinstance(other, int) or isinstance(other, float) \
- or isinstance(other, str):
- return self.value == other
- if other.is_leaf():
- return self.value == other.value
- return False
- def extract_polynome_properties(self):
- """
- An expression leaf will return the polynome tuple (1, r, 1), where r is
- the leaf itself. See also the method extract_polynome_properties in
- ExpressionBase.
- """
- # rule: 1 * r ^ 1 -> (1, r, 1)
- return (ExpressionLeaf(1), self, ExpressionLeaf(1))
|