diff --git a/SYMBOLS_MANIFEST.txt b/SYMBOLS_MANIFEST.txt index 54cafb2e5..1369d207f 100644 --- a/SYMBOLS_MANIFEST.txt +++ b/SYMBOLS_MANIFEST.txt @@ -187,6 +187,7 @@ System`Brown System`ButtonBox System`Byte System`ByteArray +System`ByteArrayQ System`ByteCount System`ByteOrdering System`C diff --git a/mathics/builtin/arithmetic.py b/mathics/builtin/arithmetic.py index d5ea760ba..1d4e9c52c 100644 --- a/mathics/builtin/arithmetic.py +++ b/mathics/builtin/arithmetic.py @@ -73,6 +73,7 @@ SymbolTable, SymbolUndefined, ) +from mathics.eval.arithmetic import eval_RealValuedNumberQ from mathics.eval.inference import get_assumptions_list from mathics.eval.nevaluator import eval_N from mathics.eval.numeric import eval_Sign @@ -961,11 +962,7 @@ class RealValuedNumberQ(Test): summary_text = "test whether an expression is a real number" def test(self, expr) -> bool: - return ( - isinstance(expr, (Integer, Rational, Real)) - or expr.has_form("Underflow", 0) - or expr.has_form("Overflow", 0) - ) + return eval_RealValuedNumberQ(expr) class Sum(IterationFunction, SympyFunction, PrefixOperator): diff --git a/mathics/builtin/atomic/atomic.py b/mathics/builtin/atomic/atomic.py index 71e4df3f5..d36fbb294 100644 --- a/mathics/builtin/atomic/atomic.py +++ b/mathics/builtin/atomic/atomic.py @@ -4,7 +4,7 @@ """ from mathics.core.builtin import Builtin, Test -from mathics.core.symbols import Atom +from mathics.eval.atomic.atomic import eval_AtomQ class AtomQ(Test): @@ -57,7 +57,7 @@ class AtomQ(Test): summary_text = "test whether an expression is an atom" def test(self, expr) -> bool: - return isinstance(expr, Atom) + return eval_AtomQ(expr) class Head(Builtin): diff --git a/mathics/builtin/atomic/symbols.py b/mathics/builtin/atomic/symbols.py index 04b77a67b..628bc0af0 100644 --- a/mathics/builtin/atomic/symbols.py +++ b/mathics/builtin/atomic/symbols.py @@ -6,7 +6,7 @@ or namespace, and can have a variety of type of values and attributes. """ import re -from typing import Callable, List, Optional +from typing import Callable, Optional from mathics_scanner.tokeniser import NAMES_WILDCARDS, is_symbol_name @@ -45,20 +45,21 @@ SymbolGrid, SymbolInputForm, SymbolLeft, + SymbolMissing, SymbolOptions, SymbolRule, SymbolSet, ) from mathics.doc.online import online_doc_string +from mathics.eval.atomic.symbols import eval_SymbolQ from mathics.eval.stackframe import get_eval_Expression -SymbolMissing = Symbol("System`Missing") SymbolUnknownSymbol = Symbol("System`UnknownSymbol") def gather_and_format_definition_rules( symbol: Symbol, evaluation: Evaluation -) -> Optional[List[Expression]]: +) -> Optional[list[Expression]]: """Return a list of lines describing the definition of `symbol`""" lines = [] @@ -727,7 +728,7 @@ class SymbolQ(Test): summary_text = "test whether is a symbol" def test(self, expr) -> bool: - return isinstance(expr, Symbol) + return eval_SymbolQ(expr) class ValueQ(Builtin): diff --git a/mathics/builtin/binary/__init__.py b/mathics/builtin/binary/__init__.py index 8916f0d5a..3bd4705cc 100644 --- a/mathics/builtin/binary/__init__.py +++ b/mathics/builtin/binary/__init__.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- - """ Binary Data diff --git a/mathics/builtin/binary/bytearray.py b/mathics/builtin/binary/bytearray.py index ae3a4e947..8e7af122f 100644 --- a/mathics/builtin/binary/bytearray.py +++ b/mathics/builtin/binary/bytearray.py @@ -6,10 +6,12 @@ from typing import Optional from mathics.core.atoms import ByteArray, Integer, String -from mathics.core.builtin import Builtin +from mathics.core.attributes import A_PROTECTED +from mathics.core.builtin import Builtin, Test from mathics.core.convert.expression import to_mathics_list from mathics.core.evaluation import Evaluation from mathics.core.list import ListExpression +from mathics.eval.binary.bytearray import eval_ByteArrayQ class ByteArray_(Builtin): @@ -54,6 +56,8 @@ class ByteArray_(Builtin): ), } + expected_args = 1 + eval_error = Builtin.generic_argument_error name = "ByteArray" summary_text = "array of bytes" @@ -88,4 +92,33 @@ def eval_list(self, values, evaluation) -> Optional[ByteArray]: return ba -# TODO: BaseEncode, BaseDecode, ByteArrayQ, ByteArrayToString, StringToByteArray, ImportByteArray, ExportByteArray +class ByteArrayQ(Test): + r""" + :WMA link: + https://reference.wolfram.com/language/ref/ByteArrayQ.html + +
+
'ByteArrayQ'[{$expr$}] +
returns True if $expr$ is a ByteArray object, and False otherwise. +
+ + >> ByteArrayQ[ByteArray[Range[16]]] + = True + + >> ByteArrayQ[Range[3]] + = False + + >> ByteArrayQ[ByteArray["xyz"]] + : The argument at position 1 in ByteArray[xyz] should be a vector of unsigned byte values or a Base64-encoded string. + = False + """ + + attributes = A_PROTECTED + summary_text = "test whether an expression is a ByteArray" + + def test(self, expr) -> bool: + """Return True if expr is a ByteArray atom.""" + return eval_ByteArrayQ(expr) + + +# TODO: BaseEncode, BaseDecode, ByteArrayToString, StringToByteArray, ImportByteArray, ExportByteArray diff --git a/mathics/core/builtin.py b/mathics/core/builtin.py index 0c30758a3..125cc9e5e 100644 --- a/mathics/core/builtin.py +++ b/mathics/core/builtin.py @@ -14,18 +14,7 @@ from functools import total_ordering from itertools import chain from types import ModuleType -from typing import ( - Any, - Callable, - Dict, - Iterable, - List, - Optional, - Sequence, - Tuple, - Union, - cast, -) +from typing import Any, Callable, Iterable, List, Optional, Sequence, Tuple, Union, cast import mpmath import sympy @@ -205,16 +194,16 @@ def eval_with_options(x, evaluation: Evaluation, options: dict): _is_numeric: bool = False attributes: int = A_PROTECTED context: str = "" - defaults: Dict[Optional[int], str] = {} + defaults: dict[Optional[int], str] = {} # Number of arguments expected. -1 is used for an arbitrary number. expected_args: Union[int, Tuple[int, int], range] = -1 - formats: Dict[str, Any] = {} - messages: Dict[str, Any] = {} + formats: dict[str, Any] = {} + messages: dict[str, Any] = {} name: Optional[str] = None - options: Dict[str, Any] = {} - rules: Dict[str, Any] = {} + options: dict[str, Any] = {} + rules: dict[str, Any] = {} def __getnewargs_ex__(self): return tuple(), { @@ -363,7 +352,7 @@ def contextify_form_name(f): forms = [""] return forms, pattern - formatvalues: Dict[str, List[BaseRule]] = {"": []} + formatvalues: dict[str, List[BaseRule]] = {"": []} for pattern, function in self.get_functions("format_"): forms, pattern = extract_forms(pattern) pat_attr = attributes if pattern.get_head_name() == name else None @@ -676,7 +665,7 @@ def is_literal(self) -> bool: class BuiltinElement(Builtin, BaseElement): - options: Dict[str, Any] + options: dict[str, Any] def __new__(cls, *args, **kwargs): new_kwargs = kwargs.copy() @@ -707,7 +696,7 @@ def __hash__(self): class SympyObject(Builtin): sympy_name: Optional[str] = None - mathics_to_sympy: Dict[str, str] = {} + mathics_to_sympy: dict[str, str] = {} def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -715,7 +704,7 @@ def __init__(self, *args, **kwargs): self.sympy_name = strip_context(self.get_name()).lower() self.mathics_to_sympy[self.__class__.__name__] = self.sympy_name - def get_sympy_names(self) -> List[str]: + def get_sympy_names(self) -> list[str]: if self.sympy_name: return [self.sympy_name] return [] @@ -848,8 +837,8 @@ def eval(self, z, evaluation: Evaluation): class MPMathMultiFunction(MPMathFunction): - sympy_names: Optional[Dict[int, str]] = None - mpmath_names: Optional[Dict[int, str]] = None + sympy_names: Optional[dict[int, str]] = None + mpmath_names: Optional[dict[int, str]] = None def get_sympy_names(self): if self.sympy_names is None: @@ -972,7 +961,7 @@ def has_option(options, name, evaluation): return get_option(options, name, evaluation, evaluate=False) is not None -mathics_to_python: Dict[str, Any] = {} # here we have: name -> string +mathics_to_python: dict[str, Any] = {} # here we have: name -> string @total_ordering @@ -1638,8 +1627,8 @@ def add_no_meaning_builtin_classes( class PatternObject(BuiltinElement, BasePattern): needs_verbatim = True - arg_counts: List[int] = [] - options: Dict[str, Any] + arg_counts: list[int] = [] + options: dict[str, Any] def init(self, expr: Expression, evaluation: Optional[Evaluation] = None): super().init(expr, evaluation=evaluation) @@ -1705,6 +1694,9 @@ def pattern_precedence(self) -> tuple: class Test(Builtin, ABC): + expected_args = 1 + eval_error = Builtin.generic_argument_error + def eval(self, expr, evaluation: Evaluation) -> Optional[BooleanType]: # Note: in the docstring below, we need to use %(name)s for # subclasses like ExactNumberQ to work with function-application diff --git a/mathics/eval/arithmetic.py b/mathics/eval/arithmetic.py index 8e09d28ea..d552b2f52 100644 --- a/mathics/eval/arithmetic.py +++ b/mathics/eval/arithmetic.py @@ -184,6 +184,14 @@ def eval_negate_number(n: Number) -> Number: return eval_multiply_numbers(IntegerM1, n) +def eval_RealValuedNumberQ(expr) -> bool: + return ( + isinstance(expr, (Integer, Rational, Real)) + or expr.has_form("Underflow", 0) + or expr.has_form("Overflow", 0) + ) + + def segregate_numbers( *elements: BaseElement, ) -> Tuple[List[Number], List[BaseElement]]: diff --git a/mathics/eval/atomic/atomic.py b/mathics/eval/atomic/atomic.py new file mode 100644 index 000000000..a8b541016 --- /dev/null +++ b/mathics/eval/atomic/atomic.py @@ -0,0 +1,10 @@ +""" +Evaluation methods for mathics.builtin.atomic.atomic. +""" + +from mathics.core.symbols import Atom + + +def eval_AtomQ(expr) -> bool: + """Return True if expr is an Atom.""" + return isinstance(expr, Atom) diff --git a/mathics/eval/atomic/symbols.py b/mathics/eval/atomic/symbols.py new file mode 100644 index 000000000..9d94a58aa --- /dev/null +++ b/mathics/eval/atomic/symbols.py @@ -0,0 +1,10 @@ +""" +Evaluation methods for mathics.builtin.atomic.atomic. +""" + +from mathics.core.symbols import Symbol + + +def eval_SymbolQ(expr) -> bool: + """Return True if expr is an Symbol.""" + return isinstance(expr, Symbol) diff --git a/mathics/eval/binary/bytearray.py b/mathics/eval/binary/bytearray.py new file mode 100644 index 000000000..e28c6f926 --- /dev/null +++ b/mathics/eval/binary/bytearray.py @@ -0,0 +1,10 @@ +""" +Evaluation methods for mathics.builtin.binary.bytearray. +""" + +from mathics.core.atoms import ByteArray + + +def eval_ByteArrayQ(expr) -> bool: + """Return True if expr is a ByteArray atom.""" + return isinstance(expr, ByteArray) diff --git a/test/builtin/test_binary.py b/test/builtin/test_binary.py index 0aebbb817..2fbcfbf70 100644 --- a/test/builtin/test_binary.py +++ b/test/builtin/test_binary.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import sys -from test.helper import check_evaluation +from test.helper import check_arg_counts, check_evaluation import pytest @@ -456,3 +456,23 @@ def test_type_conversion(): assert expr.value.dtype == np.int64 expr = evaluate('NumericArray[{1,2}, "ComplexReal32"]') assert expr.value.dtype == np.complex64 + + +@pytest.mark.parametrize( + ("function_name", "msg_fragment"), + [ + # FIXME: ToString in check_arg_counts() is giving a traceback + # for ByteArray[]. + # ( + # "ByteArray", + # "1 argument is", + # ), + ( + "ByteArrayQ", + "1 argument is", + ), + ], +) +def test_arg_count_errors(function_name, msg_fragment): + """ """ + check_arg_counts(function_name, msg_fragment)