Skip to content
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
13 changes: 7 additions & 6 deletions mathics/builtin/mainloop.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,17 +82,17 @@ class In(Builtin):
>> Definition[In]
= Attributes[In] = {Listable, Protected}
.
. In[6] = Definition[In]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, the order was wrong because the rules were not properly built in mathics.core.evaluation. Adding the evaluation parameter when the rules are created, the rules can determine that that the patterns are literal patterns, and therefore the right precedence is used.

.
. In[5] = In[-1]
. In[1] = x = 1
.
. In[4] = x
. In[2] = x = x + 1
.
. In[3] = Do[In[2], {3}]
.
. In[2] = x = x + 1
. In[4] = x
.
. In[1] = x = 1
. In[5] = In[-1]
.
. In[6] = Definition[In]
.
"""

Expand Down Expand Up @@ -226,3 +226,4 @@ class Line(Builtin):

name = "$Line"
summary_text = "current line number"
rules = {"$Line": "1"}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This automatically sets the initial value of $Line after we reset the Definitions object.

4 changes: 2 additions & 2 deletions mathics/core/definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ def get_history_length(self) -> int:

def get_line_no(self) -> int:
"""Get $Line, the current input line number"""
return self.get_config_value("$Line", 0) or 0
return self.get_config_value("$Line", 1) or 1

def get_matching_names(self, pattern: Union[str, re.Pattern]) -> List[str]:
"""
Expand Down Expand Up @@ -666,7 +666,7 @@ def increment_line_no(self, increment: int = 1) -> None:
"""Increment $Line, the current input line number"""
line_number = self.get_line_no()
if line_number is not None:
self.set_config_value("$Line", +increment)
self.set_config_value("$Line", line_number + increment)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems we erase it by mistake at some point...

@rocky rocky Oct 8, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably during Dialog[] addition. We should make sure that this isn't broken by these changes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could use in Evaluation.evaluation, in the finally block. Right now, it is not used anywhere


def is_uncertain_final_value(self, last_evaluated_time: int, symbols: set) -> bool:
"""
Expand Down
18 changes: 13 additions & 5 deletions mathics/core/evaluation.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,8 +229,6 @@ def evaluate(self, query, timeout=None, format=None):
output_forms = self.definitions.outputforms

line_no = self.definitions.get_line_no()
line_no += 1

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should happend at the end of the evaluation, not before.

self.definitions.set_line_no(line_no)

history_length = self.definitions.get_history_length()

Expand All @@ -242,7 +240,8 @@ def check_io_hook(hook):
def evaluate():
if history_length > 0:
self.definitions.add_rule(
"In", RewriteRule(to_expression("In", line_no), query)
"In",
RewriteRule(to_expression("In", line_no), query, evaluation=self),
)
if check_io_hook("System`$Pre"):
self.last_eval = Expression(SymbolPre, query).evaluate(self)
Expand All @@ -261,8 +260,13 @@ def evaluate():
stored_result = self.get_stored_result(out_result, output_forms)
self.definitions.add_rule(
"Out",
RewriteRule(Expression(SymbolOut, Integer(line_no)), stored_result),
RewriteRule(
Expression(SymbolOut, Integer(line_no)),
stored_result,
evaluation=self,
),
)

if self.last_eval != self.SymbolNull:
if check_io_hook("System`$PrePrint"):
self.last_eval = Expression(
Expand Down Expand Up @@ -345,10 +349,14 @@ def evaluate():
self.out = []
finally:
self.stop()
# $Line could have changed during the evaluation.
line_no = self.definitions.get_line_no()
line_no += 1
self.definitions.set_line_no(line_no)

history_length = self.definitions.get_history_length()

line = line_no - history_length
line = line_no - history_length - 1
while line > 0:
unset_in = self.definitions.unset("In", Expression(SymbolIn, Integer(line)))
unset_out = self.definitions.unset(
Expand Down
18 changes: 11 additions & 7 deletions mathics/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,18 +190,22 @@ def reset(self, add_builtin=True, catch_interrupt=False):
"""
reset the definitions and the evaluation objects.
"""
try:
self.definitions = Definitions(add_builtin)
except KeyError:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to have worked before I fixed the MakeBoxes rule. The proper way to decide if we need to load the built-ins is to check if they were already loaded...

from mathics.core.load_builtin import import_and_load_builtins
if add_builtin:
from mathics.core.load_builtin import (
import_and_load_builtins,
mathics3_builtins_modules,
)

import_and_load_builtins()
self.definitions = Definitions(add_builtin)
if not mathics3_builtins_modules:
import_and_load_builtins()

definitions = Definitions(add_builtin)
self.evaluation = Evaluation(
definitions=self.definitions, catch_interrupt=catch_interrupt
definitions=definitions, catch_interrupt=catch_interrupt
)
self.last_result = None
definitions.set_line_no(1)
self.definitions = definitions

def evaluate(self, str_expression, timeout=None, form=None):
"""Parse str_expression and evaluate using the `evaluate` method of the Expression"""
Expand Down
9 changes: 5 additions & 4 deletions test/test_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,24 +29,25 @@ def test_session_evaluation():
Expression(Symbol("Global`F"), Symbol("Global`x"))
)
assert len(session.evaluation.out) == 0
assert session.evaluate("$Line") is Symbol("$Line")
assert session.evaluate("$Line") is Integer1


def test_session_evaluation_as_in_cli():
# `evaluation_as_in_cli` returns a `Result` object
session.reset()
assert session.evaluate("$Line") is Symbol("$Line")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change fits better with the WMA behaviour.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Please enlighten. In what way is it better?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As it is now in master,

In[1]:= $Line
Out[1]= 2

which is weird. Also, this is not compatible with the result using the MathicsSession object:

>>> from mathics.core.load_builtin  import import_and_load_builtins
>>> from mathics.session import MathicsSession
>>> import_and_load_builtins()
>>> session=MathicsSession()
>>> session.evaluate("$Line")
<Symbol: System`$Line>
>>> session.evaluate_as_in_cli("$Line").result
'1'

which looks inconsistent: three ways to evaluate the same expression in the same condition, and three different results.
In WMA,

In[1]:= $Line                                                                                                                                                                 

Out[1]= 1

assert session.evaluate("$Line") is Integer1
result = session.evaluate_as_in_cli('Print["Hola"]')
assert isinstance(result, Result)
assert len(result.out) == 1 and result.out[0].text == "Hola"
# Use session.evaluate(...) does not modify the `$Line` or
# `Out` definitions, while `evaluate_as_in_cli` does:
assert session.evaluate("$Line") is Integer1
assert session.evaluate("$Line") is Integer2
assert session.evaluate_as_in_cli("$Line", form="unformatted").result == Integer2
assert session.evaluate("Out[1]") is SymbolNull
assert session.evaluate_as_in_cli("Out[1]").result is None
session.reset()
assert session.evaluate("$Line") is Symbol("$Line")
# After reset, `$Line` takes the value `1`.
assert session.evaluate("$Line") is Integer1


def test_session_format_evaluation():
Expand Down
Loading