Skip to content

Commit 8ca7514

Browse files
authored
Add stacktrace (#1240)
Start to implement StackTrace[]
1 parent 6b19e87 commit 8ca7514

File tree

4 files changed

+131
-6
lines changed

4 files changed

+131
-6
lines changed

SYMBOLS_MANIFEST.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1119,6 +1119,7 @@ System`SquareSupersetEqual
11191119
System`SquareUnion
11201120
System`SquaredEuclideanDistance
11211121
System`SquaresR
1122+
System`Stacktrace
11221123
System`StandardDeviation
11231124
System`StandardForm
11241125
System`Star

mathics/builtin/trace.py

+46-5
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from time import time
2323
from typing import Callable
2424

25+
import mathics.eval.trace
2526
import mathics.eval.tracing
2627
from mathics.core.attributes import A_HOLD_ALL, A_HOLD_ALL_COMPLETE, A_PROTECTED
2728
from mathics.core.builtin import Builtin
@@ -124,7 +125,7 @@ class PrintTrace(_TraceBase):
124125
125126
Note: before '$TraceBuiltins' is set to 'True', 'PrintTrace[]' will print an empty
126127
list.
127-
>> PrintTrace[]
128+
>> PrintTrace[] (* See console log *)
128129
129130
>> $TraceBuiltins = True
130131
= True
@@ -148,6 +149,46 @@ def eval(self, evaluation, options={}):
148149
return SymbolNull
149150

150151

152+
class Stacktrace(_TraceBase):
153+
"""
154+
## <url>:trace native symbol:</url>
155+
156+
<dl>
157+
<dt>'Stacktrace[]'
158+
<dd>Print Mathics3 stack trace of evalutations leading to this point
159+
</dl>
160+
161+
To show the Mathics3 evaluation stack at the \
162+
point where expression $expr$ is evaluated, wrap $expr$ inside '{$expr$ Stacktrace[]}[1]]' \
163+
or something similar.
164+
165+
Here is a complete example. To show the evaluation stack when computing a homegrown \
166+
factorial function:
167+
168+
>> F[0] := {1, Stacktrace[]}[[1]]; F[n_] := n * F[n-1]
169+
170+
>> F[3] (* See console log *)
171+
= 6
172+
173+
The actual 'Stacktrace[0]' call is hidden from the output; so when \
174+
run on its own, nothing appears.
175+
176+
>> Stacktrace[]
177+
178+
#> Clear[F]
179+
"""
180+
181+
summary_text = "print Mathics3 function stacktrace"
182+
183+
def eval(self, evaluation: Evaluation):
184+
"Stacktrace[]"
185+
186+
# Use longer-form resolve call
187+
# so a debugger can change this.
188+
mathics.eval.trace.eval_Stacktrace()
189+
return SymbolNull
190+
191+
151192
class TraceBuiltins(_TraceBase):
152193
"""
153194
## <url>:trace native symbol:</url>
@@ -168,22 +209,22 @@ class TraceBuiltins(_TraceBase):
168209
</ul>
169210
170211
171-
>> TraceBuiltins[Graphics3D[Tetrahedron[]]]
212+
>> TraceBuiltins[Graphics3D[Tetrahedron[]]] (* See console log *)
172213
= -Graphics3D-
173214
174215
By default, the output is sorted by the name:
175-
>> TraceBuiltins[Times[x, x]]
216+
>> TraceBuiltins[Times[x, x]] (* See console log *)
176217
= x ^ 2
177218
178219
By default, the output is sorted by the number of calls of the builtin from \
179220
highest to lowest:
180-
>> TraceBuiltins[Times[x, x], SortBy->"count"]
221+
>> TraceBuiltins[Times[x, x], SortBy->"count"] (* See console log *)
181222
= x ^ 2
182223
183224
You can have results ordered by name, or time.
184225
185226
Trace an expression and list the result by time from highest to lowest.
186-
>> TraceBuiltins[Times[x, x], SortBy->"time"]
227+
>> TraceBuiltins[Times[x, x], SortBy->"time"] (* See console log *)
187228
= x ^ 2
188229
"""
189230

mathics/eval/trace.py

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
"""
2+
Eval routines from mathics.trace
3+
"""
4+
5+
import inspect
6+
from math import log10
7+
from typing import Any, Tuple
8+
9+
from mathics.core.expression import Expression
10+
11+
12+
def eval_Stacktrace():
13+
"""
14+
Display the Python call stack but filtered so that we Builtin calls.
15+
"""
16+
17+
frame = inspect.currentframe()
18+
assert frame is not None
19+
frame = frame.f_back
20+
frame_number = -2
21+
last_was_eval = False
22+
23+
frames = []
24+
while frame is not None:
25+
is_builtin, self_obj = is_showable_frame(frame)
26+
if is_builtin:
27+
# The two frames are always Stacktrace[]
28+
# and Evaluate of that. So skip these.
29+
if frame_number > 0 and not last_was_eval:
30+
if isinstance(self_obj, Expression):
31+
last_was_eval = False
32+
frame_str = self_obj
33+
else:
34+
last_was_eval = True
35+
builtin_class = self_obj.__class__
36+
mathics_builtin_name = builtin_class.__name__
37+
eval_name = frame.f_code.co_name
38+
if hasattr(self_obj, eval_name):
39+
docstring = getattr(self_obj, eval_name).__doc__
40+
docstring = docstring.replace("%(name)s", mathics_builtin_name)
41+
args_pattern = (
42+
docstring[len(mathics_builtin_name) + 1 : -1]
43+
if docstring.startswith(mathics_builtin_name)
44+
else ""
45+
)
46+
else:
47+
args_pattern = ""
48+
49+
frame_str = f"{mathics_builtin_name}[{args_pattern}]"
50+
frames.append(frame_str)
51+
frame_number += 1
52+
frame = frame.f_back
53+
54+
# FIXME this should done in a separate function and the
55+
# we should return the above.
56+
n = len(frames)
57+
max_width = int(log10(n + 1)) + 1
58+
number_template = "%%%dd" % max_width
59+
for frame_number, frame_str in enumerate(frames):
60+
formatted_frame_number = number_template % (n - frame_number)
61+
print(f"{formatted_frame_number}: {frame_str}")
62+
pass
63+
64+
65+
def is_showable_frame(frame) -> Tuple[bool, Any]:
66+
"""
67+
Return True if frame is the frame for an eval() function of a
68+
Mathics3 Builtin function, {List,}Expression.evaluate(),
69+
or a rewrite step.
70+
71+
We make the check based on whether the function name starts with "eval",
72+
has a "self" parameter and the class that self is an instance of the Builtin
73+
class.
74+
"""
75+
from mathics.core.builtin import Builtin
76+
from mathics.core.expression import Expression
77+
78+
if not inspect.isframe(frame):
79+
return False, None
80+
if not frame.f_code.co_name.startswith("eval"):
81+
return False, None
82+
self_obj = frame.f_locals.get("self")
83+
return isinstance(self_obj, (Builtin, Expression)), self_obj

0 commit comments

Comments
 (0)