-
-
Notifications
You must be signed in to change notification settings - Fork 55
/
expression.py
1975 lines (1685 loc) · 74.5 KB
/
expression.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# cython: language_level=3
# -*- coding: utf-8 -*-
import sympy
import math
import time
import typing
from typing import Any, Callable, Iterable, Optional, Tuple
from itertools import chain
from bisect import bisect_left
from mathics.core.atoms import Integer, Number, String
# FIXME: adjust mathics.core.attributes to uppercase attribute names
from mathics.core.attributes import (
flat as FLAT,
hold_all as HOLD_ALL,
hold_all_complete as HOLD_ALL_COMPLETE,
hold_first as HOLD_FIRST,
hold_rest as HOLD_REST,
listable as LISTABLE,
no_attributes as NO_ATTRIBUTES,
numeric_function as NUMERIC_FUNCTION,
orderless as ORDERLESS,
sequence_hold as SEQUENCE_HOLD,
)
from mathics.core.convert.sympy import sympy_symbol_prefix, SympyExpression
from mathics.core.convert.python import from_python
from mathics.core.element import ensure_context, ElementsProperties
from mathics.core.evaluation import Evaluation
from mathics.core.interrupt import ReturnInterrupt
from mathics.core.number import dps
from mathics.core.symbols import (
Atom,
BaseElement,
EvalMixin,
Monomial,
NumericOperators,
Symbol,
SymbolList,
SymbolN,
SymbolTimes,
SymbolTrue,
system_symbols,
)
from mathics.core.systemsymbols import (
SymbolAborted,
SymbolAlternatives,
SymbolBlank,
SymbolCondition,
SymbolDirectedInfinity,
SymbolSequence,
SymbolUnevaluated,
)
# from mathics.core.util import timeit
SymbolBlankSequence = Symbol("System`BlankSequence")
SymbolBlankNullSequence = Symbol("System`BlankNullSequence")
SymbolCompile = Symbol("Compile")
SymbolCompiledFunction = Symbol("CompiledFunction")
SymbolDefault = Symbol("Default")
SymbolFunction = Symbol("Function")
SymbolOptional = Symbol("Optional")
SymbolOptionsPattern = Symbol("OptionsPattern")
SymbolPattern = Symbol("Pattern")
SymbolPatternTest = Symbol("PatternTest")
SymbolSlot = Symbol("Slot")
SymbolSlotSequence = Symbol("SlotSequence")
SymbolVerbatim = Symbol("Verbatim")
symbols_arithmetic_operations = system_symbols(
"Sqrt",
"Times",
"Plus",
"Subtract",
"Minus",
"Power",
"Abs",
"Divide",
"Sin",
)
class BoxError(Exception):
def __init__(self, box, form) -> None:
super().__init__("Box %s cannot be formatted as %s" % (box, form))
self.box = box
self.form = form
# ExpressionCache keeps track of the following attributes for one Expression instance:
# time: (1) the last time (in terms of Definitions.now) this expression was evaluated
# or (2) None, if the current expression has not yet been evaluated (i.e. is new or
# changed).
# symbols: (1) a set of symbols occuring in this expression's head, its elements'
# heads, any of its sub expressions' heads or as Symbol leaves somewhere (maybe deep
# down) in the expression tree start by this expressions' leaves, or (2) None, if no
# information on which symbols are contained in this expression is available
# sequences: (1) a list of element indices that indicate the position of all Sequence
# heads that are either in the element's head or any of the indicated elements's sub
# expressions' heads, or (2) None, if no information is available.
class ExpressionCache:
def __init__(self, time=None, symbols=None, sequences=None, copy=None):
if copy is not None:
time = time or copy.time
symbols = symbols or copy.symbols
sequences = sequences or copy.sequences
self.time = time
self.symbols = symbols
self.sequences = sequences
def copy(self):
return ExpressionCache(self.time, self.symbols, self.sequences)
def sliced(self, lower, upper):
# indicates that the Expression's leaves have been slices with
# the given indices.
seq = self.sequences
if seq:
a = bisect_left(seq, lower) # all(val >= i for val in seq[a:])
b = bisect_left(seq, upper) # all(val >= j for val in seq[b:])
new_sequences = tuple(x - lower for x in seq[a:b])
elif seq is not None:
new_sequences = tuple()
else:
new_sequences = None
return ExpressionCache(self.time, self.symbols, new_sequences)
def reordered(self):
# indicates that the Expression's leaves have been reordered
# or reduced (i.e. that the leaves have changed, but that
# no new element instances were added).
sequences = self.sequences
# note that we keep sequences == [], since they are fine even
# after having reordered leaves.
if sequences:
sequences = None
return ExpressionCache(None, self.symbols, sequences)
@staticmethod
def union(expressions, evaluation) -> Optional["ExpressionCache"]:
definitions = evaluation.definitions
for expr in expressions:
if not hasattr(expr, "_cache") or expr.is_uncertain_final_definitions(
definitions
):
return None
# FIXME: this is workaround the current situtation that some
# Atoms, like String, have a cache even though they don't need
# it, by virtue of this getting set up in
# BaseElement.__init__. Removing the self._cache in there the
# causes Boxing to mess up. Untangle this mess.
if expr._cache is None:
return None
symbols = set.union(*[expr._cache.symbols for expr in expressions])
return ExpressionCache(
definitions.now, symbols, None if "System`Sequence" in symbols else tuple()
)
class Expression(BaseElement, NumericOperators, EvalMixin):
"""
A Mathics M-Expression.
A Mathics M-Expression is a list where the head is a function designator.
(In the more common S-Expression the head is an a Symbol. In Mathics this can be
an expression that acts as a function.
positional Arguments:
- head -- The head of the M-Expression
- *elements - optional: the remaining elements
Keyword Arguments:
- element_properties -- properties of the collection of elements
"""
head: "Symbol"
leaves: typing.List[Any]
_sequences: Any
def __init__(
self, head, *elements, elements_properties: Optional[ElementsProperties] = None
):
self.options = None
self.pattern_sequence = False
if isinstance(head, str):
# We should fix or convert to to_expression all nonSymbol uses.
head = Symbol(head)
self._head = head
# This is useful for finding potential improprer calls
# for element in elements:
# if not isinstance(element, BaseElement):
# from trepan.api import debug; debug()
# assert isinstance(element, BaseElement)
# Note: After we make a pass over all Expression() calls, these lines will get removed
# and replaced with the two commented-out lines below them:
self._elements, self.elements_properties = convert_expression_elements(
elements, from_python
)
assert isinstance(self._elements, tuple)
# self._elements = elements
# self.elements_properties = elements_properties
self._sequences = None
self._cache = None
# comment @mmatera: this cache should be useful in BoxConstruct, but not
# here...
self._format_cache = None
def __getnewargs__(self):
return (self._head, self._elements)
def __hash__(self):
return hash(("Expression", self._head) + tuple(self._elements))
def __repr__(self) -> str:
return "<Expression: %s>" % self
def __str__(self) -> str:
return "%s[%s]" % (
self._head,
", ".join([element.__str__() for element in self._elements]),
)
def _as_sympy_function(self, **kwargs) -> sympy.Function:
sym_args = [element.to_sympy(**kwargs) for element in self._elements]
if None in sym_args:
return None
f = sympy.Function(str(sympy_symbol_prefix + self.get_head_name()))
return f(*sym_args)
# Note: this function is called a *lot* so it needs to be fast.
def _build_elements_properties(self):
"""
Compute ElementsProperties and store in self.elements_properties
"""
# All of the properties start out optimistic (True) and are reset when that proves wrong.
self.elements_properties = ElementsProperties(True, True, True)
last_element = None
for element in self._elements:
# Test for the three properties mentioned above.
if not element.is_literal:
self.elements_properties.elements_fully_evaluated = False
if isinstance(element, Expression):
self.elements_properties.is_flat = False
if self.elements_properties.elements_fully_evaluated:
self._elements_fully_evaluated = (
element.elements_properties.elements_fully_evaluated
)
if self.elements_properties.is_ordered and last_element is not None:
try:
self.elements_properties.is_ordered = last_element <= element
except Exception:
self.elements_properties.is_ordered = False
last_element = element
def _flatten_sequence(self, sequence, evaluation) -> "Expression":
indices = self.sequences()
if not indices:
return self
elements = self._elements
flattened = []
extend = flattened.extend
k = 0
for i in indices:
extend(elements[k:i])
extend(sequence(elements[i]))
k = i + 1
extend(elements[k:])
return self.restructure(self._head, flattened, evaluation)
def _no_symbol(self, symbol_name):
# if this return True, it's safe to say that self.elements or its
# sub elements contain no Symbol with symbol_name. if this returns
# False, such a Symbol might or might not exist.
cache = self._cache
if cache is None:
return False
symbols = cache.symbols
if symbols is not None and symbol_name not in symbols:
return True
else:
return False
def _rebuild_cache(self):
cache = self._cache
if cache is None:
time = None
elif cache.symbols is None:
time = cache.time
elif cache.sequences is None:
time = cache.time
else:
return cache
sym = set((self.get_head_name(),))
seq = []
for i, element in enumerate(self._elements):
if isinstance(element, Expression):
element_symbols = element._rebuild_cache().symbols
sym.update(element_symbols)
if "System`Sequence" in element_symbols:
seq.append(i)
elif isinstance(element, Symbol):
sym.add(element.get_name())
cache = ExpressionCache(time, sym, seq)
self._cache = cache
return cache
def _timestamp_cache(self, evaluation):
self._cache = ExpressionCache(evaluation.definitions.now, copy=self._cache)
def clear_cache(self):
self._cache = None
def copy(self, reevaluate=False) -> "Expression":
expr = Expression(self._head.copy(reevaluate))
expr.elements = tuple(element.copy(reevaluate) for element in self._elements)
if not reevaluate:
# rebuilding the cache in self speeds up large operations, e.g.
# First[Timing[Fold[#1+#2&, Range[750]]]]
expr._cache = self._rebuild_cache()
expr.options = self.options
expr.original = self
expr._sequences = self._sequences
expr._format_cache = self._format_cache
return expr
def default_format(self, evaluation, form) -> str:
return "%s[%s]" % (
self._head.default_format(evaluation, form),
", ".join(
[element.default_format(evaluation, form) for element in self._elements]
),
)
def do_format(self, evaluation, form):
if self._format_cache is None:
self._format_cache = {}
if isinstance(form, str):
raise Exception("Expression.do_format\n", form, " should be a Symbol")
form = Symbol(form)
last_evaluated_time, expr = self._format_cache.get(form, (None, None))
if last_evaluated_time is not None and expr is not None:
symbolname = expr.get_name()
if symbolname != "":
if not evaluation.definitions.is_uncertain_final_value(
last_evaluated_time, set((symbolname,))
):
return expr
expr = super().do_format(evaluation, form)
self._format_cache[form] = (evaluation.definitions.now, expr)
return expr
@property
def elements(self):
return self._elements
@elements.setter
def elements(self, values: Iterable):
self._elements = tuple(values)
# Set to build self.elements_properties on next evaluation()
self.elements_properties = None
def equal2(self, rhs: Any) -> Optional[bool]:
"""Mathics two-argument Equal (==)
returns True if self and rhs are identical.
"""
if self.sameQ(rhs):
return True
# if rhs is an Atom, return None
elif isinstance(rhs, Atom):
return None
head = self._head
# Here we only need to deal with Expressions.
equal_heads = head.equal2(rhs._head)
if not equal_heads:
return equal_heads
# From here, we can assume that both heads are the same
if head in (SymbolList, SymbolSequence):
if len(self.elements) != len(rhs.elements):
return False
for item1, item2 in zip(self.elements, rhs.elements):
result = item1.equal2(item2)
if not result:
return result
return True
elif head in (SymbolDirectedInfinity,):
return self.elements[0].equal2(rhs.elements[0])
return None
# Note that the return type is some subclass of BaseElement, it could be
# a Real, an Expression, etc. It probably will *not* be a BaseElement since
# the point of evaluation when there is not an error is to produce a concrete result.
def evaluate(
self,
evaluation: Evaluation,
) -> typing.Type["BaseElement"]:
"""
Apply transformation rules and expression evaluation to ``evaluation`` via
``rewrite_apply_eval_step()`` until that method tells us to stop,
or unti we hit an $IterationLimit or TimeConstrained limit.
Evaluation is a recusive:``rewrite_apply_eval_step()`` may call us.
"""
if evaluation.timeout:
return
expr = self
reevaluate = True
limit = None
iteration = 1
names = set()
definitions = evaluation.definitions
old_options = evaluation.options
evaluation.inc_recursion_depth()
if evaluation.definitions.trace_evaluation:
if evaluation.definitions.timing_trace_evaluation:
evaluation.print_out(time.time() - evaluation.start_time)
evaluation.print_out(
" " * evaluation.recursion_depth + "Evaluating: %s" % expr
)
try:
# Evaluation loop:
while reevaluate:
# If definitions have not changed in the last evaluation,
# then evaluating again will produce the same result
if not expr.is_uncertain_final_definitions(definitions):
break
# Here the names of the lookupname of the expression
# are stored. This is necesary for the implementation
# of the builtin `Return[]`
names.add(expr.get_lookup_name())
# This loads the default options associated
# to the expression
if hasattr(expr, "options") and expr.options:
evaluation.options = expr.options
# ``rewrite_apply_eval_step()`` makes a pass at
# evaluating the expression. If we know that a further
# evaluation will not be needed, ``reevaluate`` is set
# False. Note that ``rewrite_apply_eval_step()`` can
# perform further ``evaluate`` and we will recurse
# back into this routine.
expr, reevaluate = expr.rewrite_apply_eval_step(evaluation)
if not reevaluate:
break
# TraceEvaluation[] logging.
if evaluation.definitions.trace_evaluation:
evaluation.print_out(
" " * evaluation.recursion_depth + "-> %s" % expr
)
iteration += 1
# Check whether we have hit $Iterationlimit: is the number of times
# ``reevaluate`` came back False in this loop.
if limit is None:
limit = definitions.get_config_value("$IterationLimit")
if limit is None:
limit = "inf"
if limit != "inf" and iteration > limit:
evaluation.error("$IterationLimit", "itlim", limit)
return SymbolAborted
# "Return gets discarded only if it was called from within the r.h.s.
# of a user-defined rule."
# http://mathematica.stackexchange.com/questions/29353/how-does-return-work
# Otherwise it propogates up.
#
except ReturnInterrupt as ret:
if names.intersection(definitions.user.keys()):
return ret.expr
else:
raise ret
finally:
# Restores the state
evaluation.options = old_options
evaluation.dec_recursion_depth()
return expr
def evaluate_elements(self, evaluation) -> "Expression":
elements = [
element.evaluate(evaluation) if isinstance(element, EvalMixin) else element
for element in self._elements
]
head = self._head.evaluate_elements(evaluation)
return Expression(head, *elements)
def filter(self, head, cond, evaluation):
# faster equivalent to: Expression(head, [element in self.elements if cond(element)])
return structure(head, self, evaluation).filter(self, cond)
# FIXME: go over and preserve elements_properties.
def flatten_pattern_sequence(self, evaluation):
def sequence(element):
flattened = element.flatten_pattern_sequence(evaluation)
if element.get_head() is SymbolSequence and element.pattern_sequence:
return flattened._elements
else:
return [flattened]
expr = self._flatten_sequence(sequence, evaluation)
if hasattr(self, "options"):
expr.options = self.options
if expr.elements_properties is None:
expr._build_elements_properties()
else:
expr.elements_properties.is_flat = True
return expr
def flatten_sequence(self, evaluation):
def sequence(element):
if element.get_head_name() == "System`Sequence":
return element._elements
else:
return [element]
return self._flatten_sequence(sequence, evaluation)
def flatten_with_respect_to_head(
self, head, pattern_only=False, callback=None, level=100
) -> "Expression":
"""
Flatten elements in self which have `head` in them.
The idea is that in an expression like:
Expression(Plus, 1, Expression(Plus, 2, 3), 4)
when "Plus" is specified as the head, this expression should get changed to:
Expression(Plus, 1, 2, 3, 4)
In other words, all of the Plus operands are collected to together into one operation.
This is more efficiently evaluated. Note that we only flatten Plus functions, not other functions,
whether or not they contain Plus.
So in:
Expression(Plus, Times(1, 2, Plus(3, 4)))
the expression is unchanged.
head: head element to be consdier flattening on. Only expressions with this will be flattened.
This is always the head element or the next head element of the expression that the
elements are drawn from
callback: a callback function called each time a element is flattened.
level: maximum depth to flatten. This often isn't used and seems to have been put in
as a potential safety measure possibly for the future. If you don't want a limit
on flattening pass a negative number.
pattern_only: if True, just apply to elements that are pattern_sequence (see ExpressionPattern.get_wrappings)
"""
if level == 0:
return self
if self._no_symbol(head.get_name()):
return self
sub_level = level - 1
do_flatten = False
for element in self._elements:
if element.get_head().sameQ(head) and (
not pattern_only or element.pattern_sequence
):
do_flatten = True
break
if do_flatten:
new_elements = []
for element in self._elements:
if element.get_head().sameQ(head) and (
not pattern_only or element.pattern_sequence
):
new_element = element.flatten_with_respect_to_head(
head, pattern_only, callback, level=sub_level
)
if callback is not None:
callback(new_element._elements, element)
new_elements.extend(new_element._elements)
else:
new_elements.append(element)
return Expression(self._head, *new_elements)
else:
return self
def get_atoms(self, include_heads=True):
"""Returns a list of atoms involved in the expression."""
# Comment @mmatera: maybe, what we really want here are the Symbol's
# involved in the expression, not the atoms.
if include_heads:
atoms = self._head.get_atoms()
else:
atoms = []
for element in self._elements:
atoms.extend(element.get_atoms())
return atoms
def get_attributes(self, definitions):
if self._head is SymbolFunction and len(self._elements) > 2:
res = self._elements[2]
if isinstance(res, Symbol):
return (str(res),)
elif res.has_form("List", None):
return set(str(a) for a in res._elements)
return NO_ATTRIBUTES
def get_elements(self):
# print("Use of get_elements is deprecated. Use elements instead.")
return self._elements
# Compatibily with old code. Deprecated, but remove after a little bit
get_leaves = get_elements
def get_head(self):
return self._head
def get_head_name(self):
return self._head.name if isinstance(self._head, Symbol) else ""
def get_lookup_name(self) -> bool:
lookup_symbol = self._head
while True:
if isinstance(lookup_symbol, Symbol):
return lookup_symbol.name
if isinstance(lookup_symbol, Atom):
return lookup_symbol.get_head().name
lookup_symbol = lookup_symbol._head
def get_mutable_elements(self) -> list:
"""
Return a shallow mutable copy of the elements
"""
return list(self._elements)
def get_option_values(
self, evaluation, allow_symbols=False, stop_on_error=True
) -> dict:
"""
Build a dictionary of options from an expression.
For example Symbol("Integrate").get_option_values(evaluation, allow_symbols=True)
will return a list of options associated to the definition of the symbol "Integrate".
"""
options = self
if options.has_form("List", None):
options = options.flatten_with_respect_to_head(SymbolList)
values = options.elements
else:
values = [options]
option_values = {}
for option in values:
symbol_name = option.get_name()
if allow_symbols and symbol_name:
options = evaluation.definitions.get_options(symbol_name)
option_values.update(options)
else:
if not option.has_form(("Rule", "RuleDelayed"), 2):
if stop_on_error:
return None
else:
continue
name = option.leaves[0].get_name()
if not name and isinstance(option.leaves[0], String):
name = ensure_context(option.leaves[0].get_string_value())
if not name:
if stop_on_error:
return None
else:
continue
option_values[name] = option.leaves[1]
return option_values
def get_sort_key(self, pattern_sort=False):
if pattern_sort:
"""
Pattern sort key structure:
0: 0/2: Atom / Expression
1: pattern: 0 / 11-31 for blanks / 1 for empty Alternatives /
40 for OptionsPattern
2: 0/1: 0 for PatternTest
3: 0/1: 0 for Pattern
4: 0/1: 1 for Optional
5: head / 0 for atoms
6: leaves / 0 for atoms
7: 0/1: 0 for Condition
"""
head = self._head
pattern = 0
if head is SymbolBlank:
pattern = 1
elif head is SymbolBlankSequence:
pattern = 2
elif head is SymbolBlankNullSequence:
pattern = 3
if pattern > 0:
if self._elements:
pattern += 10
else:
pattern += 20
if pattern > 0:
return [
2,
pattern,
1,
1,
0,
head.get_sort_key(True),
tuple(element.get_sort_key(True) for element in self._elements),
1,
]
if head is SymbolPatternTest:
if len(self._elements) != 2:
return [3, 0, 0, 0, 0, head, self._elements, 1]
sub = self._elements[0].get_sort_key(True)
sub[2] = 0
return sub
elif head is SymbolCondition:
if len(self._elements) != 2:
return [3, 0, 0, 0, 0, head, self._elements, 1]
sub = self._elements[0].get_sort_key(True)
sub[7] = 0
return sub
elif head is SymbolPattern:
if len(self._elements) != 2:
return [3, 0, 0, 0, 0, head, self._elements, 1]
sub = self._elements[1].get_sort_key(True)
sub[3] = 0
return sub
elif head is SymbolOptional:
if len(self._elements) not in (1, 2):
return [3, 0, 0, 0, 0, head, self._elements, 1]
sub = self._elements[0].get_sort_key(True)
sub[4] = 1
return sub
elif head is SymbolAlternatives:
min_key = [4]
min = None
for element in self._elements:
key = element.get_sort_key(True)
if key < min_key:
min = element
min_key = key
if min is None:
# empty alternatives -> very restrictive pattern
return [2, 1]
return min_key
elif head is SymbolVerbatim:
if len(self._elements) != 1:
return [3, 0, 0, 0, 0, head, self._elements, 1]
return self._elements[0].get_sort_key(True)
elif head is SymbolOptionsPattern:
return [2, 40, 0, 1, 1, 0, head, self._elements, 1]
else:
# Append [4] to leaves so that longer expressions have higher
# precedence
return [
2,
0,
1,
1,
0,
head.get_sort_key(True),
tuple(
chain(
(element.get_sort_key(True) for element in self._elements),
([4],),
)
),
1,
]
else:
exps = {}
head = self._head
if head is SymbolTimes:
for element in self._elements:
name = element.get_name()
if element.has_form("Power", 2):
var = element._elements[0].get_name()
exp = element._elements[1].round_to_float()
if var and exp is not None:
exps[var] = exps.get(var, 0) + exp
elif name:
exps[name] = exps.get(name, 0) + 1
elif self.has_form("Power", 2):
var = self._elements[0].get_name()
exp = self._elements[1].round_to_float()
if var and exp is not None:
exps[var] = exps.get(var, 0) + exp
if exps:
return [
1 if self.is_numeric() else 2,
2,
Monomial(exps),
1,
head,
self._elements,
1,
]
else:
return [1 if self.is_numeric() else 2, 3, head, self._elements, 1]
@property
def is_literal(self) -> bool:
"""
True if the value doesn't change after evaluation, i.e. a
value is set and it does not depend on definition
bindings. That is why, in contrast to
`is_uncertain_final_definitions()` we don't need a
`definitions` parameter.
"""
# Right now we are pessimisitic. We might consider changing this for
# Lists. Lists definitions can't be changed right?
return False
# If we have a List we may do something like:
# return self._elements_fully_evaluated
def is_uncertain_final_definitions(self, definitions) -> bool:
"""
Used in Expression.evaluate() to determine if we need to reevaluate
an expression.
"""
# Some Atoms just don't have a cache.
if not hasattr(self, "_cache"):
return False
cache = self._cache
# FIXME: why do we return True when no cache is found? Explain.
if cache is None:
return True
time = cache.time
if time is None:
return True
if cache.symbols is None:
cache = self._rebuild_cache()
return definitions.is_uncertain_final_value(time, cache.symbols)
def has_form(self, heads, *element_counts):
"""
element_counts:
(,): no elements allowed
(None,): no constraint on number of elements
(n, None): element count >= n
(n1, n2, ...): element count in {n1, n2, ...}
"""
head_name = self._head.get_name()
if isinstance(heads, (tuple, list, set)):
if head_name not in [ensure_context(h) for h in heads]:
return False
else:
if head_name != ensure_context(heads):
return False
if not element_counts:
return False
if element_counts and element_counts[0] is not None:
count = len(self._elements)
if count not in element_counts:
if (
len(element_counts) == 2
and element_counts[1] is None # noqa
and count >= element_counts[0]
):
return True
else:
return False
return True
def has_symbol(self, symbol_name) -> bool:
if self._no_symbol(symbol_name):
return False
return self._head.has_symbol(symbol_name) or any(
element.has_symbol(symbol_name) for element in self._elements
)
@property
def head(self):
return self._head
@head.setter
def head(self, value):
raise ValueError("Expression.head is write protected.")
# Deprecated - remove eventually
@property
def leaves(self):
return self._elements
# Deprecated - remove eventually
@leaves.setter
def leaves(self, value):
raise ValueError("Expression.leaves is write protected.")
def restructure(self, head, elements, evaluation, structure_cache=None, deps=None):
"""Faster equivalent of: Expression(head, *elements)
The caller guarantees that _all_ elements are either from
self.elements (or its subtrees) or from one of the expression given
in the tuple "deps" (or its subtrees).
If this method is called repeatedly, and the caller guarantees
that no definitions change between subsequent calls, then heads_cache
may be passed an initially empty dict to speed up calls.
"""
if deps is None:
deps = self
# FIXME: look over
s = structure(head, deps, evaluation, structure_cache=structure_cache)
return s(list(elements))
def rewrite_apply_eval_step(self, evaluation) -> typing.Tuple["Expression", bool]:
"""Perform a single rewrite/apply/eval step of the bigger
Expression.evaluate() process.
We return the Expression as well as a Boolean which indicates
whether the caller `evaluate()` should consider reevaluating
the expression.
Note that this is a recursive process: we may call something
that may call our parent: evaluate() which calls us again.
Also note that this step is time consuming, complicated, and involved.
Therefore, subclasses of the BaseEvaluation class may decide
to specialize this code so that it is simpler and faster. In
particular, a specialization for a particular kind of object
like a particular kind of Atom, may decide it does not need to
do the rule rewriting step. Or that it knows that after
performing this step no further transformation is needed.
See also https://mathics-development-guide.readthedocs.io/en/latest/extending/code-overview/evaluation.html#detailed-rewrite-apply-eval-process
"""
# Step 1 : evaluate the Head and get its Attributes. These attributes, used later, include
# HoldFirst / HoldAll / HoldRest / HoldAllComplete.
# Note: self._head can be not just a symbol, but some arbitrary expression.
# This is what makes expressions in Mathics be M-expressions rather than
# S-expressions.
head = self._head.evaluate(evaluation)
attributes = head.get_attributes(evaluation.definitions)
if self.elements_properties is None:
self._build_elements_properties()
# @timeit
def eval_elements():
# @timeit
def eval_range(indices):
recompute_properties = False