diff --git a/mathics/builtin/lowlevelprofile.py b/mathics/builtin/lowlevelprofile.py
new file mode 100644
index 000000000..6292a6779
--- /dev/null
+++ b/mathics/builtin/lowlevelprofile.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+
+"""
+Low-level Profiling
+
+Low-level (Python) profile from inside the Mathics interpreter
+
+"""
+
+import cProfile
+import pstats
+import sys
+from io import StringIO
+
+from mathics.builtin import Builtin
+from mathics.core.attributes import A_HOLD_ALL_COMPLETE, A_PROTECTED
+from mathics.core.convert.python import from_python
+from mathics.core.evaluation import Evaluation
+from mathics.core.expression import Expression
+from mathics.core.list import ListExpression
+from mathics.core.symbols import SymbolNull
+
+
+class PythonCProfileEvaluation(Builtin):
+ """
+ :Python:https://docs.python.org/3/library/profile.html
+
+
+ - 'PythonProfileEvaluation[$expr$]'
+
- profile $expr$ with the Python's cProfiler.
+
+
+ >> PythonCProfileEvaluation[a + b + 1]
+ = ...
+ """
+
+ attributes = A_HOLD_ALL_COMPLETE | A_PROTECTED
+ summary_text = "profile the internal evaluation of an expression"
+
+ def eval(self, expr: Expression, evaluation: Evaluation):
+ "PythonCProfileEvaluation[expr_]"
+ profile_result = SymbolNull
+ textstream = StringIO()
+ if sys.version_info >= (3, 8):
+ with cProfile.Profile() as pr:
+ result = expr.evaluate(evaluation)
+ stats = pstats.Stats(pr, stream=textstream)
+ stats.strip_dirs().sort_stats(-1).print_stats()
+ # TODO: convert the string (or the statistics)
+ # into something like a WL Table, by splitting the
+ # rows and the columns. By now, just a string
+ # is returned.
+ profile_result = from_python(textstream.getvalue())
+ else:
+ result = expr.evaluate(evaluation)
+ return ListExpression(result, profile_result)
diff --git a/mathics/docpipeline.py b/mathics/docpipeline.py
index 0696f4e59..5fc005940 100644
--- a/mathics/docpipeline.py
+++ b/mathics/docpipeline.py
@@ -58,7 +58,7 @@ def print_and_log(*args):
def compare(result, wanted) -> bool:
- if result == wanted:
+ if wanted == "..." or result == wanted:
return True
if result is None or wanted is None:
@@ -67,8 +67,10 @@ def compare(result, wanted) -> bool:
wanted = wanted.splitlines()
if result == [] and wanted == ["#<--#"]:
return True
+
if len(result) != len(wanted):
return False
+
for r, w in zip(result, wanted):
wanted_re = re.escape(w.strip())
wanted_re = wanted_re.replace("\\.\\.\\.", ".*?")