1# cython: language_level=3
2# -*- coding: utf-8 -*-
3
4import sympy
5import mpmath
6import math
7import re
8
9import typing
10from typing import Any, Optional
11from itertools import chain
12from bisect import bisect_left
13from functools import lru_cache
14
15
16from mathics.core.numbers import get_type, dps, prec, min_prec, machine_precision
17from mathics.core.convert import sympy_symbol_prefix, SympyExpression
18import base64
19
20# Imperical number that seems to work.
21# We have to be able to match mpmath values with sympy values
22COMPARE_PREC = 50
23
24
25def fully_qualified_symbol_name(name) -> bool:
26    return (
27        isinstance(name, str)
28        and "`" in name
29        and not name.startswith("`")
30        and not name.endswith("`")
31        and "``" not in name
32    )
33
34
35def valid_context_name(ctx, allow_initial_backquote=False) -> bool:
36    return (
37        isinstance(ctx, str)
38        and ctx.endswith("`")
39        and "``" not in ctx
40        and (allow_initial_backquote or not ctx.startswith("`"))
41    )
42
43
44def ensure_context(name, context="System`") -> str:
45    assert isinstance(name, str)
46    assert name != ""
47    if "`" in name:
48        # Symbol has a context mark -> it came from the parser
49        assert fully_qualified_symbol_name(name)
50        return name
51    # Symbol came from Python code doing something like
52    # Expression('Plus', ...) -> use System` or more generally
53    # context + name
54    return context + name
55
56
57def strip_context(name) -> str:
58    if "`" in name:
59        return name[name.rindex("`") + 1 :]
60    return name
61
62
63# system_symbols('A', 'B', ...) -> ['System`A', 'System`B', ...]
64def system_symbols(*symbols) -> typing.List[str]:
65    return [ensure_context(s) for s in symbols]
66
67
68# system_symbols_dict({'SomeSymbol': ...}) -> {'System`SomeSymbol': ...}
69def system_symbols_dict(d):
70    return {ensure_context(k): v for k, v in d.items()}
71
72
73class BoxError(Exception):
74    def __init__(self, box, form) -> None:
75        super().__init__("Box %s cannot be formatted as %s" % (box, form))
76        self.box = box
77        self.form = form
78
79
80class ExpressionPointer(object):
81    def __init__(self, parent, position) -> None:
82        self.parent = parent
83        self.position = position
84
85    def replace(self, new) -> None:
86        if self.position == 0:
87            self.parent.set_head(new)
88        else:
89            self.parent.set_leaf(self.position - 1, new)
90
91    def __str__(self) -> str:
92        return "%s[[%s]]" % (self.parent, self.position)
93
94
95def from_python(arg):
96    """Converts a Python expression into a Mathics expression.
97
98    TODO: I think there are number of subtleties to be explained here.
99    In particular, the expression might beeen the result of evaluation
100    a sympy expression which contains sympy symbols.
101
102    If the end result is to go back into Mathics for further
103    evaluation, then probably no problem.  However if the end result
104    is produce say a Python string, then at a minimum we may want to
105    convert backtick (context) symbols into some Python identifier
106    symbol like underscore.
107    """
108    from mathics.builtin.base import BoxConstruct
109
110    number_type = get_type(arg)
111    if arg is None:
112        return SymbolNull
113    if isinstance(arg, bool):
114        return SymbolTrue if arg else SymbolFalse
115    if isinstance(arg, int) or number_type == "z":
116        return Integer(arg)
117    elif isinstance(arg, float) or number_type == "f":
118        return Real(arg)
119    elif number_type == "q":
120        return Rational(arg)
121    elif isinstance(arg, complex):
122        return Complex(Real(arg.real), Real(arg.imag))
123    elif number_type == "c":
124        return Complex(arg.real, arg.imag)
125    elif isinstance(arg, str):
126        return String(arg)
127        # if arg[0] == arg[-1] == '"':
128        #     return String(arg[1:-1])
129        # else:
130        #     return Symbol(arg)
131    elif isinstance(arg, dict):
132        entries = [
133            Expression("Rule", from_python(key), from_python(arg[key]),) for key in arg
134        ]
135        return Expression(SymbolList, *entries)
136    elif isinstance(arg, BaseExpression):
137        return arg
138    elif isinstance(arg, BoxConstruct):
139        return arg
140    elif isinstance(arg, list) or isinstance(arg, tuple):
141        return Expression(SymbolList, *[from_python(leaf) for leaf in arg])
142    elif isinstance(arg, bytearray) or isinstance(arg, bytes):
143        return Expression(SymbolByteArray, ByteArrayAtom(arg))
144    else:
145        raise NotImplementedError
146
147
148class KeyComparable(object):
149    def get_sort_key(self):
150        raise NotImplementedError
151
152    def __lt__(self, other) -> bool:
153        return self.get_sort_key() < other.get_sort_key()
154
155    def __gt__(self, other) -> bool:
156        return self.get_sort_key() > other.get_sort_key()
157
158    def __le__(self, other) -> bool:
159        return self.get_sort_key() <= other.get_sort_key()
160
161    def __ge__(self, other) -> bool:
162        return self.get_sort_key() >= other.get_sort_key()
163
164    def __eq__(self, other) -> bool:
165        return (
166            hasattr(other, "get_sort_key")
167            and self.get_sort_key() == other.get_sort_key()
168        )
169
170    def __ne__(self, other) -> bool:
171        return (
172            not hasattr(other, "get_sort_key")
173        ) or self.get_sort_key() != other.get_sort_key()
174
175
176# ExpressionCache keeps track of the following attributes for one Expression instance:
177
178# time: (1) the last time (in terms of Definitions.now) this expression was evaluated
179#   or (2) None, if the current expression has not yet been evaluatec (i.e. is new or
180#   changed).
181# symbols: (1) a set of symbols occuring in this expression's head, its leaves'
182#   heads, any of its sub expressions' heads or as Symbol leaves somewhere (maybe deep
183#   down) in the expression tree start by this expressions' leaves, or (2) None, if no
184#   information on which symbols are contained in this expression is available
185# sequences: (1) a list of leaf indices that indicate the position of all Sequence
186#   heads that are either in the leaf's head or any of the indicated leaf's sub
187#   expressions' heads, or (2) None, if no information is available.
188
189
190class ExpressionCache:
191    def __init__(self, time=None, symbols=None, sequences=None, copy=None):
192        if copy is not None:
193            time = time or copy.time
194            symbols = symbols or copy.symbols
195            sequences = sequences or copy.sequences
196        self.time = time
197        self.symbols = symbols
198        self.sequences = sequences
199
200    def copy(self):
201        return ExpressionCache(self.time, self.symbols, self.sequences)
202
203    def sliced(self, lower, upper):
204        # indicates that the Expression's leaves have been slices with
205        # the given indices.
206
207        seq = self.sequences
208
209        if seq:
210            a = bisect_left(seq, lower)  # all(val >= i for val in seq[a:])
211            b = bisect_left(seq, upper)  # all(val >= j for val in seq[b:])
212            new_sequences = tuple(x - lower for x in seq[a:b])
213        elif seq is not None:
214            new_sequences = tuple()
215        else:
216            new_sequences = None
217
218        return ExpressionCache(self.time, self.symbols, new_sequences)
219
220    def reordered(self):
221        # indicates that the Expression's leaves have been reordered
222        # or reduced (i.e. that the leaves have changed, but that
223        # no new leaf instances were added).
224
225        sequences = self.sequences
226
227        # note that we keep sequences == [], since they are fine even
228        # after having reordered leaves.
229        if sequences:
230            sequences = None
231
232        return ExpressionCache(None, self.symbols, sequences)
233
234    @staticmethod
235    def union(expressions, evaluation):
236        definitions = evaluation.definitions
237
238        for expr in expressions:
239            if expr.has_changed(definitions):
240                return None
241
242        symbols = set.union(*[expr._cache.symbols for expr in expressions])
243
244        return ExpressionCache(
245            definitions.now, symbols, None if "System`Sequence" in symbols else tuple()
246        )
247
248
249class BaseExpression(KeyComparable):
250    options: Any
251    pattern_sequence: bool
252    unformatted: Any
253    last_evaluated: Any
254
255    def __new__(cls, *args, **kwargs):
256        self = object.__new__(cls)
257        self.options = None
258        self.pattern_sequence = False
259        self.unformatted = self
260        self._cache = None
261        return self
262
263    def clear_cache(self):
264        self._cache = None
265
266    def equal2(self, rhs: Any) -> Optional[bool]:
267        """Mathics two-argument Equal (==)
268        returns True if self and rhs are identical.
269        """
270        if self.sameQ(rhs):
271            return True
272
273        # If the types are the same then we'll use the classes definition of == (or __eq__).
274        # Superclasses which need to specialized this behavior should redefine equal2()
275        #
276        # I would use `is` instead `==` here, to compare classes.
277        if type(self) is type(rhs):
278            return self == rhs
279        return None
280
281    def has_changed(self, definitions):
282        return True
283
284    def sequences(self):
285        return None
286
287    def flatten_sequence(self, evaluation) -> "BaseExpression":
288        return self
289
290    def flatten_pattern_sequence(self, evaluation) -> "BaseExpression":
291        return self
292
293    def get_attributes(self, definitions):
294        return set()
295
296    def evaluate_next(self, evaluation):
297        return self.evaluate(evaluation), False
298
299    def evaluate(self, evaluation) -> "BaseExpression":
300        evaluation.check_stopped()
301        return self
302
303    def get_atoms(self, include_heads=True):
304        return []
305
306    def get_name(self):
307        " Returns symbol's name if Symbol instance "
308
309        return ""
310
311    def is_symbol(self) -> bool:
312        return False
313
314    def is_machine_precision(self) -> bool:
315        return False
316
317    def get_lookup_name(self):
318        " Returns symbol name of leftmost head "
319
320        return self.get_name()
321
322    def get_head(self):
323        return None
324
325    def get_head_name(self):
326        return self.get_head().get_name()
327
328    def get_leaves(self):
329        return []
330
331    def get_int_value(self):
332        return None
333
334    def get_float_value(self, permit_complex=False):
335        return None
336
337    def get_string_value(self):
338        return None
339
340    def is_atom(self) -> bool:
341        return False
342
343    def is_true(self) -> bool:
344        return False
345
346    def is_numeric(self) -> bool:
347        # used by NumericQ and expression ordering
348        return False
349
350    def has_form(self, heads, *leaf_counts):
351        return False
352
353    def flatten(self, head, pattern_only=False, callback=None) -> "BaseExpression":
354        return self
355
356    def __hash__(self):
357        """
358        To allow usage of expression as dictionary keys,
359        as in Expression.get_pre_choices
360        """
361        raise NotImplementedError
362
363    def user_hash(self, update) -> None:
364        # whereas __hash__ is for internal Mathics purposes like using Expressions as dictionary keys and fast
365        # comparison of elements, user_hash is called for Hash[]. user_hash should strive to give stable results
366        # across versions, whereas __hash__ must not. user_hash should try to hash all the data available, whereas
367        # __hash__ might only hash a sample of the data available.
368        raise NotImplementedError
369
370    def sameQ(self, rhs) -> bool:
371        """Mathics SameQ"""
372        return id(self) == id(rhs)
373
374    def get_sequence(self):
375        if self.get_head().get_name() == "System`Sequence":
376            return self.leaves
377        else:
378            return [self]
379
380    def evaluate_leaves(self, evaluation) -> "BaseExpression":
381        return self
382
383    def apply_rules(
384        self, rules, evaluation, level=0, options=None
385    ) -> typing.Tuple["BaseExpression", bool]:
386        if options:
387            l1, l2 = options["levelspec"]
388            if level < l1:
389                return self, False
390            elif l2 is not None and level > l2:
391                return self, False
392
393        for rule in rules:
394            result = rule.apply(self, evaluation, fully=False)
395            if result is not None:
396                return result, True
397        return self, False
398
399    def do_format(self, evaluation, form):
400        """
401        Applies formats associated to the expression and removes
402        superfluous enclosing formats.
403        """
404        formats = system_symbols(
405            "InputForm",
406            "OutputForm",
407            "StandardForm",
408            "FullForm",
409            "TraditionalForm",
410            "TeXForm",
411            "MathMLForm",
412        )
413
414        evaluation.inc_recursion_depth()
415        try:
416            expr = self
417            head = self.get_head_name()
418            leaves = self.get_leaves()
419            include_form = False
420            # If the expression is enclosed by a Format
421            # takes the form from the expression and
422            # removes the format from the expression.
423            if head in formats and len(leaves) == 1:
424                expr = leaves[0]
425                if not (form == "System`OutputForm" and head == "System`StandardForm"):
426                    form = head
427                    include_form = True
428            unformatted = expr
429            # If form is Fullform, return it without changes
430            if form == "System`FullForm":
431                if include_form:
432                    expr = Expression(form, expr)
433                    expr.unformatted = unformatted
434                return expr
435
436            # Repeated and RepeatedNull confuse the formatter,
437            # so we need to hardlink their format rules:
438            if head == "System`Repeated":
439                if len(leaves) == 1:
440                    return Expression(
441                        "System`HoldForm",
442                        Expression(
443                            "System`Postfix",
444                            Expression("System`List", leaves[0]),
445                            "..",
446                            170,
447                        ),
448                    )
449                else:
450                    return Expression("System`HoldForm", expr)
451            elif head == "System`RepeatedNull":
452                if len(leaves) == 1:
453                    return Expression(
454                        "System`HoldForm",
455                        Expression(
456                            "System`Postfix",
457                            Expression("System`List", leaves[0]),
458                            "...",
459                            170,
460                        ),
461                    )
462                else:
463                    return Expression("System`HoldForm", expr)
464
465            # If expr is not an atom, looks for formats in its definition
466            # and apply them.
467            def format_expr(expr):
468                if not (expr.is_atom()) and not (expr.head.is_atom()):
469                    # expr is of the form f[...][...]
470                    return None
471                name = expr.get_lookup_name()
472                formats = evaluation.definitions.get_formats(name, form)
473                for rule in formats:
474                    result = rule.apply(expr, evaluation)
475                    if result is not None and result != expr:
476                        return result.evaluate(evaluation)
477                return None
478
479            formatted = format_expr(expr)
480            if formatted is not None:
481                result = formatted.do_format(evaluation, form)
482                if include_form:
483                    result = Expression(form, result)
484                result.unformatted = unformatted
485                return result
486
487            # If the expression is still enclosed by a Format,
488            # iterate.
489            # If the expression is not atomic or of certain
490            # specific cases, iterate over the leaves.
491            head = expr.get_head_name()
492            if head in formats:
493                expr = expr.do_format(evaluation, form)
494            elif (
495                head != "System`NumberForm"
496                and not expr.is_atom()
497                and head != "System`Graphics"
498                and head != "System`Graphics3D"
499            ):
500                # print("Not inside graphics or numberform, and not is atom")
501                new_leaves = [leaf.do_format(evaluation, form) for leaf in expr.leaves]
502                expr = Expression(expr.head.do_format(evaluation, form), *new_leaves)
503
504            if include_form:
505                expr = Expression(form, expr)
506            expr.unformatted = unformatted
507            return expr
508        finally:
509            evaluation.dec_recursion_depth()
510
511    def format(
512        self, evaluation, form, **kwargs
513    ) -> typing.Union["Expression", "Symbol"]:
514        """
515        Applies formats associated to the expression, and then calls Makeboxes
516        """
517        expr = self.do_format(evaluation, form)
518        result = Expression("MakeBoxes", expr, Symbol(form)).evaluate(evaluation)
519        return result
520
521    def is_free(self, form, evaluation) -> bool:
522        from mathics.builtin.patterns import item_is_free
523
524        return item_is_free(self, form, evaluation)
525
526    def is_inexact(self) -> bool:
527        return self.get_precision() is not None
528
529    def get_precision(self):
530        return None
531
532    def get_option_values(self, evaluation, allow_symbols=False, stop_on_error=True):
533        options = self
534        if options.has_form("List", None):
535            options = options.flatten(SymbolList)
536            values = options.leaves
537        else:
538            values = [options]
539        option_values = {}
540        for option in values:
541            symbol_name = option.get_name()
542            if allow_symbols and symbol_name:
543                options = evaluation.definitions.get_options(symbol_name)
544                option_values.update(options)
545            else:
546                if not option.has_form(("Rule", "RuleDelayed"), 2):
547                    if stop_on_error:
548                        return None
549                    else:
550                        continue
551                name = option.leaves[0].get_name()
552                if not name and isinstance(option.leaves[0], String):
553                    name = ensure_context(option.leaves[0].get_string_value())
554                if not name:
555                    if stop_on_error:
556                        return None
557                    else:
558                        continue
559                option_values[name] = option.leaves[1]
560        return option_values
561
562    def get_rules_list(self):
563        from mathics.core.rules import Rule
564
565        list_expr = self.flatten(SymbolList)
566        list = []
567        if list_expr.has_form("List", None):
568            list.extend(list_expr.leaves)
569        else:
570            list.append(list_expr)
571        rules = []
572        for item in list:
573            if not item.has_form(("Rule", "RuleDelayed"), 2):
574                return None
575            rule = Rule(item.leaves[0], item.leaves[1])
576            rules.append(rule)
577        return rules
578
579    def to_sympy(self, **kwargs):
580        raise NotImplementedError
581
582    def to_mpmath(self):
583        return None
584
585    def round_to_float(self, evaluation=None, permit_complex=False):
586        """
587        Try to round to python float. Return None if not possible.
588        """
589        if evaluation is None:
590            value = self
591        else:
592            value = Expression(SymbolN, self).evaluate(evaluation)
593        if isinstance(value, Number):
594            value = value.round()
595            return value.get_float_value(permit_complex=permit_complex)
596
597    def __abs__(self) -> "Expression":
598        return Expression("Abs", self)
599
600    def __pos__(self):
601        return self
602
603    def __neg__(self):
604        return Expression("Times", self, -1)
605
606    def __add__(self, other) -> "Expression":
607        return Expression("Plus", self, other)
608
609    def __sub__(self, other) -> "Expression":
610        return Expression("Plus", self, Expression("Times", other, -1))
611
612    def __mul__(self, other) -> "Expression":
613        return Expression("Times", self, other)
614
615    def __truediv__(self, other) -> "Expression":
616        return Expression("Divide", self, other)
617
618    def __floordiv__(self, other) -> "Expression":
619        return Expression("Floor", Expression("Divide", self, other))
620
621    def __pow__(self, other) -> "Expression":
622        return Expression("Power", self, other)
623
624
625class Monomial(object):
626    """
627    An object to sort monomials, used in Expression.get_sort_key and
628    Symbol.get_sort_key.
629    """
630
631    def __init__(self, exps_dict):
632        self.exps = exps_dict
633
634    def __lt__(self, other) -> bool:
635        return self.__cmp(other) < 0
636
637    def __gt__(self, other) -> bool:
638        return self.__cmp(other) > 0
639
640    def __le__(self, other) -> bool:
641        return self.__cmp(other) <= 0
642
643    def __ge__(self, other) -> bool:
644        return self.__cmp(other) >= 0
645
646    def __eq__(self, other) -> bool:
647        return self.__cmp(other) == 0
648
649    def __ne__(self, other) -> bool:
650        return self.__cmp(other) != 0
651
652    def __cmp(self, other) -> int:
653        self_exps = self.exps.copy()
654        other_exps = other.exps.copy()
655        for var in self.exps:
656            if var in other.exps:
657                dec = min(self_exps[var], other_exps[var])
658                self_exps[var] -= dec
659                if not self_exps[var]:
660                    del self_exps[var]
661                other_exps[var] -= dec
662                if not other_exps[var]:
663                    del other_exps[var]
664        self_exps = sorted((var, exp) for var, exp in self_exps.items())
665        other_exps = sorted((var, exp) for var, exp in other_exps.items())
666
667        index = 0
668        self_len = len(self_exps)
669        other_len = len(other_exps)
670        while True:
671            if index >= self_len and index >= other_len:
672                return 0
673            if index >= self_len:
674                return -1  # self < other
675            if index >= other_len:
676                return 1  # self > other
677            self_var, self_exp = self_exps[index]
678            other_var, other_exp = other_exps[index]
679            if self_var < other_var:
680                return -1
681            if self_var > other_var:
682                return 1
683            if self_exp != other_exp:
684                if index + 1 == self_len or index + 1 == other_len:
685                    # smaller exponents first
686                    if self_exp < other_exp:
687                        return -1
688                    elif self_exp == other_exp:
689                        return 0
690                    else:
691                        return 1
692                else:
693                    # bigger exponents first
694                    if self_exp < other_exp:
695                        return 1
696                    elif self_exp == other_exp:
697                        return 0
698                    else:
699                        return -1
700            index += 1
701        return 0
702
703
704class Expression(BaseExpression):
705    head: "Symbol"
706    leaves: typing.List[Any]
707    _sequences: Any
708
709    def __new__(cls, head, *leaves, **kwargs) -> "Expression":
710        self = super().__new__(cls)
711        if isinstance(head, str):
712            head = Symbol(head)
713        self._head = head
714        self._leaves = tuple(from_python(leaf) for leaf in leaves)
715        self._sequences = None
716        self._format_cache = None
717        return self
718
719    @property
720    def head(self):
721        return self._head
722
723    @head.setter
724    def head(self, value):
725        raise ValueError("Expression.head is write protected.")
726
727    @property
728    def leaves(self):
729        return self._leaves
730
731    @leaves.setter
732    def leaves(self, value):
733        raise ValueError("Expression.leaves is write protected.")
734
735    def equal2(self, rhs: Any) -> Optional[bool]:
736        """Mathics two-argument Equal (==)
737        returns True if self and rhs are identical.
738        """
739        if self.sameQ(rhs):
740            return True
741        # if rhs is an Atom, return None
742        elif isinstance(rhs, Atom):
743            return None
744
745        # Here we only need to deal with Expressions.
746        equal_heads = self._head.equal2(rhs._head)
747        if not equal_heads:
748            return equal_heads
749        # From here, we can assume that both heads are the same
750        if self.get_head_name() in ("System`List", "System`Sequence"):
751            if len(self._leaves) != len(rhs._leaves):
752                return False
753            for item1, item2 in zip(self._leaves, rhs._leaves):
754                result = item1.equal2(item2)
755                if not result:
756                    return result
757            return True
758        elif self.get_head_name() in ("System`DirectedInfinity",):
759            return self._leaves[0].equal2(rhs._leaves[0])
760        return None
761
762    def slice(self, head, py_slice, evaluation):
763        # faster equivalent to: Expression(head, *self.leaves[py_slice])
764        return structure(head, self, evaluation).slice(self, py_slice)
765
766    def filter(self, head, cond, evaluation):
767        # faster equivalent to: Expression(head, [leaf in self.leaves if cond(leaf)])
768        return structure(head, self, evaluation).filter(self, cond)
769
770    def restructure(self, head, leaves, evaluation, structure_cache=None, deps=None):
771        # faster equivalent to: Expression(head, *leaves)
772
773        # the caller guarantees that _all_ elements in leaves are either from
774        # self.leaves (or its sub trees) or from one of the expression given
775        # in the tuple "deps" (or its sub trees).
776
777        # if this method is called repeatedly, and the caller guarantees
778        # that no definitions change between subsequent calls, then heads_cache
779        # may be passed an initially empty dict to speed up calls.
780
781        if deps is None:
782            deps = self
783        s = structure(head, deps, evaluation, structure_cache=structure_cache)
784        return s(list(leaves))
785
786    def _no_symbol(self, symbol_name):
787        # if this return True, it's safe to say that self.leaves or its
788        # sub leaves contain no Symbol with symbol_name. if this returns
789        # False, such a Symbol might or might not exist.
790
791        cache = self._cache
792        if cache is None:
793            return False
794
795        symbols = cache.symbols
796        if symbols is not None and symbol_name not in symbols:
797            return True
798        else:
799            return False
800
801    def sequences(self):
802        cache = self._cache
803        if cache:
804            seq = cache.sequences
805            if seq is not None:
806                return seq
807
808        return self._rebuild_cache().sequences
809
810    def _flatten_sequence(self, sequence, evaluation) -> "Expression":
811        indices = self.sequences()
812        if not indices:
813            return self
814
815        leaves = self._leaves
816
817        flattened = []
818        extend = flattened.extend
819
820        k = 0
821        for i in indices:
822            extend(leaves[k:i])
823            extend(sequence(leaves[i]))
824            k = i + 1
825        extend(leaves[k:])
826
827        return self.restructure(self._head, flattened, evaluation)
828
829    def flatten_sequence(self, evaluation):
830        def sequence(leaf):
831            if leaf.get_head_name() == "System`Sequence":
832                return leaf._leaves
833            else:
834                return [leaf]
835
836        return self._flatten_sequence(sequence, evaluation)
837
838    def flatten_pattern_sequence(self, evaluation):
839        def sequence(leaf):
840            flattened = leaf.flatten_pattern_sequence(evaluation)
841            if leaf.get_head_name() == "System`Sequence" and leaf.pattern_sequence:
842                return flattened._leaves
843            else:
844                return [flattened]
845
846        expr = self._flatten_sequence(sequence, evaluation)
847        if hasattr(self, "options"):
848            expr.options = self.options
849        return expr
850
851    def _rebuild_cache(self):
852        cache = self._cache
853
854        if cache is None:
855            time = None
856        elif cache.symbols is None:
857            time = cache.time
858        elif cache.sequences is None:
859            time = cache.time
860        else:
861            return cache
862
863        sym = set((self.get_head_name(),))
864        seq = []
865
866        for i, leaf in enumerate(self._leaves):
867            if isinstance(leaf, Expression):
868                leaf_symbols = leaf._rebuild_cache().symbols
869                sym.update(leaf_symbols)
870                if "System`Sequence" in leaf_symbols:
871                    seq.append(i)
872            elif isinstance(leaf, Symbol):
873                sym.add(leaf.get_name())
874
875        cache = ExpressionCache(time, sym, seq)
876        self._cache = cache
877        return cache
878
879    def has_changed(self, definitions):
880        cache = self._cache
881
882        if cache is None:
883            return True
884
885        time = cache.time
886
887        if time is None:
888            return True
889
890        if cache.symbols is None:
891            cache = self._rebuild_cache()
892
893        return definitions.has_changed(time, cache.symbols)
894
895    def _timestamp_cache(self, evaluation):
896        self._cache = ExpressionCache(evaluation.definitions.now, copy=self._cache)
897
898    def copy(self, reevaluate=False) -> "Expression":
899        expr = Expression(self._head.copy(reevaluate))
900        expr._leaves = tuple(leaf.copy(reevaluate) for leaf in self._leaves)
901        if not reevaluate:
902            # rebuilding the cache in self speeds up large operations, e.g.
903            # First[Timing[Fold[#1+#2&, Range[750]]]]
904            expr._cache = self._rebuild_cache()
905        expr.options = self.options
906        expr.original = self
907        expr._sequences = self._sequences
908        expr._format_cache = self._format_cache
909        return expr
910
911    def do_format(self, evaluation, form):
912        if self._format_cache is None:
913            self._format_cache = {}
914
915        last_evaluated, expr = self._format_cache.get(form, (None, None))
916        if last_evaluated is not None and expr is not None:
917            symbolname = expr.get_name()
918            if symbolname != "":
919                if not evaluation.definitions.has_changed(
920                    last_evaluated, (symbolname,)
921                ):
922                    return expr
923        expr = super().do_format(evaluation, form)
924        self._format_cache[form] = (evaluation.definitions.now, expr)
925        return expr
926
927    def shallow_copy(self) -> "Expression":
928        # this is a minimal, shallow copy: head, leaves are shared with
929        # the original, only the Expression instance is new.
930        expr = Expression(self._head)
931        expr._leaves = self._leaves
932        # rebuilding the cache in self speeds up large operations, e.g.
933        # First[Timing[Fold[#1+#2&, Range[750]]]]
934        expr._cache = self._rebuild_cache()
935        expr.options = self.options
936        # expr.last_evaluated = self.last_evaluated
937        return expr
938
939    def set_positions(self, position=None) -> None:
940        self.position = position
941        self._head.set_positions(ExpressionPointer(self, 0))
942        for index, leaf in enumerate(self._leaves):
943            leaf.set_positions(ExpressionPointer(self, index + 1))
944
945    def get_head(self):
946        return self._head
947
948    def set_head(self, head):
949        self._head = head
950        self._cache = None
951
952    def get_leaves(self):
953        return self._leaves
954
955    def get_mutable_leaves(self):  # shallow, mutable copy of the leaves array
956        return list(self._leaves)
957
958    def set_leaf(self, index, value):  # leaves are removed, added or replaced
959        leaves = list(self._leaves)
960        leaves[index] = value
961        self._leaves = tuple(leaves)
962        self._cache = None
963
964    def set_reordered_leaves(self, leaves):  # same leaves, but in a different order
965        self._leaves = tuple(leaves)
966        if self._cache:
967            self._cache = self._cache.reordered()
968
969    def get_attributes(self, definitions):
970        if self.get_head_name() == "System`Function" and len(self._leaves) > 2:
971            res = self._leaves[2]
972            if res.is_symbol():
973                return (str(res),)
974            elif res.has_form("List", None):
975                return set(str(a) for a in res._leaves)
976        return set()
977
978    def get_lookup_name(self) -> bool:
979        return self._head.get_lookup_name()
980
981    def has_form(self, heads, *leaf_counts):
982        """
983        leaf_counts:
984            (,):        no leaves allowed
985            (None,):    no constraint on number of leaves
986            (n, None):  leaf count >= n
987            (n1, n2, ...):    leaf count in {n1, n2, ...}
988        """
989
990        head_name = self._head.get_name()
991        if isinstance(heads, (tuple, list, set)):
992            if head_name not in [ensure_context(h) for h in heads]:
993                return False
994        else:
995            if head_name != ensure_context(heads):
996                return False
997        if not leaf_counts:
998            return False
999        if leaf_counts and leaf_counts[0] is not None:
1000            count = len(self._leaves)
1001            if count not in leaf_counts:
1002                if (
1003                    len(leaf_counts) == 2
1004                    and leaf_counts[1] is None  # noqa
1005                    and count >= leaf_counts[0]
1006                ):
1007                    return True
1008                else:
1009                    return False
1010        return True
1011
1012    def has_symbol(self, symbol_name) -> bool:
1013        if self._no_symbol(symbol_name):
1014            return False
1015        return self._head.has_symbol(symbol_name) or any(
1016            leaf.has_symbol(symbol_name) for leaf in self._leaves
1017        )
1018
1019    def _as_sympy_function(self, **kwargs) -> sympy.Function:
1020        sym_args = [leaf.to_sympy(**kwargs) for leaf in self.leaves]
1021
1022        if None in sym_args:
1023            return None
1024
1025        f = sympy.Function(str(sympy_symbol_prefix + self.get_head_name()))
1026        return f(*sym_args)
1027
1028    def to_sympy(self, **kwargs):
1029        from mathics.builtin import mathics_to_sympy
1030
1031        if "convert_all_global_functions" in kwargs:
1032            if len(self.leaves) > 0 and kwargs["convert_all_global_functions"]:
1033                if self.get_head_name().startswith("Global`"):
1034                    return self._as_sympy_function(**kwargs)
1035
1036        if "converted_functions" in kwargs:
1037            functions = kwargs["converted_functions"]
1038            if len(self._leaves) > 0 and self.get_head_name() in functions:
1039                sym_args = [leaf.to_sympy() for leaf in self._leaves]
1040                if None in sym_args:
1041                    return None
1042                func = sympy.Function(str(sympy_symbol_prefix + self.get_head_name()))(
1043                    *sym_args
1044                )
1045                return func
1046
1047        lookup_name = self.get_lookup_name()
1048        builtin = mathics_to_sympy.get(lookup_name)
1049        if builtin is not None:
1050            sympy_expr = builtin.to_sympy(self, **kwargs)
1051            if sympy_expr is not None:
1052                return sympy_expr
1053
1054        return SympyExpression(self)
1055
1056    def to_python(self, *args, **kwargs):
1057        """
1058        Convert the Expression to a Python object:
1059        List[...]  -> Python list
1060        DirectedInfinity[1] -> inf
1061        DirectedInfinity[-1] -> -inf
1062        True/False -> True/False
1063        Null       -> None
1064        Symbol     -> '...'
1065        String     -> '"..."'
1066        Function   -> python function
1067        numbers    -> Python number
1068        If kwarg n_evaluation is given, apply N first to the expression.
1069        """
1070        from mathics.builtin.base import mathics_to_python
1071
1072        n_evaluation = kwargs.get("n_evaluation")
1073        head_name = self._head.get_name()
1074        if n_evaluation is not None:
1075            if head_name == "System`Function":
1076                compiled = Expression("System`Compile", *(self._leaves))
1077                compiled = compiled.evaluate(n_evaluation)
1078                if compiled.get_head_name() == "System`CompiledFunction":
1079                    return compiled.leaves[2].cfunc
1080            value = Expression(SymbolN, self).evaluate(n_evaluation)
1081            return value.to_python()
1082
1083        if head_name == "System`DirectedInfinity" and len(self._leaves) == 1:
1084            direction = self._leaves[0].get_int_value()
1085            if direction == 1:
1086                return math.inf
1087            if direction == -1:
1088                return -math.inf
1089        elif head_name == "System`List":
1090            return [leaf.to_python(*args, **kwargs) for leaf in self._leaves]
1091
1092        if head_name in mathics_to_python:
1093            py_obj = mathics_to_python[head_name]
1094            # Start here
1095            # if inspect.isfunction(py_obj) or inspect.isbuiltin(py_obj):
1096            #     args = [leaf.to_python(*args, **kwargs) for leaf in self._leaves]
1097            #     return ast.Call(
1098            #         func=py_obj.__name__,
1099            #         args=args,
1100            #         keywords=[],
1101            #         )
1102            return py_obj
1103        return self
1104
1105    def get_sort_key(self, pattern_sort=False):
1106
1107        if pattern_sort:
1108            """
1109            Pattern sort key structure:
1110            0: 0/2:        Atom / Expression
1111            1: pattern:    0 / 11-31 for blanks / 1 for empty Alternatives /
1112                               40 for OptionsPattern
1113            2: 0/1:        0 for PatternTest
1114            3: 0/1:        0 for Pattern
1115            4: 0/1:        1 for Optional
1116            5: head / 0 for atoms
1117            6: leaves / 0 for atoms
1118            7: 0/1:        0 for Condition
1119            """
1120
1121            name = self._head.get_name()
1122            pattern = 0
1123            if name == "System`Blank":
1124                pattern = 1
1125            elif name == "System`BlankSequence":
1126                pattern = 2
1127            elif name == "System`BlankNullSequence":
1128                pattern = 3
1129            if pattern > 0:
1130                if self._leaves:
1131                    pattern += 10
1132                else:
1133                    pattern += 20
1134            if pattern > 0:
1135                return [
1136                    2,
1137                    pattern,
1138                    1,
1139                    1,
1140                    0,
1141                    self._head.get_sort_key(True),
1142                    tuple(leaf.get_sort_key(True) for leaf in self._leaves),
1143                    1,
1144                ]
1145
1146            if name == "System`PatternTest":
1147                if len(self._leaves) != 2:
1148                    return [3, 0, 0, 0, 0, self._head, self._leaves, 1]
1149                sub = self._leaves[0].get_sort_key(True)
1150                sub[2] = 0
1151                return sub
1152            elif name == "System`Condition":
1153                if len(self._leaves) != 2:
1154                    return [3, 0, 0, 0, 0, self._head, self._leaves, 1]
1155                sub = self._leaves[0].get_sort_key(True)
1156                sub[7] = 0
1157                return sub
1158            elif name == "System`Pattern":
1159                if len(self._leaves) != 2:
1160                    return [3, 0, 0, 0, 0, self._head, self._leaves, 1]
1161                sub = self._leaves[1].get_sort_key(True)
1162                sub[3] = 0
1163                return sub
1164            elif name == "System`Optional":
1165                if len(self._leaves) not in (1, 2):
1166                    return [3, 0, 0, 0, 0, self._head, self._leaves, 1]
1167                sub = self._leaves[0].get_sort_key(True)
1168                sub[4] = 1
1169                return sub
1170            elif name == "System`Alternatives":
1171                min_key = [4]
1172                min = None
1173                for leaf in self._leaves:
1174                    key = leaf.get_sort_key(True)
1175                    if key < min_key:
1176                        min = leaf
1177                        min_key = key
1178                if min is None:
1179                    # empty alternatives -> very restrictive pattern
1180                    return [2, 1]
1181                return min_key
1182            elif name == "System`Verbatim":
1183                if len(self._leaves) != 1:
1184                    return [3, 0, 0, 0, 0, self._head, self._leaves, 1]
1185                return self._leaves[0].get_sort_key(True)
1186            elif name == "System`OptionsPattern":
1187                return [2, 40, 0, 1, 1, 0, self._head, self._leaves, 1]
1188            else:
1189                # Append [4] to leaves so that longer expressions have higher
1190                # precedence
1191                return [
1192                    2,
1193                    0,
1194                    1,
1195                    1,
1196                    0,
1197                    self._head.get_sort_key(True),
1198                    tuple(
1199                        chain(
1200                            (leaf.get_sort_key(True) for leaf in self._leaves), ([4],)
1201                        )
1202                    ),
1203                    1,
1204                ]
1205        else:
1206            exps = {}
1207            head = self._head.get_name()
1208            if head == "System`Times":
1209                for leaf in self._leaves:
1210                    name = leaf.get_name()
1211                    if leaf.has_form("Power", 2):
1212                        var = leaf._leaves[0].get_name()
1213                        exp = leaf._leaves[1].round_to_float()
1214                        if var and exp is not None:
1215                            exps[var] = exps.get(var, 0) + exp
1216                    elif name:
1217                        exps[name] = exps.get(name, 0) + 1
1218            elif self.has_form("Power", 2):
1219                var = self._leaves[0].get_name()
1220                exp = self._leaves[1].round_to_float()
1221                if var and exp is not None:
1222                    exps[var] = exps.get(var, 0) + exp
1223            if exps:
1224                return [
1225                    1 if self.is_numeric() else 2,
1226                    2,
1227                    Monomial(exps),
1228                    1,
1229                    self._head,
1230                    self._leaves,
1231                    1,
1232                ]
1233            else:
1234                return [1 if self.is_numeric() else 2, 3, self._head, self._leaves, 1]
1235
1236    def sameQ(self, other) -> bool:
1237        """Mathics SameQ"""
1238        if id(self) == id(other):
1239            return True
1240        if self.get_head_name() != other.get_head_name():
1241            return False
1242        if not self._head.sameQ(other.get_head()):
1243            return False
1244        if len(self._leaves) != len(other.get_leaves()):
1245            return False
1246        for leaf, other in zip(self._leaves, other.get_leaves()):
1247            if not leaf.sameQ(other):
1248                return False
1249        return True
1250
1251    def flatten(
1252        self, head, pattern_only=False, callback=None, level=None
1253    ) -> "Expression":
1254        if level is not None and level <= 0:
1255            return self
1256        if self._no_symbol(head.get_name()):
1257            return self
1258        sub_level = None if level is None else level - 1
1259        do_flatten = False
1260        for leaf in self._leaves:
1261            if leaf.get_head().sameQ(head) and (
1262                not pattern_only or leaf.pattern_sequence
1263            ):
1264                do_flatten = True
1265                break
1266        if do_flatten:
1267            new_leaves = []
1268            for leaf in self._leaves:
1269                if leaf.get_head().sameQ(head) and (
1270                    not pattern_only or leaf.pattern_sequence
1271                ):
1272                    new_leaf = leaf.flatten(
1273                        head, pattern_only, callback, level=sub_level
1274                    )
1275                    if callback is not None:
1276                        callback(new_leaf._leaves, leaf)
1277                    new_leaves.extend(new_leaf._leaves)
1278                else:
1279                    new_leaves.append(leaf)
1280            return Expression(self._head, *new_leaves)
1281        else:
1282            return self
1283
1284    def evaluate(self, evaluation) -> typing.Union["Expression", "Symbol"]:
1285        from mathics.core.evaluation import ReturnInterrupt
1286
1287        if evaluation.timeout:
1288            return
1289
1290        expr = self
1291        reevaluate = True
1292        limit = None
1293        iteration = 1
1294        names = set()
1295        definitions = evaluation.definitions
1296
1297        old_options = evaluation.options
1298        evaluation.inc_recursion_depth()
1299        try:
1300            while reevaluate:
1301                # changed before last evaluated?
1302                if not expr.has_changed(definitions):
1303                    break
1304
1305                names.add(expr.get_lookup_name())
1306
1307                if hasattr(expr, "options") and expr.options:
1308                    evaluation.options = expr.options
1309
1310                expr, reevaluate = expr.evaluate_next(evaluation)
1311                if not reevaluate:
1312                    break
1313
1314                iteration += 1
1315
1316                if limit is None:
1317                    limit = definitions.get_config_value("$IterationLimit")
1318                    if limit is None:
1319                        limit = "inf"
1320                if limit != "inf" and iteration > limit:
1321                    evaluation.error("$IterationLimit", "itlim", limit)
1322                    return Symbol("$Aborted")
1323
1324        # "Return gets discarded only if it was called from within the r.h.s.
1325        # of a user-defined rule."
1326        # http://mathematica.stackexchange.com/questions/29353/how-does-return-work
1327        # Otherwise it propogates up.
1328        #
1329        except ReturnInterrupt as ret:
1330            if names.intersection(definitions.user.keys()):
1331                return ret.expr
1332            else:
1333                raise ret
1334        finally:
1335            evaluation.options = old_options
1336            evaluation.dec_recursion_depth()
1337
1338        return expr
1339
1340    def evaluate_next(self, evaluation) -> typing.Tuple["Expression", bool]:
1341        from mathics.builtin.base import BoxConstruct
1342
1343        head = self._head.evaluate(evaluation)
1344        attributes = head.get_attributes(evaluation.definitions)
1345        leaves = self.get_mutable_leaves()
1346
1347        def rest_range(indices):
1348            if "System`HoldAllComplete" not in attributes:
1349                if self._no_symbol("System`Evaluate"):
1350                    return
1351                for index in indices:
1352                    leaf = leaves[index]
1353                    if leaf.has_form("Evaluate", 1):
1354                        leaves[index] = leaf.evaluate(evaluation)
1355
1356        def eval_range(indices):
1357            for index in indices:
1358                leaf = leaves[index]
1359                if not leaf.has_form("Unevaluated", 1):
1360                    leaf = leaf.evaluate(evaluation)
1361                    if leaf:
1362                        leaves[index] = leaf
1363
1364        if "System`HoldAll" in attributes or "System`HoldAllComplete" in attributes:
1365            # eval_range(range(0, 0))
1366            rest_range(range(len(leaves)))
1367        elif "System`HoldFirst" in attributes:
1368            rest_range(range(0, min(1, len(leaves))))
1369            eval_range(range(1, len(leaves)))
1370        elif "System`HoldRest" in attributes:
1371            eval_range(range(0, min(1, len(leaves))))
1372            rest_range(range(1, len(leaves)))
1373        else:
1374            eval_range(range(len(leaves)))
1375            # rest_range(range(0, 0))
1376
1377        new = Expression(head)
1378        new._leaves = tuple(leaves)
1379
1380        if (
1381            "System`SequenceHold" not in attributes
1382            and "System`HoldAllComplete" not in attributes  # noqa
1383        ):
1384            new = new.flatten_sequence(evaluation)
1385            leaves = new._leaves
1386
1387        for leaf in leaves:
1388            leaf.unevaluated = False
1389
1390        if "System`HoldAllComplete" not in attributes:
1391            dirty_leaves = None
1392
1393            for index, leaf in enumerate(leaves):
1394                if leaf.has_form("Unevaluated", 1):
1395                    if dirty_leaves is None:
1396                        dirty_leaves = list(leaves)
1397                    dirty_leaves[index] = leaf._leaves[0]
1398                    dirty_leaves[index].unevaluated = True
1399
1400            if dirty_leaves:
1401                new = Expression(head)
1402                new._leaves = tuple(dirty_leaves)
1403                leaves = dirty_leaves
1404
1405        def flatten_callback(new_leaves, old):
1406            for leaf in new_leaves:
1407                leaf.unevaluated = old.unevaluated
1408
1409        if "System`Flat" in attributes:
1410            new = new.flatten(new._head, callback=flatten_callback)
1411        if "System`Orderless" in attributes:
1412            new.sort()
1413
1414        new._timestamp_cache(evaluation)
1415
1416        if "System`Listable" in attributes:
1417            done, threaded = new.thread(evaluation)
1418            if done:
1419                if threaded.sameQ(new):
1420                    new._timestamp_cache(evaluation)
1421                    return new, False
1422                else:
1423                    return threaded, True
1424
1425        def rules():
1426            rules_names = set()
1427            if "System`HoldAllComplete" not in attributes:
1428                for leaf in leaves:
1429                    name = leaf.get_lookup_name()
1430                    if len(name) > 0:  # only lookup rules if this is a symbol
1431                        if name not in rules_names:
1432                            rules_names.add(name)
1433                            for rule in evaluation.definitions.get_upvalues(name):
1434                                yield rule
1435            lookup_name = new.get_lookup_name()
1436            if lookup_name == new.get_head_name():
1437                for rule in evaluation.definitions.get_downvalues(lookup_name):
1438                    yield rule
1439            else:
1440                for rule in evaluation.definitions.get_subvalues(lookup_name):
1441                    yield rule
1442
1443        for rule in rules():
1444            result = rule.apply(new, evaluation, fully=False)
1445            if result is not None:
1446                if isinstance(result, BoxConstruct):
1447                    return result, False
1448                if result.sameQ(new):
1449                    new._timestamp_cache(evaluation)
1450                    return new, False
1451                else:
1452                    return result, True
1453
1454        dirty_leaves = None
1455
1456        # Expression did not change, re-apply Unevaluated
1457        for index, leaf in enumerate(new._leaves):
1458            if leaf.unevaluated:
1459                if dirty_leaves is None:
1460                    dirty_leaves = list(new._leaves)
1461                dirty_leaves[index] = Expression("Unevaluated", leaf)
1462
1463        if dirty_leaves:
1464            new = Expression(head)
1465            new._leaves = tuple(dirty_leaves)
1466
1467        new.unformatted = self.unformatted
1468        new._timestamp_cache(evaluation)
1469        return new, False
1470
1471    def evaluate_leaves(self, evaluation) -> "Expression":
1472        leaves = [leaf.evaluate(evaluation) for leaf in self._leaves]
1473        head = self._head.evaluate_leaves(evaluation)
1474        return Expression(head, *leaves)
1475
1476    def __str__(self) -> str:
1477        return "%s[%s]" % (
1478            self._head,
1479            ", ".join([leaf.__str__() for leaf in self._leaves]),
1480        )
1481
1482    def __repr__(self) -> str:
1483        return "<Expression: %s>" % self
1484
1485    def process_style_box(self, options):
1486        if self.has_form("StyleBox", 1, None):
1487            rules = self._leaves[1:]
1488            for rule in rules:
1489                if rule.has_form("Rule", 2):
1490                    name = rule._leaves[0].get_name()
1491                    value = rule._leaves[1]
1492                    if name == "System`ShowStringCharacters":
1493                        value = value.is_true()
1494                        options = options.copy()
1495                        options["show_string_characters"] = value
1496                    elif name == "System`ImageSizeMultipliers":
1497                        if value.has_form("List", 2):
1498                            m1 = value._leaves[0].round_to_float()
1499                            m2 = value._leaves[1].round_to_float()
1500                            if m1 is not None and m2 is not None:
1501                                options = options.copy()
1502                                options["image_size_multipliers"] = (m1, m2)
1503            return True, options
1504        else:
1505            return False, options
1506
1507    def boxes_to_text(self, **options) -> str:
1508        is_style, options = self.process_style_box(options)
1509        if is_style:
1510            return self._leaves[0].boxes_to_text(**options)
1511        if self.has_form("RowBox", 1) and self._leaves[0].has_form(  # nopep8
1512            "List", None
1513        ):
1514            return "".join(
1515                [leaf.boxes_to_text(**options) for leaf in self._leaves[0]._leaves]
1516            )
1517        elif self.has_form("SuperscriptBox", 2):
1518            return "^".join([leaf.boxes_to_text(**options) for leaf in self._leaves])
1519        elif self.has_form("FractionBox", 2):
1520            return "/".join(
1521                [" ( " + leaf.boxes_to_text(**options) + " ) " for leaf in self._leaves]
1522            )
1523        else:
1524            raise BoxError(self, "text")
1525
1526    def boxes_to_mathml(self, **options) -> str:
1527        is_style, options = self.process_style_box(options)
1528        if is_style:
1529            return self._leaves[0].boxes_to_mathml(**options)
1530        name = self._head.get_name()
1531        if (
1532            name == "System`RowBox"
1533            and len(self._leaves) == 1
1534            and self._leaves[0].get_head_name() == "System`List"  # nopep8
1535        ):
1536            result = []
1537            inside_row = options.get("inside_row")
1538            # inside_list = options.get('inside_list')
1539            options = options.copy()
1540
1541            def is_list_interior(content):
1542                if content.has_form("List", None) and all(
1543                    leaf.get_string_value() == "," for leaf in content._leaves[1::2]
1544                ):
1545                    return True
1546                return False
1547
1548            is_list_row = False
1549            if (
1550                len(self._leaves[0]._leaves) == 3
1551                and self._leaves[0]._leaves[0].get_string_value() == "{"  # nopep8
1552                and self._leaves[0]._leaves[2].get_string_value() == "}"
1553                and self._leaves[0]._leaves[1].has_form("RowBox", 1)
1554            ):
1555                content = self._leaves[0]._leaves[1]._leaves[0]
1556                if is_list_interior(content):
1557                    is_list_row = True
1558
1559            if not inside_row and is_list_interior(self._leaves[0]):
1560                is_list_row = True
1561
1562            if is_list_row:
1563                options["inside_list"] = True
1564            else:
1565                options["inside_row"] = True
1566
1567            for leaf in self._leaves[0].get_leaves():
1568                result.append(leaf.boxes_to_mathml(**options))
1569            return "<mrow>%s</mrow>" % " ".join(result)
1570        else:
1571            options = options.copy()
1572            options["inside_row"] = True
1573            if name == "System`SuperscriptBox" and len(self._leaves) == 2:
1574                return "<msup>%s %s</msup>" % (
1575                    self._leaves[0].boxes_to_mathml(**options),
1576                    self._leaves[1].boxes_to_mathml(**options),
1577                )
1578            if name == "System`SubscriptBox" and len(self._leaves) == 2:
1579                return "<msub>%s %s</msub>" % (
1580                    self._leaves[0].boxes_to_mathml(**options),
1581                    self._leaves[1].boxes_to_mathml(**options),
1582                )
1583            if name == "System`SubsuperscriptBox" and len(self._leaves) == 3:
1584                return "<msubsup>%s %s %s</msubsup>" % (
1585                    self._leaves[0].boxes_to_mathml(**options),
1586                    self._leaves[1].boxes_to_mathml(**options),
1587                    self._leaves[2].boxes_to_mathml(**options),
1588                )
1589            elif name == "System`FractionBox" and len(self._leaves) == 2:
1590                return "<mfrac>%s %s</mfrac>" % (
1591                    self._leaves[0].boxes_to_mathml(**options),
1592                    self._leaves[1].boxes_to_mathml(**options),
1593                )
1594            elif name == "System`SqrtBox" and len(self._leaves) == 1:
1595                return "<msqrt>%s</msqrt>" % (
1596                    self._leaves[0].boxes_to_mathml(**options)
1597                )
1598            elif name == "System`GraphBox":
1599                return "<mi>%s</mi>" % (self._leaves[0].boxes_to_mathml(**options))
1600            else:
1601                raise BoxError(self, "xml")
1602
1603    def boxes_to_tex(self, **options) -> str:
1604        def block(tex, only_subsup=False):
1605            if len(tex) == 1:
1606                return tex
1607            else:
1608                if not only_subsup or "_" in tex or "^" in tex:
1609                    return "{%s}" % tex
1610                else:
1611                    return tex
1612
1613        is_style, options = self.process_style_box(options)
1614        if is_style:
1615            return self._leaves[0].boxes_to_tex(**options)
1616        name = self._head.get_name()
1617        if (
1618            name == "System`RowBox"
1619            and len(self._leaves) == 1
1620            and self._leaves[0].get_head_name() == "System`List"  # nopep8
1621        ):
1622            return "".join(
1623                [leaf.boxes_to_tex(**options) for leaf in self._leaves[0].get_leaves()]
1624            )
1625        elif name == "System`SuperscriptBox" and len(self._leaves) == 2:
1626            tex1 = self._leaves[0].boxes_to_tex(**options)
1627            sup_string = self._leaves[1].get_string_value()
1628            if sup_string == "\u2032":
1629                return "%s'" % tex1
1630            elif sup_string == "\u2032\u2032":
1631                return "%s''" % tex1
1632            else:
1633                return "%s^%s" % (
1634                    block(tex1, True),
1635                    block(self._leaves[1].boxes_to_tex(**options)),
1636                )
1637        elif name == "System`SubscriptBox" and len(self._leaves) == 2:
1638            return "%s_%s" % (
1639                block(self._leaves[0].boxes_to_tex(**options), True),
1640                block(self._leaves[1].boxes_to_tex(**options)),
1641            )
1642        elif name == "System`SubsuperscriptBox" and len(self._leaves) == 3:
1643            return "%s_%s^%s" % (
1644                block(self._leaves[0].boxes_to_tex(**options), True),
1645                block(self._leaves[1].boxes_to_tex(**options)),
1646                block(self._leaves[2].boxes_to_tex(**options)),
1647            )
1648        elif name == "System`FractionBox" and len(self._leaves) == 2:
1649            return "\\frac{%s}{%s}" % (
1650                self._leaves[0].boxes_to_tex(**options),
1651                self._leaves[1].boxes_to_tex(**options),
1652            )
1653        elif name == "System`SqrtBox" and len(self._leaves) == 1:
1654            return "\\sqrt{%s}" % self._leaves[0].boxes_to_tex(**options)
1655        else:
1656            raise BoxError(self, "tex")
1657
1658    def default_format(self, evaluation, form) -> str:
1659        return "%s[%s]" % (
1660            self._head.default_format(evaluation, form),
1661            ", ".join([leaf.default_format(evaluation, form) for leaf in self._leaves]),
1662        )
1663
1664    def sort(self, pattern=False):
1665        " Sort the leaves according to internal ordering. "
1666        leaves = list(self._leaves)
1667        if pattern:
1668            leaves.sort(key=lambda e: e.get_sort_key(pattern_sort=True))
1669        else:
1670            leaves.sort()
1671        self.set_reordered_leaves(leaves)
1672
1673    def filter_leaves(self, head_name):
1674        # TODO: should use sorting
1675        head_name = ensure_context(head_name)
1676
1677        if self._no_symbol(head_name):
1678            return []
1679        else:
1680            return [leaf for leaf in self._leaves if leaf.get_head_name() == head_name]
1681
1682    def apply_rules(self, rules, evaluation, level=0, options=None):
1683        """for rule in rules:
1684        result = rule.apply(self, evaluation, fully=False)
1685        if result is not None:
1686            return result"""
1687
1688        # to be able to access it inside inner function
1689        new_applied = [False]
1690
1691        def apply_leaf(leaf):
1692            new, sub_applied = leaf.apply_rules(rules, evaluation, level + 1, options)
1693            new_applied[0] = new_applied[0] or sub_applied
1694            return new
1695
1696        def descend(expr):
1697            return Expression(expr._head, *[apply_leaf(leaf) for leaf in expr._leaves])
1698
1699        if options is None:  # default ReplaceAll mode; replace breadth first
1700            result, applied = super().apply_rules(rules, evaluation, level, options)
1701            if applied:
1702                return result, True
1703            head, applied = self._head.apply_rules(rules, evaluation, level, options)
1704            new_applied[0] = applied
1705            return descend(Expression(head, *self._leaves)), new_applied[0]
1706        else:  # Replace mode; replace depth first
1707            expr = descend(self)
1708            expr, applied = super(Expression, expr).apply_rules(
1709                rules, evaluation, level, options
1710            )
1711            new_applied[0] = new_applied[0] or applied
1712            if not applied and options["heads"]:
1713                # heads in Replace are treated at the level of the arguments, i.e. level + 1
1714                head, applied = expr._head.apply_rules(
1715                    rules, evaluation, level + 1, options
1716                )
1717                new_applied[0] = new_applied[0] or applied
1718                expr = Expression(head, *expr._leaves)
1719            return expr, new_applied[0]
1720
1721    def replace_vars(
1722        self, vars, options=None, in_scoping=True, in_function=True
1723    ) -> "Expression":
1724        from mathics.builtin.scoping import get_scoping_vars
1725
1726        if not in_scoping:
1727            if (
1728                self._head.get_name()
1729                in ("System`Module", "System`Block", "System`With")
1730                and len(self._leaves) > 0
1731            ):  # nopep8
1732
1733                scoping_vars = set(
1734                    name for name, new_def in get_scoping_vars(self._leaves[0])
1735                )
1736                """for var in new_vars:
1737                    if var in scoping_vars:
1738                        del new_vars[var]"""
1739                vars = {
1740                    var: value for var, value in vars.items() if var not in scoping_vars
1741                }
1742
1743        leaves = self._leaves
1744        if in_function:
1745            if (
1746                self._head.get_name() == "System`Function"
1747                and len(self._leaves) > 1
1748                and (
1749                    self._leaves[0].has_form("List", None) or self._leaves[0].get_name()
1750                )
1751            ):
1752                if self._leaves[0].get_name():
1753                    func_params = [self._leaves[0].get_name()]
1754                else:
1755                    func_params = [leaf.get_name() for leaf in self._leaves[0]._leaves]
1756                if "" not in func_params:
1757                    body = self._leaves[1]
1758                    replacement = {name: Symbol(name + "$") for name in func_params}
1759                    func_params = [Symbol(name + "$") for name in func_params]
1760                    body = body.replace_vars(replacement, options, in_scoping)
1761                    leaves = chain(
1762                        [Expression(SymbolList, *func_params), body], self._leaves[2:]
1763                    )
1764
1765        if not vars:  # might just be a symbol set via Set[] we looked up here
1766            return self.shallow_copy()
1767
1768        return Expression(
1769            self._head.replace_vars(vars, options=options, in_scoping=in_scoping),
1770            *[
1771                leaf.replace_vars(vars, options=options, in_scoping=in_scoping)
1772                for leaf in leaves
1773            ]
1774        )
1775
1776    def replace_slots(self, slots, evaluation):
1777        if self._head.get_name() == "System`Slot":
1778            if len(self._leaves) != 1:
1779                evaluation.message_args("Slot", len(self._leaves), 1)
1780            else:
1781                slot = self._leaves[0].get_int_value()
1782                if slot is None or slot < 0:
1783                    evaluation.message("Function", "slot", self._leaves[0])
1784                elif slot > len(slots) - 1:
1785                    evaluation.message("Function", "slotn", slot)
1786                else:
1787                    return slots[int(slot)]
1788        elif self._head.get_name() == "System`SlotSequence":
1789            if len(self._leaves) != 1:
1790                evaluation.message_args("SlotSequence", len(self._leaves), 1)
1791            else:
1792                slot = self._leaves[0].get_int_value()
1793                if slot is None or slot < 1:
1794                    evaluation.error("Function", "slot", self._leaves[0])
1795            return Expression(SymbolSequence, *slots[slot:])
1796        elif self._head.get_name() == "System`Function" and len(self._leaves) == 1:
1797            # do not replace Slots in nested Functions
1798            return self
1799        return Expression(
1800            self._head.replace_slots(slots, evaluation),
1801            *[leaf.replace_slots(slots, evaluation) for leaf in self._leaves]
1802        )
1803
1804    def thread(self, evaluation, head=None) -> typing.Tuple[bool, "Expression"]:
1805        if head is None:
1806            head = Symbol("List")
1807
1808        items = []
1809        dim = None
1810        for leaf in self._leaves:
1811            if leaf.get_head().sameQ(head):
1812                if dim is None:
1813                    dim = len(leaf._leaves)
1814                    items = [(items + [innerleaf]) for innerleaf in leaf._leaves]
1815                elif len(leaf._leaves) != dim:
1816                    evaluation.message("Thread", "tdlen")
1817                    return True, self
1818                else:
1819                    for index in range(dim):
1820                        items[index].append(leaf._leaves[index])
1821            else:
1822                if dim is None:
1823                    items.append(leaf)
1824                else:
1825                    for item in items:
1826                        item.append(leaf)
1827        if dim is None:
1828            return False, self
1829        else:
1830            leaves = [Expression(self._head, *item) for item in items]
1831            return True, Expression(head, *leaves)
1832
1833    def is_numeric(self) -> bool:
1834        return self._head.get_name() in system_symbols(
1835            "Sqrt",
1836            "Times",
1837            "Plus",
1838            "Subtract",
1839            "Minus",
1840            "Power",
1841            "Abs",
1842            "Divide",
1843            "Sin",
1844        ) and all(leaf.is_numeric() for leaf in self._leaves)
1845        # TODO: complete list of numeric functions, or access NumericFunction
1846        # attribute
1847
1848    def numerify(self, evaluation) -> "Expression":
1849        _prec = None
1850        for leaf in self._leaves:
1851            if leaf.is_inexact():
1852                leaf_prec = leaf.get_precision()
1853                if _prec is None or leaf_prec < _prec:
1854                    _prec = leaf_prec
1855        if _prec is not None:
1856            new_leaves = self.get_mutable_leaves()
1857            for index in range(len(new_leaves)):
1858                leaf = new_leaves[index]
1859                # Don't "numerify" numbers: they should be numerified
1860                # automatically by the processing function,
1861                # and we don't want to lose exactness in e.g. 1.0+I.
1862                if not isinstance(leaf, Number):
1863                    n_expr = Expression(SymbolN, leaf, Integer(dps(_prec)))
1864                    n_result = n_expr.evaluate(evaluation)
1865                    if isinstance(n_result, Number):
1866                        new_leaves[index] = n_result
1867            return Expression(self._head, *new_leaves)
1868        else:
1869            return self
1870
1871    def get_atoms(self, include_heads=True):
1872        if include_heads:
1873            atoms = self._head.get_atoms()
1874        else:
1875            atoms = []
1876        for leaf in self._leaves:
1877            atoms.extend(leaf.get_atoms())
1878        return atoms
1879
1880    def __hash__(self):
1881        return hash(("Expression", self._head) + tuple(self._leaves))
1882
1883    def user_hash(self, update):
1884        update(("%s>%d>" % (self.get_head_name(), len(self._leaves))).encode("utf8"))
1885        for leaf in self._leaves:
1886            leaf.user_hash(update)
1887
1888    def __getnewargs__(self):
1889        return (self._head, self._leaves)
1890
1891
1892class Atom(BaseExpression):
1893    def is_atom(self) -> bool:
1894        return True
1895
1896    def equal2(self, rhs: Any) -> Optional[bool]:
1897        """Mathics two-argument Equal (==)
1898        returns True if self and rhs are identical.
1899        """
1900        if self.sameQ(rhs):
1901            return True
1902        if isinstance(rhs, Symbol) or not isinstance(rhs, Atom):
1903            return None
1904        return self == rhs
1905
1906    def has_form(self, heads, *leaf_counts) -> bool:
1907        if leaf_counts:
1908            return False
1909        name = self.get_atom_name()
1910        if isinstance(heads, tuple):
1911            return name in heads
1912        else:
1913            return heads == name
1914
1915    def has_symbol(self, symbol_name) -> bool:
1916        return False
1917
1918    def get_head(self) -> "Symbol":
1919        return Symbol(self.get_atom_name())
1920
1921    def get_atom_name(self) -> str:
1922        return self.__class__.__name__
1923
1924    def __repr__(self) -> str:
1925        return "<%s: %s>" % (self.get_atom_name(), self)
1926
1927    def replace_vars(self, vars, options=None, in_scoping=True) -> "Atom":
1928        return self
1929
1930    def replace_slots(self, slots, evaluation) -> "Atom":
1931        return self
1932
1933    def numerify(self, evaluation) -> "Atom":
1934        return self
1935
1936    def copy(self, reevaluate=False) -> "Atom":
1937        result = self.do_copy()
1938        result.original = self
1939        return result
1940
1941    def set_positions(self, position=None) -> None:
1942        self.position = position
1943
1944    def get_sort_key(self, pattern_sort=False):
1945        if pattern_sort:
1946            return [0, 0, 1, 1, 0, 0, 0, 1]
1947        else:
1948            raise NotImplementedError
1949
1950    def get_atoms(self, include_heads=True) -> typing.List["Atom"]:
1951        return [self]
1952
1953    def atom_to_boxes(self, f, evaluation):
1954        raise NotImplementedError
1955
1956
1957class Symbol(Atom):
1958    name: str
1959    sympy_dummy: Any
1960    defined_symbols = {}
1961
1962    def __new__(cls, name, sympy_dummy=None):
1963        name = ensure_context(name)
1964        self = cls.defined_symbols.get(name, None)
1965        if self is None:
1966            self = super(Symbol, cls).__new__(cls)
1967            self.name = name
1968            self.sympy_dummy = sympy_dummy
1969            # cls.defined_symbols[name] = self
1970        return self
1971
1972    def __str__(self) -> str:
1973        return self.name
1974
1975    def do_copy(self) -> "Symbol":
1976        return Symbol(self.name)
1977
1978    def boxes_to_text(self, **options) -> str:
1979        return str(self.name)
1980
1981    def atom_to_boxes(self, f, evaluation) -> "String":
1982        return String(evaluation.definitions.shorten_name(self.name))
1983
1984    def to_sympy(self, **kwargs):
1985        from mathics.builtin import mathics_to_sympy
1986
1987        if self.sympy_dummy is not None:
1988            return self.sympy_dummy
1989
1990        builtin = mathics_to_sympy.get(self.name)
1991        if (
1992            builtin is None
1993            or not builtin.sympy_name
1994            or not builtin.is_constant()  # nopep8
1995        ):
1996            return sympy.Symbol(sympy_symbol_prefix + self.name)
1997        return builtin.to_sympy(self, **kwargs)
1998
1999    def to_python(self, *args, **kwargs):
2000        if self == SymbolTrue:
2001            return True
2002        if self == SymbolFalse:
2003            return False
2004        if self == SymbolNull:
2005            return None
2006        n_evaluation = kwargs.get("n_evaluation")
2007        if n_evaluation is not None:
2008            value = Expression(SymbolN, self).evaluate(n_evaluation)
2009            return value.to_python()
2010
2011        if kwargs.get("python_form", False):
2012            return self.to_sympy(**kwargs)
2013        else:
2014            return self.name
2015
2016    def default_format(self, evaluation, form) -> str:
2017        return self.name
2018
2019    def get_attributes(self, definitions):
2020        return definitions.get_attributes(self.name)
2021
2022    def get_name(self) -> str:
2023        return self.name
2024
2025    def is_symbol(self) -> bool:
2026        return True
2027
2028    def get_sort_key(self, pattern_sort=False):
2029        if pattern_sort:
2030            return super(Symbol, self).get_sort_key(True)
2031        else:
2032            return [
2033                1 if self.is_numeric() else 2,
2034                2,
2035                Monomial({self.name: 1}),
2036                0,
2037                self.name,
2038                1,
2039            ]
2040
2041    def equal2(self, rhs: Any) -> Optional[bool]:
2042        """Mathics two-argument Equal (==) """
2043        if self.sameQ(rhs):
2044            return True
2045
2046        # Booleans are treated like constants, but all other symbols
2047        # are treated None. We could create a Bool class and
2048        # define equal2 in that, but for just this doesn't
2049        # seem to be worth it. If other things come up, this may change.
2050        if self in (SymbolTrue, SymbolFalse) and rhs in (SymbolTrue, SymbolFalse):
2051            return self == rhs
2052        return None
2053
2054    def sameQ(self, rhs: Any) -> bool:
2055        """Mathics SameQ"""
2056        return id(self) == id(rhs) or isinstance(rhs, Symbol) and self.name == rhs.name
2057
2058    def replace_vars(self, vars, options={}, in_scoping=True):
2059        assert all(fully_qualified_symbol_name(v) for v in vars)
2060        var = vars.get(self.name, None)
2061        if var is None:
2062            return self
2063        else:
2064            return var
2065
2066    def has_symbol(self, symbol_name) -> bool:
2067        return self.name == ensure_context(symbol_name)
2068
2069    def evaluate(self, evaluation):
2070        rules = evaluation.definitions.get_ownvalues(self.name)
2071        for rule in rules:
2072            result = rule.apply(self, evaluation, fully=True)
2073            if result is not None and not result.sameQ(self):
2074                return result.evaluate(evaluation)
2075        return self
2076
2077    def is_true(self) -> bool:
2078        return self == SymbolTrue
2079
2080    def is_numeric(self) -> bool:
2081        return self.name in system_symbols(
2082            "Pi", "E", "EulerGamma", "GoldenRatio", "MachinePrecision", "Catalan"
2083        )
2084
2085    def __hash__(self):
2086        return hash(("Symbol", self.name))  # to distinguish from String
2087
2088    def user_hash(self, update) -> None:
2089        update(b"System`Symbol>" + self.name.encode("utf8"))
2090
2091    def __getnewargs__(self):
2092        return (self.name, self.sympy_dummy)
2093
2094
2095# Some common Symbols. This list is sorted in alpabetic order.
2096SymbolAborted = Symbol("$Aborted")
2097SymbolAssociation = Symbol("Association")
2098SymbolByteArray = Symbol("ByteArray")
2099SymbolComplexInfinity = Symbol("ComplexInfinity")
2100SymbolDirectedInfinity = Symbol("DirectedInfinity")
2101SymbolFailed = Symbol("$Failed")
2102SymbolFalse = Symbol("False")
2103SymbolInfinity = Symbol("Infinity")
2104SymbolList = Symbol("List")
2105SymbolMakeBoxes = Symbol("MakeBoxes")
2106SymbolN = Symbol("N")
2107SymbolNull = Symbol("Null")
2108SymbolRule = Symbol("Rule")
2109SymbolSequence = Symbol("Sequence")
2110SymbolTrue = Symbol("True")
2111SymbolUndefined = Symbol("Undefined")
2112
2113
2114@lru_cache(maxsize=1024)
2115def from_mpmath(value, prec=None):
2116    "Converts mpf or mpc to Number."
2117    if isinstance(value, mpmath.mpf):
2118        if prec is None:
2119            return MachineReal(float(value))
2120        else:
2121            # HACK: use str here to prevent loss of precision
2122            return PrecisionReal(sympy.Float(str(value), prec))
2123    elif isinstance(value, mpmath.mpc):
2124        if value.imag == 0.0:
2125            return from_mpmath(value.real, prec)
2126        real = from_mpmath(value.real, prec)
2127        imag = from_mpmath(value.imag, prec)
2128        return Complex(real, imag)
2129    else:
2130        raise TypeError(type(value))
2131
2132
2133class Number(Atom):
2134    def __str__(self) -> str:
2135        return str(self.value)
2136
2137    def is_numeric(self) -> bool:
2138        return True
2139
2140
2141def _ExponentFunction(value):
2142    n = value.get_int_value()
2143    if -5 <= n <= 5:
2144        return SymbolNull
2145    else:
2146        return value
2147
2148
2149def _NumberFormat(man, base, exp, options):
2150    if exp.get_string_value():
2151        if options["_Form"] in (
2152            "System`InputForm",
2153            "System`OutputForm",
2154            "System`FullForm",
2155        ):
2156            return Expression("RowBox", Expression(SymbolList, man, String("*^"), exp))
2157        else:
2158            return Expression(
2159                "RowBox",
2160                Expression(
2161                    "List",
2162                    man,
2163                    String(options["NumberMultiplier"]),
2164                    Expression("SuperscriptBox", base, exp),
2165                ),
2166            )
2167    else:
2168        return man
2169
2170
2171_number_form_options = {
2172    "DigitBlock": [0, 0],
2173    "ExponentFunction": _ExponentFunction,
2174    "ExponentStep": 1,
2175    "NumberFormat": _NumberFormat,
2176    "NumberPadding": ["", "0"],
2177    "NumberPoint": ".",
2178    "NumberSigns": ["-", ""],
2179    "SignPadding": False,
2180    "NumberMultiplier": "\u00d7",
2181}
2182
2183
2184class Integer(Number):
2185    value: int
2186
2187    def __new__(cls, value) -> "Integer":
2188        n = int(value)
2189        self = super(Integer, cls).__new__(cls)
2190        self.value = n
2191        return self
2192
2193    @lru_cache()
2194    def __init__(self, value) -> "Integer":
2195        super().__init__()
2196
2197    def boxes_to_text(self, **options) -> str:
2198        return str(self.value)
2199
2200    def boxes_to_mathml(self, **options) -> str:
2201        return self.make_boxes("MathMLForm").boxes_to_mathml(**options)
2202
2203    def boxes_to_tex(self, **options) -> str:
2204        return str(self.value)
2205
2206    def make_boxes(self, form) -> "String":
2207        return String(str(self.value))
2208
2209    def atom_to_boxes(self, f, evaluation):
2210        return self.make_boxes(f.get_name())
2211
2212    def default_format(self, evaluation, form) -> str:
2213        return str(self.value)
2214
2215    def to_sympy(self, **kwargs):
2216        return sympy.Integer(self.value)
2217
2218    def to_mpmath(self):
2219        return mpmath.mpf(self.value)
2220
2221    def to_python(self, *args, **kwargs):
2222        return self.value
2223
2224    def round(self, d=None) -> typing.Union["MachineReal", "PrecisionReal"]:
2225        if d is None:
2226            return MachineReal(float(self.value))
2227        else:
2228            return PrecisionReal(sympy.Float(self.value, d))
2229
2230    def get_int_value(self) -> int:
2231        return self.value
2232
2233    def sameQ(self, other) -> bool:
2234        """Mathics SameQ"""
2235        return isinstance(other, Integer) and self.value == other.value
2236
2237    def evaluate(self, evaluation):
2238        evaluation.check_stopped()
2239        return self
2240
2241    def get_sort_key(self, pattern_sort=False):
2242        if pattern_sort:
2243            return super().get_sort_key(True)
2244        else:
2245            return [0, 0, self.value, 0, 1]
2246
2247    def do_copy(self) -> "Integer":
2248        return Integer(self.value)
2249
2250    def __hash__(self):
2251        return hash(("Integer", self.value))
2252
2253    def user_hash(self, update):
2254        update(b"System`Integer>" + str(self.value).encode("utf8"))
2255
2256    def __getnewargs__(self):
2257        return (self.value,)
2258
2259    def __neg__(self) -> "Integer":
2260        return Integer(-self.value)
2261
2262    @property
2263    def is_zero(self) -> bool:
2264        return self.value == 0
2265
2266Integer0 = Integer(0)
2267Integer1 = Integer(1)
2268
2269class Rational(Number):
2270    @lru_cache()
2271    def __new__(cls, numerator, denominator=1) -> "Rational":
2272        self = super().__new__(cls)
2273        self.value = sympy.Rational(numerator, denominator)
2274        return self
2275
2276    def atom_to_boxes(self, f, evaluation):
2277        return self.format(evaluation, f.get_name())
2278
2279    def to_sympy(self, **kwargs):
2280        return self.value
2281
2282    def to_mpmath(self):
2283        return mpmath.mpf(self.value)
2284
2285    def to_python(self, *args, **kwargs) -> float:
2286        return float(self.value)
2287
2288    def round(self, d=None) -> typing.Union["MachineReal", "PrecisionReal"]:
2289        if d is None:
2290            return MachineReal(float(self.value))
2291        else:
2292            return PrecisionReal(self.value.n(d))
2293
2294    def sameQ(self, other) -> bool:
2295        """Mathics SameQ"""
2296        return isinstance(other, Rational) and self.value == other.value
2297
2298    def numerator(self) -> "Integer":
2299        return Integer(self.value.as_numer_denom()[0])
2300
2301    def denominator(self) -> "Integer":
2302        return Integer(self.value.as_numer_denom()[1])
2303
2304    def do_format(self, evaluation, form) -> "Expression":
2305        assert fully_qualified_symbol_name(form)
2306        if form == "System`FullForm":
2307            return Expression(
2308                Expression("HoldForm", Symbol("Rational")),
2309                self.numerator(),
2310                self.denominator(),
2311            ).do_format(evaluation, form)
2312        else:
2313            numerator = self.numerator()
2314            minus = numerator.value < 0
2315            if minus:
2316                numerator = Integer(-numerator.value)
2317            result = Expression("Divide", numerator, self.denominator())
2318            if minus:
2319                result = Expression("Minus", result)
2320            result = Expression("HoldForm", result)
2321            return result.do_format(evaluation, form)
2322
2323    def default_format(self, evaluation, form) -> str:
2324        return "Rational[%s, %s]" % self.value.as_numer_denom()
2325
2326    def evaluate(self, evaluation) -> "Rational":
2327        evaluation.check_stopped()
2328        return self
2329
2330    def get_sort_key(self, pattern_sort=False):
2331        if pattern_sort:
2332            return super().get_sort_key(True)
2333        else:
2334            # HACK: otherwise "Bus error" when comparing 1==1.
2335            return [0, 0, sympy.Float(self.value), 0, 1]
2336
2337    def do_copy(self) -> "Rational":
2338        return Rational(self.value)
2339
2340    def __hash__(self):
2341        return hash(("Rational", self.value))
2342
2343    def user_hash(self, update) -> None:
2344        update(
2345            b"System`Rational>" + ("%s>%s" % self.value.as_numer_denom()).encode("utf8")
2346        )
2347
2348    def __getnewargs__(self):
2349        return (self.numerator().get_int_value(), self.denominator().get_int_value())
2350
2351    def __neg__(self) -> "Rational":
2352        return Rational(
2353            -self.numerator().get_int_value(), self.denominator().get_int_value()
2354        )
2355
2356    @property
2357    def is_zero(self) -> bool:
2358        return (
2359            self.numerator().is_zero
2360        )  # (implicit) and not (self.denominator().is_zero)
2361
2362RationalOneHalf = Rational(1, 2)
2363
2364class Real(Number):
2365    def __new__(cls, value, p=None) -> "Real":
2366        if isinstance(value, str):
2367            value = str(value)
2368            if p is None:
2369                digits = ("".join(re.findall("[0-9]+", value))).lstrip("0")
2370                if digits == "":  # Handle weird Mathematica zero case
2371                    p = max(prec(len(value.replace("0.", ""))), machine_precision)
2372                else:
2373                    p = prec(len(digits.zfill(dps(machine_precision))))
2374        elif isinstance(value, sympy.Float):
2375            if p is None:
2376                p = value._prec + 1
2377        elif isinstance(value, (Integer, sympy.Number, mpmath.mpf, float, int)):
2378            if p is not None and p > machine_precision:
2379                value = str(value)
2380        else:
2381            raise TypeError("Unknown number type: %s (type %s)" % (value, type(value)))
2382
2383        # return either machine precision or arbitrary precision real
2384        if p is None or p == machine_precision:
2385            return MachineReal.__new__(MachineReal, value)
2386        else:
2387            return PrecisionReal.__new__(PrecisionReal, value)
2388
2389    def boxes_to_text(self, **options) -> str:
2390        return self.make_boxes("System`OutputForm").boxes_to_text(**options)
2391
2392    def boxes_to_mathml(self, **options) -> str:
2393        return self.make_boxes("System`MathMLForm").boxes_to_mathml(**options)
2394
2395    def boxes_to_tex(self, **options) -> str:
2396        return self.make_boxes("System`TeXForm").boxes_to_tex(**options)
2397
2398    def atom_to_boxes(self, f, evaluation):
2399        return self.make_boxes(f.get_name())
2400
2401    def evaluate(self, evaluation) -> "Real":
2402        evaluation.check_stopped()
2403        return self
2404
2405    def get_sort_key(self, pattern_sort=False):
2406        if pattern_sort:
2407            return super().get_sort_key(True)
2408        return [0, 0, self.value, 0, 1]
2409
2410    def __eq__(self, other) -> bool:
2411        if isinstance(other, Real):
2412            # MMA Docs: "Approximate numbers that differ in their last seven
2413            # binary digits are considered equal"
2414            _prec = min_prec(self, other)
2415            with mpmath.workprec(_prec):
2416                rel_eps = 0.5 ** (_prec - 7)
2417                return mpmath.almosteq(
2418                    self.to_mpmath(), other.to_mpmath(), abs_eps=0, rel_eps=rel_eps
2419                )
2420        else:
2421            return self.get_sort_key() == other.get_sort_key()
2422
2423    def __ne__(self, other) -> bool:
2424        # Real is a total order
2425        return not (self == other)
2426
2427    def __hash__(self):
2428        # ignore last 7 binary digits when hashing
2429        _prec = self.get_precision()
2430        return hash(("Real", self.to_sympy().n(dps(_prec))))
2431
2432    def user_hash(self, update):
2433        # ignore last 7 binary digits when hashing
2434        _prec = self.get_precision()
2435        update(b"System`Real>" + str(self.to_sympy().n(dps(_prec))).encode("utf8"))
2436
2437    def get_atom_name(self) -> str:
2438        return "Real"
2439
2440
2441class MachineReal(Real):
2442    """
2443    Machine precision real number.
2444
2445    Stored internally as a python float.
2446    """
2447
2448    value: float
2449
2450    def __new__(cls, value) -> "MachineReal":
2451        self = Number.__new__(cls)
2452        self.value = float(value)
2453        if math.isinf(self.value) or math.isnan(self.value):
2454            raise OverflowError
2455        return self
2456
2457    def to_python(self, *args, **kwargs) -> float:
2458        return self.value
2459
2460    def to_sympy(self, *args, **kwargs):
2461        return sympy.Float(self.value)
2462
2463    def to_mpmath(self):
2464        return mpmath.mpf(self.value)
2465
2466    def round(self, d=None) -> "MachineReal":
2467        return self
2468
2469    def sameQ(self, other) -> bool:
2470        """Mathics SameQ"""
2471        if isinstance(other, MachineReal):
2472            return self.value == other.value
2473        elif isinstance(other, PrecisionReal):
2474            return self.to_sympy() == other.value
2475        return False
2476
2477    def is_machine_precision(self) -> bool:
2478        return True
2479
2480    def get_precision(self) -> int:
2481        return machine_precision
2482
2483    def get_float_value(self, permit_complex=False) -> float:
2484        return self.value
2485
2486    def make_boxes(self, form):
2487        from mathics.builtin.inout import number_form
2488
2489        _number_form_options["_Form"] = form  # passed to _NumberFormat
2490        if form in ("System`InputForm", "System`FullForm"):
2491            n = None
2492        else:
2493            n = 6
2494        return number_form(self, n, None, None, _number_form_options)
2495
2496    def __getnewargs__(self):
2497        return (self.value,)
2498
2499    def do_copy(self) -> "MachineReal":
2500        return MachineReal(self.value)
2501
2502    def __neg__(self) -> "MachineReal":
2503        return MachineReal(-self.value)
2504
2505    @property
2506    def is_zero(self) -> bool:
2507        return self.value == 0.0
2508
2509    @property
2510    def is_approx_zero(self) -> bool:
2511        # In WMA, Chop[10.^(-10)] == 0,
2512        # so, lets take it.
2513        res = abs(self.value) <= 1e-10
2514        return res
2515
2516
2517class PrecisionReal(Real):
2518    """
2519    Arbitrary precision real number.
2520
2521    Stored internally as a sympy.Float.
2522
2523    Note: Plays nicely with the mpmath.mpf (float) type.
2524    """
2525
2526    value: sympy.Float
2527
2528    def __new__(cls, value) -> "PrecisionReal":
2529        self = Number.__new__(cls)
2530        self.value = sympy.Float(value)
2531        return self
2532
2533    def to_python(self, *args, **kwargs):
2534        return float(self.value)
2535
2536    def to_sympy(self, *args, **kwargs):
2537        return self.value
2538
2539    def to_mpmath(self):
2540        return mpmath.mpf(self.value)
2541
2542    def round(self, d=None) -> typing.Union["MachineReal", "PrecisionReal"]:
2543        if d is None:
2544            return MachineReal(float(self.value))
2545        else:
2546            d = min(dps(self.get_precision()), d)
2547            return PrecisionReal(self.value.n(d))
2548
2549    def sameQ(self, other) -> bool:
2550        """Mathics SameQ"""
2551        if isinstance(other, PrecisionReal):
2552            return self.value == other.value
2553        elif isinstance(other, MachineReal):
2554            return self.value == other.to_sympy()
2555        return False
2556
2557    def get_precision(self) -> int:
2558        return self.value._prec + 1
2559
2560    def make_boxes(self, form):
2561        from mathics.builtin.inout import number_form
2562
2563        _number_form_options["_Form"] = form  # passed to _NumberFormat
2564        return number_form(
2565            self, dps(self.get_precision()), None, None, _number_form_options
2566        )
2567
2568    def __getnewargs__(self):
2569        return (self.value,)
2570
2571    def do_copy(self) -> "PrecisionReal":
2572        return PrecisionReal(self.value)
2573
2574    def __neg__(self) -> "PrecisionReal":
2575        return PrecisionReal(-self.value)
2576
2577    @property
2578    def is_zero(self) -> bool:
2579        return self.value == 0.0
2580
2581
2582class Complex(Number):
2583    """
2584    Complex wraps two real-valued Numbers.
2585    """
2586
2587    real: Any
2588    imag: Any
2589
2590    def __new__(cls, real, imag):
2591        self = super().__new__(cls)
2592        if isinstance(real, Complex) or not isinstance(real, Number):
2593            raise ValueError("Argument 'real' must be a real number.")
2594        if isinstance(imag, Complex) or not isinstance(imag, Number):
2595            raise ValueError("Argument 'imag' must be a real number.")
2596
2597        if imag.sameQ(Integer0):
2598            return real
2599
2600        if isinstance(real, MachineReal) and not isinstance(imag, MachineReal):
2601            imag = imag.round()
2602        if isinstance(imag, MachineReal) and not isinstance(real, MachineReal):
2603            real = real.round()
2604
2605        self.real = real
2606        self.imag = imag
2607        return self
2608
2609    def atom_to_boxes(self, f, evaluation):
2610        return self.format(evaluation, f.get_name())
2611
2612    def __str__(self) -> str:
2613        return str(self.to_sympy())
2614
2615    def to_sympy(self, **kwargs):
2616        return self.real.to_sympy() + sympy.I * self.imag.to_sympy()
2617
2618    def to_python(self, *args, **kwargs):
2619        return complex(self.real.to_python(*args, **kwargs),
2620                       self.imag.to_python(*args, **kwargs))
2621
2622    def to_mpmath(self):
2623        return mpmath.mpc(self.real.to_mpmath(), self.imag.to_mpmath())
2624
2625    def do_format(self, evaluation, form) -> "Expression":
2626        if form == "System`FullForm":
2627            return Expression(
2628                Expression("HoldForm", Symbol("Complex")), self.real, self.imag
2629            ).do_format(evaluation, form)
2630
2631        parts: typing.List[Any] = []
2632        if self.is_machine_precision() or not self.real.is_zero:
2633            parts.append(self.real)
2634        if self.imag.sameQ(Integer(1)):
2635            parts.append(Symbol("I"))
2636        else:
2637            parts.append(Expression("Times", self.imag, Symbol("I")))
2638
2639        if len(parts) == 1:
2640            result = parts[0]
2641        else:
2642            result = Expression("Plus", *parts)
2643
2644        return Expression("HoldForm", result).do_format(evaluation, form)
2645
2646    def default_format(self, evaluation, form) -> str:
2647        return "Complex[%s, %s]" % (
2648            self.real.default_format(evaluation, form),
2649            self.imag.default_format(evaluation, form),
2650        )
2651
2652    def get_sort_key(self, pattern_sort=False):
2653        if pattern_sort:
2654            return super().get_sort_key(True)
2655        else:
2656            return [0, 0, self.real.get_sort_key()[2], self.imag.get_sort_key()[2], 1]
2657
2658    def sameQ(self, other) -> bool:
2659        """Mathics SameQ"""
2660        return (
2661            isinstance(other, Complex)
2662            and self.real == other.real
2663            and self.imag == other.imag
2664        )
2665
2666    def evaluate(self, evaluation) -> "Complex":
2667        evaluation.check_stopped()
2668        return self
2669
2670    def round(self, d=None) -> "Complex":
2671        real = self.real.round(d)
2672        imag = self.imag.round(d)
2673        return Complex(real, imag)
2674
2675    def is_machine_precision(self) -> bool:
2676        if self.real.is_machine_precision() or self.imag.is_machine_precision():
2677            return True
2678        return False
2679
2680    def get_float_value(self, permit_complex=False) -> typing.Optional[complex]:
2681        if permit_complex:
2682            real = self.real.get_float_value()
2683            imag = self.imag.get_float_value()
2684            if real is not None and imag is not None:
2685                return complex(real, imag)
2686        else:
2687            return None
2688
2689    def get_precision(self) -> typing.Optional[int]:
2690        real_prec = self.real.get_precision()
2691        imag_prec = self.imag.get_precision()
2692        if imag_prec is None or real_prec is None:
2693            return None
2694        return min(real_prec, imag_prec)
2695
2696    def do_copy(self) -> "Complex":
2697        return Complex(self.real.do_copy(), self.imag.do_copy())
2698
2699    def __hash__(self):
2700        return hash(("Complex", self.real, self.imag))
2701
2702    def user_hash(self, update) -> None:
2703        update(b"System`Complex>")
2704        update(self.real)
2705        update(self.imag)
2706
2707    def __eq__(self, other) -> bool:
2708        if isinstance(other, Complex):
2709            return self.real == other.real and self.imag == other.imag
2710        else:
2711            return self.get_sort_key() == other.get_sort_key()
2712
2713    def __getnewargs__(self):
2714        return (self.real, self.imag)
2715
2716    def __neg__(self):
2717        return Complex(-self.real, -self.imag)
2718
2719    @property
2720    def is_zero(self) -> bool:
2721        return self.real.is_zero and self.imag.is_zero
2722
2723    @property
2724    def is_approx_zero(self) -> bool:
2725        real_zero = (
2726            self.real.is_approx_zero
2727            if hasattr(self.real, "is_approx_zero")
2728            else self.real.is_zero
2729        )
2730        imag_zero = (
2731            self.imag.is_approx_zero
2732            if hasattr(self.imag, "is_approx_zero")
2733            else self.imag.is_zero
2734        )
2735        return real_zero and imag_zero
2736
2737
2738def encode_mathml(text: str) -> str:
2739    text = text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
2740    text = text.replace('"', "&quot;").replace(" ", "&nbsp;")
2741    text = text.replace("\n", '<mspace linebreak="newline" />')
2742    return text
2743
2744
2745TEX_REPLACE = {
2746    "{": r"\{",
2747    "}": r"\}",
2748    "_": r"\_",
2749    "$": r"\$",
2750    "%": r"\%",
2751    "#": r"\#",
2752    "&": r"\&",
2753    "\\": r"\backslash{}",
2754    "^": r"{}^{\wedge}",
2755    "~": r"\sim{}",
2756    "|": r"\vert{}",
2757}
2758TEX_TEXT_REPLACE = TEX_REPLACE.copy()
2759TEX_TEXT_REPLACE.update(
2760    {
2761        "<": r"$<$",
2762        ">": r"$>$",
2763        "~": r"$\sim$",
2764        "|": r"$\vert$",
2765        "\\": r"$\backslash$",
2766        "^": r"${}^{\wedge}$",
2767    }
2768)
2769TEX_REPLACE_RE = re.compile("([" + "".join([re.escape(c) for c in TEX_REPLACE]) + "])")
2770
2771
2772def encode_tex(text: str, in_text=False) -> str:
2773    def replace(match):
2774        c = match.group(1)
2775        repl = TEX_TEXT_REPLACE if in_text else TEX_REPLACE
2776        # return TEX_REPLACE[c]
2777        return repl.get(c, c)
2778
2779    text = TEX_REPLACE_RE.sub(replace, text)
2780    text = text.replace("\n", "\\newline\n")
2781    return text
2782
2783
2784extra_operators = set(
2785    (
2786        ",",
2787        "(",
2788        ")",
2789        "[",
2790        "]",
2791        "{",
2792        "}",
2793        "\u301a",
2794        "\u301b",
2795        "\u00d7",
2796        "\u2032",
2797        "\u2032\u2032",
2798        " ",
2799        "\u2062",
2800        "\u222b",
2801        "\u2146",
2802    )
2803)
2804
2805
2806class String(Atom):
2807    value: str
2808
2809    def __new__(cls, value):
2810        self = super().__new__(cls)
2811        self.value = str(value)
2812        return self
2813
2814    def __str__(self) -> str:
2815        return '"%s"' % self.value
2816
2817    def boxes_to_text(self, show_string_characters=False, **options) -> str:
2818        value = self.value
2819
2820        if (
2821            not show_string_characters
2822            and value.startswith('"')  # nopep8
2823            and value.endswith('"')
2824        ):
2825            value = value[1:-1]
2826
2827        return value
2828
2829    def boxes_to_mathml(self, show_string_characters=False, **options) -> str:
2830        from mathics.core.parser import is_symbol_name
2831        from mathics.builtin import builtins_by_module
2832
2833        operators = set()
2834        for modname, builtins in builtins_by_module.items():
2835            for builtin in builtins:
2836                # name = builtin.get_name()
2837                operator = builtin.get_operator_display()
2838                if operator is not None:
2839                    operators.add(operator)
2840
2841        text = self.value
2842
2843        def render(format, string):
2844            encoded_text = encode_mathml(string)
2845            return format % encoded_text
2846
2847        if text.startswith('"') and text.endswith('"'):
2848            if show_string_characters:
2849                return render("<ms>%s</ms>", text[1:-1])
2850            else:
2851                outtext = ""
2852                for line in text[1:-1].split("\n"):
2853                    outtext += render("<mtext>%s</mtext>", line)
2854                return outtext
2855        elif text and ("0" <= text[0] <= "9" or text[0] == "."):
2856            return render("<mn>%s</mn>", text)
2857        else:
2858            if text in operators or text in extra_operators:
2859                if text == "\u2146":
2860                    return render(
2861                        '<mo form="prefix" lspace="0.2em" rspace="0">%s</mo>', text
2862                    )
2863                if text == "\u2062":
2864                    return render(
2865                        '<mo form="prefix" lspace="0" rspace="0.2em">%s</mo>', text
2866                    )
2867                return render("<mo>%s</mo>", text)
2868            elif is_symbol_name(text):
2869                return render("<mi>%s</mi>", text)
2870            else:
2871                outtext = ""
2872                for line in text.split("\n"):
2873                    outtext += render("<mtext>%s</mtext>", line)
2874                return outtext
2875
2876    def boxes_to_tex(self, show_string_characters=False, **options) -> str:
2877        from mathics.builtin import builtins_by_module
2878
2879        operators = set()
2880
2881        for modname, builtins in builtins_by_module.items():
2882            for builtin in builtins:
2883                operator = builtin.get_operator_display()
2884                if operator is not None:
2885                    operators.add(operator)
2886
2887        text = self.value
2888
2889        def render(format, string, in_text=False):
2890            return format % encode_tex(string, in_text)
2891
2892        if text.startswith('"') and text.endswith('"'):
2893            if show_string_characters:
2894                return render(r'\text{"%s"}', text[1:-1], in_text=True)
2895            else:
2896                return render(r"\text{%s}", text[1:-1], in_text=True)
2897        elif text and text[0] in "0123456789-.":
2898            return render("%s", text)
2899        else:
2900            if text == "\u2032":
2901                return "'"
2902            elif text == "\u2032\u2032":
2903                return "''"
2904            elif text == "\u2062":
2905                return " "
2906            elif text == "\u221e":
2907                return r"\infty "
2908            elif text == "\u00d7":
2909                return r"\times "
2910            elif text in ("(", "[", "{"):
2911                return render(r"\left%s", text)
2912            elif text in (")", "]", "}"):
2913                return render(r"\right%s", text)
2914            elif text == "\u301a":
2915                return r"\left[\left["
2916            elif text == "\u301b":
2917                return r"\right]\right]"
2918            elif text == "," or text == ", ":
2919                return text
2920            elif text == "\u222b":
2921                return r"\int"
2922            elif text == "\u2146":
2923                return r"\, d"
2924            elif text == "\u2211":
2925                return r"\sum"
2926            elif text == "\u220f":
2927                return r"\prod"
2928            elif len(text) > 1:
2929                return render(r"\text{%s}", text, in_text=True)
2930            else:
2931                return render("%s", text)
2932
2933    def atom_to_boxes(self, f, evaluation):
2934        inner = str(self.value)
2935
2936        if f.get_name() in system_symbols("InputForm", "FullForm"):
2937            inner = inner.replace("\\", "\\\\")
2938
2939        return String('"' + inner + '"')
2940
2941    def do_copy(self) -> "String":
2942        return String(self.value)
2943
2944    def default_format(self, evaluation, form) -> str:
2945        value = self.value.replace("\\", "\\\\").replace('"', '\\"')
2946        return '"%s"' % value
2947
2948    def get_sort_key(self, pattern_sort=False):
2949        if pattern_sort:
2950            return super().get_sort_key(True)
2951        else:
2952            return [0, 1, self.value, 0, 1]
2953
2954    def sameQ(self, other) -> bool:
2955        """Mathics SameQ"""
2956        return isinstance(other, String) and self.value == other.value
2957
2958    def get_string_value(self) -> str:
2959        return self.value
2960
2961    def to_sympy(self, **kwargs):
2962        return None
2963
2964    def to_python(self, *args, **kwargs) -> str:
2965        if kwargs.get("string_quotes", True):
2966            return '"%s"' % self.value  # add quotes to distinguish from Symbols
2967        else:
2968            return self.value
2969
2970
2971    def __hash__(self):
2972        return hash(("String", self.value))
2973
2974    def user_hash(self, update):
2975        # hashing a String is the one case where the user gets the untampered
2976        # hash value of the string's text. this corresponds to MMA behavior.
2977        update(self.value.encode("utf8"))
2978
2979    def __getnewargs__(self):
2980        return (self.value,)
2981
2982
2983class ByteArrayAtom(Atom):
2984    value: str
2985
2986    def __new__(cls, value):
2987        self = super().__new__(cls)
2988        if type(value) in (bytes, bytearray):
2989            self.value = value
2990        elif type(value) is list:
2991            self.value = bytearray(list)
2992        elif type(value) is str:
2993            self.value = base64.b64decode(value)
2994        else:
2995            raise Exception("value does not belongs to a valid type")
2996        return self
2997
2998    def __str__(self) -> str:
2999        return base64.b64encode(self.value).decode("utf8")
3000
3001    def boxes_to_text(self, **options) -> str:
3002        return '"' + self.__str__() + '"'
3003
3004    def boxes_to_mathml(self, **options) -> str:
3005        return encode_mathml(String('"' + self.__str__() + '"'))
3006
3007    def boxes_to_tex(self, **options) -> str:
3008        return encode_tex(String('"' + self.__str__() + '"'))
3009
3010    def atom_to_boxes(self, f, evaluation):
3011        res = String('""' + self.__str__() + '""')
3012        return res
3013
3014    def do_copy(self) -> "ByteArrayAtom":
3015        return ByteArrayAtom(self.value)
3016
3017    def default_format(self, evaluation, form) -> str:
3018        value = self.value
3019        return '"' + value.__str__() + '"'
3020
3021    def get_sort_key(self, pattern_sort=False):
3022        if pattern_sort:
3023            return super().get_sort_key(True)
3024        else:
3025            return [0, 1, self.value, 0, 1]
3026
3027    def sameQ(self, other) -> bool:
3028        """Mathics SameQ"""
3029        # FIX: check
3030        if isinstance(other, ByteArrayAtom):
3031            return self.value == other.value
3032        return False
3033
3034    def get_string_value(self) -> str:
3035        try:
3036            return self.value.decode("utf-8")
3037        except:
3038            return None
3039
3040    def to_sympy(self, **kwargs):
3041        return None
3042
3043    def to_python(self, *args, **kwargs) -> str:
3044        return self.value
3045
3046    def __hash__(self):
3047        return hash(("ByteArrayAtom", self.value))
3048
3049    def user_hash(self, update):
3050        # hashing a String is the one case where the user gets the untampered
3051        # hash value of the string's text. this corresponds to MMA behavior.
3052        update(self.value)
3053
3054    def __getnewargs__(self):
3055        return (self.value,)
3056
3057
3058class StringFromPython(String):
3059    def __new__(cls, value):
3060        self = super().__new__(cls, value)
3061        if isinstance(value, sympy.NumberSymbol):
3062            self.value = "sympy." + str(value)
3063
3064        # Note that the test is done with math.inf first.
3065        # This is to use float's ==, which may not strictly be necessary.
3066        if math.inf == value:
3067            self.value = "math.inf"
3068        return self
3069
3070
3071def get_default_value(name, evaluation, k=None, n=None):
3072    pos = []
3073    if k is not None:
3074        pos.append(k)
3075    if n is not None:
3076        pos.append(n)
3077    for pos_len in reversed(list(range(len(pos) + 1))):
3078        # Try patterns from specific to general
3079        defaultexpr = Expression(
3080            "Default", Symbol(name), *[Integer(index) for index in pos[:pos_len]]
3081        )
3082        result = evaluation.definitions.get_value(
3083            name, "System`DefaultValues", defaultexpr, evaluation
3084        )
3085        if result is not None:
3086            if result.sameQ(defaultexpr):
3087                result = result.evaluate(evaluation)
3088            return result
3089    return None
3090
3091
3092def print_parenthesizes(
3093    precedence, outer_precedence=None, parenthesize_when_equal=False
3094) -> bool:
3095    return outer_precedence is not None and (
3096        outer_precedence > precedence
3097        or (outer_precedence == precedence and parenthesize_when_equal)
3098    )
3099
3100
3101def _is_neutral_symbol(symbol_name, cache, evaluation):
3102    # a symbol is neutral if it does not invoke any rules, but is sure to make its Expression stay
3103    # the way it is (e.g. List[1, 2, 3] will always stay List[1, 2, 3], so long as nobody defines
3104    # a rule on this).
3105
3106    if cache:
3107        r = cache.get(symbol_name)
3108        if r is not None:
3109            return r
3110
3111    definitions = evaluation.definitions
3112
3113    definition = definitions.get_definition(symbol_name, only_if_exists=True)
3114    if definition is None:
3115        r = True
3116    else:
3117        r = all(
3118            len(definition.get_values_list(x)) == 0
3119            for x in ("up", "sub", "down", "own")
3120        )
3121
3122    if cache:
3123        cache[symbol_name] = r
3124
3125    return r
3126
3127
3128def _is_neutral_head(head, cache, evaluation):
3129    if not isinstance(head, Symbol):
3130        return False
3131
3132    return _is_neutral_symbol(head.get_name(), cache, evaluation)
3133
3134
3135# Structure helps implementations make the ExpressionCache not invalidate across simple commands
3136# such as Take[], Most[], etc. without this, constant reevaluation of lists happens, which results
3137# in quadratic runtimes for command like Fold[#1+#2&, Range[x]].
3138
3139# A good performance test case for Structure: x = Range[50000]; First[Timing[Partition[x, 15, 1]]]
3140
3141
3142class Structure(object):
3143    def __call__(self, leaves):
3144        # create an Expression with the given list "leaves" as leaves.
3145        # NOTE: the caller guarantees that "leaves" only contains items that are from "origins".
3146        raise NotImplementedError
3147
3148    def filter(self, expr, cond):
3149        # create an Expression with a subset of "expr".leaves (picked out by the filter "cond").
3150        # NOTE: the caller guarantees that "expr" is from "origins".
3151        raise NotImplementedError
3152
3153    def slice(self, expr, py_slice):
3154        # create an Expression, using the given slice of "expr".leaves as leaves.
3155        # NOTE: the caller guarantees that "expr" is from "origins".
3156        raise NotImplementedError
3157
3158
3159# UnlinkedStructure produces Expressions that are not linked to "origins" in terms of cache.
3160# This produces the same thing as doing Expression(head, *leaves).
3161
3162
3163class UnlinkedStructure(Structure):
3164    def __init__(self, head):
3165        self._head = head
3166        self._cache = None
3167
3168    def __call__(self, leaves):
3169        expr = Expression(self._head)
3170        expr._leaves = tuple(leaves)
3171        return expr
3172
3173    def filter(self, expr, cond):
3174        return self([leaf for leaf in expr._leaves if cond(leaf)])
3175
3176    def slice(self, expr, py_slice):
3177        leaves = expr._leaves
3178        lower, upper, step = py_slice.indices(len(leaves))
3179        if step != 1:
3180            raise ValueError("Structure.slice only supports slice steps of 1")
3181        return self(leaves[lower:upper])
3182
3183
3184# LinkedStructure produces Expressions that are linked to "origins" in terms of cache. This
3185# carries over information from the cache of the originating Expressions into the Expressions
3186# that are newly created.
3187
3188
3189class LinkedStructure(Structure):
3190    def __init__(self, head, cache):
3191        self._head = head
3192        self._cache = cache
3193
3194    def __call__(self, leaves):
3195        expr = Expression(self._head)
3196        expr._leaves = tuple(leaves)
3197        expr._cache = self._cache.reordered()
3198        return expr
3199
3200    def filter(self, expr, cond):
3201        return self([leaf for leaf in expr._leaves if cond(leaf)])
3202
3203    def slice(self, expr, py_slice):
3204        leaves = expr._leaves
3205        lower, upper, step = py_slice.indices(len(leaves))
3206        if step != 1:
3207            raise ValueError("Structure.slice only supports slice steps of 1")
3208
3209        new = Expression(self._head)
3210        new._leaves = tuple(leaves[lower:upper])
3211        if expr._cache:
3212            new._cache = expr._cache.sliced(lower, upper)
3213
3214        return new
3215
3216
3217def structure(head, origins, evaluation, structure_cache=None):
3218    # creates a Structure for building Expressions with head "head" and leaves
3219    # originating (exlusively) from "origins" (leaves are passed into the functions
3220    # of Structure further down).
3221
3222    # "origins" may either be an Expression (i.e. all leaves must originate from that
3223    # expression), a Structure (all leaves passed in this "self" Structure must be
3224    # manufactured using that Structure), or a list of Expressions (i.e. all leaves
3225    # must originate from one of the listed Expressions).
3226
3227    if isinstance(head, (str,)):
3228        head = Symbol(head)
3229
3230    if isinstance(origins, (Expression, Structure)):
3231        cache = origins._cache
3232        if cache is not None and not _is_neutral_head(
3233            head, structure_cache, evaluation
3234        ):
3235            cache = None
3236    elif isinstance(origins, (list, tuple)):
3237        if _is_neutral_head(head, structure_cache, evaluation):
3238            cache = ExpressionCache.union(origins, evaluation)
3239        else:
3240            cache = None
3241    else:
3242        raise ValueError("expected Expression, Structure, tuple or list as orig param")
3243
3244    if cache is None:
3245        return UnlinkedStructure(head)
3246    else:
3247        return LinkedStructure(head, cache)
3248
3249
3250def atom_list_constructor(evaluation, head, *atom_names):
3251    # if we encounter an Expression that consists wholly of atoms and those atoms (and the
3252    # expression's head) have no rules associated with them, we can speed up evaluation.
3253
3254    # note that you may use a constructor constructed via atom_list_constructor() only as
3255    # long as the evaluation's Definitions are guaranteed to not change.
3256
3257    if not _is_neutral_head(head, None, evaluation) or any(
3258        not atom for atom in atom_names
3259    ):
3260        optimize = False
3261    else:
3262        full_atom_names = [ensure_context(atom) for atom in atom_names]
3263
3264        if not all(
3265            _is_neutral_symbol(atom, None, evaluation) for atom in full_atom_names
3266        ):
3267            optimize = False
3268        else:
3269            optimize = True
3270
3271    if optimize:
3272
3273        def construct(leaves):
3274            expr = Expression(head)
3275            expr._leaves = list(leaves)
3276            sym = set(chain([head.get_name()], full_atom_names))
3277            expr._cache = ExpressionCache(evaluation.definitions.now, sym, None)
3278            return expr
3279
3280    else:
3281
3282        def construct(leaves):
3283            expr = Expression(head)
3284            expr._leaves = list(leaves)
3285            return expr
3286
3287    return construct
3288
3289
3290def string_list(head, leaves, evaluation):
3291    return atom_list_constructor(evaluation, head, "String")(leaves)
3292