diff --git a/mathics/builtin/assignment.py b/mathics/builtin/assignment.py index 5cd94153e..1cd561e8a 100644 --- a/mathics/builtin/assignment.py +++ b/mathics/builtin/assignment.py @@ -65,6 +65,7 @@ class _SetOperator(object): def assign_elementary(self, lhs, rhs, evaluation, tags=None, upset=False): # TODO: This function should be splitted and simplified + evaluation.cache_result = False name = lhs.get_head_name() lhs._format_cache = None condition = None @@ -547,7 +548,7 @@ class Set(BinaryOperator, _SetOperator): def apply(self, lhs, rhs, evaluation): "lhs_ = rhs_" - + evaluation.cache_result = False self.assign(lhs, rhs, evaluation) return rhs @@ -600,7 +601,7 @@ class SetDelayed(Set): def apply(self, lhs, rhs, evaluation): "lhs_ := rhs_" - + evaluation.cache_result = False if self.assign(lhs, rhs, evaluation): return Symbol("Null") else: @@ -648,7 +649,7 @@ class UpSet(BinaryOperator, _SetOperator): def apply(self, lhs, rhs, evaluation): "lhs_ ^= rhs_" - + evaluation.cache_result = False self.assign_elementary(lhs, rhs, evaluation, upset=True) return rhs @@ -681,7 +682,7 @@ class UpSetDelayed(UpSet): def apply(self, lhs, rhs, evaluation): "lhs_ ^:= rhs_" - + evaluation.cache_result = False if self.assign_elementary(lhs, rhs, evaluation, upset=True): return Symbol("Null") else: @@ -723,7 +724,7 @@ class TagSet(Builtin, _SetOperator): def apply(self, f, lhs, rhs, evaluation): "f_ /: lhs_ = rhs_" - + evaluation.cache_result = False name = f.get_name() if not name: evaluation.message(self.get_name(), "sym", f, 1) @@ -747,7 +748,7 @@ class TagSetDelayed(TagSet): def apply(self, f, lhs, rhs, evaluation): "f_ /: lhs_ := rhs_" - + evaluation.cache_result = False name = f.get_name() if not name: evaluation.message(self.get_name(), "sym", f, 1) @@ -1240,6 +1241,7 @@ def do_clear(self, definition): def apply(self, symbols, evaluation): "%(name)s[symbols___]" + evaluation.cache_result = False if isinstance(symbols, Symbol): symbols = [symbols] elif isinstance(symbols, Expression): @@ -1276,6 +1278,8 @@ def apply(self, symbols, evaluation): def apply_all(self, evaluation): "Clear[System`All]" + evaluation.cache_result = False + evaluation.cache_expr = {} evaluation.definitions.set_user_definitions({}) evaluation.definitions.clear_pymathics_modules() return @@ -1315,6 +1319,8 @@ def do_clear(self, definition): def apply_all(self, evaluation): "ClearAll[System`All]" + evaluation.cache_result = False + evaluation.cache_expr = {} evaluation.definitions.set_user_definitions({}) evaluation.definitions.clear_pymathics_modules() return @@ -1403,6 +1409,7 @@ class Unset(PostfixOperator): def apply(self, expr, evaluation): "Unset[expr_]" + evaluation.cache_result = False name = expr.get_head_name() if name in system_symbols( @@ -1659,7 +1666,7 @@ class Messages(Builtin): def apply(self, symbol, evaluation): "Messages[symbol_]" - + evaluation.cache_result = False return get_symbol_values(symbol, "Messages", "messages", evaluation) @@ -1922,6 +1929,7 @@ class LoadModule(Builtin): def apply(self, module, evaluation): "LoadModule[module_String]" + evaluation.cache_result = False try: evaluation.definitions.load_pymathics_module(module.value) except PyMathicsLoadException: diff --git a/mathics/builtin/control.py b/mathics/builtin/control.py index 990d6b6d3..692b0560d 100644 --- a/mathics/builtin/control.py +++ b/mathics/builtin/control.py @@ -337,6 +337,7 @@ class For(Builtin): def apply(self, start, test, incr, body, evaluation): "For[start_, test_, incr_, body_]" + evaluation.cache_result = False while test.evaluate(evaluation).is_true(): evaluation.check_stopped() try: @@ -382,7 +383,7 @@ class While(Builtin): def apply(self, test, body, evaluation): "While[test_, body_]" - + evaluation.cache_result = False while test.evaluate(evaluation).is_true(): try: evaluation.check_stopped() @@ -645,7 +646,7 @@ class Abort(Builtin): def apply(self, evaluation): "Abort[]" - + evaluation.cache_result = False raise AbortInterrupt @@ -662,7 +663,7 @@ class Interrupt(Builtin): def apply(self, evaluation): "Interrupt[]" - + evaluation.cache_result = False raise AbortInterrupt @@ -707,7 +708,7 @@ class Return(Builtin): def apply(self, expr, evaluation): "Return[expr_]" - + evaluation.cache_result = False raise ReturnInterrupt(expr) @@ -729,7 +730,7 @@ class Break(Builtin): def apply(self, evaluation): "Break[]" - + evaluation.cache_result = False raise BreakInterrupt @@ -753,7 +754,7 @@ class Continue(Builtin): def apply(self, evaluation): "Continue[]" - + evaluation.cache_result = False raise ContinueInterrupt @@ -790,6 +791,7 @@ class Catch(Builtin): def apply1(self, expr, evaluation): "Catch[expr_]" + evaluation.cache_result = False try: ret = expr.evaluate(evaluation) except WLThrowInterrupt as e: @@ -798,6 +800,7 @@ def apply1(self, expr, evaluation): def apply3(self, expr, form, f, evaluation): "Catch[expr_, form_, f__:Identity]" + evaluation.cache_result = False try: ret = expr.evaluate(evaluation) except WLThrowInterrupt as e: @@ -840,8 +843,10 @@ class Throw(Builtin): def apply1(self, value, evaluation): "Throw[value_]" + evaluation.cache_result = False raise WLThrowInterrupt(value) def apply_with_tag(self, value, tag, evaluation): "Throw[value_, tag_]" + evaluation.cache_result = False raise WLThrowInterrupt(value, tag) diff --git a/mathics/builtin/datentime.py b/mathics/builtin/datentime.py index c415d7a31..0ab4bc004 100644 --- a/mathics/builtin/datentime.py +++ b/mathics/builtin/datentime.py @@ -121,6 +121,7 @@ class TimeRemaining(Builtin): def apply(self, evaluation): "TimeRemaining[]" + evaluation.cache_result = False if len(evaluation.timeout_queue) > 0: t, start_time = evaluation.timeout_queue[-1] curr_time = datetime.now().timestamp() @@ -167,10 +168,12 @@ class TimeConstrained(Builtin): def apply_2(self, expr, t, evaluation): "TimeConstrained[expr_, t_]" + evaluation.cache_result = False return self.apply_3(expr, t, SymbolAborted, evaluation) def apply_3(self, expr, t, failexpr, evaluation): "TimeConstrained[expr_, t_, failexpr_]" + evaluation.cache_result = False t = t.evaluate(evaluation) if not t.is_numeric(): evaluation.message("TimeConstrained", "timc", t) @@ -208,10 +211,10 @@ class Timing(Builtin): def apply(self, expr, evaluation): "Timing[expr_]" - start = time.process_time() result = expr.evaluate(evaluation) stop = time.process_time() + evaluation.cache_result = False return Expression("List", Real(stop - start), result) @@ -232,7 +235,7 @@ class AbsoluteTiming(Builtin): def apply(self, expr, evaluation): "AbsoluteTiming[expr_]" - + evaluation.cache_result = False start = time.time() result = expr.evaluate(evaluation) stop = time.time() @@ -498,7 +501,6 @@ class DateList(_DateFormat): def apply(self, epochtime, evaluation): "%(name)s[epochtime_]" datelist = self.to_datelist(epochtime, evaluation) - if datelist is None: return @@ -754,7 +756,7 @@ class AbsoluteTime(_DateFormat): def apply_now(self, evaluation): "AbsoluteTime[]" - + evaluation.cache_result = False return from_python(total_seconds(datetime.now() - EPOCH_START)) def apply_spec(self, epochtime, evaluation): @@ -849,6 +851,7 @@ def apply(self, evaluation): "TimeUsed[]" # time.process_time() is better than # time.clock(). See https://bugs.python.org/issue31803 + evaluation.cache_result = False return Real(time.process_time()) @@ -865,6 +868,7 @@ class SessionTime(Builtin): def apply(self, evaluation): "SessionTime[]" + evaluation.cache_result = False return Real(time.time() - START_TIME) @@ -886,11 +890,11 @@ class Pause(Builtin): def apply(self, n, evaluation): "Pause[n_]" + evaluation.cache_result = False sleeptime = n.to_python() if not isinstance(sleeptime, (int, float)) or sleeptime < 0: evaluation.message("Pause", "numnm", Expression("Pause", n)) return - time.sleep(sleeptime) return Symbol("Null") diff --git a/mathics/builtin/evaluation.py b/mathics/builtin/evaluation.py index 708698fd4..8d8a07da3 100644 --- a/mathics/builtin/evaluation.py +++ b/mathics/builtin/evaluation.py @@ -78,6 +78,7 @@ class RecursionLimit(Predefined): } def evaluate(self, evaluation) -> Integer: + evaluation.cache_result = False return Integer(self.value) @@ -138,6 +139,7 @@ class IterationLimit(Predefined): } def evaluate(self, evaluation): + evaluation.cache_result = False return Integer(self.value) diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py index 7d6bff361..db4fcecde 100644 --- a/mathics/builtin/files_io/files.py +++ b/mathics/builtin/files_io/files.py @@ -476,7 +476,7 @@ def check_options(self, options): def apply(self, channel, types, evaluation, options): "Read[channel_, types_, OptionsPattern[Read]]" - + evaluation.cache_result = False if channel.has_form("OutputStream", 2): evaluation.message("General", "openw", channel) return @@ -699,6 +699,7 @@ def reader(stream, word_separators, accepted=None): def apply_nostream(self, arg1, arg2, evaluation): "Read[arg1_, arg2_]" + evaluation.cache_result = False evaluation.message("General", "stream", arg1) return @@ -726,6 +727,7 @@ class Write(Builtin): def apply(self, channel, expr, evaluation): "Write[channel_, expr___]" + evaluation.cache_result = False strm = _channel_to_stream(channel) @@ -1314,10 +1316,12 @@ class BinaryWrite(Builtin): def apply_notype(self, name, n, b, evaluation): "BinaryWrite[OutputStream[name_, n_], b_]" + evaluation.cache_result = False return self.apply(name, n, b, None, evaluation) def apply(self, name, n, b, typ, evaluation): "BinaryWrite[OutputStream[name_, n_], b_, typ_]" + evaluation.cache_result = False channel = Expression("OutputStream", name, n) @@ -1679,10 +1683,12 @@ class BinaryRead(Builtin): def apply_empty(self, name, n, evaluation): "BinaryRead[InputStream[name_, n_]]" + evaluation.cache_result = False return self.apply(name, n, None, evaluation) def apply(self, name, n, typ, evaluation): "BinaryRead[InputStream[name_, n_], typ_]" + evaluation.cache_result = False channel = Expression("InputStream", name, n) @@ -1783,6 +1789,7 @@ class WriteString(Builtin): def apply(self, channel, expr, evaluation): "WriteString[channel_, expr___]" + evaluation.cache_result = False strm = _channel_to_stream(channel, "w") if strm is None: @@ -1843,6 +1850,7 @@ class _OpenAction(Builtin): def apply_empty(self, evaluation, options): "%(name)s[OptionsPattern[]]" + evaluation.cache_result = False if isinstance(self, (OpenWrite, OpenAppend)): tmpf = tempfile.NamedTemporaryFile(dir=TMP_DIR) @@ -1855,6 +1863,7 @@ def apply_empty(self, evaluation, options): def apply_path(self, path, evaluation, options): "%(name)s[path_?NotOptionQ, OptionsPattern[]]" + evaluation.cache_result = False # Options # BinaryFormat @@ -2026,6 +2035,7 @@ class Get(PrefixOperator): def apply(self, path, evaluation, options): "Get[path_String, OptionsPattern[Get]]" + evaluation.cache_result = False def check_options(options): # Options @@ -2078,6 +2088,8 @@ def check_options(options): def apply_default(self, filename, evaluation): "Get[filename_]" + evaluation.cache_result = False + expr = Expression("Get", filename) evaluation.message("General", "stream", filename) return expr @@ -2139,6 +2151,8 @@ class Put(BinaryOperator): def apply(self, exprs, filename, evaluation): "Put[exprs___, filename_String]" + evaluation.cache_result = False + instream = Expression("OpenWrite", filename).evaluate(evaluation) if len(instream.leaves) == 2: name, n = instream.leaves @@ -2150,6 +2164,8 @@ def apply(self, exprs, filename, evaluation): def apply_input(self, exprs, name, n, evaluation): "Put[exprs___, OutputStream[name_, n_]]" + evaluation.cache_result = False + stream = stream_manager.lookup_stream(n.get_int_value()) if stream is None or stream.io.closed: @@ -2169,6 +2185,8 @@ def apply_input(self, exprs, name, n, evaluation): def apply_default(self, exprs, filename, evaluation): "Put[exprs___, filename_]" + evaluation.cache_result = False + expr = Expression("Put", exprs, filename) evaluation.message("General", "stream", filename) return expr @@ -2229,6 +2247,8 @@ class PutAppend(BinaryOperator): def apply(self, exprs, filename, evaluation): "PutAppend[exprs___, filename_String]" + evaluation.cache_result = False + instream = Expression("OpenAppend", filename).evaluate(evaluation) if len(instream.leaves) == 2: name, n = instream.leaves @@ -2240,6 +2260,8 @@ def apply(self, exprs, filename, evaluation): def apply_input(self, exprs, name, n, evaluation): "PutAppend[exprs___, OutputStream[name_, n_]]" + evaluation.cache_result = False + stream = stream_manager.lookup_stream(n.get_int_value()) if stream is None or stream.io.closed: @@ -2259,6 +2281,8 @@ def apply_input(self, exprs, name, n, evaluation): def apply_default(self, exprs, filename, evaluation): "PutAppend[exprs___, filename_]" + evaluation.cache_result = False + expr = Expression("PutAppend", exprs, filename) evaluation.message("General", "stream", filename) return expr @@ -2331,6 +2355,7 @@ class ReadList(Read): def apply(self, channel, types, evaluation, options): "ReadList[channel_, types_, OptionsPattern[ReadList]]" + evaluation.cache_result = False # Options # TODO: Implement extra options @@ -2358,6 +2383,7 @@ def apply(self, channel, types, evaluation, options): def apply_m(self, channel, types, m, evaluation, options): "ReadList[channel_, types_, m_, OptionsPattern[ReadList]]" + evaluation.cache_result = False # Options # TODO: Implement extra options @@ -2425,6 +2451,8 @@ class FilePrint(Builtin): def apply(self, path, evaluation, options): "FilePrint[path_ OptionsPattern[FilePrint]]" + evaluation.cache_result = False + pypath = path.to_python() if not ( isinstance(pypath, str) @@ -2507,6 +2535,7 @@ class Close(Builtin): def apply(self, channel, evaluation): "Close[channel_]" + evaluation.cache_result = False if channel.has_form(("InputStream", "OutputStream"), 2): [name, n] = channel.get_leaves() @@ -2544,6 +2573,7 @@ class StreamPosition(Builtin): def apply_input(self, name, n, evaluation): "StreamPosition[InputStream[name_, n_]]" stream = stream_manager.lookup_stream(n.get_int_value()) + evaluation.cache_result = False if stream is None or stream.io is None or stream.io.closed: evaluation.message("General", "openx", name) @@ -2553,10 +2583,14 @@ def apply_input(self, name, n, evaluation): def apply_output(self, name, n, evaluation): "StreamPosition[OutputStream[name_, n_]]" + evaluation.cache_result = False + self.input_apply(name, n, evaluation) def apply_default(self, stream, evaluation): "StreamPosition[stream_]" + evaluation.cache_result = False + evaluation.message("General", "stream", stream) return @@ -2604,6 +2638,8 @@ class SetStreamPosition(Builtin): def apply_input(self, name, n, m, evaluation): "SetStreamPosition[InputStream[name_, n_], m_]" + evaluation.cache_result = False + stream = stream_manager.lookup_stream(n.get_int_value()) if stream is None or stream.io is None or stream.io.closed: @@ -2635,10 +2671,14 @@ def apply_input(self, name, n, m, evaluation): def apply_output(self, name, n, m, evaluation): "SetStreamPosition[OutputStream[name_, n_], m_]" + evaluation.cache_result = False + return self.apply_input(name, n, m, evaluation) def apply_default(self, stream, evaluation): "SetStreamPosition[stream_]" + evaluation.cache_result = False + evaluation.message("General", "stream", stream) return @@ -2691,6 +2731,7 @@ class Skip(Read): def apply(self, name, n, types, m, evaluation, options): "Skip[InputStream[name_, n_], types_, m_, OptionsPattern[Skip]]" + evaluation.cache_result = False channel = Expression("InputStream", name, n) @@ -2754,6 +2795,7 @@ class Find(Read): def apply(self, name, n, text, evaluation, options): "Find[InputStream[name_, n_], text_, OptionsPattern[Find]]" + evaluation.cache_result = False # Options # TODO Implement extra options @@ -2811,6 +2853,8 @@ class InputStream(Builtin): def apply(self, name, n, evaluation): "InputStream[name_, n_]" + evaluation.cache_result = False + return @@ -2831,6 +2875,8 @@ class OutputStream(Builtin): def apply(self, name, n, evaluation): "OutputStream[name_, n_]" + evaluation.cache_result = False + return @@ -2858,6 +2904,8 @@ class StringToStream(Builtin): def apply(self, string, evaluation): "StringToStream[string_]" + evaluation.cache_result = False + pystring = string.to_python()[1:-1] fp = io.StringIO(str(pystring)) @@ -2892,10 +2940,14 @@ class Streams(Builtin): def apply(self, evaluation): "Streams[]" + evaluation.cache_result = False + return self.apply_name(None, evaluation) def apply_name(self, name, evaluation): "Streams[name_String]" + evaluation.cache_result = False + result = [] for stream in stream_manager.STREAMS.values(): if stream is None or stream.io.closed: diff --git a/mathics/builtin/files_io/importexport.py b/mathics/builtin/files_io/importexport.py index 176d34dbd..b69a1fb61 100644 --- a/mathics/builtin/files_io/importexport.py +++ b/mathics/builtin/files_io/importexport.py @@ -1093,6 +1093,7 @@ class RegisterImport(Builtin): def apply(self, formatname, function, posts, evaluation, options): """ImportExport`RegisterImport[formatname_String, function_, posts_, OptionsPattern[ImportExport`RegisterImport]]""" + evaluation.cache_result = False if function.has_form("List", None): leaves = function.get_leaves() @@ -1175,6 +1176,8 @@ class RegisterExport(Builtin): def apply(self, formatname, function, evaluation, options): """ImportExport`RegisterExport[formatname_String, function_, OptionsPattern[ImportExport`RegisterExport]]""" + + evaluation.cache_result = False EXPORTERS[formatname.get_string_value()] = (function, options) return Symbol("Null") @@ -1205,6 +1208,7 @@ def apply(self, url, elements, evaluation, options={}): import tempfile import os + evaluation.cache_result = False py_url = url.get_string_value() temp_handle, temp_path = tempfile.mkstemp(suffix="") @@ -1329,18 +1333,21 @@ class Import(Builtin): def apply(self, filename, evaluation, options={}): "Import[filename_, OptionsPattern[]]" + evaluation.cache_result = False return self.apply_elements( filename, Expression(SymbolList), evaluation, options ) def apply_element(self, filename, element, evaluation, options={}): "Import[filename_, element_String, OptionsPattern[]]" + evaluation.cache_result = False return self.apply_elements( filename, Expression(SymbolList, element), evaluation, options ) def apply_elements(self, filename, elements, evaluation, options={}): "Import[filename_, elements_List?(AllTrue[#, NotOptionQ]&), OptionsPattern[]]" + evaluation.cache_result = False # Check filename path = filename.to_python() if not (isinstance(path, str) and path[0] == path[-1] == '"'): @@ -1742,7 +1749,7 @@ class Export(Builtin): def apply(self, filename, expr, evaluation, **options): "Export[filename_, expr_, OptionsPattern[Export]]" - + evaluation.cache_result = False # Check filename if not self._check_filename(filename, evaluation): return SymbolFailed @@ -1760,13 +1767,14 @@ def apply(self, filename, expr, evaluation, **options): def apply_element(self, filename, expr, element, evaluation, options={}): "Export[filename_, expr_, element_String, OptionsPattern[]]" + evaluation.cache_result = False return self.apply_elements( filename, expr, Expression(SymbolList, element), evaluation, options ) def apply_elements(self, filename, expr, elems, evaluation, options={}): "Export[filename_, expr_, elems_List?(AllTrue[#, NotOptionQ]&), OptionsPattern[]]" - + evaluation.cache_result = False # Check filename if not self._check_filename(filename, evaluation): return SymbolFailed @@ -2095,7 +2103,7 @@ class FileFormat(Builtin): def apply(self, filename, evaluation): "FileFormat[filename_String]" - + evaluation.cache_result = False findfile = Expression("FindFile", filename).evaluate(evaluation) if findfile == SymbolFailed: evaluation.message( diff --git a/mathics/builtin/inout.py b/mathics/builtin/inout.py index 4ef568e3f..e893a8a7e 100644 --- a/mathics/builtin/inout.py +++ b/mathics/builtin/inout.py @@ -1859,7 +1859,7 @@ class Print(Builtin): def apply(self, expr, evaluation): "Print[expr__]" - + evaluation.cache_result = False expr = expr.get_sequence() expr = Expression("Row", Expression(SymbolList, *expr)) evaluation.print_out(expr) diff --git a/mathics/builtin/lists.py b/mathics/builtin/lists.py index 3ed247cd4..e06d8c097 100644 --- a/mathics/builtin/lists.py +++ b/mathics/builtin/lists.py @@ -2906,6 +2906,7 @@ class AppendTo(Builtin): def apply(self, s, item, evaluation): "AppendTo[s_, item_]" + evaluation.cache_result = False resolved_s = s.evaluate(evaluation) if s == resolved_s: return evaluation.message("AppendTo", "rvalue", s) @@ -3008,6 +3009,7 @@ class PrependTo(Builtin): def apply(self, s, item, evaluation): "PrependTo[s_, item_]" + evaluation.cache_result = False resolved_s = s.evaluate(evaluation) if s == resolved_s: return evaluation.message("PrependTo", "rvalue", s) diff --git a/mathics/builtin/manipulate.py b/mathics/builtin/manipulate.py index 5cf8d80da..3bae9cbc7 100755 --- a/mathics/builtin/manipulate.py +++ b/mathics/builtin/manipulate.py @@ -289,6 +289,7 @@ class Manipulate(Builtin): def apply(self, expr, args, evaluation): "Manipulate[expr_, args__]" + evaluation.cache_result = False if (not _jupyter) or (not Kernel.initialized()) or (Kernel.instance() is None): return evaluation.message("Manipulate", "jupyter") diff --git a/mathics/builtin/numbers/randomnumbers.py b/mathics/builtin/numbers/randomnumbers.py index 476730076..53c9a96e8 100644 --- a/mathics/builtin/numbers/randomnumbers.py +++ b/mathics/builtin/numbers/randomnumbers.py @@ -165,7 +165,7 @@ class RandomState(Builtin): def apply(self, evaluation): "$RandomState" - + evaluation.cache_result = False with RandomEnv(evaluation): return Integer(get_random_state()) @@ -213,7 +213,7 @@ class SeedRandom(Builtin): def apply(self, x, evaluation): "SeedRandom[x_]" - + evaluation.cache_result = False if isinstance(x, Integer): value = x.value elif isinstance(x, String): @@ -315,7 +315,7 @@ class RandomInteger(Builtin): def apply(self, rmin, rmax, evaluation): "RandomInteger[{rmin_, rmax_}]" - + evaluation.cache_result = False if not isinstance(rmin, Integer) or not isinstance(rmax, Integer): return evaluation.message( "RandomInteger", "unifr", Expression("List", rmin, rmax) @@ -326,6 +326,7 @@ def apply(self, rmin, rmax, evaluation): def apply_list(self, rmin, rmax, ns, evaluation): "RandomInteger[{rmin_, rmax_}, ns_?ListQ]" + evaluation.cache_result = False if not isinstance(rmin, Integer) or not isinstance(rmax, Integer): return evaluation.message( "RandomInteger", "unifr", Expression("List", rmin, rmax) @@ -392,7 +393,7 @@ class RandomReal(Builtin): def apply(self, xmin, xmax, evaluation): "RandomReal[{xmin_, xmax_}]" - + evaluation.cache_result = False if not ( isinstance(xmin, (Real, Integer)) and isinstance(xmax, (Real, Integer)) ): @@ -414,7 +415,7 @@ def apply_list(self, xmin, xmax, ns, evaluation): return evaluation.message( "RandomReal", "unifr", Expression("List", xmin, xmax) ) - + evaluation.cache_result = False min_value, max_value = xmin.to_python(), xmax.to_python() result = ns.to_python() @@ -497,7 +498,7 @@ def to_complex(value, evaluation): def apply(self, zmin, zmax, evaluation): "RandomComplex[{zmin_, zmax_}]" - + evaluation.cache_result = False min_value, max_value = ( self.to_complex(zmin, evaluation), self.to_complex(zmax, evaluation), @@ -515,7 +516,7 @@ def apply(self, zmin, zmax, evaluation): def apply_list(self, zmin, zmax, ns, evaluation): "RandomComplex[{zmin_, zmax_}, ns_]" expr = Expression("RandomComplex", Expression("List", zmin, zmax), ns) - + evaluation.cache_result = False min_value, max_value = ( self.to_complex(zmin, evaluation), self.to_complex(zmax, evaluation), @@ -556,6 +557,7 @@ class _RandomSelection(_RandomBase): def apply(self, domain, size, evaluation): """%(name)s[domain_, size_]""" + evaluation.cache_result = False if domain.get_head_name() == "System`Rule": # elements and weights err, py_weights = self._weights_to_python(domain.leaves[0], evaluation) if py_weights is None: diff --git a/mathics/builtin/system.py b/mathics/builtin/system.py index 7153ddee8..f2516eca6 100644 --- a/mathics/builtin/system.py +++ b/mathics/builtin/system.py @@ -360,6 +360,7 @@ class Run(Builtin): def apply(self, command, evaluation): "Run[command_?StringQ]" command_str = command.to_python() + evaluation.cache_result = False return Integer(subprocess.call(command_str, shell=True)) @@ -612,3 +613,31 @@ def apply_1(self, symbol, evaluation) -> Integer: # remplace them by references. # Return the amount of memory recovered. return Integer0 + + +class ClearSystemCache(Builtin): + """ +
+
'ClearSystemCache[]' +
Clears the internal system cache of expressions. +
+ + """ + + def apply_clear(self, evaluation): + "ClearSystemCache[]" + evaluation.cache_result = False + evaluation.definitions.cache_eval = {} + return + + def apply_clear_symbolic(self, evaluation): + 'ClearSystemCache["Symbolic"]' + evaluation.cache_result = False + evaluation.definitions.cache_eval = {} + return + + def apply_clear_numeric(self, evaluation): + 'ClearSystemCache["Numeric"]' + evaluation.cache_result = False + evaluation.definitions.cache_eval = {} + return diff --git a/mathics/core/definitions.py b/mathics/core/definitions.py index 224db67a5..fcf0085e7 100644 --- a/mathics/core/definitions.py +++ b/mathics/core/definitions.py @@ -84,6 +84,7 @@ def __init__( self.proxy = defaultdict(set) self.now = 0 # increments whenever something is updated self._packages = [] + self.cache_eval = {} if add_builtin: from mathics.builtin import modules, contribute diff --git a/mathics/core/evaluation.py b/mathics/core/evaluation.py index 15643f443..f4c046433 100644 --- a/mathics/core/evaluation.py +++ b/mathics/core/evaluation.py @@ -264,6 +264,8 @@ def __init__( # Necesary to handle OneIdentity on # lhs in assignment self.ignore_oneidentity = False + self.cache_eval = {} + self.cache_result = False def parse(self, query): "Parse a single expression and print the messages." @@ -309,6 +311,7 @@ def evaluate(self, query, timeout=None, format=None): self.stopped = False self.exc_result = self.SymbolNull self.last_eval = None + self.cache_result = True if format is None: format = self.format @@ -330,11 +333,13 @@ def evaluate(): self.last_eval = Expression("System`$Pre", query).evaluate(self) else: self.last_eval = query.evaluate(self) - if check_io_hook("System`$Post"): self.last_eval = Expression("System`$Post", self.last_eval).evaluate( self ) + # From there, it is all about format. Do not catch evaluations. + self.cache_result = False + if history_length > 0: if self.predetermined_out is not None: out_result = self.predetermined_out diff --git a/mathics/core/expression.py b/mathics/core/expression.py index ec49c97a2..91d0f2ceb 100644 --- a/mathics/core/expression.py +++ b/mathics/core/expression.py @@ -20,6 +20,20 @@ # Imperical number that seems to work. # We have to be able to match mpmath values with sympy values COMPARE_PREC = 50 +# Expressions that should not be cached +NO_CACHE_EXPR = [ + "System`Set", + "System`SetDelayed", + "System`TimeRemaining", + "System`Timing", + "System`AbsoluteTiming", + "System`Timing", + "System`Return", + "System`Run", + "System`GetEnvironment", + "System`DateList", + "System`TimeUsed", +] def fully_qualified_symbol_name(name) -> bool: @@ -1301,6 +1315,33 @@ def evaluate(self, evaluation) -> typing.Union["Expression", "Symbol"]: old_options = evaluation.options evaluation.inc_recursion_depth() + # evaluation.cache_result can be set here or from inside the evaluation + # of a branch. Once it is set to false, the result is not cached, + # and hence, not used. + if evaluation.cache_result and ( + self.get_head_name() + in ( + "System`Out", + "System`ToBoxes", + "System`MakeBoxes", + ) + ): + evaluation.cache_result = False + + if evaluation.cache_result: + expr_hash = str(self.__hash__()) + else: + expr_hash = None + + if expr_hash: + cache_expr_result = evaluation.definitions.cache_eval.get(expr_hash, None) + if cache_expr_result is not None: + expr = cache_expr_result[0] + if not expr.has_changed(definitions): + expr = cache_expr_result[1] + if not expr.has_changed(definitions): + return expr + try: while reevaluate: # changed before last evaluated? @@ -1311,7 +1352,6 @@ def evaluate(self, evaluation) -> typing.Union["Expression", "Symbol"]: if hasattr(expr, "options") and expr.options: evaluation.options = expr.options - expr, reevaluate = expr.evaluate_next(evaluation) if not reevaluate: break @@ -1340,12 +1380,19 @@ def evaluate(self, evaluation) -> typing.Union["Expression", "Symbol"]: evaluation.options = old_options evaluation.dec_recursion_depth() + if evaluation.cache_result: + self._timestamp_cache(evaluation) + evaluation.definitions.cache_eval[expr_hash] = (self, expr) return expr def evaluate_next(self, evaluation) -> typing.Tuple["Expression", bool]: from mathics.builtin.base import BoxConstruct + up_cache_result = evaluation.cache_result + cache_result = up_cache_result head = self._head.evaluate(evaluation) + cache_result = cache_result and evaluation.cache_result + evaluation.cache_result = up_cache_result attributes = head.get_attributes(evaluation.definitions) leaves = self.get_mutable_leaves() @@ -1356,7 +1403,11 @@ def rest_range(indices): for index in indices: leaf = leaves[index] if leaf.has_form("Evaluate", 1): + up_cache_result = evaluation.cache_result + cache_result = up_cache_result leaves[index] = leaf.evaluate(evaluation) + cache_result = cache_result and evaluation.cache_result + evaluation.cache_result = up_cache_result def eval_range(indices): for index in indices: @@ -1372,13 +1423,19 @@ def eval_range(indices): elif "System`HoldFirst" in attributes: rest_range(range(0, min(1, len(leaves)))) eval_range(range(1, len(leaves))) + cache_result = cache_result and evaluation.cache_result + evaluation.cache_result = up_cache_result elif "System`HoldRest" in attributes: eval_range(range(0, min(1, len(leaves)))) + cache_result = cache_result and evaluation.cache_result + evaluation.cache_result = up_cache_result rest_range(range(1, len(leaves))) else: eval_range(range(len(leaves))) - # rest_range(range(0, 0)) + cache_result = cache_result and evaluation.cache_result + evaluation.cache_result = up_cache_result + # rest_range(range(0, 0)) new = Expression(head) new._leaves = tuple(leaves) @@ -1421,6 +1478,7 @@ def flatten_callback(new_leaves, old): if "System`Listable" in attributes: done, threaded = new.thread(evaluation) if done: + evaluation.cache_result = cache_result if threaded.sameQ(new): new._timestamp_cache(evaluation) return new, False @@ -1448,6 +1506,7 @@ def rules(): for rule in rules(): result = rule.apply(new, evaluation, fully=False) if result is not None: + evaluation.cache_result = cache_result and evaluation.cache_result if isinstance(result, BoxConstruct): return result, False if result.sameQ(new): @@ -1471,11 +1530,20 @@ def rules(): new.unformatted = self.unformatted new._timestamp_cache(evaluation) + evaluation.cache_result = cache_result + return new, False def evaluate_leaves(self, evaluation) -> "Expression": - leaves = [leaf.evaluate(evaluation) for leaf in self._leaves] + up_cache_result = evaluation.cache_result + cache_result = up_cache_result + leaves = [] + for leaf in self._leaves: + leaves.append(leaf.evaluate(evaluation)) + cache_result = cache_result and evaluation.cache_result + evaluation.cache_result = up_cache_result head = self._head.evaluate_leaves(evaluation) + evaluation.cache_result = cache_result and evaluation.cache_result return Expression(head, *leaves) def __str__(self) -> str: