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("&", "&").replace("<", "<").replace(">", ">") 2740 text = text.replace('"', """).replace(" ", " ") 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