diff --git a/mathics/builtin/list/eol.py b/mathics/builtin/list/eol.py
index 21eb8072c..259f89254 100644
--- a/mathics/builtin/list/eol.py
+++ b/mathics/builtin/list/eol.py
@@ -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,
@@ -580,39 +581,74 @@ class First(Builtin):
- 'First[$expr$]'
- returns the first element in $expr$.
+
+
- 'First[$expr$, $def$]'
+
- returns the first element in $expr$ if it exists or $def$ otherwise.
'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
"""
+ 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]
- return expr.elements[0]
+ return first_elem
class FirstCase(Builtin):
@@ -813,35 +849,64 @@ class Last(Builtin):
- 'Last[$expr$]'
- returns the last element in $expr$.
+
+
- 'Last[$expr$, $def$]'
+
- returns the last element in $expr$ if it exists or $def$ otherwise.
'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
return expr.elements[-1]
@@ -906,17 +971,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)
@@ -1395,7 +1464,7 @@ 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.
@@ -1403,16 +1472,16 @@ class Rest(Builtin):
"""
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)
diff --git a/mathics/eval/files_io/files.py b/mathics/eval/files_io/files.py
index 3678f6de1..a47b0c406 100644
--- a/mathics/eval/files_io/files.py
+++ b/mathics/eval/files_io/files.py
@@ -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)
diff --git a/test/builtin/list/__init__.py b/test/builtin/list/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/builtin/list/test_eol.py b/test/builtin/list/test_eol.py
index a0f223998..79a4f0bab 100644
--- a/test/builtin/list/test_eol.py
+++ b/test/builtin/list/test_eol.py
@@ -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),
(
@@ -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),