diff --git a/mathics/builtin/algebra.py b/mathics/builtin/algebra.py index ca7c3d2377..ddcfe903d0 100644 --- a/mathics/builtin/algebra.py +++ b/mathics/builtin/algebra.py @@ -1236,13 +1236,14 @@ def _nth(poly, dims, exponents): if not dims: return from_sympy(poly.coeff_monomial(exponents)) - result = Expression('List') + leaves = [] first_dim = dims[0] for i in range(first_dim+1): exponents.append(i) subs = _nth(poly, dims[1:], exponents) - result.leaves.append(subs) + leaves.append(subs) exponents.pop() + result = Expression('List', *leaves) return result return _nth(sympy_poly, dimensions, []) diff --git a/mathics/builtin/arithmetic.py b/mathics/builtin/arithmetic.py index 5bc344d8fd..a9bc41980a 100644 --- a/mathics/builtin/arithmetic.py +++ b/mathics/builtin/arithmetic.py @@ -265,8 +265,8 @@ def append_last(): leaves.append(last_item) else: if last_item.has_form('Times', None): - last_item.leaves.insert(0, from_sympy(last_count)) - leaves.append(last_item) + leaves.append(Expression( + 'Times', from_sympy(last_count), *last_item.leaves)) else: leaves.append(Expression( 'Times', from_sympy(last_count), last_item)) @@ -280,7 +280,7 @@ def append_last(): for leaf in item.leaves: if isinstance(leaf, Number): count = leaf.to_sympy() - rest = item.leaves[:] + rest = item.get_mutable_leaves() rest.remove(leaf) if len(rest) == 1: rest = rest[0] @@ -589,8 +589,8 @@ def apply(self, items, evaluation): elif (leaves and item.has_form('Power', 2) and leaves[-1].has_form('Power', 2) and item.leaves[0].same(leaves[-1].leaves[0])): - leaves[-1].leaves[1] = Expression( - 'Plus', item.leaves[1], leaves[-1].leaves[1]) + leaves[-1] = Expression('Power', leaves[-1].leaves[0], Expression( + 'Plus', item.leaves[1], leaves[-1].leaves[1])) elif (leaves and item.has_form('Power', 2) and item.leaves[0].same(leaves[-1])): leaves[-1] = Expression( @@ -625,12 +625,13 @@ def apply(self, items, evaluation): elif number.is_zero: return number elif number.same(Integer(-1)) and leaves and leaves[0].has_form('Plus', None): - leaves[0].leaves = [Expression('Times', Integer(-1), leaf) - for leaf in leaves[0].leaves] + leaves[0] = Expression( + leaves[0].get_head(), + *[Expression('Times', Integer(-1), leaf) for leaf in leaves[0].leaves]) number = None for leaf in leaves: - leaf.last_evaluated = None + leaf.clear_cache() if number is not None: leaves.insert(0, number) diff --git a/mathics/builtin/assignment.py b/mathics/builtin/assignment.py index 6fa762c2cc..c2e5f914fd 100644 --- a/mathics/builtin/assignment.py +++ b/mathics/builtin/assignment.py @@ -36,6 +36,7 @@ def get_symbol_list(list, error_callback): class _SetOperator(object): def assign_elementary(self, lhs, rhs, evaluation, tags=None, upset=False): name = lhs.get_head_name() + lhs._format_cache = None if name in system_symbols('OwnValues', 'DownValues', 'SubValues', 'UpValues', 'NValues', 'Options', @@ -290,6 +291,7 @@ def assign_elementary(self, lhs, rhs, evaluation, tags=None, upset=False): return True def assign(self, lhs, rhs, evaluation): + lhs._format_cache = None if lhs.get_head_name() == 'System`List': if (not (rhs.get_head_name() == 'System`List') or len(lhs.leaves) != len(rhs.leaves)): # nopep8 @@ -916,7 +918,6 @@ def format_definition(self, symbol, evaluation, options, grid=True): # Instead, I just copy the code from Definition def show_definitions(self, symbol, evaluation, lines): - def print_rule(rule, up=False, lhs=lambda l: l, rhs=lambda r: r): evaluation.check_stopped() if isinstance(rule, Rule): @@ -1243,7 +1244,7 @@ def get_symbol_values(symbol, func_name, position, evaluation): definition = evaluation.definitions.get_definition(name) else: definition = evaluation.definitions.get_user_definition(name) - result = Expression('List') + leaves = [] for rule in definition.get_values_list(position): if isinstance(rule, Rule): pattern = rule.pattern @@ -1251,9 +1252,9 @@ def get_symbol_values(symbol, func_name, position, evaluation): pattern = pattern.expr else: pattern = Expression('HoldPattern', pattern.expr) - result.leaves.append(Expression( + leaves.append(Expression( 'RuleDelayed', pattern, rule.replace)) - return result + return Expression('List', *leaves) class DownValues(Builtin): diff --git a/mathics/builtin/combinatorial.py b/mathics/builtin/combinatorial.py index 7f523a785f..a8551b9e37 100644 --- a/mathics/builtin/combinatorial.py +++ b/mathics/builtin/combinatorial.py @@ -91,13 +91,13 @@ def apply(self, values, evaluation): 'Multinomial[values___]' values = values.get_sequence() - result = Expression('Times') + leaves = [] total = [] for value in values: total.append(value) - result.leaves.append(Expression( + leaves.append(Expression( 'Binomial', Expression('Plus', *total), value)) - return result + return Expression('Times', *leaves) class _NoBoolVector(Exception): diff --git a/mathics/builtin/files.py b/mathics/builtin/files.py index 7de7977b92..1bb2d5933b 100644 --- a/mathics/builtin/files.py +++ b/mathics/builtin/files.py @@ -21,6 +21,9 @@ import tempfile +from itertools import chain + + from mathics.core.expression import (Expression, Real, Complex, String, Symbol, from_python, Integer, BoxError, MachineReal, Number, valid_context_name) @@ -1501,17 +1504,17 @@ def apply(self, name, n, b, typ, evaluation): elif t.startswith('Character'): if isinstance(x, Integer): x = [String(char) for char in str(x.get_int_value())] - pyb = pyb[:i] + x + pyb[i + 1:] + pyb = list(chain(pyb[:i], x, pyb[i + 1:])) x = pyb[i] if isinstance(x, String) and len(x.get_string_value()) > 1: x = [String(char) for char in x.get_string_value()] - pyb = pyb[:i] + x + pyb[i + 1:] + pyb = list(chain(pyb[:i], x, pyb[i + 1:])) x = pyb[i] x = x.get_string_value() elif t == 'Byte' and isinstance(x, String): if len(x.get_string_value()) > 1: x = [String(char) for char in x.get_string_value()] - pyb = pyb[:i] + x + pyb[i + 1:] + pyb = list(chain(pyb[:i], x, pyb[i + 1:])) x = pyb[i] x = ord(x.get_string_value()) else: diff --git a/mathics/builtin/functional.py b/mathics/builtin/functional.py index 5fb2db966f..dbbd4c4ad6 100644 --- a/mathics/builtin/functional.py +++ b/mathics/builtin/functional.py @@ -5,7 +5,7 @@ Functional programming """ - +from itertools import chain from mathics.builtin.base import Builtin, PostfixOperator from mathics.core.expression import Expression @@ -68,8 +68,7 @@ class Function(PostfixOperator): def apply_slots(self, body, args, evaluation): 'Function[body_][args___]' - args = args.get_sequence() - args.insert(0, Expression('Function', body)) + args = list(chain([Expression('Function', body)], args.get_sequence())) return body.replace_slots(args, evaluation) def apply_named(self, vars, body, args, evaluation): diff --git a/mathics/builtin/lists.py b/mathics/builtin/lists.py index 3c0cba54d6..275de31e84 100644 --- a/mathics/builtin/lists.py +++ b/mathics/builtin/lists.py @@ -15,6 +15,7 @@ from mathics.builtin.base import MessageException, NegativeIntegerException, CountableInteger from mathics.core.expression import Expression, String, Symbol, Integer, Number, Real, strip_context, from_python from mathics.core.expression import min_prec, machine_precision +from mathics.core.expression import structure from mathics.core.evaluation import BreakInterrupt, ContinueInterrupt, ReturnInterrupt from mathics.core.rules import Pattern from mathics.core.convert import from_sympy @@ -221,32 +222,32 @@ def join_lists(lists): return new_list -def get_part(list, indices): +def get_part(varlist, indices): " Simple part extraction. indices must be a list of python integers. " def rec(cur, rest): if rest: - pos = rest[0] if cur.is_atom(): - raise PartDepthError + raise PartDepthError(rest[0]) + pos = rest[0] + leaves = cur.get_leaves() try: if pos > 0: - part = cur.leaves[pos - 1] + part = leaves[pos - 1] elif pos == 0: - part = cur.head + part = cur.get_head() else: - part = cur.leaves[pos] + part = leaves[pos] except IndexError: raise PartRangeError return rec(part, rest[1:]) else: return cur - return rec(list, indices).copy() + return rec(varlist, indices).copy() -def set_part(list, indices, new): +def set_part(varlist, indices, newval): " Simple part replacement. indices must be a list of python integers. " - def rec(cur, rest): if len(rest) > 1: pos = rest[0] @@ -254,63 +255,30 @@ def rec(cur, rest): raise PartDepthError try: if pos > 0: - part = cur.leaves[pos - 1] + part = cur._leaves[pos - 1] elif pos == 0: - part = cur.head + part = cur.get_head() else: - part = cur.leaves[pos] + part = cur._leaves[pos] except IndexError: raise PartRangeError - rec(part, rest[1:]) + return rec(part, rest[1:]) elif len(rest) == 1: pos = rest[0] if cur.is_atom(): raise PartDepthError try: if pos > 0: - cur.leaves[pos - 1] = new + cur.set_leaf(pos - 1, newval) elif pos == 0: - cur.head = new + cur.set_head(newval) else: - cur.leaves[pos] = new + cur.set_leaf(pos, newval) except IndexError: raise PartRangeError - rec(list, indices) - -def set_sequence(list, indices): - "Replace a part to Sequence. indices must be a list of python integers. " - - def sequence(cur, rest): - if len(rest) > 1: - pos = rest[0] - if cur.is_atom(): - raise PartDepthError(pos) - try: - if pos > 0: - part = cur.leaves[pos - 1] - elif pos == 0: - part = cur.head - else: - part = cur.leaves[pos] - except IndexError: - raise PartRangeError - sequence(part, rest[1:]) - elif len(rest) == 1: - pos = rest[0] - if cur.is_atom(): - raise PartDepthError(pos) - try: - if pos > 0: - cur.leaves[pos - 1] = Expression('Sequence') - elif pos == 0: - cur.head = Symbol('Sequence') - else: - cur.leaves[pos] = Expression('Sequence') - except IndexError: - raise PartRangeError + rec(varlist, indices) - sequence(list, indices) def _parts_span_selector(pspec): if len(pspec.leaves) > 3: @@ -349,7 +317,7 @@ def select(inner): def _parts_sequence_selector(pspec): - if not isinstance(pspec, list): + if not isinstance(pspec, (tuple, list)): indices = [pspec] else: indices = pspec @@ -392,7 +360,7 @@ def _part_selectors(indices): raise MessageException('Part', 'pspec', index) -def _list_parts(items, selectors, assignment): +def _list_parts(items, selectors, heads, evaluation, assignment): if not selectors: for item in items: yield item @@ -408,24 +376,24 @@ def _list_parts(items, selectors, assignment): selected = list(select(item)) picked = list(_list_parts( - selected, selectors[1:], assignment)) + selected, selectors[1:], heads, evaluation, assignment)) if unwrap is None: - expr = item.shallow_copy() - expr.leaves = picked - expr.last_evaluated = None - if assignment: + expr = Expression(item.head, *picked) expr.original = None expr.set_positions() + else: + expr = item.restructure(item.head, picked, evaluation) yield expr else: yield unwrap(picked) -def _parts(items, selectors, assignment=False): - return list(_list_parts([items], list(selectors), assignment))[0] +def _parts(items, selectors, evaluation, assignment=False): + heads = {} + return list(_list_parts([items], list(selectors), heads, evaluation, assignment))[0] def walk_parts(list_of_list, indices, evaluation, assign_list=None): @@ -448,6 +416,7 @@ def walk_parts(list_of_list, indices, evaluation, assign_list=None): result = _parts( walk_list, _part_selectors(indices), + evaluation, assign_list is not None) except MessageException as e: e.message(evaluation) @@ -478,7 +447,7 @@ def process_level(item, assignment): process_level(result, assign_list) result = list_of_list[0] - result.last_evaluated = None + result.clear_cache() return result @@ -903,21 +872,36 @@ class Partition(Builtin): 'Parition[list_, n_, d_, k]': 'Partition[list, n, d, {k, k}]', } - def chunks(self, l, n, d): + def _partition(self, expr, n, d, evaluation): assert n > 0 and d > 0 - return [x for x in [l[i:i + n] for i in range(0, len(l), d)] if len(x) == n] + + inner = structure('List', expr, evaluation) + outer = structure('List', inner, evaluation) + + make_slice = inner.slice + + def slices(): + leaves = expr.leaves + for lower in range(0, len(leaves), d): + upper = lower + n + + chunk = leaves[lower:upper] + if len(chunk) != n: + continue + + yield make_slice(expr, slice(lower, upper)) + + return outer(slices()) def apply_no_overlap(self, l, n, evaluation): 'Partition[l_List, n_Integer]' # TODO: Error checking - return Expression('List', *self.chunks( - l.get_leaves(), n.get_int_value(), n.get_int_value())) + return self._partition(l, n.get_int_value(), n.get_int_value(), evaluation) def apply(self, l, n, d, evaluation): 'Partition[l_List, n_Integer, d_Integer]' # TODO: Error checking - return Expression('List', *self.chunks( - l.get_leaves(), n.get_int_value(), d.get_int_value())) + return self._partition(l, n.get_int_value(), d.get_int_value(), evaluation) class Extract(Builtin): @@ -1013,6 +997,11 @@ class Most(Builtin): >> Most[x] : Nonatomic expression expected. = Most[x] + + #> A[x__] := 7 /; Length[{x}] == 3; + #> Most[A[1, 2, 3, 4]] + = 7 + #> ClearAll[A]; """ def apply(self, expr, evaluation): @@ -1021,7 +1010,7 @@ def apply(self, expr, evaluation): if expr.is_atom(): evaluation.message('Most', 'normal') return - return Expression(expr.head, *expr.leaves[:-1]) + return expr.slice(expr.head, slice(0, -1), evaluation) class Rest(Builtin): @@ -1048,7 +1037,7 @@ def apply(self, expr, evaluation): if expr.is_atom(): evaluation.message('Rest', 'normal') return - return Expression(expr.head, *expr.leaves[1:]) + return expr.slice(expr.head, slice(1, len(expr.leaves)), evaluation) class ReplacePart(Builtin): @@ -1114,7 +1103,7 @@ def apply(self, expr, replacements, evaluation): position = replacement.leaves[0] replace = replacement.leaves[1] if position.has_form('List', None): - position = position.leaves + position = position.get_mutable_leaves() else: position = [position] for index, pos in enumerate(position): @@ -1300,7 +1289,7 @@ def _take_span_selector(seq): def _drop_span_selector(seq): def sliced(x, s): - y = x[:] + y = list(x[:]) del y[s] return y @@ -1373,7 +1362,7 @@ def apply(self, items, seqs, evaluation): 'Take', 'normal', 1, Expression('Take', items, *seqs)) try: - return _parts(items, [_take_span_selector(seq) for seq in seqs]) + return _parts(items, [_take_span_selector(seq) for seq in seqs], evaluation) except MessageException as e: e.message(evaluation) @@ -1423,7 +1412,7 @@ def apply(self, items, seqs, evaluation): 'Drop', 'normal', 1, Expression('Drop', items, *seqs)) try: - return _parts(items, [_drop_span_selector(seq) for seq in seqs]) + return _parts(items, [_drop_span_selector(seq) for seq in seqs], evaluation) except MessageException as e: e.message(evaluation) @@ -1447,20 +1436,25 @@ class Select(Builtin): >> Select[a, True] : Nonatomic expression expected. = Select[a, True] + + #> A[x__] := 31415 /; Length[{x}] == 3; + #> Select[A[5, 2, 7, 1], OddQ] + = 31415 + #> ClearAll[A]; """ - def apply(self, list, expr, evaluation): - 'Select[list_, expr_]' + def apply(self, items, expr, evaluation): + 'Select[items_, expr_]' - if list.is_atom(): + if items.is_atom(): evaluation.message('Select', 'normal') return - new_leaves = [] - for leaf in list.leaves: + + def cond(leaf): test = Expression(expr, leaf) - if test.evaluate(evaluation).is_true(): - new_leaves.append(leaf) - return Expression(list.head, *new_leaves) + return test.evaluate(evaluation).is_true() + + return items.filter(items.head, cond, evaluation) class Split(Builtin): @@ -1492,6 +1486,11 @@ class Split(Builtin): #> Split[{}] = {} + + #> A[x__] := 321 /; Length[{x}] == 5; + #> Split[A[x, x, x, y, x, y, y, z]] + = 321 + #> ClearAll[A]; """ rules = { @@ -1511,19 +1510,20 @@ def apply(self, mlist, test, evaluation): evaluation.message('Select', 'normal', 1, expr) return - if len(mlist.leaves) == 0: - result = [] - else: - result = [[mlist.leaves[0]]] - for leaf in mlist.leaves[1:]: - applytest = Expression(test, result[-1][-1], leaf) - if applytest.evaluate(evaluation).is_true(): - result[-1].append(leaf) - else: - result.append([leaf]) + if not mlist.leaves: + return Expression(mlist.head) - return Expression(mlist.head, *[Expression('List', *l) - for l in result]) + result = [[mlist.leaves[0]]] + for leaf in mlist.leaves[1:]: + applytest = Expression(test, result[-1][-1], leaf) + if applytest.evaluate(evaluation).is_true(): + result[-1].append(leaf) + else: + result.append([leaf]) + + inner = structure('List', mlist, evaluation) + outer = structure(mlist.head, inner, evaluation) + return outer([inner(l) for l in result]) class SplitBy(Builtin): @@ -1573,8 +1573,9 @@ def apply(self, mlist, func, evaluation): result.append([leaf]) prev = curr - return Expression(mlist.head, *[Expression('List', *l) - for l in result]) + inner = structure('List', mlist, evaluation) + outer = structure(mlist.head, inner, evaluation) + return outer([inner(l) for l in result]) def apply_multiple(self, mlist, funcs, evaluation): 'SplitBy[mlist_, funcs_?ListQ]' @@ -1610,13 +1611,13 @@ class Pick(Builtin): = {a, b, d} """ - def _do(self, items0, sel0, match): + def _do(self, items0, sel0, match, evaluation): def pick(items, sel): for x, s in zip(items, sel): if match(s): yield x elif not x.is_atom() and not s.is_atom(): - yield Expression(x.get_head(), *list(pick(x.leaves, s.leaves))) + yield x.restructure(x.head, pick(x.leaves, s.leaves), evaluation) r = list(pick([items0], [sel0])) if not r: @@ -1626,13 +1627,13 @@ def pick(items, sel): def apply(self, items, sel, evaluation): 'Pick[items_, sel_]' - return self._do(items, sel, lambda s: s.is_true()) + return self._do(items, sel, lambda s: s.is_true(), evaluation) def apply_pattern(self, items, sel, pattern, evaluation): 'Pick[items_, sel_, pattern_]' from mathics.builtin.patterns import Matcher match = Matcher(pattern).match - return self._do(items, sel, lambda s: match(s, evaluation)) + return self._do(items, sel, lambda s: match(s, evaluation), evaluation) class Cases(Builtin): @@ -1717,96 +1718,6 @@ def callback(level): -class Delete(Builtin): - """ -
-
'Delete[$expr$, $n$]' -
returns $expr$ with part $n$ removed. -
- - >> Delete[{a, b, c, d}, 3] - = {a, b, d} - >> Delete[{a, b, c, d}, -2] - = {a, b, d} - >> Delete[{{1, 2}, {3, 4}}, {1, 2}] - = {{1}, {3, 4}} - #> Delete[{1,2,3,4},5] - : Cannot delete position 5 in Delete[{1, 2, 3, 4}, 5]. - = Delete[{1, 2, 3, 4}, 5] - """ - - messages = { - 'normal': 'Nonatomic expression expected at position `1` in `2`.', - 'delete': "Cannot delete position `1` in `2`.", - } - - - def del_one(self,cur,pos): - l = len(cur.leaves) - if cur.is_atom(): - raise PartDepthError - if pos > l: - raise PartRangeError - if pos > 0: - cur.leaves = cur.leaves[:pos-1] + cur.leaves[pos:] - return cur - elif pos == 0: - cur.head = Symbol('System`Sequence') - return cur - elif pos >= -l: - cur.leaves = cur.leaves[:l+pos] + cur.leaves[l+pos+1:] - return cur - else: - raise PartRangeError - - def del_rec(self, cur, rest): - if cur.is_atom(): - raise PartDepthError - if len(rest) > 1: - pos = rest[0] - try: - if pos > 0: - part = get_part(cur,[pos]) - part = self.del_rec(part,rest[1:]) - cur.leaves = cur.leaves[:pos-1] + [part] + cur.leaves[pos:] - return cur - elif pos == 0: - raise PartRangeError - elif pos >= -len(cur.leaves): - l = len(cur.leaves) - part = get_part(cur,[l+pos+1]) - part = self.del_rec(part,rest[1:]) - cur.leaves = cur.leaves[:l+pos] + [part] + cur.leaves[l+pos+1:] - return cur - else: - raise PartRangeError - except IndexError: - raise PartRangeError - else: - return self.del_one(cur, rest[0]) - - def del_part(self, expr,indices,evaluation): - if indices.is_atom(): - return self.del_one(expr,indices.get_int_value()) - else: - indices = [index.get_int_value() for index in indices.leaves] - return self.del_rec(expr.copy(), indices) - - def apply(self, items, n, evaluation): - 'Delete[items_, n_]' - - if items.is_atom(): - return evaluation.message( - 'Delete', 'normal', 1, Expression('Delete', items, n)) - try: - return self.del_part(items,n,evaluation) - except MessageException as e: - e.message(evaluation) - except PartRangeError: - evaluation.message('Delete', 'delete', n, Expression('Delete', items, n)) - except PartDepthError: - evaluation.message('Delete', 'delete', n, Expression('Delete', items, n)) - class DeleteCases(Builtin): """ @@ -1834,7 +1745,11 @@ def apply(self, items, pattern, evaluation): from mathics.builtin.patterns import Matcher match = Matcher(pattern).match - return Expression('List', *[leaf for leaf in items.leaves if not match(leaf, evaluation)]) + + def cond(leaf): + return not match(leaf, evaluation) + + return items.filter('List', cond, evaluation) class Count(Builtin): @@ -2259,7 +2174,7 @@ def apply(self, f, dimsexpr, origins, head, evaluation): 'Array[f_, dimsexpr_, origins_:1, head_:List]' if dimsexpr.has_form('List', None): - dims = dimsexpr.leaves[:] + dims = dimsexpr.get_mutable_leaves() else: dims = [dimsexpr] for index, dim in enumerate(dims): @@ -2272,7 +2187,7 @@ def apply(self, f, dimsexpr, origins, head, evaluation): if len(origins.leaves) != len(dims): evaluation.message('Array', 'plen', dimsexpr, origins) return - origins = origins.leaves[:] + origins = origins.get_mutable_leaves() else: origins = [origins] * len(dims) for index, origin in enumerate(origins): @@ -2381,7 +2296,9 @@ def apply(self, lists, evaluation): result = [] head = None - for list in lists.get_sequence(): + sequence = lists.get_sequence() + + for list in sequence: if list.is_atom(): return if head is not None and list.get_head() != head: @@ -2391,7 +2308,7 @@ def apply(self, lists, evaluation): result.extend(list.leaves) if result: - return Expression(head, *result) + return sequence[0].restructure(head, result, evaluation, deps=sequence) else: return Expression('List') @@ -2422,7 +2339,12 @@ def parts(): raise MessageException('Catenate', 'invrp', l) try: - return Expression('List', *list(chain(*list(parts())))) + result = list(chain(*list(parts()))) + if result: + return lists.leaves[0].restructure( + 'List', result, evaluation, deps=lists.leaves) + else: + return Expression('List') except MessageException as e: e.message(evaluation) @@ -2456,8 +2378,8 @@ def apply(self, expr, item, evaluation): if expr.is_atom(): return evaluation.message('Append', 'normal') - return Expression(expr.get_head(), - *(expr.get_leaves() + [item])) + return expr.restructure( + expr.head, list(chain(expr.get_leaves(), [item])), evaluation, deps=(expr, item)) class AppendTo(Builtin): @@ -2536,8 +2458,8 @@ def apply(self, expr, item, evaluation): if expr.is_atom(): return evaluation.message('Prepend', 'normal') - return Expression(expr.get_head(), - *([item] + expr.get_leaves())) + return expr.restructure( + expr.head, list(chain([item], expr.get_leaves())), evaluation, deps=(expr, item)) class PrependTo(Builtin): @@ -2745,11 +2667,11 @@ def listener(e, tag): result = expr.evaluate(evaluation) items = [] for pattern, tags in sown: - list = Expression('List') + leaves = [] for tag, elements in tags: - list.leaves.append(Expression( + leaves.append(Expression( f, tag, Expression('List', *elements))) - items.append(list) + items.append(Expression('List', *leaves)) return Expression('List', result, Expression('List', *items)) finally: evaluation.remove_listener('sow', listener) @@ -2880,9 +2802,11 @@ def apply(self, list, sep, evaluation): 'Riffle[list_List, sep_]' if sep.has_form('List', None): - return Expression('List', *riffle_lists(list.get_leaves(), sep.leaves)) + result = riffle_lists(list.get_leaves(), sep.leaves) else: - return Expression('List', *riffle_lists(list.get_leaves(), [sep])) + result = riffle_lists(list.get_leaves(), [sep]) + + return list.restructure('List', result, evaluation, deps=(list, sep)) def _is_sameq(same_test): @@ -3458,20 +3382,29 @@ class Reverse(Builtin): } @staticmethod - def _reverse(expr, level, levels): # depth >= 1, levels are expected to be unique and sorted + def _reverse(expr, level, levels, evaluation): # depth >= 1, levels are expected to be unique and sorted if not isinstance(expr, Expression): return expr + if levels[0] == level: - new_leaves = reversed(expr.leaves) + expr = expr.restructure(expr.head, reversed(expr.leaves), evaluation) + if len(levels) > 1: - new_leaves = (Reverse._reverse(leaf, level + 1, levels[1:]) for leaf in new_leaves) + expr = expr.restructure( + expr.head, + [Reverse._reverse(leaf, level + 1, levels[1:], evaluation) for leaf in expr.leaves], + evaluation) else: - new_leaves = (Reverse._reverse(leaf, level + 1, levels) for leaf in expr.leaves) - return Expression(expr.get_head(), *new_leaves) + expr = expr.restructure( + expr.head, + [Reverse._reverse(leaf, level + 1, levels, evaluation) for leaf in expr.leaves], + evaluation) + + return expr def apply_top_level(self, expr, evaluation): 'Reverse[expr_]' - return Reverse._reverse(expr, 1, (1,)) + return Reverse._reverse(expr, 1, (1,), evaluation) def apply(self, expr, levels, evaluation): 'Reverse[expr_, levels_]' @@ -3492,7 +3425,7 @@ def apply(self, expr, levels, evaluation): if py_levels is None: evaluation.message('Reverse', 'ilsmp', Expression('Reverse', expr, levels)) else: - return Reverse._reverse(expr, 1, py_levels) + return Reverse._reverse(expr, 1, py_levels, evaluation) class CentralMoment(Builtin): # see https://en.wikipedia.org/wiki/Central_moment @@ -3749,7 +3682,7 @@ class _Rotate(Builtin): 'rspec': '`` should be an integer or a list of integers.' } - def _rotate(self, expr, n): + def _rotate(self, expr, n, evaluation): if not isinstance(expr, Expression): return expr @@ -3761,13 +3694,13 @@ def _rotate(self, expr, n): new_leaves = chain(leaves[index:], leaves[:index]) if len(n) > 1: - new_leaves = [self._rotate(item, n[1:]) for item in new_leaves] + new_leaves = [self._rotate(item, n[1:], evaluation) for item in new_leaves] - return Expression(expr.get_head(), *new_leaves) + return expr.restructure(expr.head, new_leaves, evaluation) def apply_one(self, expr, evaluation): '%(name)s[expr_]' - return self._rotate(expr, [1]) + return self._rotate(expr, [1], evaluation) def apply(self, expr, n, evaluation): '%(name)s[expr_, n_]' @@ -3781,7 +3714,7 @@ def apply(self, expr, n, evaluation): evaluation.message(self.get_name(), 'rspec', n) return - return self._rotate(expr, py_cycles) + return self._rotate(expr, py_cycles, evaluation) class RotateLeft(_Rotate): @@ -3867,7 +3800,7 @@ def apply(self, l, evaluation): except _NotRectangularException: evaluation.message('Median', 'rectn', Expression('Median', l)) elif all(leaf.is_numeric() for leaf in l.leaves): - v = l.leaves[:] # copy needed for introselect + v = l.get_mutable_leaves() # copy needed for introselect n = len(v) if n % 2 == 0: # even number of elements? i = n // 2 @@ -3906,7 +3839,7 @@ def apply(self, l, n, evaluation): elif py_n > len(l.leaves): evaluation.message('RankedMin', 'rank', py_n, len(l.leaves)) else: - return introselect(l.leaves[:], py_n - 1) + return introselect(l.get_mutable_leaves(), py_n - 1) class RankedMax(Builtin): @@ -3934,7 +3867,7 @@ def apply(self, l, n, evaluation): elif py_n > len(l.leaves): evaluation.message('RankedMax', 'rank', py_n, len(l.leaves)) else: - return introselect(l.leaves[:], len(l.leaves) - py_n) + return introselect(l.get_mutable_leaves(), len(l.leaves) - py_n) class Quantile(Builtin): @@ -3964,7 +3897,7 @@ def apply(self, l, qs, a, b, c, d, evaluation): '''Quantile[l_List, qs_List, {{a_, b_}, {c_, d_}}]''' n = len(l.leaves) - partially_sorted = l.leaves[:] + partially_sorted = l.get_mutable_leaves() def ranked(i): return introselect(partially_sorted, min(max(0, i - 1), n - 1)) @@ -4105,7 +4038,7 @@ def exclude(item): else: result = self._get_n(py_n, heap) - return Expression('List', *[x[leaf_pos] for x in result]) + return l.restructure('List', [x[leaf_pos] for x in result], evaluation) class _RankedTakeSmallest(_RankedTake): @@ -4939,9 +4872,10 @@ def apply_n(self, l, n, evaluation): if rs is None: rs = range(py_n + 1) - return Expression('List', *[Expression('List', *p) - for r in rs - for p in permutations(l.leaves, r)]) + inner = structure('List', l, evaluation) + outer = structure('List', inner, evaluation) + + return outer([inner(p) for r in rs for p in permutations(l.leaves, r)]) class SubsetQ(Builtin): @@ -5021,6 +4955,47 @@ def apply(self, expr, subset, evaluation): else: return Symbol('False') +def delete_one(expr, pos): + if expr.is_atom(): + raise PartDepthError(pos) + leaves = expr.leaves + if pos == 0: + return Expression(Symbol("System`Sequence"), *leaves) + l = len(leaves) + truepos = pos + if truepos < 0: + truepos = l + truepos + else: + truepos = truepos - 1 + if truepos < 0 or truepos>=l: + raise PartRangeError + leaves = leaves[:truepos] + (Expression("System`Sequence"),) + leaves[truepos+1:] + return Expression(expr.get_head(), *leaves) + + + +def delete_rec(expr, pos): + if len(pos)==1: + return delete_one(expr, pos[0]) + truepos = pos[0] + if truepos == 0 or expr.is_atom(): + raise PartDepthError(pos[0]) + leaves = expr.leaves + l = len(leaves) + if truepos < 0: + truepos = truepos + l + if truepos < 0: + raise PartRangeError + newleaf = delete_rec(leaves[truepos], pos[1:]) + leaves = leaves[:truepos] + (newleaf,) + leaves[truepos+1:] + else: + if truepos > l: + raise PartRangeError + newleaf = delete_rec(leaves[truepos-1 ], pos[1:]) + leaves = leaves[:truepos-1] + (newleaf,) + leaves[truepos:] + return Expression(expr.get_head(), *leaves) + + class Delete(Builtin): """
@@ -5116,22 +5091,18 @@ class Delete(Builtin): def apply_one(self, expr, position, evaluation): 'Delete[expr_, position_Integer]' - - new_expr = expr.copy() - pos = [position.get_int_value()] + pos = position.get_int_value() try: - set_sequence(new_expr, pos) - except PartError: - return evaluation.message('Delete', 'partw', Expression('List', *pos), expr) - - return new_expr + return delete_one(expr, pos) + except PartRangeError: + evaluation.message('Delete', 'partw', Expression('List', pos), expr) def apply(self, expr, positions, evaluation): 'Delete[expr_, positions___]' - positions = positions.get_sequence() if len(positions) > 1: - return evaluation.message('Delete', 'argt', Integer(len(positions) + 1)) + return evaluation.message('Delete', 'argt', + Integer(len(positions) + 1)) elif len(positions) == 0: return evaluation.message('Delete', 'argr') @@ -5142,8 +5113,8 @@ def apply(self, expr, positions, evaluation): # Create new python list of the positions and sort it positions = [l for l in positions.leaves] if positions.leaves[0].has_form('List', None) else [positions] positions.sort(key=lambda e: e.get_sort_key(pattern_sort=True)) - - new_expr = expr.copy() + leaves = expr.leaves + newexpr = expr for position in positions: pos = [p.get_int_value() for p in position.get_leaves()] if None in pos: @@ -5151,13 +5122,13 @@ def apply(self, expr, positions, evaluation): if len(pos) == 0: return evaluation.message('Delete', 'psl', Expression('List', *positions), expr) try: - set_sequence(new_expr, pos) + newexpr = delete_rec(newexpr, pos) except PartDepthError as exc: return evaluation.message('Delete', 'partw', Integer(exc.index), expr) except PartError: return evaluation.message('Delete', 'partw', Expression('List', *pos), expr) + return newexpr - return new_expr class Association(Builtin): """ diff --git a/mathics/builtin/numeric.py b/mathics/builtin/numeric.py index e4fdfc1443..8d0c1bd645 100644 --- a/mathics/builtin/numeric.py +++ b/mathics/builtin/numeric.py @@ -244,8 +244,8 @@ def apply_other(self, expr, prec, evaluation): eval_range = () else: eval_range = range(len(expr.leaves)) - head = Expression("N", expr.head, prec).evaluate(evaluation) - leaves = expr.leaves[:] + head = Expression('N', expr.head, prec).evaluate(evaluation) + leaves = expr.get_mutable_leaves() for index in eval_range: leaves[index] = Expression("N", leaves[index], prec).evaluate( evaluation @@ -1162,12 +1162,12 @@ def apply_rational_with_base(self, n, b, evaluation): py_n.as_numer_denom()[0], py_n.as_numer_denom()[1], py_b ) - list_str = Expression("List") + leaves = [] for x in head: if not x == "0": - list_str.leaves.append(from_python(int(x))) - list_str.leaves.append(from_python(tails)) - + leaves.append(from_python(int(x))) + leaves.append(from_python(tails)) + list_str = Expression("List", *leaves) return Expression("List", list_str, exp) def apply_rational_without_base(self, n, evaluation): @@ -1268,42 +1268,39 @@ def apply_2(self, n, b, evaluation, nr_elements=None, pos=None): digits = digits[abs(move) :] display_len = display_len - move - list_str = Expression("List") - + leaves = [] for x in digits: if x == "e" or x == "E": break # Convert to Mathics' list format - list_str.leaves.append(from_python(int(x))) + leaves.append(from_python(int(x))) if not rational_no: - while len(list_str.leaves) < display_len: - list_str.leaves.append(from_python(0)) + while len(leaves) < display_len: + leaves.append(from_python(0)) if nr_elements is not None: # display_len == nr_elements - if len(list_str.leaves) >= nr_elements: + if len(leaves) >= nr_elements: # Truncate, preserving the digits on the right - list_str = list_str.leaves[:nr_elements] + leaves = leaves[:nr_elements] else: if isinstance(n, Integer): - while len(list_str.leaves) < nr_elements: - list_str.leaves.append(from_python(0)) + while len(leaves) < nr_elements: + leaves.append(from_python(0)) else: # Adding Indeterminate if the length is greater than the precision - while len(list_str.leaves) < nr_elements: - list_str.leaves.append(from_python(Symbol("Indeterminate"))) - + while len(leaves) < nr_elements: + leaves.append(from_python(Symbol("Indeterminate"))) + list_str = Expression("List", *leaves) return Expression("List", list_str, exp) def apply_3(self, n, b, length, evaluation, pos=None): "RealDigits[n_?NumericQ, b_Integer, length_]" - - expr = Expression("RealDigits", n, b, length) - + leaves = [] if pos is not None: - expr.leaves.append(from_python(pos)) - + leaves.append(from_python(pos)) + expr = Expression("RealDigits", n, b, length, *leaves) if not (isinstance(length, Integer) and length.get_int_value() >= 0): return evaluation.message("RealDigits", "intnm", expr) @@ -1313,7 +1310,6 @@ def apply_3(self, n, b, length, evaluation, pos=None): def apply_4(self, n, b, length, p, evaluation): "RealDigits[n_?NumericQ, b_Integer, length_, p_]" - if not isinstance(p, Integer): return evaluation.message( "RealDigits", "intm", Expression("RealDigits", n, b, length, p) diff --git a/mathics/builtin/quantities.py b/mathics/builtin/quantities.py index 59036285fa..0af2c6d774 100644 --- a/mathics/builtin/quantities.py +++ b/mathics/builtin/quantities.py @@ -108,10 +108,10 @@ def convert_unit(leaves, target): else: return if expr.has_form("List", None): - abc = Expression("List") + abc = [] for i in range(len(expr.leaves)): - abc.leaves.append(convert_unit(expr.leaves[i].leaves, targetUnit)) - return abc + abc.append(convert_unit(expr.leaves[i].leaves, targetUnit)) + return Expression("List", *abc) else: return convert_unit(expr.leaves, targetUnit) @@ -131,10 +131,10 @@ def convert_unit(leaves): if len(evaluation.out) > 0: return if expr.has_form("List", None): - abc = Expression("List") + abc = [] for i in range(len(expr.leaves)): - abc.leaves.append(convert_unit(expr.leaves[i].leaves)) - return abc + abc.append(convert_unit(expr.leaves[i].leaves)) + return Expression("List", *abc) else: return convert_unit(expr.leaves) @@ -194,18 +194,17 @@ def apply_n(self, mag, unit, evaluation): if(self.validate(unit, evaluation)): if(mag.has_form("List", None)): - result = Expression("List") + results = [] for i in range(len(mag.leaves)): quantity = Q_(mag.leaves[i], unit.get_string_value().lower()) - result.leaves.append(Expression("Quantity", quantity.magnitude, String(quantity.units))) - - return result + results.append(Expression("Quantity", quantity.magnitude, String(quantity.units))) + return Expression("List", *results) else: quantity = Q_(mag, unit.get_string_value().lower()) return Expression('Quantity', quantity.magnitude, String(quantity.units)) else: return evaluation.message('Quantity', 'unkunit', unit) - + def apply_1(self, unit, evaluation): 'Quantity[unit_]' if not isinstance(unit, String): @@ -293,10 +292,10 @@ def get_unit(leaves): if len(evaluation.out) > 0: return if expr.has_form("List", None): - quantity_expr = Expression("List") + results = [] for i in range(len(expr.leaves)): - quantity_expr.leaves.append(get_unit(expr.leaves[i].leaves)) - return quantity_expr + results.append(get_unit(expr.leaves[i].leaves)) + return Expression("List", *results) else: return get_unit(expr.leaves) @@ -350,10 +349,10 @@ def get_magnitude(leaves): if len(evaluation.out) > 0: return if expr.has_form("List", None): - quantity_expr = Expression("List") + results = [] for i in range(len(expr.leaves)): - quantity_expr.leaves.append(get_magnitude(expr.leaves[i].leaves)) - return quantity_expr + results.append(get_magnitude(expr.leaves[i].leaves)) + return Expression("List", *results) else: return get_magnitude(expr.leaves) @@ -389,9 +388,9 @@ def get_magnitude(leaves, targetUnit, evaluation): #convert the quantity to the target unit and return the magnitude if expr.has_form("List", None): - quantity_expr = Expression("List") + results = [] for i in range(len(expr.leaves)): - quantity_expr.leaves.append(get_magnitude(expr.leaves[i].leaves, targetUnit, evaluation)) - return quantity_expr + results.append(get_magnitude(expr.leaves[i].leaves, targetUnit, evaluation)) + return Expression("List", *results) else: return get_magnitude(expr.leaves, targetUnit, evaluation) diff --git a/mathics/builtin/strings.py b/mathics/builtin/strings.py index e211d762ec..d0eaeea086 100644 --- a/mathics/builtin/strings.py +++ b/mathics/builtin/strings.py @@ -14,7 +14,7 @@ from mathics.builtin.base import BinaryOperator, Builtin, Test, Predefined from mathics.core.expression import (Expression, Symbol, String, Integer, - from_python) + from_python, string_list) from mathics.builtin.lists import python_seq, convert_seq @@ -907,7 +907,7 @@ def apply(self, string, patt, evaluation, options): for re_patt in re_patts: result = [t for s in result for t in mathics_split(re_patt, s, flags=flags)] - return Expression('List', *[String(x) for x in result if x != '']) + return string_list('List', [String(x) for x in result if x != ''], evaluation) class StringPosition(Builtin): diff --git a/mathics/builtin/structure.py b/mathics/builtin/structure.py index c356d24a80..2a79046507 100644 --- a/mathics/builtin/structure.py +++ b/mathics/builtin/structure.py @@ -82,7 +82,7 @@ def apply(self, list, evaluation): evaluation.message("Sort", "normal") else: new_leaves = sorted(list.leaves) - return Expression(list.head, *new_leaves) + return list.restructure(list.head, new_leaves, evaluation) def apply_predicate(self, list, p, evaluation): "Sort[list_, p_]" @@ -103,7 +103,7 @@ def __gt__(self, other): ) new_leaves = sorted(list.leaves, key=Key) - return Expression(list.head, *new_leaves) + return list.restructure(list.head, new_leaves, evaluation) class SortBy(Builtin): @@ -173,7 +173,7 @@ def __gt__(self, other): # we sort a list of indices. after sorting, we reorder the leaves. new_indices = sorted(list(range(len(raw_keys))), key=Key) new_leaves = [raw_keys[i] for i in new_indices] # reorder leaves - return Expression(l.head, *new_leaves) + return l.restructure(l.head, new_leaves, evaluation) class BinarySearch(Builtin): @@ -1006,10 +1006,9 @@ def callback(expr, pos): expr, depth = walk_levels(expr, callback=callback, include_pos=True) # build new tree inserting nodes as needed - result = Expression(h) leaves = sorted(new_indices.items()) - def insert_leaf(expr, leaves): + def insert_leaf(leaves): # gather leaves into groups with the same leading index # e.g. [((0, 0), a), ((0, 1), b), ((1, 0), c), ((1, 1), d)] # -> [[(0, a), (1, b)], [(0, c), (1, d)]] @@ -1023,16 +1022,16 @@ def insert_leaf(expr, leaves): grouped_leaves.append([(index[1:], leaf)]) # for each group of leaves we either insert them into the current level # or make a new level and recurse + new_leaves = [] for group in grouped_leaves: if len(group[0][0]) == 0: # bottom level leaf assert len(group) == 1 - expr.leaves.append(group[0][1]) + new_leaves.append(group[0][1]) else: - expr.leaves.append(Expression(h)) - insert_leaf(expr.leaves[-1], group) + new_leaves.append(Expression(h, *insert_leaf(group))) - insert_leaf(result, leaves) - return result + return new_leaves + return Expression(h, *insert_leaf(leaves)) def apply(self, expr, n, h, evaluation): "Flatten[expr_, n_, h_]" @@ -1279,7 +1278,7 @@ def apply(self, p, expr, n, evaluation): # Otherwise, if we get here, e.head points to the head we need # to apply p to. Python's reference semantics mean that this # assignment modifies expr as well. - e.head = Expression(p, e.head) + e.set_head(Expression(p, e.head)) return expr diff --git a/mathics/builtin/tensors.py b/mathics/builtin/tensors.py index a62b80c52a..caa129993e 100644 --- a/mathics/builtin/tensors.py +++ b/mathics/builtin/tensors.py @@ -272,35 +272,36 @@ def apply(self, f, list1, list2, g, evaluation): if not m or not n: evaluation.message('Inner', 'normal') return - if list1.head != list2.head: - evaluation.message('Inner', 'heads', list1.head, list2.head) + if list1.get_head() != list2.get_head(): + evaluation.message('Inner', 'heads', list1.get_head(), list2.get_head()) return if m[-1] != n[0]: evaluation.message( 'Inner', 'incom', m[-1], len(m), list1, n[0], list2) return - head = list1.head + head = list1.get_head() inner_dim = n[0] def rec(i_cur, j_cur, i_rest, j_rest): evaluation.check_stopped() if i_rest: - new = Expression(head) + leaves = [] for i in range(1, i_rest[0] + 1): - new.leaves.append( + leaves.append( rec(i_cur + [i], j_cur, i_rest[1:], j_rest)) - return new + return Expression(head, *leaves) elif j_rest: - new = Expression(head) + leaves = [] for j in range(1, j_rest[0] + 1): - new.leaves.append( + leaves.append( rec(i_cur, j_cur + [j], i_rest, j_rest[1:])) - return new + return Expression(head, *leaves) else: def summand(i): - return Expression(f, get_part(list1, i_cur + [i]), - get_part(list2, [i] + j_cur)) + part1 = get_part(list1, i_cur + [i]) + part2 = get_part(list2, [i] + j_cur) + return Expression(f, part1, part2) part = Expression( g, *[summand(i) for i in range(1, inner_dim + 1)]) # cur_expr.leaves.append(part) diff --git a/mathics/core/definitions.py b/mathics/core/definitions.py index 2faffbaaf8..d221bfc266 100644 --- a/mathics/core/definitions.py +++ b/mathics/core/definitions.py @@ -204,27 +204,22 @@ def clear_definitions_cache(self, name) -> None: for k in self.proxy.pop(tail, []): definitions_cache.pop(k, None) - def last_changed(self, expr) -> int: + def has_changed(self, maximum, symbols): # timestamp for the most recently changed part of a given expression. - if isinstance(expr, Symbol): - symb = self.get_definition(expr.get_name(), only_if_exists=True) + for name in symbols: + symb = self.get_definition(name, only_if_exists=True) if symb is None: # symbol doesn't exist so it was never changed - return 0 - try: - return symb.changed - except AttributeError: - # must be system symbol - symb.changed = 0 - return 0 - result = 0 - head = expr.get_head() - head_changed = self.last_changed(head) - result = max(result, head_changed) - for leaf in expr.get_leaves(): - leaf_changed = self.last_changed(leaf) - result = max(result, leaf_changed) - return result + pass + else: + changed = getattr(symb, 'changed', None) + if changed is None: + # must be system symbol + symb.changed = 0 + elif changed > maximum: + return True + + return False def get_current_context(self): # It's crucial to specify System` in this get_ownvalue() call, diff --git a/mathics/core/expression.py b/mathics/core/expression.py index 75c7663efe..34c2febe3f 100644 --- a/mathics/core/expression.py +++ b/mathics/core/expression.py @@ -10,6 +10,9 @@ import typing from typing import Any +from itertools import chain +from bisect import bisect_left + from mathics.core.numbers import get_type, dps, prec, min_prec, machine_precision from mathics.core.convert import sympy_symbol_prefix, SympyExpression @@ -73,9 +76,9 @@ def __init__(self, parent, position) -> None: def replace(self, new) -> None: if self.position == 0: - self.parent.head = new + self.parent.set_head(new) else: - self.parent.leaves[self.position - 1] = new + self.parent.set_leaf(self.position - 1, new) def __str__(self) -> str: return '%s[[%s]]' % (self.parent, self.position) @@ -130,6 +133,80 @@ def __ne__(self, other) -> bool: return self.get_sort_key() != other.get_sort_key() +# ExpressionCache keeps track of the following attributes for one Expression instance: + +# time: (1) the last time (in terms of Definitions.now) this expression was evaluated +# or (2) None, if the current expression has not yet been evaluatec (i.e. is new or +# changed). +# symbols: (1) a set of symbols occuring in this expression's head, its leaves' +# heads, any of its sub expressions' heads or as Symbol leaves somewhere (maybe deep +# down) in the expression tree start by this expressions' leaves, or (2) None, if no +# information on which symbols are contained in this expression is available +# sequences: (1) a list of leaf indices that indicate the position of all Sequence +# heads that are either in the leaf's head or any of the indicated leaf's sub +# expressions' heads, or (2) None, if no information is available. + +class ExpressionCache: + def __init__(self, time=None, symbols=None, sequences=None, copy=None): + if copy is not None: + time = time or copy.time + symbols = symbols or copy.symbols + sequences = sequences or copy.sequences + self.time = time + self.symbols = symbols + self.sequences = sequences + + def copy(self): + return ExpressionCache(self.time, self.symbols, self.sequences) + + def sliced(self, lower, upper): + # indicates that the Expression's leaves have been slices with + # the given indices. + + seq = self.sequences + + if seq: + a = bisect_left(seq, lower) # all(val >= i for val in seq[a:]) + b = bisect_left(seq, upper) # all(val >= j for val in seq[b:]) + new_sequences = tuple(x - lower for x in seq[a:b]) + elif seq is not None: + new_sequences = tuple() + else: + new_sequences = None + + return ExpressionCache(self.time, self.symbols, new_sequences) + + def reordered(self): + # indicates that the Expression's leaves have been reordered + # or reduced (i.e. that the leaves have changed, but that + # no new leaf instances were added). + + sequences = self.sequences + + # note that we keep sequences == [], since they are fine even + # after having reordered leaves. + if sequences: + sequences = None + + return ExpressionCache(None, self.symbols, sequences) + + @staticmethod + def union(expressions, evaluation): + definitions = evaluation.definitions + + for expr in expressions: + if expr.has_changed(definitions): + return None + + symbols = set.union( + *[expr._cache.symbols for expr in expressions]) + + return ExpressionCache( + definitions.now, + symbols, + None if 'System`Sequence' in symbols else tuple()) + + class BaseExpression(KeyComparable): options: Any pattern_sequence: bool @@ -141,16 +218,22 @@ def __new__(cls, *args, **kwargs): self.options = None self.pattern_sequence = False self.unformatted = self - self.last_evaluated = None + self._cache = None return self + def clear_cache(self): + self._cache = None + + def has_changed(self, definitions): + return True + def sequences(self): return None - def flatten_sequence(self) -> 'BaseExpression': + def flatten_sequence(self, evaluation) -> 'BaseExpression': return self - def flatten_pattern_sequence(self) -> 'BaseExpression': + def flatten_pattern_sequence(self, evaluation) -> 'BaseExpression': return self def get_attributes(self, definitions): @@ -498,12 +581,6 @@ def __cmp(self, other) -> int: return 0 -def _sequences(leaves): - for i, leaf in enumerate(leaves): - if leaf.get_head_name() == 'System`Sequence' or leaf.sequences(): - yield i - - class Expression(BaseExpression): head: 'Symbol' leaves: typing.List[Any] @@ -513,25 +590,82 @@ def __new__(cls, head, *leaves) -> 'Expression': self = super(Expression, cls).__new__(cls) if isinstance(head, str): head = Symbol(head) - self.head = head - self.leaves = [from_python(leaf) for leaf in leaves] + self._head = head + self._leaves = tuple(from_python(leaf) for leaf in leaves) self._sequences = None self._format_cache = None return self + @property + def head(self): + return self._head + + @head.setter + def head(self, value): + raise ValueError('Expression.head is write protected.') + + @property + def leaves(self): + return self._leaves + + @leaves.setter + def leaves(self, value): + raise ValueError('Expression.leaves is write protected.') + + def slice(self, head, py_slice, evaluation): + # faster equivalent to: Expression(head, *self.leaves[py_slice]) + return structure(head, self, evaluation).slice(self, py_slice) + + def filter(self, head, cond, evaluation): + # faster equivalent to: Expression(head, [leaf in self.leaves if cond(leaf)]) + return structure(head, self, evaluation).filter(self, cond) + + def restructure(self, head, leaves, evaluation, structure_cache=None, deps=None): + # faster equivalent to: Expression(head, *leaves) + + # the caller guarantees that _all_ elements in leaves are either from + # self.leaves (or its sub trees) or from one of the expression given + # in the tuple "deps" (or its sub trees). + + # if this method is called repeatedly, and the caller guarantees + # that no definitions change between subsequent calls, then heads_cache + # may be passed an initially empty dict to speed up calls. + + if deps is None: + deps = self + s = structure(head, deps, evaluation, structure_cache=structure_cache) + return s(list(leaves)) + + def _no_symbol(self, symbol_name): + # if this return True, it's safe to say that self.leaves or its + # sub leaves contain no Symbol with symbol_name. if this returns + # False, such a Symbol might or might not exist. + + cache = self._cache + if cache is None: + return False + + symbols = cache.symbols + if symbols is not None and symbol_name not in symbols: + return True + else: + return False + def sequences(self): - seq = self._sequences - if seq is None: - seq = list(_sequences(self.leaves)) - self._sequences = seq - return seq + cache = self._cache + if cache: + seq = cache.sequences + if seq is not None: + return seq - def _flatten_sequence(self, sequence) -> 'Expression': + return self._rebuild_cache().sequences + + def _flatten_sequence(self, sequence, evaluation) -> "Expression": indices = self.sequences() if not indices: return self - leaves = self.leaves + leaves = self._leaves flattened = [] extend = flattened.extend @@ -543,77 +677,149 @@ def _flatten_sequence(self, sequence) -> 'Expression': k = i + 1 extend(leaves[k:]) - return Expression(self.head, *flattened) + return self.restructure(self._head, flattened, evaluation) - def flatten_sequence(self): + def flatten_sequence(self, evaluation): def sequence(leaf): if leaf.get_head_name() == 'System`Sequence': - return leaf.leaves + return leaf._leaves else: return [leaf] - return self._flatten_sequence(sequence) + return self._flatten_sequence(sequence, evaluation) - def flatten_pattern_sequence(self): + def flatten_pattern_sequence(self, evaluation): def sequence(leaf): - flattened = leaf.flatten_pattern_sequence() + flattened = leaf.flatten_pattern_sequence(evaluation) if leaf.get_head_name() == 'System`Sequence' and leaf.pattern_sequence: - return flattened.leaves + return flattened._leaves else: return [flattened] - expr = self._flatten_sequence(sequence) + expr = self._flatten_sequence(sequence, evaluation) if hasattr(self, 'options'): expr.options = self.options return expr + def _rebuild_cache(self): + cache = self._cache + + if cache is None: + time = None + elif cache.symbols is None: + time = cache.time + elif cache.sequences is None: + time = cache.time + else: + return cache + + sym = set((self.get_head_name(),)) + seq = [] + + for i, leaf in enumerate(self._leaves): + if isinstance(leaf, Expression): + leaf_symbols = leaf._rebuild_cache().symbols + sym.update(leaf_symbols) + if 'System`Sequence' in leaf_symbols: + seq.append(i) + elif isinstance(leaf, Symbol): + sym.add(leaf.get_name()) + + cache = ExpressionCache(time, sym, seq) + self._cache = cache + return cache + + def has_changed(self, definitions): + cache = self._cache + + if cache is None: + return True + + time = cache.time + + if time is None: + return True + + if cache.symbols is None: + cache = self._rebuild_cache() + + return definitions.has_changed(time, cache.symbols) + + def _timestamp_cache(self, evaluation): + self._cache = ExpressionCache(evaluation.definitions.now, copy=self._cache) + + + def copy(self, reevaluate=False) -> 'Expression': + expr = Expression(self._head.copy(reevaluate)) + expr._leaves = tuple(leaf.copy(reevaluate) for leaf in self._leaves) + if not reevaluate: + # rebuilding the cache in self speeds up large operations, e.g. + # First[Timing[Fold[#1+#2&, Range[750]]]] + expr._cache = self._rebuild_cache() + expr.options = self.options + expr.original = self + expr._sequences = self._sequences + expr._format_cache = self._format_cache + return expr + def do_format(self, evaluation, form): if self._format_cache is None: self._format_cache = {} last_evaluated, expr = self._format_cache.get(form, (None, None)) - if last_evaluated is not None and evaluation.definitions.last_changed(self) <= last_evaluated: - return expr - + if last_evaluated is not None and expr is not None: + symbolname = expr.get_name() + if symbolname != "" : + if not evaluation.definitions.has_changed(last_evaluated, (symbolname,)): + return expr expr = super(Expression, self).do_format(evaluation, form) self._format_cache[form] = (evaluation.definitions.now, expr) - return expr - def copy(self) -> 'Expression': - result = Expression( - self.head.copy(), *[leaf.copy() for leaf in self.leaves]) - result._sequences = self._sequences - result.options = self.options - result.original = self - # result.last_evaluated = self.last_evaluated - result._format_cache = self._format_cache - return result - def shallow_copy(self) -> 'Expression': # this is a minimal, shallow copy: head, leaves are shared with # the original, only the Expression instance is new. - expr = Expression(self.head) - expr.leaves = self.leaves - expr._sequences = self._sequences + expr = Expression(self._head) + expr._leaves = self._leaves + # rebuilding the cache in self speeds up large operations, e.g. + # First[Timing[Fold[#1+#2&, Range[750]]]] + expr._cache = self._rebuild_cache() expr.options = self.options - expr.last_evaluated = self.last_evaluated + # expr.last_evaluated = self.last_evaluated return expr def set_positions(self, position=None) -> None: self.position = position - self.head.set_positions(ExpressionPointer(self, 0)) - for index, leaf in enumerate(self.leaves): + self._head.set_positions(ExpressionPointer(self, 0)) + for index, leaf in enumerate(self._leaves): leaf.set_positions(ExpressionPointer(self, index + 1)) def get_head(self): - return self.head + return self._head + + def set_head(self, head): + self._head = head + self._cache = None def get_leaves(self): - return self.leaves + return self._leaves - def get_lookup_name(self) -> bool: - return self.head.get_lookup_name() + def get_mutable_leaves(self): # shallow, mutable copy of the leaves array + return list(self._leaves) + + def set_leaf(self, index, value): # leaves are removed, added or replaced + leaves = list(self._leaves) + leaves[index] = value + self._leaves = tuple(leaves) + self._cache = None + + def set_reordered_leaves(self, leaves): # same leaves, but in a different order + self._leaves = tuple(leaves) + if self._cache: + self._cache = self._cache.reordered() + + def get_lookup_name(self)-> bool: + return self._head.get_lookup_name() def has_form(self, heads, *leaf_counts): """ @@ -624,7 +830,7 @@ def has_form(self, heads, *leaf_counts): (n1, n2, ...): leaf count in {n1, n2, ...} """ - head_name = self.head.get_name() + head_name = self._head.get_name() if isinstance(heads, (tuple, list, set)): if head_name not in [ensure_context(h) for h in heads]: return False @@ -634,7 +840,7 @@ def has_form(self, heads, *leaf_counts): if not leaf_counts: return False if leaf_counts and leaf_counts[0] is not None: - count = len(self.leaves) + count = len(self._leaves) if count not in leaf_counts: if (len(leaf_counts) == 2 and # noqa leaf_counts[1] is None and count >= leaf_counts[0]): @@ -643,9 +849,11 @@ def has_form(self, heads, *leaf_counts): return False return True - def has_symbol(self, symbol_name) -> bool: - return self.head.has_symbol(symbol_name) or any( - leaf.has_symbol(symbol_name) for leaf in self.leaves) + def has_symbol(self, symbol_name)-> bool: + if self._no_symbol(symbol_name): + return False + return self._head.has_symbol(symbol_name) or any( + leaf.has_symbol(symbol_name) for leaf in self._leaves) def _as_sympy_function(self, **kwargs) -> sympy.Function: sym_args = [leaf.to_sympy(**kwargs) for leaf in self.leaves] @@ -666,8 +874,14 @@ def to_sympy(self, **kwargs): if 'converted_functions' in kwargs: functions = kwargs['converted_functions'] - if len(self.leaves) > 0 and self.get_head_name() in functions: - return self._as_sympy_function(**kwargs) + if len(self._leaves) > 0 and self.get_head_name() in functions: + sym_args = [leaf.to_sympy() for leaf in self._leaves] + if None in sym_args: + return None + func = sympy.Function(str( + sympy_symbol_prefix + self.get_head_name()))(*sym_args) + return func + lookup_name = self.get_lookup_name() builtin = mathics_to_sympy.get(lookup_name) @@ -696,11 +910,11 @@ def to_python(self, *args, **kwargs): if n_evaluation is not None: value = Expression('N', self).evaluate(n_evaluation) return value.to_python() - head_name = self.head.get_name() + head_name = self._head.get_name() if head_name == 'System`List': - return [leaf.to_python(*args, **kwargs) for leaf in self.leaves] - if head_name == 'System`DirectedInfinity' and len(self.leaves) == 1: - direction = self.leaves[0].get_int_value() + return [leaf.to_python(*args, **kwargs) for leaf in self._leaves] + if head_name == 'System`DirectedInfinity' and len(self._leaves) == 1: + direction = self._leaves[0].get_int_value() if direction == 1: return float('inf') if direction == -1: @@ -723,7 +937,7 @@ def get_sort_key(self, pattern_sort=False): 7: 0/1: 0 for Condition """ - name = self.head.get_name() + name = self._head.get_name() pattern = 0 if name == 'System`Blank': pattern = 1 @@ -732,42 +946,42 @@ def get_sort_key(self, pattern_sort=False): elif name == 'System`BlankNullSequence': pattern = 3 if pattern > 0: - if self.leaves: + if self._leaves: pattern += 10 else: pattern += 20 if pattern > 0: - return [2, pattern, 1, 1, 0, self.head.get_sort_key(True), - [leaf.get_sort_key(True) for leaf in self.leaves], 1] + return [2, pattern, 1, 1, 0, self._head.get_sort_key(True), + tuple(leaf.get_sort_key(True) for leaf in self._leaves), 1] if name == 'System`PatternTest': - if len(self.leaves) != 2: - return [3, 0, 0, 0, 0, self.head, self.leaves, 1] - sub = self.leaves[0].get_sort_key(True) + if len(self._leaves) != 2: + return [3, 0, 0, 0, 0, self._head, self._leaves, 1] + sub = self._leaves[0].get_sort_key(True) sub[2] = 0 return sub elif name == 'System`Condition': - if len(self.leaves) != 2: - return [3, 0, 0, 0, 0, self.head, self.leaves, 1] - sub = self.leaves[0].get_sort_key(True) + if len(self._leaves) != 2: + return [3, 0, 0, 0, 0, self._head, self._leaves, 1] + sub = self._leaves[0].get_sort_key(True) sub[7] = 0 return sub elif name == 'System`Pattern': - if len(self.leaves) != 2: - return [3, 0, 0, 0, 0, self.head, self.leaves, 1] - sub = self.leaves[1].get_sort_key(True) + if len(self._leaves) != 2: + return [3, 0, 0, 0, 0, self._head, self._leaves, 1] + sub = self._leaves[1].get_sort_key(True) sub[3] = 0 return sub elif name == 'System`Optional': - if len(self.leaves) not in (1, 2): - return [3, 0, 0, 0, 0, self.head, self.leaves, 1] - sub = self.leaves[0].get_sort_key(True) + if len(self._leaves) not in (1, 2): + return [3, 0, 0, 0, 0, self._head, self._leaves, 1] + sub = self._leaves[0].get_sort_key(True) sub[4] = 1 return sub elif name == 'System`Alternatives': min_key = [4] min = None - for leaf in self.leaves: + for leaf in self._leaves: key = leaf.get_sort_key(True) if key < min_key: min = leaf @@ -777,53 +991,53 @@ def get_sort_key(self, pattern_sort=False): return [2, 1] return min_key elif name == 'System`Verbatim': - if len(self.leaves) != 1: - return [3, 0, 0, 0, 0, self.head, self.leaves, 1] - return self.leaves[0].get_sort_key(True) + if len(self._leaves) != 1: + return [3, 0, 0, 0, 0, self._head, self._leaves, 1] + return self._leaves[0].get_sort_key(True) elif name == 'System`OptionsPattern': - return [2, 40, 0, 1, 1, 0, self.head, self.leaves, 1] + return [2, 40, 0, 1, 1, 0, self._head, self._leaves, 1] else: # Append [4] to leaves so that longer expressions have higher # precedence return [ - 2, 0, 1, 1, 0, self.head.get_sort_key(True), - [leaf.get_sort_key(True) for leaf in self.leaves] + [[4]], + 2, 0, 1, 1, 0, self._head.get_sort_key(True), + tuple(chain((leaf.get_sort_key(True) for leaf in self._leaves), ([4],))), 1] else: exps = {} - head = self.head.get_name() + head = self._head.get_name() if head == 'System`Times': - for leaf in self.leaves: + for leaf in self._leaves: name = leaf.get_name() if leaf.has_form('Power', 2): - var = leaf.leaves[0].get_name() - exp = leaf.leaves[1].round_to_float() + var = leaf._leaves[0].get_name() + exp = leaf._leaves[1].round_to_float() if var and exp is not None: exps[var] = exps.get(var, 0) + exp elif name: exps[name] = exps.get(name, 0) + 1 elif self.has_form('Power', 2): - var = self.leaves[0].get_name() - exp = self.leaves[1].round_to_float() + var = self._leaves[0].get_name() + exp = self._leaves[1].round_to_float() if var and exp is not None: exps[var] = exps.get(var, 0) + exp if exps: return [1 if self.is_numeric() else 2, 2, Monomial(exps), 1, - self.head, self.leaves, 1] + self._head, self._leaves, 1] else: - return [1 if self.is_numeric() else 2, 3, self.head, - self.leaves, 1] + return [1 if self.is_numeric() else 2, 3, self._head, + self._leaves, 1] def same(self, other) -> bool: if id(self) == id(other): return True if self.get_head_name() != other.get_head_name(): return False - if not self.head.same(other.get_head()): + if not self._head.same(other.get_head()): return False - if len(self.leaves) != len(other.get_leaves()): + if len(self._leaves) != len(other.get_leaves()): return False - for leaf, other in zip(self.leaves, other.get_leaves()): + for leaf, other in zip(self._leaves, other.get_leaves()): if not leaf.same(other): return False return True @@ -831,23 +1045,25 @@ def same(self, other) -> bool: def flatten(self, head, pattern_only=False, callback=None, level=None) -> 'Expression': if level is not None and level <= 0: return self + if self._no_symbol(head.get_name()): + return self sub_level = None if level is None else level - 1 do_flatten = False - for leaf in self.leaves: + for leaf in self._leaves: if leaf.get_head().same(head) and (not pattern_only or leaf.pattern_sequence): do_flatten = True break if do_flatten: new_leaves = [] - for leaf in self.leaves: + for leaf in self._leaves: if leaf.get_head().same(head) and (not pattern_only or leaf.pattern_sequence): new_leaf = leaf.flatten(head, pattern_only, callback, level=sub_level) if callback is not None: - callback(new_leaf.leaves, leaf) - new_leaves.extend(new_leaf.leaves) + callback(new_leaf._leaves, leaf) + new_leaves.extend(new_leaf._leaves) else: new_leaves.append(leaf) - return Expression(self.head, *new_leaves) + return Expression(self._head, *new_leaves) else: return self @@ -867,7 +1083,7 @@ def evaluate(self, evaluation) -> typing.Union['Expression', 'Symbol']: try: while reevaluate: # changed before last evaluated? - if expr.last_evaluated is not None and definitions.last_changed(expr) <= expr.last_evaluated: + if not expr.has_changed(definitions): break names.add(expr.get_lookup_name()) @@ -906,12 +1122,14 @@ def evaluate(self, evaluation) -> typing.Union['Expression', 'Symbol']: return expr def evaluate_next(self, evaluation) -> typing.Tuple['Expression', bool]: - head = self.head.evaluate(evaluation) + head = self._head.evaluate(evaluation) attributes = head.get_attributes(evaluation.definitions) - leaves = self.leaves[:] + leaves = self.get_mutable_leaves() def rest_range(indices): if 'System`HoldAllComplete' not in attributes: + if self._no_symbol('System`Evaluate'): + return for index in indices: leaf = leaves[index] if leaf.has_form('Evaluate', 1): @@ -936,44 +1154,48 @@ def eval_range(indices): eval_range(range(len(leaves))) # rest_range(range(0, 0)) - new = Expression(head, *leaves) + new = Expression(head) + new._leaves = tuple(leaves) if ('System`SequenceHold' not in attributes and # noqa 'System`HoldAllComplete' not in attributes): - new = new.flatten_sequence() - leaves = new.leaves + new = new.flatten_sequence(evaluation) + leaves = new._leaves for leaf in leaves: leaf.unevaluated = False if 'System`HoldAllComplete' not in attributes: - dirty_new = False + dirty_leaves = None for index, leaf in enumerate(leaves): if leaf.has_form('Unevaluated', 1): - leaves[index] = leaf.leaves[0] - leaves[index].unevaluated = True - dirty_new = True + if dirty_leaves is None: + dirty_leaves = list(leaves) + dirty_leaves[index] = leaf._leaves[0] + dirty_leaves[index].unevaluated = True - if dirty_new: - new = Expression(head, *leaves) + if dirty_leaves: + new = Expression(head) + new._leaves = tuple(dirty_leaves) + leaves = dirty_leaves def flatten_callback(new_leaves, old): for leaf in new_leaves: leaf.unevaluated = old.unevaluated if 'System`Flat' in attributes: - new = new.flatten(new.head, callback=flatten_callback) + new = new.flatten(new._head, callback=flatten_callback) if 'System`Orderless' in attributes: new.sort() - new.last_evaluated = evaluation.definitions.now + new._timestamp_cache(evaluation) if 'System`Listable' in attributes: done, threaded = new.thread(evaluation) if done: if threaded.same(new): - new.last_evaluated = evaluation.definitions.now + new._timestamp_cache(evaluation) return new, False else: return threaded, True @@ -1000,47 +1222,56 @@ def rules(): result = rule.apply(new, evaluation, fully=False) if result is not None: if result.same(new): - new.last_evaluated = evaluation.definitions.now + new._timestamp_cache(evaluation) return new, False else: return result, True + dirty_leaves = None + # Expression did not change, re-apply Unevaluated - for index, leaf in enumerate(new.leaves): + for index, leaf in enumerate(new._leaves): if leaf.unevaluated: - new.leaves[index] = Expression('Unevaluated', leaf) + if dirty_leaves is None: + dirty_leaves = list(new._leaves) + dirty_leaves[index] = Expression('Unevaluated', leaf) + + if dirty_leaves: + new = Expression(head) + new._leaves = tuple(dirty_leaves) new.unformatted = self.unformatted - new.last_evaluated = evaluation.definitions.now + new._timestamp_cache(evaluation) return new, False - def evaluate_leaves(self, evaluation) -> 'Expression': - leaves = [leaf.evaluate(evaluation) for leaf in self.leaves] - head = self.head.evaluate_leaves(evaluation) + + def evaluate_leaves(self, evaluation)-> 'Expression': + leaves = [leaf.evaluate(evaluation) for leaf in self._leaves] + head = self._head.evaluate_leaves(evaluation) return Expression(head, *leaves) def __str__(self) -> str: return '%s[%s]' % ( - self.head, ', '.join([str(leaf) for leaf in self.leaves])) + self._head, ', '.join([leaf.__str__() for leaf in self._leaves])) def __repr__(self) -> str: return '' % self def process_style_box(self, options): if self.has_form('StyleBox', 1, None): - rules = self.leaves[1:] + rules = self._leaves[1:] for rule in rules: if rule.has_form('Rule', 2): - name = rule.leaves[0].get_name() - value = rule.leaves[1] + name = rule._leaves[0].get_name() + value = rule._leaves[1] if name == 'System`ShowStringCharacters': value = value.is_true() options = options.copy() options['show_string_characters'] = value elif name == 'System`ImageSizeMultipliers': if value.has_form('List', 2): - m1 = value.leaves[0].round_to_float() - m2 = value.leaves[1].round_to_float() + m1 = value._leaves[0].round_to_float() + m2 = value._leaves[1].round_to_float() if m1 is not None and m2 is not None: options = options.copy() options['image_size_multipliers'] = (m1, m2) @@ -1054,21 +1285,21 @@ def boxes_to_text(self, **options) -> str: is_style, options = self.process_style_box(options) if is_style: - return self.leaves[0].boxes_to_text(**options) - head = self.head.get_name() + return self._leaves[0].boxes_to_text(**options) + head = self._head.get_name() box_construct = box_constructs.get(head) if box_construct is not None: try: - return box_construct.boxes_to_text(self.leaves, **options) + return box_construct.boxes_to_text(self._leaves, **options) except BoxConstructError: raise BoxError(self, 'text') if (self.has_form('RowBox', 1) and # nopep8 - self.leaves[0].has_form('List', None)): + self._leaves[0].has_form('List', None)): return ''.join([leaf.boxes_to_text(**options) - for leaf in self.leaves[0].leaves]) + for leaf in self._leaves[0]._leaves]) elif self.has_form('SuperscriptBox', 2): return '^'.join([leaf.boxes_to_text(**options) - for leaf in self.leaves]) + for leaf in self._leaves]) else: raise BoxError(self, 'text') @@ -1078,20 +1309,20 @@ def boxes_to_xml(self, **options) -> str: is_style, options = self.process_style_box(options) if is_style: - return self.leaves[0].boxes_to_xml(**options) - head = self.head.get_name() + return self._leaves[0].boxes_to_xml(**options) + head = self._head.get_name() box_construct = box_constructs.get(head) if box_construct is not None: try: - return box_construct.boxes_to_xml(self.leaves, **options) + return box_construct.boxes_to_xml(self._leaves, **options) except BoxConstructError: # raise # uncomment this to see what is going wrong in # constructing boxes raise BoxError(self, 'xml') - name = self.head.get_name() - if (name == 'System`RowBox' and len(self.leaves) == 1 and # nopep8 - self.leaves[0].get_head_name() == 'System`List'): - result: typing.List[str] = [] + name = self._head.get_name() + if (name == 'System`RowBox' and len(self._leaves) == 1 and # nopep8 + self._leaves[0].get_head_name() == 'System`List'): + result = [] inside_row = options.get('inside_row') # inside_list = options.get('inside_list') options = options.copy() @@ -1099,20 +1330,20 @@ def boxes_to_xml(self, **options) -> str: def is_list_interior(content): if (content.has_form('List', None) and all(leaf.get_string_value() == ',' - for leaf in content.leaves[1::2])): + for leaf in content._leaves[1::2])): return True return False is_list_row = False - if (len(self.leaves[0].leaves) == 3 and # nopep8 - self.leaves[0].leaves[0].get_string_value() == '{' and - self.leaves[0].leaves[2].get_string_value() == '}' and - self.leaves[0].leaves[1].has_form('RowBox', 1)): - content = self.leaves[0].leaves[1].leaves[0] + if (len(self._leaves[0]._leaves) == 3 and # nopep8 + self._leaves[0]._leaves[0].get_string_value() == '{' and + self._leaves[0]._leaves[2].get_string_value() == '}' and + self._leaves[0]._leaves[1].has_form('RowBox', 1)): + content = self._leaves[0]._leaves[1]._leaves[0] if is_list_interior(content): is_list_row = True - if not inside_row and is_list_interior(self.leaves[0]): + if not inside_row and is_list_interior(self._leaves[0]): is_list_row = True if is_list_row: @@ -1120,32 +1351,32 @@ def is_list_interior(content): else: options['inside_row'] = True - for leaf in self.leaves[0].get_leaves(): + for leaf in self._leaves[0].get_leaves(): result.append(leaf.boxes_to_xml(**options)) return '%s' % ' '.join(result) else: options = options.copy() options['inside_row'] = True - if name == 'System`SuperscriptBox' and len(self.leaves) == 2: + if name == 'System`SuperscriptBox' and len(self._leaves) == 2: return '%s %s' % ( - self.leaves[0].boxes_to_xml(**options), - self.leaves[1].boxes_to_xml(**options)) - if name == 'System`SubscriptBox' and len(self.leaves) == 2: + self._leaves[0].boxes_to_xml(**options), + self._leaves[1].boxes_to_xml(**options)) + if name == 'System`SubscriptBox' and len(self._leaves) == 2: return '%s %s' % ( - self.leaves[0].boxes_to_xml(**options), - self.leaves[1].boxes_to_xml(**options)) - if name == 'System`SubsuperscriptBox' and len(self.leaves) == 3: + self._leaves[0].boxes_to_xml(**options), + self._leaves[1].boxes_to_xml(**options)) + if name == 'System`SubsuperscriptBox' and len(self._leaves) == 3: return '%s %s %s' % ( - self.leaves[0].boxes_to_xml(**options), - self.leaves[1].boxes_to_xml(**options), - self.leaves[2].boxes_to_xml(**options)) - elif name == 'System`FractionBox' and len(self.leaves) == 2: + self._leaves[0].boxes_to_xml(**options), + self._leaves[1].boxes_to_xml(**options), + self._leaves[2].boxes_to_xml(**options)) + elif name == 'System`FractionBox' and len(self._leaves) == 2: return '%s %s' % ( - self.leaves[0].boxes_to_xml(**options), - self.leaves[1].boxes_to_xml(**options)) - elif name == 'System`SqrtBox' and len(self.leaves) == 1: + self._leaves[0].boxes_to_xml(**options), + self._leaves[1].boxes_to_xml(**options)) + elif name == 'System`SqrtBox' and len(self._leaves) == 1: return '%s' % ( - self.leaves[0].boxes_to_xml(**options)) + self._leaves[0].boxes_to_xml(**options)) else: raise BoxError(self, 'xml') @@ -1164,22 +1395,22 @@ def block(tex, only_subsup=False): is_style, options = self.process_style_box(options) if is_style: - return self.leaves[0].boxes_to_tex(**options) - head = self.head.get_name() + return self._leaves[0].boxes_to_tex(**options) + head = self._head.get_name() box_construct = box_constructs.get(head) if box_construct is not None: try: - return box_construct.boxes_to_tex(self.leaves, **options) + return box_construct.boxes_to_tex(self._leaves, **options) except BoxConstructError: raise BoxError(self, 'tex') - name = self.head.get_name() - if (name == 'System`RowBox' and len(self.leaves) == 1 and # nopep8 - self.leaves[0].get_head_name() == 'System`List'): + name = self._head.get_name() + if (name == 'System`RowBox' and len(self._leaves) == 1 and # nopep8 + self._leaves[0].get_head_name() == 'System`List'): return ''.join([leaf.boxes_to_tex(**options) - for leaf in self.leaves[0].get_leaves()]) - elif name == 'System`SuperscriptBox' and len(self.leaves) == 2: - tex1 = self.leaves[0].boxes_to_tex(**options) - sup_string = self.leaves[1].get_string_value() + for leaf in self._leaves[0].get_leaves()]) + elif name == 'System`SuperscriptBox' and len(self._leaves) == 2: + tex1 = self._leaves[0].boxes_to_tex(**options) + sup_string = self._leaves[1].get_string_value() if sup_string == '\u2032': return "%s'" % tex1 elif sup_string == '\u2032\u2032': @@ -1187,44 +1418,47 @@ def block(tex, only_subsup=False): else: return '%s^%s' % ( block(tex1, True), - block(self.leaves[1].boxes_to_tex(**options))) - elif name == 'System`SubscriptBox' and len(self.leaves) == 2: + block(self._leaves[1].boxes_to_tex(**options))) + elif name == 'System`SubscriptBox' and len(self._leaves) == 2: return '%s_%s' % ( - block(self.leaves[0].boxes_to_tex(**options), True), - block(self.leaves[1].boxes_to_tex(**options))) - elif name == 'System`SubsuperscriptBox' and len(self.leaves) == 3: + block(self._leaves[0].boxes_to_tex(**options), True), + block(self._leaves[1].boxes_to_tex(**options))) + elif name == 'System`SubsuperscriptBox' and len(self._leaves) == 3: return '%s_%s^%s' % ( - block(self.leaves[0].boxes_to_tex(**options), True), - block(self.leaves[1].boxes_to_tex(**options)), - block(self.leaves[2].boxes_to_tex(**options))) - elif name == 'System`FractionBox' and len(self.leaves) == 2: + block(self._leaves[0].boxes_to_tex(**options), True), + block(self._leaves[1].boxes_to_tex(**options)), + block(self._leaves[2].boxes_to_tex(**options))) + elif name == 'System`FractionBox' and len(self._leaves) == 2: return '\\frac{%s}{%s}' % ( - self.leaves[0].boxes_to_tex(**options), - self.leaves[1].boxes_to_tex(**options)) - elif name == 'System`SqrtBox' and len(self.leaves) == 1: - return '\\sqrt{%s}' % self.leaves[0].boxes_to_tex(**options) + self._leaves[0].boxes_to_tex(**options), + self._leaves[1].boxes_to_tex(**options)) + elif name == 'System`SqrtBox' and len(self._leaves) == 1: + return '\\sqrt{%s}' % self._leaves[0].boxes_to_tex(**options) else: raise BoxError(self, 'tex') def default_format(self, evaluation, form) -> str: - return '%s[%s]' % (self.head.default_format(evaluation, form), + return '%s[%s]' % (self._head.default_format(evaluation, form), ', '.join([leaf.default_format(evaluation, form) - for leaf in self.leaves])) + for leaf in self._leaves])) def sort(self, pattern=False): " Sort the leaves according to internal ordering. " - + leaves = list(self._leaves) if pattern: - self.leaves.sort(key=lambda e: e.get_sort_key(pattern_sort=True)) + leaves.sort(key=lambda e: e.get_sort_key(pattern_sort=True)) else: - self.leaves.sort() + leaves.sort() + self.set_reordered_leaves(leaves) def filter_leaves(self, head_name): # TODO: should use sorting head_name = ensure_context(head_name) - return [leaf for leaf in self.leaves - if leaf.get_head_name() == head_name] + if self._no_symbol(head_name): + return [] + else: + return [leaf for leaf in self._leaves if leaf.get_head_name() == head_name] def apply_rules(self, rules, evaluation, level=0, options=None): """for rule in rules: @@ -1242,16 +1476,16 @@ def apply_leaf(leaf): return new def descend(expr): - return Expression(expr.head, *[apply_leaf(leaf) for leaf in expr.leaves]) + return Expression(expr._head, *[apply_leaf(leaf) for leaf in expr._leaves]) if options is None: # default ReplaceAll mode; replace breadth first result, applied = super( Expression, self).apply_rules(rules, evaluation, level, options) if applied: return result, True - head, applied = self.head.apply_rules(rules, evaluation, level, options) + head, applied = self._head.apply_rules(rules, evaluation, level, options) new_applied[0] = applied - return descend(Expression(head, *self.leaves)), new_applied[0] + return descend(Expression(head, *self._leaves)), new_applied[0] else: # Replace mode; replace depth first expr = descend(self) expr, applied = super( @@ -1259,9 +1493,9 @@ def descend(expr): new_applied[0] = new_applied[0] or applied if not applied and options['heads']: # heads in Replace are treated at the level of the arguments, i.e. level + 1 - head, applied = expr.head.apply_rules(rules, evaluation, level + 1, options) + head, applied = expr._head.apply_rules(rules, evaluation, level + 1, options) new_applied[0] = new_applied[0] or applied - expr = Expression(head, *expr.leaves) + expr = Expression(head, *expr._leaves) return expr, new_applied[0] def replace_vars(self, vars, options=None, @@ -1269,93 +1503,70 @@ def replace_vars(self, vars, options=None, from mathics.builtin.scoping import get_scoping_vars if not in_scoping: - if (self.head.get_name() in ('System`Module', 'System`Block', 'System`With') and - len(self.leaves) > 0): # nopep8 + if (self._head.get_name() in ('System`Module', 'System`Block', 'System`With') and + len(self._leaves) > 0): # nopep8 - scoping_vars = set(name for name, new_def in get_scoping_vars(self.leaves[0])) + scoping_vars = set(name for name, new_def in get_scoping_vars(self._leaves[0])) """for var in new_vars: if var in scoping_vars: del new_vars[var]""" vars = {var: value for var, value in vars.items() if var not in scoping_vars} - leaves = self.leaves + leaves = self._leaves if in_function: - if (self.head.get_name() == 'System`Function' and - len(self.leaves) > 1 and - (self.leaves[0].has_form('List', None) or - self.leaves[0].get_name())): - if self.leaves[0].get_name(): - func_params = [self.leaves[0].get_name()] + if (self._head.get_name() == 'System`Function' and + len(self._leaves) > 1 and + (self._leaves[0].has_form('List', None) or + self._leaves[0].get_name())): + if self._leaves[0].get_name(): + func_params = [self._leaves[0].get_name()] else: func_params = [leaf.get_name() - for leaf in self.leaves[0].leaves] + for leaf in self._leaves[0]._leaves] if '' not in func_params: - body = self.leaves[1] + body = self._leaves[1] replacement = {name: Symbol(name + '$') for name in func_params} func_params = [Symbol(name + '$') for name in func_params] body = body.replace_vars(replacement, options, in_scoping) - leaves = [Expression('List', *func_params), body] + \ - self.leaves[2:] + leaves = chain([Expression('List', *func_params), body], self._leaves[2:]) if not vars: # might just be a symbol set via Set[] we looked up here return self.shallow_copy() return Expression( - self.head.replace_vars( + self._head.replace_vars( vars, options=options, in_scoping=in_scoping), *[leaf.replace_vars(vars, options=options, in_scoping=in_scoping) for leaf in leaves]) - def replace_slots(self, slots, evaluation) -> 'Expression': - if not isinstance(slots, dict): - # If the user passes an explicit association - if len(slots) == 2 and slots[1].has_form('Association', None, ): - rules = slots[1] - slots = {0: slots[0]} - - for rule in rules.leaves: - if rule.has_form('Rule', 2): - name, val = rule.leaves - slots[name] = val - else: - evaluation.message('Function', 'iassoc', rules) - - # If the users passes a list of values + def replace_slots(self, slots, evaluation): + if self._head.get_name() == 'System`Slot': + if len(self._leaves) != 1: + evaluation.message_args('Slot', len(self._leaves), 1) else: - slots = dict(enumerate(slots)) - - if self.has_form('Slot', 1): - slot = self.leaves[0] - int_slot = slot.get_int_value() - - if int_slot is not None: - slot = int_slot - - if slot not in slots: - evaluation.message('Function', 'slotn', slot) - return self - - return slots[slot] - elif self.has_form('SlotSequence', None, ): - if len(self.leaves) != 1: - evaluation.message_args('SlotSequence', len(self.leaves), 1) - - slot = self.leaves[0].get_int_value() - - if slot is None or slot < 1: - evaluation.error('Function', 'slot', self.leaves[0]) - - slots = [slots[i] for i in slots if isinstance(slot, int)] + slot = self._leaves[0].get_int_value() + if slot is None or slot < 0: + evaluation.message('Function', 'slot', self._leaves[0]) + elif slot > len(slots) - 1: + evaluation.message('Function', 'slotn', slot) + else: + return slots[int(slot)] + elif self._head.get_name() == 'System`SlotSequence': + if len(self._leaves) != 1: + evaluation.message_args('SlotSequence', len(self._leaves), 1) + else: + slot = self._leaves[0].get_int_value() + if slot is None or slot < 1: + evaluation.error('Function', 'slot', self._leaves[0]) return Expression('Sequence', *slots[slot:]) - - elif self.has_form('Function', 1): - # Do not replace Slots in nested Functions + elif (self._head.get_name() == 'System`Function' and + len(self._leaves) == 1): + # do not replace Slots in nested Functions return self - - return Expression(self.head.replace_slots(slots, evaluation), + return Expression(self._head.replace_slots(slots, evaluation), *[leaf.replace_slots(slots, evaluation) - for leaf in self.leaves]) + for leaf in self._leaves]) def thread(self, evaluation, head=None) -> typing.Tuple[bool, 'Expression']: if head is None: @@ -1363,17 +1574,17 @@ def thread(self, evaluation, head=None) -> typing.Tuple[bool, 'Expression']: items = [] dim = None - for leaf in self.leaves: + for leaf in self._leaves: if leaf.get_head().same(head): if dim is None: - dim = len(leaf.leaves) - items = [(items + [subleaf]) for subleaf in leaf.leaves] - elif len(leaf.leaves) != dim: + dim = len(leaf._leaves) + items = [(items + [innerleaf]) for innerleaf in leaf._leaves] + elif len(leaf._leaves) != dim: evaluation.message('Thread', 'tdlen') return True, self else: for index in range(dim): - items[index].append(leaf.leaves[index]) + items[index].append(leaf._leaves[index]) else: if dim is None: items.append(leaf) @@ -1383,28 +1594,28 @@ def thread(self, evaluation, head=None) -> typing.Tuple[bool, 'Expression']: if dim is None: return False, self else: - leaves = [Expression(self.head, *item) for item in items] + leaves = [Expression(self._head, *item) for item in items] return True, Expression(head, *leaves) def is_numeric(self) -> bool: - return (self.head.get_name() in system_symbols( + return (self._head.get_name() in system_symbols( 'Sqrt', 'Times', 'Plus', 'Subtract', 'Minus', 'Power', 'Abs', 'Divide', 'Sin') and - all(leaf.is_numeric() for leaf in self.leaves)) + all(leaf.is_numeric() for leaf in self._leaves)) # TODO: complete list of numeric functions, or access NumericFunction # attribute def numerify(self, evaluation) -> 'Expression': _prec = None - for leaf in self.leaves: + for leaf in self._leaves: if leaf.is_inexact(): leaf_prec = leaf.get_precision() if _prec is None or leaf_prec < _prec: _prec = leaf_prec if _prec is not None: - new_leaves = self.leaves[:] - for index in range(len(self.leaves)): - leaf = self.leaves[index] + new_leaves = self.get_mutable_leaves() + for index in range(len(new_leaves)): + leaf = new_leaves[index] # Don't "numerify" numbers: they should be numerified # automatically by the processing function, # and we don't want to lose exactness in e.g. 1.0+I. @@ -1413,29 +1624,29 @@ def numerify(self, evaluation) -> 'Expression': n_result = n_expr.evaluate(evaluation) if isinstance(n_result, Number): new_leaves[index] = n_result - return Expression(self.head, *new_leaves) + return Expression(self._head, *new_leaves) else: return self def get_atoms(self, include_heads=True): if include_heads: - atoms = self.head.get_atoms() + atoms = self._head.get_atoms() else: atoms = [] - for leaf in self.leaves: + for leaf in self._leaves: atoms.extend(leaf.get_atoms()) return atoms def __hash__(self): - return hash(('Expression', self.head) + tuple(self.leaves)) + return hash(('Expression', self._head) + tuple(self._leaves)) - def user_hash(self, update) -> None: - update(("%s>%d>" % (self.get_head_name(), len(self.leaves))).encode('utf8')) - for leaf in self.leaves: + def user_hash(self, update): + update(("%s>%d>" % (self.get_head_name(), len(self._leaves))).encode('utf8')) + for leaf in self._leaves: leaf.user_hash(update) def __getnewargs__(self): - return (self.head, self.leaves) + return (self._head, self._leaves) class Atom(BaseExpression): @@ -1473,7 +1684,8 @@ def replace_slots(self, slots, evaluation) -> 'Atom': def numerify(self, evaluation) -> 'Atom': return self - def copy(self) -> 'Atom': + + def copy(self, reevaluate=False) -> 'Atom': result = self.do_copy() result.original = self return result @@ -2390,3 +2602,182 @@ def print_parenthesizes(precedence, outer_precedence=None, return (outer_precedence is not None and ( outer_precedence > precedence or ( outer_precedence == precedence and parenthesize_when_equal))) + + +def _is_neutral_symbol(symbol_name, cache, evaluation): + # a symbol is neutral if it does not invoke any rules, but is sure to make its Expression stay + # the way it is (e.g. List[1, 2, 3] will always stay List[1, 2, 3], so long as nobody defines + # a rule on this). + + if cache: + r = cache.get(symbol_name) + if r is not None: + return r + + definitions = evaluation.definitions + + definition = definitions.get_definition(symbol_name, only_if_exists=True) + if definition is None: + r = True + else: + r = all(len(definition.get_values_list(x)) == 0 for x in ('up', 'sub', 'down', 'own')) + + if cache: + cache[symbol_name] = r + + return r + + +def _is_neutral_head(head, cache, evaluation): + if not isinstance(head, Symbol): + return False + + return _is_neutral_symbol(head.get_name(), cache, evaluation) + + +# Structure helps implementations make the ExpressionCache not invalidate across simple commands +# such as Take[], Most[], etc. without this, constant reevaluation of lists happens, which results +# in quadratic runtimes for command like Fold[#1+#2&, Range[x]]. + +# A good performance test case for Structure: x = Range[50000]; First[Timing[Partition[x, 15, 1]]] + + +class Structure(object): + def __call__(self, leaves): + # create an Expression with the given list "leaves" as leaves. + # NOTE: the caller guarantees that "leaves" only contains items that are from "origins". + raise NotImplementedError + + def filter(self, expr, cond): + # create an Expression with a subset of "expr".leaves (picked out by the filter "cond"). + # NOTE: the caller guarantees that "expr" is from "origins". + raise NotImplementedError + + def slice(self, expr, py_slice): + # create an Expression, using the given slice of "expr".leaves as leaves. + # NOTE: the caller guarantees that "expr" is from "origins". + raise NotImplementedError + + +# UnlinkedStructure produces Expressions that are not linked to "origins" in terms of cache. +# This produces the same thing as doing Expression(head, *leaves). + +class UnlinkedStructure(Structure): + def __init__(self, head): + self._head = head + self._cache = None + + def __call__(self, leaves): + expr = Expression(self._head) + expr._leaves = tuple(leaves) + return expr + + def filter(self, expr, cond): + return self([leaf for leaf in expr._leaves if cond(leaf)]) + + def slice(self, expr, py_slice): + leaves = expr._leaves + lower, upper, step = py_slice.indices(len(leaves)) + if step != 1: + raise ValueError('Structure.slice only supports slice steps of 1') + return self(leaves[lower:upper]) + + +# LinkedStructure produces Expressions that are linked to "origins" in terms of cache. This +# carries over information from the cache of the originating Expressions into the Expressions +# that are newly created. + +class LinkedStructure(Structure): + def __init__(self, head, cache): + self._head = head + self._cache = cache + + def __call__(self, leaves): + expr = Expression(self._head) + expr._leaves = tuple(leaves) + expr._cache = self._cache.reordered() + return expr + + def filter(self, expr, cond): + return self([leaf for leaf in expr._leaves if cond(leaf)]) + + def slice(self, expr, py_slice): + leaves = expr._leaves + lower, upper, step = py_slice.indices(len(leaves)) + if step != 1: + raise ValueError('Structure.slice only supports slice steps of 1') + + new = Expression(self._head) + new._leaves = tuple(leaves[lower:upper]) + if expr._cache: + new._cache = expr._cache.sliced(lower, upper) + + return new + + +def structure(head, origins, evaluation, structure_cache=None): + # creates a Structure for building Expressions with head "head" and leaves + # originating (exlusively) from "origins" (leaves are passed into the functions + # of Structure further down). + + # "origins" may either be an Expression (i.e. all leaves must originate from that + # expression), a Structure (all leaves passed in this "self" Structure must be + # manufactured using that Structure), or a list of Expressions (i.e. all leaves + # must originate from one of the listed Expressions). + + if isinstance(head, (str,)): + head = Symbol(head) + + if isinstance(origins, (Expression, Structure)): + cache = origins._cache + if cache is not None and not _is_neutral_head(head, structure_cache, evaluation): + cache = None + elif isinstance(origins, (list, tuple)): + if _is_neutral_head(head, structure_cache, evaluation): + cache = ExpressionCache.union(origins, evaluation) + else: + cache = None + else: + raise ValueError('expected Expression, Structure, tuple or list as orig param') + + if cache is None: + return UnlinkedStructure(head) + else: + return LinkedStructure(head, cache) + + +def atom_list_constructor(evaluation, head, *atom_names): + # if we encounter an Expression that consists wholly of atoms and those atoms (and the + # expression's head) have no rules associated with them, we can speed up evaluation. + + # note that you may use a constructor constructed via atom_list_constructor() only as + # long as the evaluation's Definitions are guaranteed to not change. + + if not _is_neutral_head(head, None, evaluation) or any(not atom for atom in atom_names): + optimize = False + else: + full_atom_names = [ensure_context(atom) for atom in atom_names] + + if not all(_is_neutral_symbol(atom, None, evaluation) for atom in full_atom_names): + optimize = False + else: + optimize = True + + if optimize: + def construct(leaves): + expr = Expression(head) + expr._leaves = list(leaves) + sym = set(chain([head.get_name()], full_atom_names)) + expr._cache = ExpressionCache(evaluation.definitions.now, sym, None) + return expr + else: + def construct(leaves): + expr = Expression(head) + expr._leaves = list(leaves) + return expr + + return construct + + +def string_list(head, leaves, evaluation): + return atom_list_constructor(evaluation, head, 'String')(leaves) diff --git a/mathics/core/pattern.py b/mathics/core/pattern.py index b6c914557e..b4e5d0bbf0 100644 --- a/mathics/core/pattern.py +++ b/mathics/core/pattern.py @@ -9,6 +9,8 @@ from mathics.core.expression import (Expression, system_symbols, ensure_context) from mathics.core.util import subsets, subranges, permutations +from itertools import chain + # from mathics.core.pattern_nocython import ( # StopGenerator #, Pattern #, ExpressionPattern) @@ -486,11 +488,11 @@ def leaf_yield(next_vars, next_rest): if next_rest is None: yield_func( next_vars, - (rest_expression[0] + items_rest[0], [])) + (list(chain(rest_expression[0], items_rest[0])), [])) else: yield_func( next_vars, - (rest_expression[0] + items_rest[0], next_rest[1])) + (list(chain(rest_expression[0], items_rest[0])), next_rest[1])) def match_yield(new_vars, _): if rest_leaves: diff --git a/mathics/core/rules.py b/mathics/core/rules.py index 9b40c564f8..7534c16ef1 100644 --- a/mathics/core/rules.py +++ b/mathics/core/rules.py @@ -7,6 +7,8 @@ from mathics.core.pattern import Pattern, StopGenerator from mathics.core.util import function_arguments +from itertools import chain + class StopGenerator_BaseRule(StopGenerator): pass @@ -39,13 +41,13 @@ def yield_match(vars, rest): if new_expression is None: new_expression = expression if rest[0] or rest[1]: - result = Expression(expression.get_head(), *( - rest[0] + [new_expression] + rest[1])) + result = Expression(expression.get_head(), *list( + chain(rest[0], [new_expression], rest[1]))) else: result = new_expression # Flatten out sequences (important for Rule itself!) - result = result.flatten_pattern_sequence() + result = result.flatten_pattern_sequence(evaluation) if return_list: result_list.append(result) # count += 1 @@ -86,16 +88,15 @@ def do_replace(self, expression, vars, options, evaluation): # options' values. this is achieved through Expression.evaluate(), which then triggers OptionValue.apply, # which in turn consults evaluation.options to return an option value. - # in order to get there, our expression 'new' (or parts of it) must have last_evaluated None, since this - # would make Expression.evaluate() quit early. doing a clean deep copy here, will reset - # Expression.last_evaluated for all nodes in the tree. + # in order to get there, we copy 'new' using copy(reevaluate=True), as this will ensure that the whole thing + # will get reevaluated. # if the expression contains OptionValue[] patterns, but options is empty here, we don't need to act, as the # expression won't change in that case. the Expression.options would be None anyway, so OptionValue.apply # would just return the unchanged expression (which is what we have already). if options: - new = new.copy() + new = new.copy(reevaluate=True) return new diff --git a/mathics/core/util.py b/mathics/core/util.py index c6c4dcf5f9..b133aaf14c 100644 --- a/mathics/core/util.py +++ b/mathics/core/util.py @@ -5,6 +5,7 @@ import re import sys +from itertools import chain FORMAT_RE = re.compile(r'\`(\d*)\`') @@ -70,10 +71,10 @@ def decide(chosen, not_chosen, rest, count): if count < 0 or len(rest) < count: return if count == 0: - yield chosen, not_chosen + rest + yield chosen, list(chain(not_chosen, rest)) elif len(rest) == count: if included is None or all(item in included for item in rest): - yield chosen + rest, not_chosen + yield list(chain(chosen, rest)), not_chosen elif rest: item = rest[0] if included is None or item in included: diff --git a/mathics/doc/utils.py b/mathics/doc/utils.py index 07d3ced5a0..2363a96589 100644 --- a/mathics/doc/utils.py +++ b/mathics/doc/utils.py @@ -5,7 +5,11 @@ import unicodedata from django.template.defaultfilters import register, stringfilter -from django.utils.functional import allow_lazy +try: + from django.utils.functional import keep_lazy as allow_lazy +except: + from django.utils.functional import allow_lazy + from django.utils.safestring import mark_safe diff --git a/mathics/web/models.py b/mathics/web/models.py index 06ac198d88..a2c33a96a6 100644 --- a/mathics/web/models.py +++ b/mathics/web/models.py @@ -54,7 +54,7 @@ class Query(models.Model): class Worksheet(models.Model): user = models.ForeignKey(User, related_name='worksheets', - null=True) + null=True, on_delete=None) name = models.CharField(max_length=30) content = models.TextField() diff --git a/pymathics/natlang/natlang.py b/pymathics/natlang/natlang.py index b44d54b5ac..980643dc49 100644 --- a/pymathics/natlang/natlang.py +++ b/pymathics/natlang/natlang.py @@ -45,7 +45,7 @@ from mathics.builtin.randomnumbers import RandomEnv from mathics.builtin.codetables import iso639_3 from mathics.builtin.strings import to_regex, anchor_pattern -from mathics.core.expression import Expression, String, Integer, Real, Symbol, strip_context +from mathics.core.expression import Expression, String, Integer, Real, Symbol, strip_context, string_list import os import re @@ -323,15 +323,15 @@ def apply(self, text, evaluation, options): doc = self._nlp(text.get_string_value(), evaluation, options) if doc: punctuation = spacy.parts_of_speech.PUNCT - return Expression('List', *[String(word.text) for word in doc if word.pos != punctuation]) + return string_list('List', [String(word.text) for word in doc if word.pos != punctuation], evaluation) def apply_n(self, text, n, evaluation, options): 'TextWords[text_String, n_Integer, OptionsPattern[%(name)s]]' doc = self._nlp(text.get_string_value(), evaluation, options) if doc: punctuation = spacy.parts_of_speech.PUNCT - return Expression('List', *itertools.islice( - (String(word.text) for word in doc if word.pos != punctuation), n.get_int_value())) + return string_list('List', itertools.islice( + (String(word.text) for word in doc if word.pos != punctuation), n.get_int_value()), evaluation) class TextSentences(_SpacyBuiltin): @@ -357,14 +357,14 @@ def apply(self, text, evaluation, options): 'TextSentences[text_String, OptionsPattern[%(name)s]]' doc = self._nlp(text.get_string_value(), evaluation, options) if doc: - return Expression('List', *[String(sent.text) for sent in doc.sents]) + return string_list('List', [String(sent.text) for sent in doc.sents], evaluation) def apply_n(self, text, n, evaluation, options): 'TextSentences[text_String, n_Integer, OptionsPattern[%(name)s]]' doc = self._nlp(text.get_string_value(), evaluation, options) if doc: - return Expression('List', *itertools.islice( - (String(sent.text) for sent in doc.sents), n.get_int_value())) + return string_list('List', itertools.islice( + (String(sent.text) for sent in doc.sents), n.get_int_value()), evaluation) class DeleteStopwords(_SpacyBuiltin): @@ -391,10 +391,10 @@ def filter_words(words): for w in words: s = w.get_string_value() if not s: - yield s + yield String(s) elif not is_stop(s): - yield s - return Expression('List', *filter_words(l.leaves)) + yield String(s) + return string_list('List', filter_words(l.leaves), evaluation) def apply_string(self, s, evaluation, options): 'DeleteStopwords[s_String, OptionsPattern[%(name)s]]'