Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add stacktrace #1240

Merged
merged 5 commits into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions SYMBOLS_MANIFEST.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1119,6 +1119,7 @@ System`SquareSupersetEqual
System`SquareUnion
System`SquaredEuclideanDistance
System`SquaresR
System`Stacktrace
System`StandardDeviation
System`StandardForm
System`Star
Expand Down
51 changes: 46 additions & 5 deletions mathics/builtin/trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from time import time
from typing import Callable

import mathics.eval.trace
import mathics.eval.tracing
from mathics.core.attributes import A_HOLD_ALL, A_HOLD_ALL_COMPLETE, A_PROTECTED
from mathics.core.builtin import Builtin
Expand Down Expand Up @@ -124,7 +125,7 @@ class PrintTrace(_TraceBase):

Note: before '$TraceBuiltins' is set to 'True', 'PrintTrace[]' will print an empty
list.
>> PrintTrace[]
>> PrintTrace[] (* See console log *)

>> $TraceBuiltins = True
= True
Expand All @@ -148,6 +149,46 @@ def eval(self, evaluation, options={}):
return SymbolNull


class Stacktrace(_TraceBase):
"""
## <url>:trace native symbol:</url>

<dl>
<dt>'Stacktrace[]'
<dd>Print Mathics3 stack trace of evalutations leading to this point
</dl>

To show the Mathics3 evaluation stack at the \
point where expression $expr$ is evaluated, wrap $expr$ inside '{$expr$ Stacktrace[]}[1]]' \
or something similar.

Here is a complete example. To show the evaluation stack when computing a homegrown \
factorial function:

>> F[0] := {1, Stacktrace[]}[[1]]; F[n_] := n * F[n-1]

>> F[3] (* See console log *)
= 6

The actual 'Stacktrace[0]' call is hidden from the output; so when \
run on its own, nothing appears.

>> Stacktrace[]

#> Clear[F]
"""

summary_text = "print Mathics3 function stacktrace"

def eval(self, evaluation: Evaluation):
"Stacktrace[]"

# Use longer-form resolve call
# so a debugger can change this.
mathics.eval.trace.eval_Stacktrace()
return SymbolNull


class TraceBuiltins(_TraceBase):
"""
## <url>:trace native symbol:</url>
Expand All @@ -168,22 +209,22 @@ class TraceBuiltins(_TraceBase):
</ul>


>> TraceBuiltins[Graphics3D[Tetrahedron[]]]
>> TraceBuiltins[Graphics3D[Tetrahedron[]]] (* See console log *)
= -Graphics3D-

By default, the output is sorted by the name:
>> TraceBuiltins[Times[x, x]]
>> TraceBuiltins[Times[x, x]] (* See console log *)
= x ^ 2

By default, the output is sorted by the number of calls of the builtin from \
highest to lowest:
>> TraceBuiltins[Times[x, x], SortBy->"count"]
>> TraceBuiltins[Times[x, x], SortBy->"count"] (* See console log *)
= x ^ 2

You can have results ordered by name, or time.

Trace an expression and list the result by time from highest to lowest.
>> TraceBuiltins[Times[x, x], SortBy->"time"]
>> TraceBuiltins[Times[x, x], SortBy->"time"] (* See console log *)
= x ^ 2
"""

Expand Down
83 changes: 83 additions & 0 deletions mathics/eval/trace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""
Eval routines from mathics.trace
"""

import inspect
from math import log10
from typing import Any, Tuple

from mathics.core.expression import Expression


def eval_Stacktrace():
"""
Display the Python call stack but filtered so that we Builtin calls.
"""

frame = inspect.currentframe()
assert frame is not None
frame = frame.f_back
frame_number = -2
last_was_eval = False

frames = []
while frame is not None:
is_builtin, self_obj = is_showable_frame(frame)
if is_builtin:
# The two frames are always Stacktrace[]
# and Evaluate of that. So skip these.
if frame_number > 0 and not last_was_eval:
if isinstance(self_obj, Expression):
last_was_eval = False
frame_str = self_obj
else:
last_was_eval = True
builtin_class = self_obj.__class__
mathics_builtin_name = builtin_class.__name__
eval_name = frame.f_code.co_name
if hasattr(self_obj, eval_name):
docstring = getattr(self_obj, eval_name).__doc__
docstring = docstring.replace("%(name)s", mathics_builtin_name)
args_pattern = (
docstring[len(mathics_builtin_name) + 1 : -1]
if docstring.startswith(mathics_builtin_name)
else ""
)
else:
args_pattern = ""

frame_str = f"{mathics_builtin_name}[{args_pattern}]"
frames.append(frame_str)
frame_number += 1
frame = frame.f_back

# FIXME this should done in a separate function and the
# we should return the above.
n = len(frames)
max_width = int(log10(n + 1)) + 1
number_template = "%%%dd" % max_width
for frame_number, frame_str in enumerate(frames):
formatted_frame_number = number_template % (n - frame_number)
print(f"{formatted_frame_number}: {frame_str}")
pass


def is_showable_frame(frame) -> Tuple[bool, Any]:
"""
Return True if frame is the frame for an eval() function of a
Mathics3 Builtin function, {List,}Expression.evaluate(),
or a rewrite step.

We make the check based on whether the function name starts with "eval",
has a "self" parameter and the class that self is an instance of the Builtin
class.
"""
from mathics.core.builtin import Builtin
from mathics.core.expression import Expression

if not inspect.isframe(frame):
return False, None
if not frame.f_code.co_name.startswith("eval"):
return False, None
self_obj = frame.f_locals.get("self")
return isinstance(self_obj, (Builtin, Expression)), self_obj
2 changes: 1 addition & 1 deletion mathics/packages/Combinatorica-repo
Loading