diff --git a/mathics/builtin/arithmetic.py b/mathics/builtin/arithmetic.py index bcd495cde..d5f1a202b 100644 --- a/mathics/builtin/arithmetic.py +++ b/mathics/builtin/arithmetic.py @@ -635,9 +635,7 @@ def apply(self, expr, evaluation): numeric_val = Expression(SymbolN, expr).evaluate(evaluation) if numeric_val and hasattr(numeric_val, "is_approx_zero"): result = numeric_val.is_approx_zero - elif ( - Expression("NumericQ", numeric_val).evaluate(evaluation) == SymbolFalse - ): + elif not numeric_val.is_numeric(evaluation): return ( SymbolTrue if Expression("Simplify", expr).evaluate(evaluation) == Integer0 diff --git a/mathics/builtin/compilation.py b/mathics/builtin/compilation.py index 016884767..1f4403c2c 100644 --- a/mathics/builtin/compilation.py +++ b/mathics/builtin/compilation.py @@ -168,6 +168,8 @@ def _pythonized_mathics_expr(*x): class CompiledCode(Atom): + class_head_name = "System`CompiledCode" + def __init__(self, cfunc, args, **kwargs): super(CompiledCode, self).__init__(**kwargs) self.cfunc = cfunc diff --git a/mathics/builtin/drawing/image.py b/mathics/builtin/drawing/image.py index 96d943507..69f456919 100644 --- a/mathics/builtin/drawing/image.py +++ b/mathics/builtin/drawing/image.py @@ -1791,6 +1791,8 @@ def test(self, expr): class Image(Atom): + class_head_name = "System`Image" + def __init__(self, pixels, color_space, metadata={}, **kwargs): super(Image, self).__init__(**kwargs) if len(pixels.shape) == 2: diff --git a/mathics/builtin/patterns.py b/mathics/builtin/patterns.py index 4b7563173..09cfca56f 100644 --- a/mathics/builtin/patterns.py +++ b/mathics/builtin/patterns.py @@ -119,9 +119,19 @@ def create_rules(rules_expr, expr, name, evaluation, extra_args=[]): rules = rules_expr.leaves else: rules = [rules_expr] - any_lists = any(item.has_form(("List", "Dispatch"), None) for item in rules) + any_lists = False + for item in rules: + if item.has_form(("List", "Dispatch"), None): + any_lists = True + break + if any_lists: - all_lists = all(item.has_form("List", None) for item in rules) + all_lists = True + for item in rules: + if not item.has_form("List", None): + all_lists = False + break + if all_lists: return ( Expression( @@ -473,44 +483,116 @@ class PatternTest(BinaryOperator, PatternObject): def init(self, expr): super(PatternTest, self).init(expr) + match_functions = { + "System`StringQ": self.match_string, + "System`NumericQ": self.match_numericq, + "System`NumberQ": self.match_numberq, + "System`RealNumberQ": self.match_real_numberq, + "Internal`RealValuedNumberQ": self.match_real_numberq, + "System`Posive": self.match_positive, + "System`Negative": self.match_negative, + "System`NonPositive": self.match_nonpositive, + "System`NonNegative": self.match_nonnegative, + } + self.pattern = Pattern.create(expr.leaves[0]) self.test = expr.leaves[1] - self.test_name = self.test.get_name() + testname = self.test.get_name() + self.test_name = testname + match_function = match_functions.get(testname, None) + if match_function: + self.match = match_function + + def match_string(self, yield_func, expression, vars, evaluation, **kwargs): + def yield_match(vars_2, rest): + items = expression.get_sequence() + for item in items: + if not isinstance(item, String): + break + else: + yield_func(vars_2, None) + + self.pattern.match(yield_match, expression, vars, evaluation) + + def match_numberq(self, yield_func, expression, vars, evaluation, **kwargs): + def yield_match(vars_2, rest): + items = expression.get_sequence() + for item in items: + if not isinstance(item, Number): + break + else: + yield_func(vars_2, None) + + self.pattern.match(yield_match, expression, vars, evaluation) + + def match_numericq(self, yield_func, expression, vars, evaluation, **kwargs): + def yield_match(vars_2, rest): + items = expression.get_sequence() + for item in items: + if not (isinstance(item, Number) or item.is_numeric(evaluation)): + break + else: + yield_func(vars_2, None) + + self.pattern.match(yield_match, expression, vars, evaluation) + + def match_real_numberq(self, yield_func, expression, vars, evaluation, **kwargs): + def yield_match(vars_2, rest): + items = expression.get_sequence() + for item in items: + if not isinstance(item, (Integer, Rational, Real)): + break + else: + yield_func(vars_2, None) + + self.pattern.match(yield_match, expression, vars, evaluation) + + def match_positive(self, yield_func, expression, vars, evaluation, **kwargs): + def yield_match(vars_2, rest): + items = expression.get_sequence() + if all( + isinstance(item, (Integer, Rational, Real)) and item.value > 0 + for item in items + ): + yield_func(vars_2, None) + + self.pattern.match(yield_match, expression, vars, evaluation) + + def match_negative(self, yield_func, expression, vars, evaluation, **kwargs): + def yield_match(vars_2, rest): + items = expression.get_sequence() + if all( + isinstance(item, (Integer, Rational, Real)) and item.value < 0 + for item in items + ): + yield_func(vars_2, None) + + self.pattern.match(yield_match, expression, vars, evaluation) + + def match_nonpositive(self, yield_func, expression, vars, evaluation, **kwargs): + def yield_match(vars_2, rest): + items = expression.get_sequence() + if all( + isinstance(item, (Integer, Rational, Real)) and item.value <= 0 + for item in items + ): + yield_func(vars_2, None) + + self.pattern.match(yield_match, expression, vars, evaluation) + + def match_nonnegative(self, yield_func, expression, vars, evaluation, **kwargs): + def yield_match(vars_2, rest): + items = expression.get_sequence() + if all( + isinstance(item, (Integer, Rational, Real)) and item.value >= 0 + for item in items + ): + yield_func(vars_2, None) + + self.pattern.match(yield_match, expression, vars, evaluation) def quick_pattern_test(self, candidate, test, evaluation): - if test == "System`NumberQ": - return isinstance(candidate, Number) - elif test == "System`NumericQ": - if isinstance(candidate, Number): - return True - # Otherwise, follow the standard evaluation - elif test == "System`RealNumberQ": - if isinstance(candidate, (Integer, Rational, Real)): - return True - candidate = Expression(SymbolN, candidate).evaluate(evaluation) - return isinstance(candidate, Real) - # pass - elif test == "System`Positive": - if isinstance(candidate, (Integer, Rational, Real)): - return candidate.value > 0 - return False - # pass - elif test == "System`NonPositive": - if isinstance(candidate, (Integer, Rational, Real)): - return candidate.value <= 0 - return False - # pass - elif test == "System`Negative": - if isinstance(candidate, (Integer, Rational, Real)): - return candidate.value < 0 - return False - # pass - elif test == "System`NonNegative": - if isinstance(candidate, (Integer, Rational, Real)): - return candidate.value >= 0 - return False - # pass - elif test == "System`NegativePowerQ": + if test == "System`NegativePowerQ": return ( candidate.has_form("Power", 2) and isinstance(candidate.leaves[1], (Integer, Rational, Real)) @@ -534,14 +616,18 @@ def quick_pattern_test(self, candidate, test, evaluation): return None def match(self, yield_func, expression, vars, evaluation, **kwargs): + # def match(self, yield_func, expression, vars, evaluation, **kwargs): # for vars_2, rest in self.pattern.match(expression, vars, evaluation): def yield_match(vars_2, rest): + testname = self.test_name items = expression.get_sequence() for item in items: item = item.evaluate(evaluation) - quick_test = self.quick_pattern_test(item, self.test_name, evaluation) + quick_test = self.quick_pattern_test(item, testname, evaluation) if quick_test is False: break + elif quick_test is True: + continue # raise StopGenerator else: test_expr = Expression(self.test, item) @@ -1502,6 +1588,8 @@ def yield_match(vars, rest): class Dispatch(Atom): + class_head_name = "System`Dispatch" + def __init__(self, rulelist, evaluation): self.src = Expression(SymbolList, *rulelist) self.rules = [Rule(rule._leaves[0], rule._leaves[1]) for rule in rulelist] @@ -1547,6 +1635,7 @@ class DispatchAtom(AtomBuiltin): messages = { "invrpl": "`1` is not a valid rule or list of rules.", } + class_head_name = "System`DispatchAtom" def __repr__(self): return "dispatchatom" diff --git a/mathics/core/expression.py b/mathics/core/expression.py index 88a9dcb71..898d06b7e 100644 --- a/mathics/core/expression.py +++ b/mathics/core/expression.py @@ -62,7 +62,7 @@ def strip_context(name) -> str: # system_symbols('A', 'B', ...) -> ['System`A', 'System`B', ...] def system_symbols(*symbols) -> typing.List[str]: - return [ensure_context(s) for s in symbols] + return tuple(ensure_context(s) for s in symbols) # system_symbols_dict({'SomeSymbol': ...}) -> {'System`SomeSymbol': ...} @@ -130,12 +130,7 @@ def from_python(arg): # return Symbol(arg) elif isinstance(arg, dict): entries = [ - Expression( - "Rule", - from_python(key), - from_python(arg[key]), - ) - for key in arg + Expression("Rule", from_python(key), from_python(arg[key]),) for key in arg ] return Expression(SymbolList, *entries) elif isinstance(arg, BaseExpression): @@ -327,8 +322,8 @@ def get_lookup_name(self): def get_head(self): return None - def get_head_name(self): - return self.get_head().get_name() + def get_head_name(self) -> str: + raise NotImplementedError def get_leaves(self): return [] @@ -952,6 +947,9 @@ def set_positions(self, position=None) -> None: def get_head(self): return self._head + def get_head_name(self): + return self._head.name if isinstance(self._head, Symbol) else "" + def set_head(self, head): self._head = head self._cache = None @@ -1240,20 +1238,24 @@ def get_sort_key(self, pattern_sort=False): else: return [1 if self.is_numeric() else 2, 3, self._head, self._leaves, 1] - def sameQ(self, other) -> bool: + def sameQ(self, other: BaseExpression) -> bool: """Mathics SameQ""" + # if id(self) == id(other): + # return True + if not isinstance(other, Expression): + return False if id(self) == id(other): return True - if self.get_head_name() != other.get_head_name(): - return False - if not self._head.sameQ(other.get_head()): + # if self.get_head_name() != other.get_head_name(): + # return False + if not self._head.sameQ(other._head): return False if len(self._leaves) != len(other.get_leaves()): return False - for leaf, other in zip(self._leaves, other.get_leaves()): - if not leaf.sameQ(other): - return False - return True + return all( + (id(leaf) == id(oleaf) or leaf.sameQ(oleaf)) + for leaf, oleaf in zip(self._leaves, other.get_leaves()) + ) def flatten( self, head, pattern_only=False, callback=None, level=None @@ -1843,23 +1845,21 @@ def is_numeric(self, evaluation=None) -> bool: self._head.get_name() ): return False - return all(leaf.is_numeric(evaluation) for leaf in self._leaves) + for leaf in self._leaves: + if not leaf.is_numeric(evaluation): + return False + return True + # return all(leaf.is_numeric(evaluation) for leaf in self._leaves) else: - return ( - self._head.get_name() - in system_symbols( - "Sqrt", - "Times", - "Plus", - "Subtract", - "Minus", - "Power", - "Abs", - "Divide", - "Sin", - ) - and all(leaf.is_numeric() for leaf in self._leaves) - ) + if self._head.get_name() not in arithmetic_head_symbols: + return False + for leaf in self._leaves: + if not leaf.is_numeric(): + return False + return True + # return self._head.get_name() in arithmetic_head_symbols and all( + # leaf.is_numeric() for leaf in self._leaves + # ) def numerify(self, evaluation) -> "Expression": _prec = None @@ -1906,6 +1906,10 @@ def __getnewargs__(self): class Atom(BaseExpression): + _head_name = "" + _symbol_head = None + class_head_name = "" + def is_atom(self) -> bool: return True @@ -1932,7 +1936,10 @@ def has_symbol(self, symbol_name) -> bool: return False def get_head(self) -> "Symbol": - return Symbol(self.get_atom_name()) + return Symbol(self.class_head_name) + + def get_head_name(self) -> "str": + return self.class_head_name # System`" + self.__class__.__name__ def get_atom_name(self) -> str: return self.__class__.__name__ @@ -1974,6 +1981,7 @@ class Symbol(Atom): name: str sympy_dummy: Any defined_symbols = {} + class_head_name = "System`Symbol" def __new__(cls, name, sympy_dummy=None): name = ensure_context(name) @@ -1982,7 +1990,7 @@ def __new__(cls, name, sympy_dummy=None): self = super(Symbol, cls).__new__(cls) self.name = name self.sympy_dummy = sympy_dummy - # cls.defined_symbols[name] = self + cls.defined_symbols[name] = self return self def __str__(self) -> str: @@ -1994,6 +2002,12 @@ def do_copy(self) -> "Symbol": def boxes_to_text(self, **options) -> str: return str(self.name) + def get_head(self) -> "Symbol": + return Symbol("Symbol") + + def get_head_name(self): + return "System`Symbol" + def atom_to_boxes(self, f, evaluation) -> "String": return String(evaluation.definitions.shorten_name(self.name)) @@ -2069,7 +2083,9 @@ def equal2(self, rhs: Any) -> Optional[bool]: def sameQ(self, rhs: Any) -> bool: """Mathics SameQ""" - return id(self) == id(rhs) or isinstance(rhs, Symbol) and self.name == rhs.name + return isinstance(rhs, Symbol) and ( + id(rhs) == id(self) or self.name == rhs.name + ) def replace_vars(self, vars, options={}, in_scoping=True): assert all(fully_qualified_symbol_name(v) for v in vars) @@ -2094,9 +2110,21 @@ def is_true(self) -> bool: return self == SymbolTrue def is_numeric(self, evaluation=None) -> bool: - return self.name in system_symbols( - "Pi", "E", "EulerGamma", "GoldenRatio", "MachinePrecision", "Catalan" - ) + return self.name in predefined_numeric_constants + # return any([self.sameQ(s) for s in predefined_numeric_constants]) + # unrecheable + + """ + if evaluation: + qexpr = Expression(SymbolNumericQ, self) + result = evaluation.definitions.get_value( + self.name, "System`UpValues", qexpr, evaluation + ) + if result is not None: + if result.is_true(): + return True + return False + """ def __hash__(self): return hash(("Symbol", self.name)) # to distinguish from String @@ -2112,22 +2140,37 @@ def __getnewargs__(self): SymbolAborted = Symbol("$Aborted") SymbolAssociation = Symbol("Association") SymbolByteArray = Symbol("ByteArray") +SymbolCatalan = Symbol("Catalan") SymbolComplexInfinity = Symbol("ComplexInfinity") SymbolDirectedInfinity = Symbol("DirectedInfinity") +SymbolE = Symbol("E") +SymbolEulerGamma = Symbol("EulerGamma") SymbolFailed = Symbol("$Failed") SymbolFalse = Symbol("False") +SymbolGoldenRatio = Symbol("GoldenRatio") +SymbolGreater = Symbol("Greater") SymbolInfinity = Symbol("Infinity") +SymbolLess = Symbol("Less") SymbolList = Symbol("List") SymbolMachinePrecision = Symbol("MachinePrecision") SymbolMakeBoxes = Symbol("MakeBoxes") SymbolN = Symbol("N") SymbolNull = Symbol("Null") +SymbolNumberQ = Symbol("NumberQ") +SymbolNumericQ = Symbol("NumericQ") +SymbolPi = Symbol("Pi") SymbolRule = Symbol("Rule") SymbolSequence = Symbol("Sequence") +SymbolStringQ = Symbol("StringQ") SymbolTrue = Symbol("True") SymbolUndefined = Symbol("Undefined") -SymbolLess = Symbol("Less") -SymbolGreater = Symbol("Greater") + +arithmetic_head_symbols = system_symbols( + "Sqrt", "Times", "Plus", "Subtract", "Minus", "Power", "Abs", "Divide", "Sin", +) +predefined_numeric_constants = system_symbols( + "MachinePrecision", "Pi", "E", "Catalan", "EulerGamma", "GoldenRatio", +) @lru_cache(maxsize=1024) @@ -2202,6 +2245,7 @@ def _NumberFormat(man, base, exp, options): class Integer(Number): value: int + class_head_name = "System`Integer" def __new__(cls, value) -> "Integer": n = int(value) @@ -2209,6 +2253,9 @@ def __new__(cls, value) -> "Integer": self.value = n return self + def get_head_name(self): + return "System`Integer" + @lru_cache() def __init__(self, value) -> "Integer": super().__init__() @@ -2288,12 +2335,17 @@ def is_zero(self) -> bool: class Rational(Number): + class_head_name = "System`Rational" + @lru_cache() def __new__(cls, numerator, denominator=1) -> "Rational": self = super().__new__(cls) self.value = sympy.Rational(numerator, denominator) return self + def get_head_name(self): + return "System`Rational" + def atom_to_boxes(self, f, evaluation): return self.format(evaluation, f.get_name()) @@ -2385,6 +2437,8 @@ def is_zero(self) -> bool: class Real(Number): + class_head_name = "System`Real" + def __new__(cls, value, p=None) -> "Real": if isinstance(value, str): value = str(value) @@ -2409,6 +2463,9 @@ def __new__(cls, value, p=None) -> "Real": else: return PrecisionReal.__new__(PrecisionReal, value) + def get_head_name(self): + return "System`Real" + def boxes_to_text(self, **options) -> str: return self.make_boxes("System`OutputForm").boxes_to_text(**options) @@ -2612,6 +2669,7 @@ class Complex(Number): real: Any imag: Any + class_head_name = "System`Complex" def __new__(cls, real, imag): self = super().__new__(cls) @@ -2832,6 +2890,7 @@ def replace(match): class String(Atom): value: str + class_head_name = "System`String" def __new__(cls, value): self = super().__new__(cls) @@ -2841,6 +2900,9 @@ def __new__(cls, value): def __str__(self) -> str: return '"%s"' % self.value + def get_head_name(self): + return "System`String" + def boxes_to_text(self, show_string_characters=False, **options) -> str: value = self.value @@ -2924,7 +2986,6 @@ def render(format, string, in_text=False): elif text and text[0] in "0123456789-.": return render("%s", text) else: - # FIXME: this should be done in a better way. if text == "\u2032": return "'" elif text == "\u2032\u2032": @@ -2947,8 +3008,7 @@ def render(format, string, in_text=False): return text elif text == "\u222b": return r"\int" - # Tolerate WL or Unicode DifferentialD - elif text in ("\u2146", "\U0001D451"): + elif text == "\u2146": return r"\, d" elif text == "\u2211": return r"\sum" @@ -3010,6 +3070,7 @@ def __getnewargs__(self): class ByteArrayAtom(Atom): value: str + class_head_name = "System`ByteArrayAtom" def __new__(cls, value): self = super().__new__(cls) diff --git a/mathics/core/pattern.py b/mathics/core/pattern.py index 8c1035a50..a0a51a3c8 100644 --- a/mathics/core/pattern.py +++ b/mathics/core/pattern.py @@ -3,7 +3,13 @@ # -*- coding: utf-8 -*- -from mathics.core.expression import Expression, system_symbols, ensure_context +from mathics.core.expression import ( + Atom, + Expression, + Symbol, + system_symbols, + ensure_context, +) from mathics.core.util import subsets, subranges, permutations from itertools import chain @@ -22,7 +28,7 @@ def Pattern_create(expr): pattern_object = pattern_objects.get(name) if pattern_object is not None: return pattern_object(expr) - if expr.is_atom(): + if isinstance(expr, Atom): return AtomPattern(expr) else: return ExpressionPattern(expr) @@ -138,10 +144,38 @@ class AtomPattern(Pattern): def __init__(self, expr): self.atom = expr self.expr = expr + if isinstance(expr, Symbol): + self.match = self.match_symbol + self.get_match_candidates = self.get_match_symbol_candidates def __repr__(self): return "" % self.atom + def match_symbol( + self, + yield_func, + expression, + vars, + evaluation, + head=None, + leaf_index=None, + leaf_count=None, + fully=True, + wrap_oneid=True, + ): + if isinstance(expression, Symbol) and expression.name == self.atom.name: + # yield vars, None + yield_func(vars, None) + + def get_match_symbol_candidates( + self, leaves, expression, attributes, evaluation, vars={} + ): + return [ + leaf + for leaf in leaves + if (isinstance(leaf, Symbol) and leaf.name == self.atom.name) + ] + def match( self, yield_func, @@ -154,12 +188,16 @@ def match( fully=True, wrap_oneid=True, ): - if expression.sameQ(self.atom): + if isinstance(expression, Atom) and expression.sameQ(self.atom): # yield vars, None yield_func(vars, None) def get_match_candidates(self, leaves, expression, attributes, evaluation, vars={}): - return [leaf for leaf in leaves if leaf.sameQ(self.atom)] + return [ + leaf + for leaf in leaves + if (isinstance(leaf, Atom) and leaf.sameQ(self.atom)) + ] def get_match_count(self, vars={}): return (1, 1)