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]]'