From 7ae7b212a7633791dee9bb11f254169c597033d6 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Sat, 7 Feb 2026 16:00:26 -0300 Subject: [PATCH 1/5] compatibility with WMA alphabetic sort --- mathics/core/atoms/strings.py | 4 ++-- mathics/core/expression.py | 7 ++++++- mathics/core/keycomparable.py | 19 +++++++++++++++++++ mathics/core/symbols.py | 7 +++++-- 4 files changed, 32 insertions(+), 5 deletions(-) diff --git a/mathics/core/atoms/strings.py b/mathics/core/atoms/strings.py index 1fbe8d0e6..87d71c111 100644 --- a/mathics/core/atoms/strings.py +++ b/mathics/core/atoms/strings.py @@ -8,7 +8,7 @@ import sympy from mathics.core.element import BoxElementMixin -from mathics.core.keycomparable import BASIC_ATOM_STRING_ELT_ORDER +from mathics.core.keycomparable import BASIC_ATOM_STRING_ELT_ORDER, wma_str_sort_key from mathics.core.symbols import Atom, Symbol, SymbolFalse, SymbolTrue, symbol_set from mathics.core.systemsymbols import SymbolFullForm, SymbolInputForm @@ -69,7 +69,7 @@ def element_order(self) -> tuple: """ return ( BASIC_ATOM_STRING_ELT_ORDER, - self.value, + wma_str_sort_key(self.value), 0, 1, ) diff --git a/mathics/core/expression.py b/mathics/core/expression.py index 35a34c978..3079d4007 100644 --- a/mathics/core/expression.py +++ b/mathics/core/expression.py @@ -45,6 +45,7 @@ GENERAL_EXPRESSION_ELT_ORDER, GENERAL_NUMERIC_EXPRESSION_ELT_ORDER, Monomial, + wma_str_sort_key, ) from mathics.core.structure import LinkedStructure from mathics.core.symbols import ( @@ -893,8 +894,9 @@ def element_order(self) -> tuple: 3: tuple: list of Elements 4: 1: No clue... """ - exps: Dict[str, Union[float, complex]] = {} + exps: Dict[Tuple[str, str], Union[float, complex]] = {} head = self._head + if head is SymbolTimes: for element in self.elements: name = element.get_name() @@ -904,8 +906,10 @@ def element_order(self) -> tuple: assert isinstance(expr, (Expression, NumericOperators)) exp = expr.round_to_float() if var and exp is not None: + var = wma_str_sort_key(var) exps[var] = exps.get(var, 0) + exp elif name: + name = wma_str_sort_key(name) exps[name] = exps.get(name, 0) + 1 elif self.has_form("Power", 2): var = self.elements[0].get_name() @@ -917,6 +921,7 @@ def element_order(self) -> tuple: except AttributeError: exp = None if var and exp is not None: + var = wma_str_sort_key(var) exps[var] = exps.get(var, 0) + exp if exps: return ( diff --git a/mathics/core/keycomparable.py b/mathics/core/keycomparable.py index dac5c4901..781fad2eb 100644 --- a/mathics/core/keycomparable.py +++ b/mathics/core/keycomparable.py @@ -2,6 +2,7 @@ Base classes for canonical order. """ +from typing import Tuple class KeyComparable: @@ -287,3 +288,21 @@ def __ne__(self, other) -> bool: BASIC_EXPRESSION_ELT_ORDER = 0x22 GENERAL_EXPRESSION_ELT_ORDER = 0x23 + + +def wma_str_sort_key(s: str) -> Tuple[str, str]: + """ + Return a Tuple providing the sort key + reproduce the order of strings and symbols + in WMA. + For example, the following is a list of sorted + strings in the WMA order: + `{Abeja, ABEJA, ave de paso, Ave de paso, Ave de Paso, AVe}` + The order criteria is: first sort case insensitive, then + for the first different character in the original string, + lower case comes before upper case. + """ + return ( + s.lower(), + s.swapcase(), + ) diff --git a/mathics/core/symbols.py b/mathics/core/symbols.py index 0426e0e3e..afae3ace5 100644 --- a/mathics/core/symbols.py +++ b/mathics/core/symbols.py @@ -19,6 +19,7 @@ BASIC_EXPRESSION_ELT_ORDER, BASIC_NUMERIC_EXPRESSION_ELT_ORDER, Monomial, + wma_str_sort_key, ) from mathics.eval.tracing import trace_evaluate @@ -556,15 +557,17 @@ def element_order(self) -> tuple: Return a tuple value that is used in ordering elements of an expression. The tuple is ultimately compared lexicographically. """ + name = self.name + name_key = wma_str_sort_key(name) return ( ( BASIC_NUMERIC_EXPRESSION_ELT_ORDER if self.is_numeric() else BASIC_EXPRESSION_ELT_ORDER ), - Monomial({self.name: 1}), + Monomial({name_key: 1}), 0, - self.name, + name, 1, ) From 462602b84fa326253843780fb03cec4c1c475892 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Sat, 7 Feb 2026 16:36:54 -0300 Subject: [PATCH 2/5] add test --- test/builtin/test_sort.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/test/builtin/test_sort.py b/test/builtin/test_sort.py index 8a2f35508..fe70f0645 100644 --- a/test/builtin/test_sort.py +++ b/test/builtin/test_sort.py @@ -1,10 +1,49 @@ # -*- coding: utf-8 -*- +from test.helper import check_evaluation from mathics.core.expression import Expression from mathics.core.symbols import Symbol, SymbolPlus, SymbolTimes +def test_sort_wma(): + """Test the alphabetic order in WMA for Strings and Symbols""" + # In Python, str are ordered as tuples of + # ascii codes of the characters. So, + # + # "Abeja" <"Ave"<"aVe"<"abeja" + # + # In WMA, strings and symbols are sorted in alphabetical order, with + # lowercaps characters coming before than the corresponding upper case. + # Then, the same words are sorted in WMA as + # + # "abeja"< "Abeja"<"aVe"<"Ave" + # + # Such order is equivalent to use + # `lambda s: (s.lower(), s.swapcaps(),)` as sort key. + # + # Finally, String atoms comes before than Symbols. The following test + # reinforce this order. + str_expr = ( + '{"Ave", "aVe", "abeja", AVe, ave, aVe, "Abeja", "ABEJA", ' + '"AVe", "ave del paraíso", "Ave del paraíso", ' + '"Ave del Paraíso"} // Sort // InputForm' + ) + str_expected = ( + '{"abeja", "Abeja", "ABEJA", "aVe", "Ave", "AVe", ' + '"ave del paraíso", "Ave del paraíso", "Ave del Paraíso", ' + "ave, aVe, AVe}//InputForm" + ) + check_evaluation( + str_expr, + str_expected, + # to_string_expr=True, + # to_string_expected=True, + # hold_expected=True, + failure_message="WMA order", + ) + + def test_Expression_sameQ(): """ Test Expression.SameQ From bd483a54a578db808f61581ebf1424b9cbd75614 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Sat, 7 Feb 2026 16:52:54 -0300 Subject: [PATCH 3/5] adding comment on other possible implementations --- mathics/core/keycomparable.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/mathics/core/keycomparable.py b/mathics/core/keycomparable.py index 781fad2eb..5d1e00c0f 100644 --- a/mathics/core/keycomparable.py +++ b/mathics/core/keycomparable.py @@ -302,6 +302,23 @@ def wma_str_sort_key(s: str) -> Tuple[str, str]: for the first different character in the original string, lower case comes before upper case. """ + # An alternative to this implementation would be to map the + # characters in a way that + # a -> A + # A -> B + # b -> C + # B -> D + # ... + # m -> Z + # M -> a + # n -> b + # N -> c + # ... + # z -> y + # Z -> z + # so the result is again a string. Another possibility would be + # to return a wrapper class that implement this special comparison + # on the fly through the method `__lt__`. return ( s.lower(), s.swapcase(), From 4b6fa3c13cf5159ee633501c526451024d3add16 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Sat, 7 Feb 2026 18:31:43 -0300 Subject: [PATCH 4/5] Fix method call from box_to_js to boxes_to_js fix wrong method name --- mathics/format/render/mathml.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mathics/format/render/mathml.py b/mathics/format/render/mathml.py index e69a7945a..220b76129 100644 --- a/mathics/format/render/mathml.py +++ b/mathics/format/render/mathml.py @@ -117,7 +117,7 @@ def graphics3dbox(box: Graphics3DBox, elements=None, **options) -> str: """Turn the Graphics3DBox into a MathML string""" indent_level = options.get("_indent_level", 0) indent_spaces = " " * indent_level - result = box.box_to_js(**options) + result = box.boxes_to_js(**options) result = ( f"{indent_spaces}\n" f"\n" From 83d20376ab9bc46ebfb401d04f8a9354c9f1105c Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Sat, 7 Feb 2026 21:14:38 -0300 Subject: [PATCH 5/5] Update keycomparable.py --- mathics/core/keycomparable.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mathics/core/keycomparable.py b/mathics/core/keycomparable.py index 5d1e00c0f..ba30cd031 100644 --- a/mathics/core/keycomparable.py +++ b/mathics/core/keycomparable.py @@ -2,6 +2,7 @@ Base classes for canonical order. """ + from typing import Tuple