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

tweak some error messages to be more like WMA... #1094

Merged
merged 5 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
123 changes: 100 additions & 23 deletions mathics/builtin/list/eol.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from mathics.core.builtin import BinaryOperator, Builtin
from mathics.core.convert.expression import to_mathics_list
from mathics.core.convert.python import from_python
from mathics.core.evaluation import Evaluation
from mathics.core.exceptions import (
InvalidLevelspecError,
MessageException,
Expand Down Expand Up @@ -580,39 +581,74 @@ class First(Builtin):
<dl>
<dt>'First[$expr$]'
<dd>returns the first element in $expr$.

<dt>'First[$expr$, $def$]'
<dd>returns the first element in $expr$ if it exists or $def$ otherwise.
</dl>

'First[$expr$]' is equivalent to '$expr$[[1]]'.

>> First[{a, b, c}]
= a

The first argument need not be a list:
>> First[a + b + c]
= a

However, the first argument must be Nonatomic when there is a single argument:
>> First[x]
: Nonatomic expression expected.
: Nonatomic expression expected at position 1 in First[x].
= First[x]

Or if it is not, but a second default argument is provided, that is \
evaluated and returned:

>> First[10, 1+2]
= 3

>> First[{}]
: {} has zero length and no first element.
= First[{}]

As before, the first argument is empty, but a default argument is given, \
evaluate and return the second argument:
>> First[{}, 1+2]
= 3
"""

Copy link

Choose a reason for hiding this comment

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

First and Lastshould have a default value as second arg and attribute A_HOLD_REST.

Copy link
Contributor

@mmatera mmatera Sep 26, 2024

Choose a reason for hiding this comment

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

@axkr, you are right:

In[1]:= First[a]                                                                

First::normal: Nonatomic expression expected at position 1 in First[a].

Out[1]= First[a]

In[2]:= First[a, b]                                                             

Out[2]= b

In[3]:= First[a, b, c]                                                          

First::argt: First called with 3 arguments; 1 or 2 arguments are expected.

Out[3]= First[a, b, c]

In[4]:= ?? First                                                                

Out[4]= First[expr] gives the first element in expr
         
        >    . First[expr, def] gives the first element if it exists, or def

         
        >       otherwise.


        Attributes[First]={HoldRest, Protected}

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks - this should be handled now.

attributes = A_HOLD_REST | A_PROTECTED
messages = {
"normal": "Nonatomic expression expected.",
"argt": "First called with `1` arguments; 1 or 2 arguments are expected.",
"normal": "Nonatomic expression expected at position 1 in `1`.",
"nofirst": "`1` has zero length and no first element.",
}
summary_text = "first element of a list or expression"

def eval(self, expr, evaluation):
"First[expr_]"
# FIXME: the code and the code for Last are similar and can be DRY'd
def eval(self, expr, evaluation: Evaluation, expression: Expression):
"First[expr__]"

if isinstance(expr, Atom):
evaluation.message("First", "normal")
evaluation.message("First", "normal", expression)
return
if len(expr.elements) == 0:
expr_len = len(expr.elements)
if expr_len == 0:
evaluation.message("First", "nofirst", expr)
return
if expr_len > 2 and expr.head is SymbolSequence:
evaluation.message("First", "argt", expr_len)
return

first_elem = expr.elements[0]

if expr.head == SymbolSequence or (
not isinstance(expr, ListExpression)
and len == 2
and isinstance(first_elem, Atom)
):
return expr.elements[1].evaluate(evaluation)
Copy link
Contributor

Choose a reason for hiding this comment

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

Just a comment: to call the evaluate(evaluation) method at the end is not needed: it is going to be called in the next rewrite_apply_eval iteration

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok - thanks. Will remove the explicit call.


return expr.elements[0]
return first_elem


class FirstCase(Builtin):
Expand Down Expand Up @@ -813,37 +849,74 @@ class Last(Builtin):
<dl>
<dt>'Last[$expr$]'
<dd>returns the last element in $expr$.

<dt>'Last[$expr$, $def$]'
<dd>returns the last element in $expr$ if it exists or $def$ otherwise.
</dl>

'Last[$expr$]' is equivalent to '$expr$[[-1]]'.

>> Last[{a, b, c}]
= c
>> Last[x]
: Nonatomic expression expected.
= Last[x]

The first argument need not be a list:
>> Last[a + b + c]
= c

However, the first argument must be Nonatomic when there is a single argument:
>> Last[10]
: Nonatomic expression expected at position 1 in Last[10].
= Last[10]

Or if it is not, but a second default argument is provided, that is \
evaluated and returned:

>> Last[10, 1+2]
= 3


>> Last[{}]
: {} has zero length and no last element.
= Last[{}]

As before, the first argument is empty, but since default argument is given, \
evaluate and return the second argument:
>> Last[{}, 1+2]
= 3
"""

attributes = A_HOLD_REST | A_PROTECTED
messages = {
"normal": "Nonatomic expression expected.",
"argt": "Last called with `1` arguments; 1 or 2 arguments are expected.",
"normal": "Nonatomic expression expected at position 1 in `1`.",
"nolast": "`1` has zero length and no last element.",
}
summary_text = "last element of a list or expression"

def eval(self, expr, evaluation):
"Last[expr_]"
# FIXME: the code and the code for First are similar and can be DRY'd
def eval(self, expr, evaluation: Evaluation, expression: Expression):
"Last[expr__]"

if isinstance(expr, Atom):
evaluation.message("Last", "normal")
evaluation.message("Last", "normal", expression)
return
if len(expr.elements) == 0:
expr_len = len(expr.elements)
if expr_len == 0:
evaluation.message("Last", "nolast", expr)
return
if expr_len > 2 and expr.head is SymbolSequence:
evaluation.message("Last", "argt", expr_len)
return

last_elem = expr.elements[-1]
if expr.head == SymbolSequence or (
not isinstance(expr, ListExpression)
and len == 2
and isinstance(expr.elements[0], Atom)
):
return last_elem.evaluate(evaluation)

return expr.elements[-1]
return last_elem


class Length(Builtin):
Expand Down Expand Up @@ -906,17 +979,21 @@ class Most(Builtin):
>> Most[a + b + c]
= a + b
>> Most[x]
: Nonatomic expression expected.
: Nonatomic expression expected at position 1 in Most[x].
= Most[x]
"""

messages = {
"normal": "Nonatomic expression expected at position 1 in `1`.",
}

summary_text = "remove the last element"

def eval(self, expr, evaluation):
def eval(self, expr, evaluation: Evaluation, expression: Expression):
"Most[expr_]"

if isinstance(expr, Atom):
evaluation.message("Most", "normal")
evaluation.message("Most", "normal", expression)
return
return expr.slice(expr.head, slice(0, -1), evaluation)

Expand Down Expand Up @@ -1395,24 +1472,24 @@ class Rest(Builtin):
>> Rest[a + b + c]
= b + c
>> Rest[x]
: Nonatomic expression expected.
: Nonatomic expression expected at position 1 in Rest[x].
= Rest[x]
>> Rest[{}]
: Cannot take Rest of expression {} with length zero.
= Rest[{}]
"""

messages = {
"normal": "Nonatomic expression expected.",
"normal": "Nonatomic expression expected at position 1 in `1`.",
"norest": "Cannot take Rest of expression `1` with length zero.",
}
summary_text = "remove the first element"

def eval(self, expr, evaluation):
def eval(self, expr, evaluation: Evaluation, expression: Expression):
"Rest[expr_]"

if isinstance(expr, Atom):
evaluation.message("Rest", "normal")
evaluation.message("Rest", "normal", expression)
return
if len(expr.elements) == 0:
evaluation.message("Rest", "norest", expr)
Expand Down
2 changes: 1 addition & 1 deletion mathics/eval/files_io/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def eval_Get(path: str, evaluation: Evaluation, trace_fn: Optional[Callable]):
continue
result = query.evaluate(evaluation)
except IOError:
evaluation.message("General", "noopen", path)
evaluation.message("Get", "noopen", path)
return SymbolFailed
except MessageException as e:
e.message(evaluation)
Expand Down
Empty file added test/builtin/list/__init__.py
Empty file.
14 changes: 13 additions & 1 deletion test/builtin/list/test_eol.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@
"Drop[{1, 2, 3, 4, 5, 6}, {-5, -2, -2}]",
None,
),
(
"First[a, b, c]",
("First called with 3 arguments; 1 or 2 arguments are expected.",),
"First[a, b, c]",
None,
),
('FirstPosition[{1, 2, 3}, _?StringQ, "NoStrings"]', None, "NoStrings", None),
("FirstPosition[a, a]", None, "{}", None),
(
Expand Down Expand Up @@ -155,7 +161,13 @@
("a = {2,3,4}; i = 1; a[[i]] = 0; a", None, "{0, 3, 4}", None),
## Negative step
("{1,2,3,4,5}[[3;;1;;-1]]", None, "{3, 2, 1}", None),
("{1, 2, 3, 4, 5}[[;; ;; -1]]", None, "{5, 4, 3, 2, 1}", "MMA bug"),
("ClearAll[a]", None, "Null", None),
(
"Last[a, b, c]",
("Last called with 3 arguments; 1 or 2 arguments are expected.",),
"Last[a, b, c]",
None,
),
("Range[11][[-3 ;; 2 ;; -2]]", None, "{9, 7, 5, 3}", None),
("Range[11][[-3 ;; -7 ;; -3]]", None, "{9, 6}", None),
("Range[11][[7 ;; -7;; -2]]", None, "{7, 5}", None),
Expand Down