diff --git a/mathics/builtin/list/eol.py b/mathics/builtin/list/eol.py index ff3c7fb93..7828030d6 100644 --- a/mathics/builtin/list/eol.py +++ b/mathics/builtin/list/eol.py @@ -581,6 +581,9 @@ 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]]'. @@ -592,9 +595,15 @@ class First(Builtin): >> First[x] : Nonatomic expression expected at position 1 in First[x]. = First[x] + >> First[{}] : {} has zero length and no first element. = First[{}] + + As before, the first argument is empty, but a default argument is given, \ + so evaluate and return the second argument: + >> First[{}, 1+2] + = 3 """ attributes = A_HOLD_REST | A_PROTECTED @@ -605,19 +614,24 @@ class First(Builtin): } summary_text = "first element of a list or expression" + # 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", expression) return - if len(expr.elements) == 0: + expr_len = len(expr.elements) + if expr_len == 0: evaluation.message("First", "nofirst", expr) return - if len(expr.elements) > 2 and expr.head is SymbolSequence: - evaluation.message("First", "argt", len(expr.elements)) + if expr_len > 2 and expr.head is SymbolSequence: + evaluation.message("First", "argt", expr_len) return + if expr_len == 2 and isinstance(expr.elements[0], ListExpression): + return expr.elements[1].evaluate(evaluation) + return expr.elements[0] @@ -819,6 +833,9 @@ 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]]'. @@ -831,24 +848,38 @@ class Last(Builtin): >> Last[{}] : {} has zero length and no last element. = Last[{}] + + As before, the first argument is empty, but a default argument is given, \ + so evaluate and return the second argument: + >> Last[{}, 1+2] + = 3 """ attributes = A_HOLD_REST | A_PROTECTED messages = { + "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" + # 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_]" + "Last[expr__]" if isinstance(expr, Atom): 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 + + if expr_len == 2 and isinstance(expr.elements[0], ListExpression): + return expr.elements[1].evaluate(evaluation) return expr.elements[-1] diff --git a/test/builtin/list/test_eol.py b/test/builtin/list/test_eol.py index 16d7c9f91..79a4f0bab 100644 --- a/test/builtin/list/test_eol.py +++ b/test/builtin/list/test_eol.py @@ -161,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),