def flatten(expr, depth=-1, head=None): """ flattens an Expression - a negative depth allows for infinite recursion - only flattens subexpressions with given head """ assert isinstance(expr, Expression) if depth == 0: return expr if head is None: head = expr.head assert isinstance(head, BaseExpression) # head could be Expression leaves = [] for leaf in expr.leaves: if leaf.head.same(head): assert isinstance(leaf, Expression) for leaf2 in leaf.leaves: if isinstance(leaf2, Expression): leaves.append(flatten(leaf2, head=head, depth=depth-1)) else: leaves.append(leaf2) else: if isinstance(leaf, Expression): leaves.append(flatten(leaf, head=head, depth=depth-1)) else: leaves.append(leaf) expr = Expression(head) expr.leaves = leaves return expr
def set_context_path(self, context_path): """ set $ContextPath given a list of str """ assert isinstance(context_path, list) assert all([isinstance(c, str) for c in context_path]) ownvalues = Expression(Symbol('System`List')) ownvalues.leaves = [String(c) for c in context_path] self.set_ownvalues('System`$ContextPath', ownvalues)
def set_ownvalues(self, name, ownvalues): assert isinstance(name, str) and isinstance(ownvalues, BaseExpression) name = self.lookup_name(name) defn = self.get_definition(name) defn.ownvalues = Expression( Symbol('System`List'), Expression( Symbol('System`RuleDelayed'), Expression(Symbol('System`HoldPattern'), Symbol(name)), ownvalues))
def evaluate(expr, definitions=Definitions()): messages = [] result = expr if isinstance(expr, Expression): # Evaluate head head, head_messages = evaluate(expr.head, definitions) messages.extend(head_messages) # Evaluate leaves leaves = [] for leaf in expr.leaves: leaf, leaf_messages = evaluate(leaf, definitions) messages.extend(leaf_messages) leaves.append(leaf) # Build the result result = Expression(head) result.leaves = leaves # Apply transformations for Orderless, Listable, Flat if isinstance(head, Symbol): head_attributes = definitions.get_attributes(head.get_name()) if 'Listable' in head_attributes: result, thread_messages = thread(result) messages.extend(thread_messages) if 'Orderless' in head_attributes: result = sort(result) if 'Flat' in head_attributes: result = flatten(result) elif isinstance(expr, Atom): if isinstance(expr, Symbol): # Apply user definitions # TODO OwnValues # TODO UpValues # TODO DownValues # Apply builtin definitions # TODO pass result = expr if expr.same(result): result = _builtin_evaluate(expr, definitions) return (result, messages) else: result, result_messages = evaluate(result, definitions) messages.extend(result_messages) return (result, messages)
def set_attributes(self, name, attributes): assert isinstance(name, str) assert isinstance(attributes, list) assert all([isinstance(attr, str) and attr in known_attributes for attr in attributes]) name = self.lookup_name(name) defn = self.get_definition(name) defn.attributes = Expression(Symbol('System`List')) defn.attributes.leaves = [Symbol('System`' + attribute) for attribute in attributes]
def thread(expr, head=Symbol('System`List')): """ threads an Expression - given expr=f[args], thread over args whose head matches head - args whose heads don't match are repeated """ assert isinstance(expr, Expression) assert isinstance(head, BaseExpression) messages = [] args = expr.leaves exprhead = expr.head # indices of args with matching heads match_indices = [i for i, arg in enumerate(args) if arg.head.same(head)] if match_indices == []: # nothing to thread over return expr, messages else: thread_len = len(args[match_indices[0]].leaves) # check all matching args have the same length for i in match_indices: if len(args[i].leaves) != thread_len: messages.append(('Thread', 'tdlen')) return expr, messages # args with heads that don't match are repeated thread_len times new_args = [] for i, arg in enumerate(args): if i in match_indices: new_args.append(arg.leaves) else: new_args.append(thread_len * [arg]) assert all([len(arg) == thread_len for arg in new_args]) # thread over args leaves = [] for i in range(thread_len): expr = Expression(exprhead) expr.leaves = [arg[i] for arg in new_args] leaves.append(expr) result = Expression(head) result.leaves = leaves return result, messages
def __init__(self, rules=Expression(Symbol('System`List')), ownvalues=Expression(Symbol('System`List')), downvalues=Expression(Symbol('System`List')), subvalues=Expression(Symbol('System`List')), upvalues=Expression(Symbol('System`List')), formatvalues=Expression(Symbol('System`List')), messages=Expression(Symbol('System`List')), attributes=Expression(Symbol('System`List')), options=Expression(Symbol('System`List')), nvalues=Expression(Symbol('System`List')), defaultvalues=Expression(Symbol('System`List'))): self.rules = rules self.ownvalues = ownvalues self.downvalues = downvalues self.subvalues = subvalues self.upvalues = upvalues self.formatvalues = formatvalues self.messages = messages self.attributes = attributes self.options = options self.nvalues = nvalues self.defaultvalues = defaultvalues
'downvalues: %s, formats: %s, attributes: %s>') % ( self.downvalues, self.formatvalues, self.attributes) return s.encode('unicode_escape') builtins = [] def builtin(patt): assert isinstance(patt, BaseExpression) def wrapper(func): builtins.append((patt, func)) return func return wrapper @builtin(Expression(Symbol('System`Plus'), Expression(Symbol('System`Pattern'), Symbol('x0'), Expression(Symbol('System`BlankNullSequence'), Symbol('System`Integer'))), Expression(Symbol('System`Pattern'), Symbol('x1'), Expression(Symbol('System`BlankNullSequence'), Symbol('System`Rational'))), Expression(Symbol('System`Pattern'), Symbol('x2'), Expression(Symbol('System`BlankNullSequence'), Symbol('System`Real'))), )) def plus(mappings): ints = mappings['x0'] rats = mappings['x1'] reals = mappings['x2'] assert ints.head.same(Symbol('System`Sequence')) assert rats.head.same(Symbol('System`Sequence')) assert reals.head.same(Symbol('System`Sequence')) ints = ints.leaves rats = rats.leaves reals = reals.leaves
def _match_seq(exprs, patts, definitions): """ matches a list of expressions against a list of patterns We match by pairing each pattern to a list of expressions - BlankSequence matches to one or more expression - BlankNullSequence matches to zero or more - everything else (including Blank[]) matches to exactly one expression Try to match the 'everything else' first. It's more efficient this way. returns (bool, {str: BaseExpression}) like the function match above. """ if len(patts) == len(exprs) == 0: return True, {} patti, name = -1, None for i, patt in enumerate(patts): if patt.head.same(Symbol('System`Pattern')): if len(patt.leaves) != 2: # TODO message Pattern::argr: return False, {} name2, patt2 = patt.leaves if patt2.head.same(Symbol('System`BlankSequence')): continue if patt2.head.same(Symbol('System`BlankNullSequence')): continue else: name = name2.get_name() patti = i break if patt.head.same(Symbol('System`BlankSequence')): continue elif patt.head.same(Symbol('System`BlankNullSequence')): continue else: patti = i break if patti >= 0: # everything else patt = patts[patti] if patt.head.same(Symbol('System`Pattern')): patt = patt.leaves[0] for expri, expr in enumerate(exprs): match0, mapping0 = match(expr, patt, definitions) if match0: match1, mapping1 = _match_seq( exprs[:expri], patts[:patti], definitions) match2, mapping2 = _match_seq( exprs[expri+1:], patts[patti+1:], definitions) try: if match1 and match2: mapping = _merge_dicts(mapping1, mapping2) mapping = _merge_dicts(mapping, mapping0) if name is not None: mapping = _merge_dicts(mapping, {name: expr}) return True, mapping except ValueError: pass return False, {} for i, patt in enumerate(patts): if patt.head.same(Symbol('System`Pattern')): assert len(patt.leaves) == 2 name2, patt2 = patt.leaves if patt2.head.same(Symbol('System`BlankSequence')): name = name2.get_name() patti = i break if patt.head.same(Symbol('System`BlankSequence')): patti = i break if patti >= 0: patt = patts[patti] if patt.head.same(Symbol('System`Pattern')): patt = patt.leaves[1] for match_len in range(1, len(exprs)+1): # begin looking for length 1 matches for start_pos in range(len(exprs)+1-match_len): if len(patt.leaves) == 0: pass elif len(patt.leaves) == 1: if not all([expr.head.same(patt.leaves[0]) for expr in exprs[start_pos:start_pos+match_len]]): continue else: raise ValueError match0, mapping0 = _match_seq( exprs[:start_pos], patts[:patti], definitions) match1, mapping1 = _match_seq( exprs[start_pos+match_len:], patts[patti+1:], definitions) expr = Expression(Symbol('System`Sequence')) expr.leaves = exprs[start_pos:start_pos+match_len] try: mapping = _merge_dicts(mapping0, mapping1) if match0 and match1: if name is not None: mapping = _merge_dicts(mapping, {name: expr}) return True, mapping except ValueError: pass return False, {} if patts == []: assert exprs != [] return False, {} patti = 0 patt = patts[patti] if patt.head.same(Symbol('System`Pattern')): assert len(patt.leaves) == 2 name2, patt2 = patt.leaves name = name2.get_name() assert patt2.head.same(Symbol('System`BlankNullSequence')) patt = patt2 else: assert patt.head.same(Symbol('System`BlankNullSequence')) for match_len in range(0, len(exprs)+1): # begin looking for length 0 matches for start_pos in range(len(exprs)+1-match_len): if len(patt.leaves) == 0: pass elif len(patt.leaves) == 1: if not all([expr.head.same(patt.leaves[0]) for expr in exprs[start_pos:start_pos+match_len]]): continue else: raise ValueError match0, mapping0 = _match_seq( exprs[:start_pos], patts[:patti], definitions) match1, mapping1 = _match_seq( exprs[start_pos+match_len:], patts[patti+1:], definitions) expr = Expression(Symbol('System`Sequence')) expr.leaves = exprs[start_pos:start_pos+match_len] try: mapping = _merge_dicts(mapping0, mapping1) if match0 and match1: if name is not None: mapping = _merge_dicts(mapping, {name: expr}) return True, mapping except ValueError: pass return False, {}
def _match_seq(exprs, patts, definitions): """ matches a list of expressions against a list of patterns We match by pairing each pattern to a list of expressions - BlankSequence matches to one or more expression - BlankNullSequence matches to zero or more - everything else (including Blank[]) matches to exactly one expression Try to match the 'everything else' first. It's more efficient this way. returns (bool, {str: BaseExpression}) like the function match above. """ if len(patts) == len(exprs) == 0: return True, {} patti, name = -1, None for i, patt in enumerate(patts): if patt.head.same(Symbol('System`Pattern')): if len(patt.leaves) != 2: # TODO message Pattern::argr: return False, {} name2, patt2 = patt.leaves if patt2.head.same(Symbol('System`BlankSequence')): continue if patt2.head.same(Symbol('System`BlankNullSequence')): continue else: name = name2.get_name() patti = i break if patt.head.same(Symbol('System`BlankSequence')): continue elif patt.head.same(Symbol('System`BlankNullSequence')): continue else: patti = i break if patti >= 0: # everything else patt = patts[patti] if patt.head.same(Symbol('System`Pattern')): patt = patt.leaves[0] for expri, expr in enumerate(exprs): match0, mapping0 = match(expr, patt, definitions) if match0: match1, mapping1 = _match_seq(exprs[:expri], patts[:patti], definitions) match2, mapping2 = _match_seq(exprs[expri + 1:], patts[patti + 1:], definitions) try: if match1 and match2: mapping = _merge_dicts(mapping1, mapping2) mapping = _merge_dicts(mapping, mapping0) if name is not None: mapping = _merge_dicts(mapping, {name: expr}) return True, mapping except ValueError: pass return False, {} for i, patt in enumerate(patts): if patt.head.same(Symbol('System`Pattern')): assert len(patt.leaves) == 2 name2, patt2 = patt.leaves if patt2.head.same(Symbol('System`BlankSequence')): name = name2.get_name() patti = i break if patt.head.same(Symbol('System`BlankSequence')): patti = i break if patti >= 0: patt = patts[patti] if patt.head.same(Symbol('System`Pattern')): patt = patt.leaves[1] for match_len in range(1, len(exprs) + 1): # begin looking for length 1 matches for start_pos in range(len(exprs) + 1 - match_len): if len(patt.leaves) == 0: pass elif len(patt.leaves) == 1: if not all([ expr.head.same(patt.leaves[0]) for expr in exprs[start_pos:start_pos + match_len] ]): continue else: raise ValueError match0, mapping0 = _match_seq(exprs[:start_pos], patts[:patti], definitions) match1, mapping1 = _match_seq(exprs[start_pos + match_len:], patts[patti + 1:], definitions) expr = Expression(Symbol('System`Sequence')) expr.leaves = exprs[start_pos:start_pos + match_len] try: mapping = _merge_dicts(mapping0, mapping1) if match0 and match1: if name is not None: mapping = _merge_dicts(mapping, {name: expr}) return True, mapping except ValueError: pass return False, {} if patts == []: assert exprs != [] return False, {} patti = 0 patt = patts[patti] if patt.head.same(Symbol('System`Pattern')): assert len(patt.leaves) == 2 name2, patt2 = patt.leaves name = name2.get_name() assert patt2.head.same(Symbol('System`BlankNullSequence')) patt = patt2 else: assert patt.head.same(Symbol('System`BlankNullSequence')) for match_len in range(0, len(exprs) + 1): # begin looking for length 0 matches for start_pos in range(len(exprs) + 1 - match_len): if len(patt.leaves) == 0: pass elif len(patt.leaves) == 1: if not all([ expr.head.same(patt.leaves[0]) for expr in exprs[start_pos:start_pos + match_len] ]): continue else: raise ValueError match0, mapping0 = _match_seq(exprs[:start_pos], patts[:patti], definitions) match1, mapping1 = _match_seq(exprs[start_pos + match_len:], patts[patti + 1:], definitions) expr = Expression(Symbol('System`Sequence')) expr.leaves = exprs[start_pos:start_pos + match_len] try: mapping = _merge_dicts(mapping0, mapping1) if match0 and match1: if name is not None: mapping = _merge_dicts(mapping, {name: expr}) return True, mapping except ValueError: pass return False, {}