diff --git a/mathics/builtin/arithfns/basic.py b/mathics/builtin/arithfns/basic.py index ceb239b0f..9f60c8a55 100644 --- a/mathics/builtin/arithfns/basic.py +++ b/mathics/builtin/arithfns/basic.py @@ -249,7 +249,7 @@ class Plus(InfixOperator, SympyFunction): 'Plus' has default value 0: >> DefaultValues[Plus] - = {HoldPattern[Default[Plus]] :> 0} + = {HoldPattern[Default[Plus]] ⧴ 0} >> a /. n_. + x_ :> {n, x} = {0, a} @@ -326,7 +326,7 @@ class Power(InfixOperator, MPMathFunction): 'Power' has default value 1 for its second argument: >> DefaultValues[Power] - = {HoldPattern[Default[Power, 2]] :> 1} + = {HoldPattern[Default[Power, 2]] ⧴ 1} >> a /. x_ ^ n_. :> {x, n} = {a, 1} @@ -537,7 +537,7 @@ class Times(InfixOperator, SympyFunction): 'Times' has default value 1: >> DefaultValues[Times] - = {HoldPattern[Default[Times]] :> 1} + = {HoldPattern[Default[Times]] ⧴ 1} >> a /. n_. * x_ :> {n, x} = {1, a} """ diff --git a/mathics/builtin/arithmetic.py b/mathics/builtin/arithmetic.py index 70df82800..f0d8ead19 100644 --- a/mathics/builtin/arithmetic.py +++ b/mathics/builtin/arithmetic.py @@ -249,7 +249,7 @@ class Boole(Builtin): >> Boole[7 < 5] = 0 >> Boole[a == 7] - = Boole[a == 7] + = Boole[a ⩵ 7] """ attributes = A_LISTABLE | A_PROTECTED diff --git a/mathics/builtin/assignments/assignment.py b/mathics/builtin/assignments/assignment.py index cc85e83ca..51877fefd 100644 --- a/mathics/builtin/assignments/assignment.py +++ b/mathics/builtin/assignments/assignment.py @@ -83,7 +83,7 @@ class Set(InfixOperator): An assignment like this creates an ownvalue: >> OwnValues[a] - = {HoldPattern[a] :> 3} + = {HoldPattern[a] ⧴ 3} You can set multiple values at once using lists: >> {a, b, c} = {10, 2, 3} @@ -246,7 +246,7 @@ class TagSet(Builtin): = {} >> UpValues[square] - = {HoldPattern[area[square[s_]]] :> s ^ 2} + = {HoldPattern[area[square[s_]]] ⧴ s ^ 2} The symbol $f$ must appear as the ultimate head of $lhs$ or as the head \ of an element in $lhs$: @@ -341,7 +341,7 @@ class UpSet(InfixOperator): >> DownValues[a] = {} >> UpValues[b] - = {HoldPattern[a[b]] :> 3} + = {HoldPattern[a[b]] ⧴ 3} You can use 'UpSet' to specify special values like format values. However, these values will not be saved in 'UpValues': @@ -388,7 +388,7 @@ class UpSetDelayed(UpSet): >> a[b] = 2 >> UpValues[b] - = {HoldPattern[a[b]] :> x} + = {HoldPattern[a[b]] ⧴ x} """ attributes = A_HOLD_ALL | A_PROTECTED | A_SEQUENCE_HOLD diff --git a/mathics/builtin/assignments/types.py b/mathics/builtin/assignments/types.py index f40ddb99d..ef755af5b 100644 --- a/mathics/builtin/assignments/types.py +++ b/mathics/builtin/assignments/types.py @@ -32,7 +32,7 @@ class DefaultValues(Builtin): >> Default[f, 1] = 4 = 4 >> DefaultValues[f] - = {HoldPattern[Default[f, 1]] :> 4} + = {HoldPattern[Default[f, 1]] ⧴ 4} You can assign values to 'DefaultValues': >> DefaultValues[g] = {Default[g] -> 3}; @@ -71,7 +71,7 @@ class Messages(Builtin): >> a::b = "foo" = foo >> Messages[a] - = {HoldPattern[a::b] :> foo} + = {HoldPattern[a::b] ⧴ foo} >> Messages[a] = {a::c :> "bar"}; >> a::c // InputForm = "bar" @@ -105,7 +105,7 @@ class NValues(Builtin): = {} >> N[a] = 3; >> NValues[a] - = {HoldPattern[N[a, MachinePrecision]] :> 3} + = {HoldPattern[N[a, MachinePrecision]] ⧴ 3} You can assign values to 'NValues': >> NValues[b] := {N[b, MachinePrecision] :> 2} @@ -124,7 +124,7 @@ class NValues(Builtin): inappropriate rules will never be used: >> NValues[d] = {foo -> bar}; >> NValues[d] - = {HoldPattern[foo] :> bar} + = {HoldPattern[foo] ⧴ bar} >> N[d] = d """ @@ -153,7 +153,7 @@ class SubValues(Builtin): >> f[1][x_] := x >> f[2][x_] := x ^ 2 >> SubValues[f] - = {HoldPattern[f[2][x_]] :> x ^ 2, HoldPattern[f[1][x_]] :> x} + = {HoldPattern[f[2][x_]] ⧴ x ^ 2, HoldPattern[f[1][x_]] ⧴ x} >> Definition[f] = f[2][x_] = x^2 . diff --git a/mathics/builtin/assignments/upvalues.py b/mathics/builtin/assignments/upvalues.py index df899d79a..e62dd9108 100644 --- a/mathics/builtin/assignments/upvalues.py +++ b/mathics/builtin/assignments/upvalues.py @@ -27,9 +27,9 @@ class UpValues(Builtin): >> a + b ^= 2 = 2 >> UpValues[a] - = {HoldPattern[a + b] :> 2} + = {HoldPattern[a + b] ⧴ 2} >> UpValues[b] - = {HoldPattern[a + b] :> 2} + = {HoldPattern[a + b] ⧴ 2} You can assign values to 'UpValues': >> UpValues[pi] := {Sin[pi] :> 0} diff --git a/mathics/builtin/atomic/strings.py b/mathics/builtin/atomic/strings.py index 81bd10052..d6b9a3b9a 100644 --- a/mathics/builtin/atomic/strings.py +++ b/mathics/builtin/atomic/strings.py @@ -881,7 +881,7 @@ class ToString(Builtin): """ options = { - "CharacterEncoding": '"Unicode"', + "CharacterEncoding": "$CharacterEncoding", "FormatType": "OutputForm", "NumberMarks": "$NumberMarks", "PageHeight": "Infinity", @@ -898,8 +898,21 @@ def eval_default(self, value, evaluation: Evaluation, options: dict): def eval_form(self, expr, form, evaluation: Evaluation, options: dict): "ToString[expr_, form_Symbol, OptionsPattern[ToString]]" - encoding = options["System`CharacterEncoding"] - return eval_ToString(expr, form, encoding.value, evaluation) + encoding = options["System`CharacterEncoding"].evaluate(evaluation) + if isinstance(encoding, String): + encoding_str = encoding.value + if encoding_str not in _encodings: + evaluation.message("$CharacterEncoding", "charcode", encoding) + encoding_str = evaluation.definitions.get_ownvalue( + "System`$SystemCharacterEncoding" + ).value + else: + evaluation.message("$CharacterEncoding", "charcode", encoding) + encoding_str = evaluation.definitions.get_ownvalue( + "System`$SystemCharacterEncoding" + ).value + + return eval_ToString(expr, form, encoding_str, evaluation) class Transliterate(Builtin): diff --git a/mathics/builtin/atomic/symbols.py b/mathics/builtin/atomic/symbols.py index 8593d9ddc..a483c9b60 100644 --- a/mathics/builtin/atomic/symbols.py +++ b/mathics/builtin/atomic/symbols.py @@ -276,7 +276,7 @@ class Definition(Builtin): . . Default[r, 1] = 2 . - .Options[r] = {Opt -> 3} + .Options[r] = {Opt ⇾ 3} . For 'ReadProtected' symbols, 'Definition' just prints attributes, default values and options: @@ -286,7 +286,7 @@ class Definition(Builtin): . . Default[r, 1] = 2 . - . Options[r] = {Opt -> 3} + . Options[r] = {Opt ⇾ 3} This is the same for built-in symbols: >> Definition[Plus] = Attributes[Plus] = {Flat, Listable, NumericFunction, OneIdentity, Orderless, Protected} @@ -295,7 +295,7 @@ class Definition(Builtin): >> Definition[Level] = Attributes[Level] = {Protected} . - . Options[Level] = {Heads -> False} + . Options[Level] = {Heads ⇾ False} 'ReadProtected' can be removed, unless the symbol is locked: >> ClearAttributes[r, ReadProtected] @@ -306,7 +306,7 @@ class Definition(Builtin): . . Default[r, 1] = 2 . - . Options[r] = {Opt -> 3} + . Options[r] = {Opt ⇾ 3} 'ClearAll' clears everything: >> ClearAll[r] >> Definition[r] @@ -359,7 +359,7 @@ class DownValues(Builtin): >> f[x_] := x ^ 2 >> DownValues[f] - = {HoldPattern[f[x_]] :> x ^ 2} + = {HoldPattern[f[x_]] ⧴ x ^ 2} Mathics will sort the rules you assign to a symbol according to \ their specificity. If it cannot decide which rule is more special, \ @@ -367,7 +367,7 @@ class DownValues(Builtin): >> f[x_Integer] := 2 >> f[x_Real] := 3 >> DownValues[f] - = {HoldPattern[f[x_Real]] :> 3, HoldPattern[f[x_Integer]] :> 2, HoldPattern[f[x_]] :> x ^ 2} + = {HoldPattern[f[x_Real]] ⧴ 3, HoldPattern[f[x_Integer]] ⧴ 2, HoldPattern[f[x_]] ⧴ x ^ 2} >> f[3] = 2 >> f[3.] @@ -417,11 +417,11 @@ class FormatValues(Builtin): Now, to see the rules, we can use 'FormatValues': >> FormatValues[F] - = {HoldPattern[Subscript[x_, F]] :> Subscript[x, F]} + = {HoldPattern[Subscript[x_, F]] ⧴ Subscript[x, F]} The replacment pattern on the right in the delayed rule is formatted according to the top-level form. To see the rule input, we can use 'InputForm': >> FormatValues[F] //InputForm - = {HoldPattern[Format[F[x_], OutputForm]] :> Subscript[x, F]} + = {HoldPattern[Format[F[x_], OutputForm]] ⧴ Subscript[x, F]} """ summary_text = ( @@ -617,13 +617,13 @@ class OwnValues(Builtin): >> x = 3; >> x = 2; >> OwnValues[x] - = {HoldPattern[x] :> 2} + = {HoldPattern[x] ⧴ 2} >> x := y >> OwnValues[x] - = {HoldPattern[x] :> y} + = {HoldPattern[x] ⧴ y} >> y = 5; >> OwnValues[x] - = {HoldPattern[x] :> y} + = {HoldPattern[x] ⧴ y} >> Hold[x] /. OwnValues[x] = Hold[y] >> Hold[x] /. OwnValues[x] // ReleaseHold diff --git a/mathics/builtin/attributes.py b/mathics/builtin/attributes.py index a4dc27382..90c950641 100644 --- a/mathics/builtin/attributes.py +++ b/mathics/builtin/attributes.py @@ -173,9 +173,9 @@ class Constant(Predefined): Constant symbols cannot be used as variables in 'Solve' and related functions: - >> Solve[x + E == 0, E] + >> Solve[x + E ⩵ 0, E] : E is not a valid variable. - = Solve[x + E == 0, E] + = Solve[x + E ⩵ 0, E] """ summary_text = "treat as a constant in differentiation, etc" diff --git a/mathics/builtin/drawing/drawing_options.py b/mathics/builtin/drawing/drawing_options.py index a1c61f6b9..a224f4f89 100644 --- a/mathics/builtin/drawing/drawing_options.py +++ b/mathics/builtin/drawing/drawing_options.py @@ -34,7 +34,7 @@ class Automatic(Builtin): graphical options: >> Cases[Options[Plot], HoldPattern[_ :> Automatic]] - = {AxesOrigin :> Automatic, Background :> Automatic, BaselinePosition :> Automatic, ContentSelectable :> Automatic, CoordinatesToolOptions :> Automatic, Exclusions :> Automatic, FrameTicks :> Automatic, ImageSize :> Automatic, Method :> Automatic, PlotRange :> Automatic, PlotRangePadding :> Automatic, PlotRegion :> Automatic, PreserveImageOptions :> Automatic, Ticks :> Automatic} + = {AxesOrigin ⧴ Automatic, Background ⧴ Automatic, BaselinePosition ⧴ Automatic, ContentSelectable ⧴ Automatic, CoordinatesToolOptions ⧴ Automatic, Exclusions ⧴ Automatic, FrameTicks ⧴ Automatic, ImageSize ⧴ Automatic, Method ⧴ Automatic, PlotRange ⧴ Automatic, PlotRangePadding ⧴ Automatic, PlotRegion ⧴ Automatic, PreserveImageOptions ⧴ Automatic, Ticks ⧴ Automatic} """ summary_text = "graph option value to choose parameters automatically" diff --git a/mathics/builtin/fileformats/htmlformat.py b/mathics/builtin/fileformats/htmlformat.py index 75aeb2c5b..ea89beb18 100644 --- a/mathics/builtin/fileformats/htmlformat.py +++ b/mathics/builtin/fileformats/htmlformat.py @@ -280,7 +280,7 @@ class DataImport(_DataImport): >> Import["ExampleData/PrimeMeridian.html", "Data"][[1, 1, 2, 3]] - = {Washington, D.C., 77...03′56.07″ W (1897) or 77...04′02.24″ W (NAD 27) or 77...04′01.16″ W (NAD 83), New Naval Observatory meridian} + = {Washington, D.C., 77°03′56.07″ W (1897) or 77°04′02.24″ W (NAD 27) or 77°04′01.16″ W (NAD 83), New Naval Observatory meridian} #> Length[Import["ExampleData/PrimeMeridian.html", "Data"]] = 3 diff --git a/mathics/builtin/fileformats/xmlformat.py b/mathics/builtin/fileformats/xmlformat.py index d5a2dd758..3217558cf 100644 --- a/mathics/builtin/fileformats/xmlformat.py +++ b/mathics/builtin/fileformats/xmlformat.py @@ -395,7 +395,7 @@ class XMLObjectImport(Builtin): = XMLElement[identification, {}, {XMLElement[encoding, {}, {XMLElement[software, {}, {MuseScore 1.2}], XMLElement[encoding-date, {}, {2012-09-12}]}]}] >> Part[Import["ExampleData/Namespaces.xml"], 2] - = XMLElement[book, {{http://www.w3.org/2000/xmlns/, xmlns} -> urn:loc.gov:books}, {XMLElement[title, {}, {Cheaper by the Dozen}], XMLElement[{urn:ISBN:0-395-36341-6, number}, {}, {1568491379}], XMLElement[notes, {}, {XMLElement[p, {{http://www.w3.org/2000/xmlns/, xmlns} -> http://www.w3.org/1999/xhtml}, {This is a, XMLElement[i, {}, {funny, book!}]}]}]}] + = XMLElement[book, {{http://www.w3.org/2000/xmlns/, xmlns} ⇾ urn:loc.gov:books}, {XMLElement[title, {}, {Cheaper by the Dozen}], XMLElement[{urn:ISBN:0-395-36341-6, number}, {}, {1568491379}], XMLElement[notes, {}, {XMLElement[p, {{http://www.w3.org/2000/xmlns/, xmlns} ⇾ http://www.w3.org/1999/xhtml}, {This is a, XMLElement[i, {}, {funny, book!}]}]}]}] """ summary_text = "import elements from xml" diff --git a/mathics/builtin/files_io/filesystem.py b/mathics/builtin/files_io/filesystem.py index ac56e3303..89713626c 100644 --- a/mathics/builtin/files_io/filesystem.py +++ b/mathics/builtin/files_io/filesystem.py @@ -561,7 +561,7 @@ class FileInformation(Builtin): This function is totally undocumented in MMA! >> FileInformation["ExampleData/sunflowers.jpg"] - = {File -> ..., FileType -> File, ByteCount -> 142286, Date -> ...} + = {File ⇾ ..., FileType ⇾ File, ByteCount ⇾ 142286, Date ⇾ ...} """ rules = { diff --git a/mathics/builtin/files_io/importexport.py b/mathics/builtin/files_io/importexport.py index 6db2700e7..e7d8c5634 100644 --- a/mathics/builtin/files_io/importexport.py +++ b/mathics/builtin/files_io/importexport.py @@ -1338,7 +1338,7 @@ class Import(Builtin): ## JSON >> Import["ExampleData/colors.json"] - = {colorsArray -> {{colorName -> black, rgbValue -> (0, 0, 0), hexValue -> #000000}, {colorName -> red, rgbValue -> (255, 0, 0), hexValue -> #FF0000}, {colorName -> green, rgbValue -> (0, 255, 0), hexValue -> #00FF00}, {colorName -> blue, rgbValue -> (0, 0, 255), hexValue -> #0000FF}, {colorName -> yellow, rgbValue -> (255, 255, 0), hexValue -> #FFFF00}, {colorName -> cyan, rgbValue -> (0, 255, 255), hexValue -> #00FFFF}, {colorName -> magenta, rgbValue -> (255, 0, 255), hexValue -> #FF00FF}, {colorName -> white, rgbValue -> (255, 255, 255), hexValue -> #FFFFFF}}} + = {colorsArray ⇾ {{colorName ⇾ black, rgbValue ⇾ (0, 0, 0), hexValue ⇾ #000000}, {colorName ⇾ red, rgbValue ⇾ (255, 0, 0), hexValue ⇾ #FF0000}, {colorName ⇾ green, rgbValue ⇾ (0, 255, 0), hexValue ⇾ #00FF00}, {colorName ⇾ blue, rgbValue ⇾ (0, 0, 255), hexValue ⇾ #0000FF}, {colorName ⇾ yellow, rgbValue ⇾ (255, 255, 0), hexValue ⇾ #FFFF00}, {colorName ⇾ cyan, rgbValue ⇾ (0, 255, 255), hexValue ⇾ #00FFFF}, {colorName ⇾ magenta, rgbValue ⇾ (255, 0, 255), hexValue ⇾ #FF00FF}, {colorName ⇾ white, rgbValue ⇾ (255, 255, 255), hexValue ⇾ #FFFFFF}}} """ messages = { diff --git a/mathics/builtin/functional/apply_fns_to_lists.py b/mathics/builtin/functional/apply_fns_to_lists.py index 4ef96d537..3ed3a6aa4 100644 --- a/mathics/builtin/functional/apply_fns_to_lists.py +++ b/mathics/builtin/functional/apply_fns_to_lists.py @@ -137,7 +137,7 @@ class Map(InfixOperator): Map $f$ onto an association: >> Map[f, <|"a" -> 1, "b" -> 2, "c" -> 3, "d" -> 4|>] - = <|a -> f[1], b -> f[2], c -> f[3], d -> f[4]|> + = <|a ⇾ f[1], b ⇾ f[2], c ⇾ f[3], d ⇾ f[4]|> Include heads: >> Map[f, a + b + c, Heads->True] @@ -256,11 +256,11 @@ class MapAt(Builtin): Map $f$ onto at the second position of an association: >> MapAt[f, <|"a" -> 1, "b" -> 2, "c" -> 3, "d" -> 4|>, 2] - = {a -> 1, b -> f[2], c -> 3, d -> 4} + = {a ⇾ 1, b ⇾ f[2], c ⇾ 3, d ⇾ 4} Same as above, but select the second-from-the-end position: >> MapAt[f, <|"a" -> 1, "b" -> 2, "c" -> 3, "d" -> 4|>, -2] - = {a -> 1, b -> 2, c -> f[3], d -> 4} + = {a ⇾ 1, b ⇾ 2, c ⇾ f[3], d ⇾ 4} """ diff --git a/mathics/builtin/layout.py b/mathics/builtin/layout.py index 09b81588e..d246705ef 100644 --- a/mathics/builtin/layout.py +++ b/mathics/builtin/layout.py @@ -99,8 +99,8 @@ class Format(Builtin): Use 'InputForm' if you want to get a 'Format' definition that can be used as \ Mathics3 input: - >> Format[{a->Integrate[F[x], x]}, StandardForm] //InputForm - = Format[{a -> Integrate[F[x], x]}, StandardForm] + >> Format[{a -> Integrate[F[x], x]}, StandardForm] //InputForm + = Format[{a ⇾ Integrate[F[x], x]}, StandardForm] In WMA, you might not get something that can be used as input. diff --git a/mathics/builtin/list/associations.py b/mathics/builtin/list/associations.py index 72282cbe6..c378bc9c3 100644 --- a/mathics/builtin/list/associations.py +++ b/mathics/builtin/list/associations.py @@ -38,14 +38,14 @@ class Association(Builtin): = Association >> <|a -> x, b -> y|> - = <|a -> x, b -> y|> + = <|a ⇾ x, b ⇾ y|> >> Association[{a -> x^2, b -> y}] - = <|a -> x ^ 2, b -> y|> + = <|a ⇾ x ^ 2, b ⇾ y|> Associations can be nested: >> <|a -> x, b -> y, <|a -> z, d -> t|>|> - = <|a -> z, b -> y, d -> t|> + = <|a ⇾ z, b ⇾ y, d ⇾ t|> """ error_idx = 0 diff --git a/mathics/builtin/list/rearrange.py b/mathics/builtin/list/rearrange.py index 34aa11475..1f2abac61 100644 --- a/mathics/builtin/list/rearrange.py +++ b/mathics/builtin/list/rearrange.py @@ -1267,7 +1267,7 @@ class Split(Builtin): Split based on first element >> Split[{x -> a, x -> y, 2 -> a, z -> c, z -> a}, First[#1] === First[#2] &] - = {{x -> a, x -> y}, {2 -> a}, {z -> c, z -> a}} + = {{x ⇾ a, x ⇾ y}, {2 ⇾ a}, {z ⇾ c, z ⇾ a}} """ rules = { @@ -1410,7 +1410,7 @@ class Union(_SetOperation): A union of two associations: >> Union[{a -> b}, {c -> d}] - = {a -> b, c -> d} + = {a ⇾ b, c ⇾ d} A union of one item is the item. Note that the list is sorted: >> Union[{c, b, a}] diff --git a/mathics/builtin/numbers/algebra.py b/mathics/builtin/numbers/algebra.py index 7acd21f5f..12f0b60cb 100644 --- a/mathics/builtin/numbers/algebra.py +++ b/mathics/builtin/numbers/algebra.py @@ -135,7 +135,7 @@ class Apart(Builtin): = Sin[1 / (x ^ 2 - y ^ 2)] >> a == "A" // Apart // InputForm - = a == "A" + = a ⩵ "A" """ attributes = A_LISTABLE | A_PROTECTED @@ -175,7 +175,7 @@ class Cancel(Builtin): But it does not touch other expressions: >> a == "A" // Cancel // InputForm - = a == "A" + = a ⩵ "A" """ attributes = A_LISTABLE | A_PROTECTED @@ -696,7 +696,7 @@ class Expand(_Expand): 'Expand' expands items in lists and rules: >> Expand[{4 (x + y), 2 (x + y) -> 4 (x + y)}] - = {4 x + 4 y, 2 x + 2 y -> 4 x + 4 y} + = {4 x + 4 y, 2 x + 2 y ⇾ 4 x + 4 y} 'Expand' expands trigonometric identities >> Expand[Sin[x + y], Trig -> True] @@ -961,7 +961,7 @@ class Factor(Builtin): Factor can also be used with equations: >> Factor[x a == x b + x c] - = a x == x (b + c) + = a x ⩵ x (b + c) And lists: >> Factor[{x + x^2, 2 x + 2 y + 2}] @@ -973,11 +973,11 @@ class Factor(Builtin): You can use Factor to find when a polynomial is zero: >> x^2 - x == 0 // Factor - = x (-1 + x) == 0 + = x (-1 + x) ⩵ 0 But it does not touch other expressions: >> a == "A" // Factor // InputForm - = a == "A" + = a ⩵ "A" """ attributes = A_LISTABLE | A_PROTECTED diff --git a/mathics/builtin/numbers/calculus.py b/mathics/builtin/numbers/calculus.py index 79afa04fc..5e35b9d35 100644 --- a/mathics/builtin/numbers/calculus.py +++ b/mathics/builtin/numbers/calculus.py @@ -766,15 +766,15 @@ class FindMaximum(_BaseFinder): >> FindMaximum[-(x-3)^2+2., {x, 1}] : Encountered a gradient that is effectively zero. The result returned may not be a maximum; it may be a minimum or a saddle point. - = {2., {x -> 3.}} + = {2., {x ⇾ 3.}} >> FindMaximum[-10*^-30 *(x-3)^2+2., {x, 1}] : Encountered a gradient that is effectively zero. The result returned may not be a maximum; it may be a minimum or a saddle point. - = {2., {x -> 3.}} + = {2., {x ⇾ 3.}} >> FindMaximum[Sin[x], {x, 1}] - = {1., {x -> 1.5708}} + = {1., {x ⇾ 1.5708}} >> phi[x_?NumberQ]:=NIntegrate[u, {u, 0., x}, Method->"Internal"]; >> Quiet[FindMaximum[-phi[x] + x, {x, 1.2}, Method->"Newton"]] - = {0.5, {x -> 1.00001}} + = {0.5, {x ⇾ 1.00001}} >> Clear[phi]; For a not so well behaving function, the result can be less accurate: >> FindMaximum[-Exp[-1/x^2]+1., {x,1.2}, MaxIterations->2] @@ -817,15 +817,15 @@ class FindMinimum(_BaseFinder): >> FindMinimum[(x-3)^2+2., {x, 1}] : Encountered a gradient that is effectively zero. The result returned may not be a minimum; it may be a maximum or a saddle point. - = {2., {x -> 3.}} + = {2., {x ⇾ 3.}} >> FindMinimum[10*^-30 *(x-3)^2+2., {x, 1}] : Encountered a gradient that is effectively zero. The result returned may not be a minimum; it may be a maximum or a saddle point. - = {2., {x -> 3.}} + = {2., {x ⇾ 3.}} >> FindMinimum[Sin[x], {x, 1}] - = {-1., {x -> -1.5708}} + = {-1., {x ⇾ -1.5708}} >> phi[x_?NumberQ]:=NIntegrate[u,{u,0,x}, Method->"Internal"]; >> Quiet[FindMinimum[phi[x]-x,{x, 1.2}, Method->"Newton"]] - = {-0.5, {x -> 1.00001}} + = {-0.5, {x ⇾ 1.00001}} >> Clear[phi]; For a not so well behaving function, the result can be less accurate: >> FindMinimum[Exp[-1/x^2]+1., {x,1.2}, MaxIterations->2] @@ -870,28 +870,28 @@ class FindRoot(_BaseFinder): should have a first derivative. >> FindRoot[Cos[x], {x, 1}] - = {x -> 1.5708} + = {x ⇾ 1.5708} >> FindRoot[Sin[x] + Exp[x],{x, 0}] - = {x -> -0.588533} + = {x ⇾ -0.588533} >> FindRoot[Sin[x] + Exp[x] == Pi,{x, 0}] - = {x -> 0.866815} + = {x ⇾ 0.866815} 'FindRoot' has attribute 'HoldAll' and effectively uses 'Block' to localize $x$. However, in the result $x$ will eventually still be replaced by its value. >> x = "I am the result!"; >> FindRoot[Tan[x] + Sin[x] == Pi, {x, 1}] - = {I am the result! -> 1.14911} + = {I am the result! ⇾ 1.14911} >> Clear[x] 'FindRoot' stops after 100 iterations: >> FindRoot[x^2 + x + 1, {x, 1}] : The maximum number of iterations was exceeded. The result might be inaccurate. - = {x -> -1.} + = {x ⇾ -1.} Find complex roots: >> FindRoot[x ^ 2 + x + 1, {x, -I}] - = {x -> -0.5 - 0.866025 I} + = {x ⇾ -0.5 - 0.866025 I} The function has to return numerical values: >> FindRoot[f[x] == 0, {x, 0}] @@ -905,7 +905,7 @@ class FindRoot(_BaseFinder): >> FindRoot[x^2 - 2, {x, 1,3}, Method->"Secant"] - = {x -> 1.41421} + = {x ⇾ 1.41421} """ rules = { @@ -954,7 +954,7 @@ class Integers(Builtin): Limit a solution to integer numbers: >> Solve[-4 - 4 x + x^4 + x^5 == 0, x, Integers] - = {{x -> -1}} + = {{x ⇾ -1}} >> Solve[x^4 == 4, x, Integers] = {} """ @@ -1629,7 +1629,7 @@ class Reals(Builtin): Limit a solution to real numbers: >> Solve[x^3 == 1, x, Reals] - = {{x -> 1}} + = {{x ⇾ 1}} """ summary_text = "the domain of the Real numbers" @@ -2260,13 +2260,13 @@ class Solve(Builtin): >> Solve[x ^ 2 - 3 x == 4, x] - = {{x -> -1}, {x -> 4}} + = {{x ⇾ -1}, {x ⇾ 4}} >> Solve[4 y - 8 == 0, y] - = {{y -> 2}} + = {{y ⇾ 2}} Apply the solution: >> sol = Solve[2 x^2 - 10 x - 12 == 0, x] - = {{x -> -1}, {x -> 6}} + = {{x ⇾ -1}, {x ⇾ 6}} >> x /. sol = {-1, 6} @@ -2280,21 +2280,21 @@ class Solve(Builtin): Rational equations: >> Solve[x / (x ^ 2 + 1) == 1, x] - = {{x -> 1 / 2 - I / 2 Sqrt[3]}, {x -> 1 / 2 + I / 2 Sqrt[3]}} + = {{x ⇾ 1 / 2 - I / 2 Sqrt[3]}, {x ⇾ 1 / 2 + I / 2 Sqrt[3]}} >> Solve[(x^2 + 3 x + 2)/(4 x - 2) == 0, x] - = {{x -> -2}, {x -> -1}} + = {{x ⇾ -2}, {x ⇾ -1}} Transcendental equations: >> Solve[Cos[x] == 0, x] - = {{x -> Pi / 2}, {x -> 3 Pi / 2}} + = {{x ⇾ Pi / 2}, {x ⇾ 3 Pi / 2}} Solve can only solve equations with respect to symbols or functions: >> Solve[f[x + y] == 3, f[x + y]] - = {{f[x + y] -> 3}} + = {{f[x + y] ⇾ 3}} >> Solve[a + b == 2, a + b] : a + b is not a valid variable. - = Solve[a + b == 2, a + b] + = Solve[a + b ⩵ 2, a + b] This happens when solving with respect to an assigned symbol: >> x = 3; @@ -2309,24 +2309,24 @@ class Solve(Builtin): Solve a system of equations: >> eqs = {3 x ^ 2 - 3 y == 0, 3 y ^ 2 - 3 x == 0}; >> sol = Solve[eqs, {x, y}] // Simplify - = {{x -> 0, y -> 0}, {x -> 1, y -> 1}, {x -> -1 / 2 + I / 2 Sqrt[3], y -> -1 / 2 - I / 2 Sqrt[3]}, {x -> -1 / 2 - I / 2 Sqrt[3], y -> -1 / 2 + I / 2 Sqrt[3]}} + = {{x ⇾ 0, y ⇾ 0}, {x ⇾ 1, y ⇾ 1}, {x ⇾ -1 / 2 + I / 2 Sqrt[3], y ⇾ -1 / 2 - I / 2 Sqrt[3]}, {x ⇾ -1 / 2 - I / 2 Sqrt[3], y ⇾ -1 / 2 + I / 2 Sqrt[3]}} >> eqs /. sol // Simplify = {{True, True}, {True, True}, {True, True}, {True, True}} Solve when given an underdetermined system: >> Solve[x^2 == 1 && z^2 == -1, {x, y, z}] : Equations may not give solutions for all "solve" variables. - = {{x -> -1, z -> -I}, {x -> -1, z -> I}, {x -> 1, z -> -I}, {x -> 1, z -> I}} + = {{x ⇾ -1, z ⇾ -I}, {x ⇾ -1, z ⇾ I}, {x ⇾ 1, z ⇾ -I}, {x ⇾ 1, z ⇾ I}} Examples using specifying the Domain in solutions: >> Solve[x^2 == -1, x, Reals] = {} >> Solve[x^2 == 1, x, Reals] - = {{x -> -1}, {x -> 1}} + = {{x ⇾ -1}, {x ⇾ 1}} >> Solve[x^2 == -1, x, Complexes] - = {{x -> -I}, {x -> I}} + = {{x ⇾ -I}, {x ⇾ I}} >> Solve[4 - 4 * x^2 - x^4 + x^6 == 0, x, Integers] - = {{x -> -1}, {x -> 1}} + = {{x ⇾ -1}, {x ⇾ 1}} """ messages = { diff --git a/mathics/builtin/numbers/diffeqns.py b/mathics/builtin/numbers/diffeqns.py index 66d0a4475..4b3630a77 100644 --- a/mathics/builtin/numbers/diffeqns.py +++ b/mathics/builtin/numbers/diffeqns.py @@ -25,23 +25,23 @@ class DSolve(Builtin): >> DSolve[y''[x] == 0, y[x], x] - = {{y[x] -> x C[2] + C[1]}} + = {{y[x] ⇾ x C[2] + C[1]}} >> DSolve[y''[x] == y[x], y[x], x] - = {{y[x] -> C[1] E ^ (-x) + C[2] E ^ x}} + = {{y[x] ⇾ C[1] E ^ (-x) + C[2] E ^ x}} >> DSolve[y''[x] == y[x], y, x] - = {{y -> Function[{x}, C[1] E ^ (-x) + C[2] E ^ x]}} + = {{y ⇾ Function[{x}, C[1] E ^ (-x) + C[2] E ^ x]}} DSolve can also solve basic PDE >> DSolve[D[f[x, y], x] / f[x, y] + 3 D[f[x, y], y] / f[x, y] == 2, f, {x, y}] - = {{f -> Function[{x, y}, E ^ (x / 5 + 3 y / 5) C[1][3 x - y]]}} + = {{f ⇾ Function[{x, y}, E ^ (x / 5 + 3 y / 5) C[1][3 x - y]]}} >> DSolve[D[f[x, y], x] x + D[f[x, y], y] y == 2, f[x, y], {x, y}] - = {{f[x, y] -> 2 Log[x] + C[1][y / x]}} + = {{f[x, y] ⇾ 2 Log[x] + C[1][y / x]}} >> DSolve[D[y[x, t], t] + 2 D[y[x, t], x] == 0, y[x, t], {x, t}] - = {{y[x, t] -> C[1][x - 2 t]}} + = {{y[x, t] ⇾ C[1][x - 2 t]}} """ # TODO: GeneratedParameters option diff --git a/mathics/builtin/numeric.py b/mathics/builtin/numeric.py index de4fbf844..b2e56cf00 100644 --- a/mathics/builtin/numeric.py +++ b/mathics/builtin/numeric.py @@ -225,7 +225,7 @@ class N(Builtin): >> UpValues[d] = {} >> NValues[d] - = {HoldPattern[N[d, MachinePrecision]] :> 5} + = {HoldPattern[N[d, MachinePrecision]] ⧴ 5} >> e /: N[e] = 6; >> N[e] = 6. @@ -337,13 +337,13 @@ class Piecewise(SympyFunction): Heaviside function >> Piecewise[{{0, x <= 0}}, 1] - = Piecewise[{{0, x <= 0}}, 1] + = Piecewise[{{0, x ≤ 0}}, 1] ## D[%, x] ## Piecewise({{0, Or[x < 0, x > 0]}}, Indeterminate). >> Integrate[Piecewise[{{1, x <= 0}, {-1, x > 0}}], x] - = Piecewise[{{x, x <= 0}}, -x] + = Piecewise[{{x, x ≤ 0}}, -x] >> Integrate[Piecewise[{{1, x <= 0}, {-1, x > 0}}], {x, -1, 2}] = -1 @@ -613,7 +613,7 @@ class RealSign(Builtin): = RealSign[2. + 3. I] >> D[RealSign[x^2],x] - = 2 x Piecewise[{{0, x ^ 2 != 0}}, Indeterminate] + = 2 x Piecewise[{{0, x ^ 2 ≠ 0}}, Indeterminate] >> Integrate[RealSign[u],{u,0,x}] = RealAbs[x] """ diff --git a/mathics/builtin/optimization.py b/mathics/builtin/optimization.py index bda30f5c4..3358d68a0 100644 --- a/mathics/builtin/optimization.py +++ b/mathics/builtin/optimization.py @@ -45,7 +45,7 @@ class Maximize(Builtin): >> Maximize[-2 x^2 - 3 x + 5, x] - = {{49 / 8, {x -> -3 / 4}}} + = {{49 / 8, {x ⇾ -3 / 4}}} """ attributes = A_PROTECTED | A_READ_PROTECTED @@ -101,7 +101,7 @@ class Minimize(Builtin): >> Minimize[2 x^2 - 3 x + 5, x] - = {{31 / 8, {x -> 3 / 4}}} + = {{31 / 8, {x ⇾ 3 / 4}}} """ attributes = A_PROTECTED | A_READ_PROTECTED diff --git a/mathics/builtin/options.py b/mathics/builtin/options.py index 629a0a906..61dcb82d5 100644 --- a/mathics/builtin/options.py +++ b/mathics/builtin/options.py @@ -99,7 +99,7 @@ class Default(Builtin): Default values are stored in 'DefaultValues': >> DefaultValues[f] - = {HoldPattern[Default[f]] :> 1} + = {HoldPattern[Default[f]] ⧴ 1} You can use patterns for $k$ and $n$: >> Default[h, k_, n_] := {k, n} @@ -145,10 +145,10 @@ class FilterRules(Builtin): >> FilterRules[{x -> 100, y -> 1000}, x] - = {x -> 100} + = {x ⇾ 100} >> FilterRules[{x -> 100, y -> 1000, z -> 10000}, {a, b, x, z}] - = {x -> 100, z -> 10000} + = {x ⇾ 100, z ⇾ 10000} """ rules = { @@ -313,9 +313,9 @@ class Options(Builtin): You can assign values to 'Options' to specify options. >> Options[f] = {n -> 2} - = {n -> 2} + = {n ⇾ 2} >> Options[f] - = {n :> 2} + = {n ⧴ 2} >> f[x_, OptionsPattern[f]] := x ^ OptionValue[n] >> f[x] = x ^ 2 @@ -338,13 +338,13 @@ class Options(Builtin): = {a} A single rule need not be given inside a list: >> Options[f] = a -> b - = a -> b + = a ⇾ b >> Options[f] - = {a :> b} + = {a ⧴ b} Options can only be assigned to symbols: >> Options[a + b] = {a -> b} : Argument a + b at position 1 is expected to be a symbol. - = {a -> b} + = {a ⇾ b} See also :'OptionValue': @@ -399,7 +399,7 @@ class OptionValue(Builtin): First, set up a symbol with some options using 'Options': >> Options[MySetting] = {"foo" -> 5, "bar" -> 6} - = {foo -> 5, bar -> 6} + = {foo ⇾ 5, bar ⇾ 6} Now get a value previously set: diff --git a/mathics/builtin/patterns/rules.py b/mathics/builtin/patterns/rules.py index 57d4d2aee..62e6d2a1d 100644 --- a/mathics/builtin/patterns/rules.py +++ b/mathics/builtin/patterns/rules.py @@ -25,7 +25,7 @@ Let us consider, for example, the 'Rule': >> rule = F[u_]->g[u] - = F[u_] -> g[u] + = F[u_] ⇾ g[u] This rule associates the pattern 'F[u_]' with the expression 'g[u]'. @@ -279,8 +279,8 @@ class ReplaceAll(InfixOperator): >> {a, b} /. {{{a->x, b->y}, {a->w, b->z}}, {a->u, b->v}} = {{{x, y}, {w, z}}, {u, v}} >> {a, b} /. {{{a->x, b->y}, a->w, b->z}, {a->u, b->v}} - : Elements of {{a -> x, b -> y}, a -> w, b -> z} are a mixture of lists and nonlists. - = {{a, b} /. {{a -> x, b -> y}, a -> w, b -> z}, {u, v}} + : Elements of {{a ⇾ x, b -> y}, a ⇾ w, b ⇾ z} are a mixture of lists and nonlists. + = {{a, b} /. {{a ⇾ x, b ⇾ y}, a ⇾ w, b ⇾ z}, {u, v}} ReplaceAll also can be used as an operator: >> ReplaceAll[{a -> 1}][{a, b}] diff --git a/mathics/builtin/recurrence.py b/mathics/builtin/recurrence.py index 610119d03..fe282e623 100644 --- a/mathics/builtin/recurrence.py +++ b/mathics/builtin/recurrence.py @@ -34,22 +34,22 @@ class RSolve(Builtin): Solve a difference equation: >> RSolve[a[n] == a[n+1], a[n], n] - = {{a[n] -> C[0]}} + = {{a[n] ⇾ C[0]}} No boundary conditions gives two general parameters: >> RSolve[{a[n + 2] == a[n]}, a, n] - = {{a -> Function[{n}, C[0] + C[1] (-1) ^ n]}} + = {{a ⇾ Function[{n}, C[0] + C[1] (-1) ^ n]}} Include one boundary condition: >> RSolve[{a[n + 2] == a[n], a[0] == 1}, a, n] = ... ## Order of terms depends on interpreter: - ## PyPy: {{a -> Function[{n}, 1 - C[1] + C[1] -1 ^ n]}} - ## CPython: {{a -> Function[{n}, 1 + C[1] -1 ^ n - C[1]]} + ## PyPy: {{a ⇾ Function[{n}, 1 - C[1] + C[1] -1 ^ n]}} + ## CPython: {{a ⇾ Function[{n}, 1 + C[1] -1 ^ n - C[1]]} Get a "pure function" solution for a with two boundary conditions: >> RSolve[{a[n + 2] == a[n], a[0] == 1, a[1] == 4}, a, n] - = {{a -> Function[{n}, 5 / 2 - 3 (-1) ^ n / 2]}} + = {{a ⇾ Function[{n}, 5 / 2 - 3 (-1) ^ n / 2]}} """ messages = { diff --git a/mathics/builtin/sparse.py b/mathics/builtin/sparse.py index fc41a569f..3688edc9c 100644 --- a/mathics/builtin/sparse.py +++ b/mathics/builtin/sparse.py @@ -38,11 +38,11 @@ class SparseArray(Builtin): >> SparseArray[{{1, 2} -> 1, {2, 1} -> 1}] - = SparseArray[Automatic, {2, 2}, 0, {{1, 2} -> 1, {2, 1} -> 1}] + = SparseArray[Automatic, {2, 2}, 0, {{1, 2} ⇾ 1, {2, 1} ⇾ 1}] >> SparseArray[{{1, 2} -> 1, {2, 1} -> 1}, {3, 3}] - = SparseArray[Automatic, {3, 3}, 0, {{1, 2} -> 1, {2, 1} -> 1}] + = SparseArray[Automatic, {3, 3}, 0, {{1, 2} ⇾ 1, {2, 1} ⇾ 1}] >> M=SparseArray[{{0, a}, {b, 0}}] - = SparseArray[Automatic, {2, 2}, 0, {{1, 2} -> a, {2, 1} -> b}] + = SparseArray[Automatic, {2, 2}, 0, {{1, 2} ⇾ a, {2, 1} ⇾ b}] >> M //Normal = {{0, a}, {b, 0}} diff --git a/mathics/builtin/system.py b/mathics/builtin/system.py index 9fcb58b45..d0963ae4b 100644 --- a/mathics/builtin/system.py +++ b/mathics/builtin/system.py @@ -703,14 +703,14 @@ class SetEnvironment(Builtin): See that the environment variable has changed: S> GetEnvironment["FOO"] - = FOO -> bar + = FOO ⇾ bar Set two environment variables: S> SetEnvironment[{"FOO" -> "baz", "A" -> "B"}] See that the environment variable has changed: S> GetEnvironment["FOO"] - = FOO -> baz + = FOO ⇾ baz Environment values must be strings: @@ -719,12 +719,12 @@ class SetEnvironment(Builtin): = $Failed S> GetEnvironment["FOO"] - = FOO -> baz + = FOO ⇾ baz If the environment name is not a string, the evaluation fails without a message. S> SetEnvironment[1 -> "bar"] - = SetEnvironment[1 -> bar] + = SetEnvironment[1 ⇾ bar] See also :'Environment': diff --git a/mathics/builtin/tensors.py b/mathics/builtin/tensors.py index fba339a51..b06ffc77f 100644 --- a/mathics/builtin/tensors.py +++ b/mathics/builtin/tensors.py @@ -187,7 +187,7 @@ class Outer(Builtin): Outer product of two sparse arrays: >> Outer[Times, SparseArray[{{1, 2} -> a, {2, 1} -> b}], SparseArray[{{1, 2} -> c, {2, 1} -> d}]] - = SparseArray[Automatic, {2, 2, 2, 2}, 0, {{1, 2, 1, 2} -> a c, {1, 2, 2, 1} -> a d, {2, 1, 1, 2} -> b c, {2, 1, 2, 1} -> b d}] + = SparseArray[Automatic, {2, 2, 2, 2}, 0, {{1, 2, 1, 2} ⇾ a c, {1, 2, 2, 1} ⇾ a d, {2, 1, 1, 2} ⇾ b c, {2, 1, 2, 1} ⇾ b d}] 'Outer' of multiple lists: >> Outer[f, {a, b}, {x, y, z}, {1, 2}] @@ -446,7 +446,7 @@ class LeviCivitaTensor(Builtin): >> LeviCivitaTensor[3] - = SparseArray[Automatic, {3, 3, 3}, 0, {{1, 2, 3} -> 1, {1, 3, 2} -> -1, {2, 1, 3} -> -1, {2, 3, 1} -> 1, {3, 1, 2} -> 1, {3, 2, 1} -> -1}] + = SparseArray[Automatic, {3, 3, 3}, 0, {{1, 2, 3} ⇾ 1, {1, 3, 2} ⇾ -1, {2, 1, 3} ⇾ -1, {2, 3, 1} ⇾ 1, {3, 1, 2} ⇾ 1, {3, 2, 1} ⇾ -1}] >> LeviCivitaTensor[3, List] = {{{0, 0, 0}, {0, 0, 1}, {0, -1, 0}}, {{0, 0, -1}, {0, 0, 0}, {1, 0, 0}}, {{0, 1, 0}, {-1, 0, 0}, {0, 0, 0}}} diff --git a/mathics/builtin/testing_expressions/equality_inequality.py b/mathics/builtin/testing_expressions/equality_inequality.py index d27ed2ddb..4a1508bd7 100644 --- a/mathics/builtin/testing_expressions/equality_inequality.py +++ b/mathics/builtin/testing_expressions/equality_inequality.py @@ -496,7 +496,7 @@ class Equal(_EqualityOperator, _SympyComparison): only if the symbols are equal: >> Clear[a, b]; a == b - = a == b + = a ⩵ b >> a == a = True @@ -520,15 +520,15 @@ class Equal(_EqualityOperator, _SympyComparison): >> g[1] == g[1] == g[1] = True - >> g[1] == g[1] == g[r] - = g[1] == g[1] == g[r] + >> g[1] ⩵ g[1] ⩵ g[r] + = g[1] ⩵ g[1] ⩵ g[r] Equality can also be combined with other inequality expressions, like: >> g[1] == g[2] != g[3] - = g[1] == g[2] && g[2] != g[3] + = g[1] ⩵ g[2] ∧ g[2] ≠ g[3] >> g[1] == g[2] <= g[3] - = g[1] == g[2] && g[2] <= g[3] + = g[1] ⩵ g[2] ∧ g[2] ≤ g[3] 'Equal' with no parameter or an empty list is 'True': >> Equal[] == True @@ -611,9 +611,9 @@ class Inequality(Builtin): >> a < b <= c - = a < b && b <= c + = a < b ∧ b ≤ c >> Inequality[a, Greater, b, LessEqual, c] - = a > b && b <= c + = a > b ∧ b ≤ c >> 1 < 2 <= 3 = True >> 1 < 2 > 0 @@ -886,7 +886,7 @@ class Unequal(_EqualityOperator, _SympyComparison): = True >> 1 != 2 != x - = 1 != 2 != x + = 1 ≠ 2 ≠ x Strings are allowed: >> Unequal["11", "11"] diff --git a/mathics/builtin/testing_expressions/logic.py b/mathics/builtin/testing_expressions/logic.py index 841f83359..49782f70d 100644 --- a/mathics/builtin/testing_expressions/logic.py +++ b/mathics/builtin/testing_expressions/logic.py @@ -85,7 +85,7 @@ class And(InfixOperator): If an expression does not evaluate to 'True' or 'False', 'And' \ returns a result in symbolic form: >> a && b && True && c - = a && b && c + = a ∧ b ∧ c """ attributes = A_FLAT | A_HOLD_ALL | A_ONE_IDENTITY | A_PROTECTED @@ -202,10 +202,10 @@ class Equivalent(InfixOperator): If all expressions do not evaluate to 'True' or 'False', 'Equivalent' \ returns a result in symbolic form: >> Equivalent[a, b, c] - = a \\[Equivalent] b \\[Equivalent] c + = a ⧦ b ⧦ c Otherwise, 'Equivalent' returns a result in DNF >> Equivalent[a, b, True, c] - = a && b && c + = a ∧ b ∧ c """ attributes = A_ORDERLESS | A_PROTECTED @@ -272,7 +272,7 @@ class Implies(InfixOperator): If an expression does not evaluate to 'True' or 'False', 'Implies' returns a result in symbolic form: >> Implies[a, Implies[b, Implies[True, c]]] - = a \[Implies] b \[Implies] c + = a ⇒ b ⇒ c """ grouping = "Right" @@ -344,7 +344,7 @@ class Or(InfixOperator): If an expression does not evaluate to 'True' or 'False', 'Or' returns a result in symbolic form: >> a || False || b - = a || b + = a ∨ b """ attributes = A_FLAT | A_HOLD_ALL | A_ONE_IDENTITY | A_PROTECTED @@ -484,7 +484,7 @@ class Xor(InfixOperator): If an expression does not evaluate to 'True' or 'False', 'Xor' returns a result in symbolic form: >> Xor[a, False, b] - = a \\[Xor] b + = a ⊻ b """ attributes = A_FLAT | A_ONE_IDENTITY | A_ORDERLESS | A_PROTECTED diff --git a/mathics/core/convert/op.py b/mathics/core/convert/op.py index 8e5689bba..5f857d5a1 100644 --- a/mathics/core/convert/op.py +++ b/mathics/core/convert/op.py @@ -62,6 +62,7 @@ } +@lru_cache(maxsize=1024) def string_to_invertible_ascii(string: str): """ Replace non-ANSI characters with their names. If the character diff --git a/mathics/core/element.py b/mathics/core/element.py index dff595127..aa43f9224 100644 --- a/mathics/core/element.py +++ b/mathics/core/element.py @@ -424,6 +424,7 @@ def to_tex(self, **options) -> str: return self.to_format("latex", **options) def to_text(self, **options) -> str: + options.setdefault("encoding", "Unicode") return self.to_format("text", **options) # Deprecated diff --git a/mathics/core/evaluation.py b/mathics/core/evaluation.py index 1c10c61e0..e599e202b 100644 --- a/mathics/core/evaluation.py +++ b/mathics/core/evaluation.py @@ -421,10 +421,15 @@ def format_output(self, expr, format=None): if result is None: return None + try: + encoding = self.definitions.get_ownvalue("System`$CharacterEncoding").value + except AttributeError: + encoding = "Unicode" + try: # With the new implementation, if result is not a ``BoxExpression`` # then we should raise a BoxError here. - boxes = result.to_text(evaluation=self) + boxes = result.to_text(evaluation=self, encoding=encoding) except BoxError: self.message( "General", "notboxes", Expression(SymbolFullForm, result).evaluate(self) diff --git a/mathics/doc/doc_entries.py b/mathics/doc/doc_entries.py index 3d50a5736..2cb345617 100644 --- a/mathics/doc/doc_entries.py +++ b/mathics/doc/doc_entries.py @@ -13,6 +13,7 @@ from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Sequence, Tuple from mathics.core.evaluation import Message, Print, _Out +from mathics.eval.encoding import encode_string_value if TYPE_CHECKING: from mathics.doc.structure import DocSection @@ -406,14 +407,18 @@ def strip_sentinal(line: str): def __str__(self) -> str: return self.test - def compare(self, result: Optional[str], out: tuple = tuple()) -> bool: + def compare( + self, result: Optional[str], out: tuple = tuple(), encoding: str = "ASCII" + ) -> bool: """ Performs a doctest comparison between ``result`` and ``wanted`` and returns True if the test should be considered a success. """ - return self.compare_result(result) and self.compare_out(out) + return self.compare_result(result, encoding=encoding) and self.compare_out( + out, encoding=encoding + ) - def compare_out(self, outs: tuple = tuple()) -> bool: + def compare_out(self, outs: tuple = tuple(), encoding: str = "ASCII") -> bool: """Compare messages and warnings produced during the evaluation of the test with the expected messages and warnings.""" # Check out @@ -434,12 +439,17 @@ def tabs_to_spaces(val): for got, wanted in zip(outs, wanted_outs): if wanted.text == "...": return True - if not tabs_to_spaces(got) == tabs_to_spaces(wanted): + # TODO: remove the encoding wrapper in the LHS + # once we finish fixing the Evaluation.format function + # to handle the encoding. + if not encode_string_value( + tabs_to_spaces(got), encoding=encoding + ) == encode_string_value(tabs_to_spaces(wanted), encoding=encoding): return False return True - def compare_result(self, result: Optional[str]): + def compare_result(self, result: Optional[str], encoding: str = "ASCII") -> bool: """Compare a result with the expected result""" wanted = self.result # Check result @@ -458,10 +468,14 @@ def compare_result(self, result: Optional[str]): return False for res, want in zip(result_list, wanted_list): - wanted_re = re.escape(want.strip()) + # TODO: Be more careful with special characters used in + # pattern matching. + res = res.strip() + want = encode_string_value(want.strip(), encoding=encoding) + wanted_re = re.escape(want) wanted_re = wanted_re.replace("\\.\\.\\.", ".*?") wanted_re = f"^{wanted_re}$" - if not re.match(wanted_re, res.strip()): + if not re.match(wanted_re, res): return False return True diff --git a/mathics/doc/documentation/1-Manual.mdoc b/mathics/doc/documentation/1-Manual.mdoc index e4116375f..22107ae96 100644 --- a/mathics/doc/documentation/1-Manual.mdoc +++ b/mathics/doc/documentation/1-Manual.mdoc @@ -656,10 +656,10 @@ You can also specify a list of rules: There is a "delayed" version of 'Rule' which can be specified by ':>' (similar to the relation of ':=' to '='): >> a :> 1 + 2 - = a :> 1 + 2 + = a ⧴ 1 + 2 >> a -> 1 + 2 - = a -> 3 + = a ⇾ 3 This is useful when the right side of a rule should not be evaluated immediately (before matching): >> {1, 2} /. x_Integer -> N[x] @@ -1124,7 +1124,7 @@ The derivatives are: To get the extreme values of 'f', compute the zeroes of the first derivatives: >> extremes = Solve[f'[x] == 0, x] - = {{x -> -Sqrt[5]}, {x -> Sqrt[5]}} + = {{x ⇾ -Sqrt[5]}, {x ⇾ Sqrt[5]}} And test the second derivative: >> f''[x] /. extremes // N @@ -1133,7 +1133,7 @@ And test the second derivative: Thus, there is a local maximum at 'x = Sqrt[5]' and a local minimum at 'x = -Sqrt[5]'. Compute the inflection points numerically, chopping imaginary parts close to 0: >> inflections = Solve[f''[x] == 0, x] // N // Chop - = {{x -> -1.0852}, {x -> -3.21463}, {x -> 4.29983}} + = {{x ⇾ -1.0852}, {x ⇾ -3.21463}, {x ⇾ 4.29983}} Insert into the third derivative: >> f'''[x] /. inflections @@ -1142,7 +1142,7 @@ Insert into the third derivative: Being different from 0, all three points are actual inflection points. 'f' is not defined where its denominator is 0: >> Solve[Denominator[f[x]] == 0, x] - = {{x -> -3 / 2 - I / 2 Sqrt[11]}, {x -> -3 / 2 + I / 2 Sqrt[11]}} + = {{x ⇾ -3 / 2 - I / 2 Sqrt[11]}, {x ⇾ -3 / 2 + I / 2 Sqrt[11]}} These are non-real numbers; consequently, 'f' is defined on all real numbers. The behaviour of 'f' at the boundaries of its definition: @@ -1225,22 +1225,22 @@ Like in most games, the ordering of the individual throws does not matter. We ca = Dice[1, 4, 4, 6] A dice object shall be displayed as a rectangle with the given number of points in it, positioned like on a traditional dice: - >> Format[Dice[n_Integer?(1 <= # <= 6 &)]] := Block[{p = 0.2, r = 0.05}, Graphics[{EdgeForm[Black], White, Rectangle[], Black, EdgeForm[], If[OddQ[n], Disk[{0.5, 0.5}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{p, p}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{1 - p, 1 - p}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{p, 1 - p}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{1 - p, p}, r]], If[n === 6, {Disk[{p, 0.5}, r], Disk[{1 - p, 0.5}, r]}]}, ImageSize -> Tiny]] + >> Format[Dice[n_Integer?(1 <= # <= 6 &)]] := Block[{p = 0.2, r = 0.05}, Graphics[{EdgeForm[Black], White, Rectangle[], Black, EdgeForm[], If[OddQ[n], Disk[{0.5, 0.5}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{p, p}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{1 - p, 1 - p}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{p, 1 - p}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{1 - p, p}, r]], If[n === 6, {Disk[{p, 0.5}, r], Disk[{1 - p, 0.5}, r]}]}, ImageSize ⇾ Tiny]] >> Dice[1] = -Graphics- #> Definition[Dice] = Attributes[Dice] = {Orderless} . - . Format[Dice[n_Integer ? (1 <= #1 <= 6&)], MathMLForm] = Block[{p = 0.2, r = 0.05}, Graphics[{EdgeForm[Black], White, Rectangle[], Black, EdgeForm[], If[OddQ[n], Disk[{0.5, 0.5}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{p, p}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{1 - p, 1 - p}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{p, 1 - p}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{1 - p, p}, r]], If[n === 6, {Disk[{p, 0.5}, r], Disk[{1 - p, 0.5}, r]}]}, ImageSize -> Tiny]] + . Format[Dice[n_Integer ? (1 ≤ #1 ≤ 6&)], MathMLForm] = Block[{p = 0.2, r = 0.05}, Graphics[{EdgeForm[Black], White, Rectangle[], Black, EdgeForm[], If[OddQ[n], Disk[{0.5, 0.5}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{p, p}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{1 - p, 1 - p}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{p, 1 - p}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{1 - p, p}, r]], If[n === 6, {Disk[{p, 0.5}, r], Disk[{1 - p, 0.5}, r]}]}, ImageSize ⇾ Tiny]] . - . Format[Dice[n_Integer ? (1 <= #1 <= 6&)], OutputForm] = Block[{p = 0.2, r = 0.05}, Graphics[{EdgeForm[Black], White, Rectangle[], Black, EdgeForm[], If[OddQ[n], Disk[{0.5, 0.5}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{p, p}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{1 - p, 1 - p}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{p, 1 - p}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{1 - p, p}, r]], If[n === 6, {Disk[{p, 0.5}, r], Disk[{1 - p, 0.5}, r]}]}, ImageSize -> Tiny]] + . Format[Dice[n_Integer ? (1 ≤ #1 ≤ 6&)], OutputForm] = Block[{p = 0.2, r = 0.05}, Graphics[{EdgeForm[Black], White, Rectangle[], Black, EdgeForm[], If[OddQ[n], Disk[{0.5, 0.5}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{p, p}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{1 - p, 1 - p}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{p, 1 - p}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{1 - p, p}, r]], If[n === 6, {Disk[{p, 0.5}, r], Disk[{1 - p, 0.5}, r]}]}, ImageSize ⇾ Tiny]] . - . Format[Dice[n_Integer ? (1 <= #1 <= 6&)], StandardForm] = Block[{p = 0.2, r = 0.05}, Graphics[{EdgeForm[Black], White, Rectangle[], Black, EdgeForm[], If[OddQ[n], Disk[{0.5, 0.5}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{p, p}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{1 - p, 1 - p}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{p, 1 - p}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{1 - p, p}, r]], If[n === 6, {Disk[{p, 0.5}, r], Disk[{1 - p, 0.5}, r]}]}, ImageSize -> Tiny]] + . Format[Dice[n_Integer ? (1 ≤ #1 ≤ 6&)], StandardForm] = Block[{p = 0.2, r = 0.05}, Graphics[{EdgeForm[Black], White, Rectangle[], Black, EdgeForm[], If[OddQ[n], Disk[{0.5, 0.5}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{p, p}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{1 - p, 1 - p}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{p, 1 - p}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{1 - p, p}, r]], If[n === 6, {Disk[{p, 0.5}, r], Disk[{1 - p, 0.5}, r]}]}, ImageSize ⇾ Tiny]] . - . Format[Dice[n_Integer ? (1 <= #1 <= 6&)], TeXForm] = Block[{p = 0.2, r = 0.05}, Graphics[{EdgeForm[Black], White, Rectangle[], Black, EdgeForm[], If[OddQ[n], Disk[{0.5, 0.5}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{p, p}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{1 - p, 1 - p}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{p, 1 - p}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{1 - p, p}, r]], If[n === 6, {Disk[{p, 0.5}, r], Disk[{1 - p, 0.5}, r]}]}, ImageSize -> Tiny]] + . Format[Dice[n_Integer ? (1 ≤ #1 ≤ 6&)], TeXForm] = Block[{p = 0.2, r = 0.05}, Graphics[{EdgeForm[Black], White, Rectangle[], Black, EdgeForm[], If[OddQ[n], Disk[{0.5, 0.5}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{p, p}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{1 - p, 1 - p}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{p, 1 - p}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{1 - p, p}, r]], If[n === 6, {Disk[{p, 0.5}, r], Disk[{1 - p, 0.5}, r]}]}, ImageSize ⇾ Tiny]] . - . Format[Dice[n_Integer ? (1 <= #1 <= 6&)], TraditionalForm] = Block[{p = 0.2, r = 0.05}, Graphics[{EdgeForm[Black], White, Rectangle[], Black, EdgeForm[], If[OddQ[n], Disk[{0.5, 0.5}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{p, p}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{1 - p, 1 - p}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{p, 1 - p}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{1 - p, p}, r]], If[n === 6, {Disk[{p, 0.5}, r], Disk[{1 - p, 0.5}, r]}]}, ImageSize -> Tiny]] + . Format[Dice[n_Integer ? (1 ≤ #1 ≤ 6&)], TraditionalForm] = Block[{p = 0.2, r = 0.05}, Graphics[{EdgeForm[Black], White, Rectangle[], Black, EdgeForm[], If[OddQ[n], Disk[{0.5, 0.5}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{p, p}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{1 - p, 1 - p}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{p, 1 - p}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{1 - p, p}, r]], If[n === 6, {Disk[{p, 0.5}, r], Disk[{1 - p, 0.5}, r]}]}, ImageSize ⇾ Tiny]] The empty series of dice shall be displayed as an empty dice: >> Format[Dice[]] := Graphics[{EdgeForm[Black], White, Rectangle[]}, ImageSize -> Tiny] diff --git a/mathics/docpipeline.py b/mathics/docpipeline.py index 403f3372f..2728398e0 100644 --- a/mathics/docpipeline.py +++ b/mathics/docpipeline.py @@ -21,6 +21,7 @@ import mathics from mathics import settings, version_string +from mathics.core.convert.op import string_to_invertible_ascii from mathics.core.evaluation import Output from mathics.core.load_builtin import _builtins, import_and_load_builtins from mathics.doc.doc_entries import DocTest, DocumentationEntry @@ -45,6 +46,8 @@ # When 3.8 is base, the below can be a Literal type. INVALID_TEST_GROUP_SETUP = (None, None) +CHARACTER_ENCODING = settings.SYSTEM_CHARACTER_ENCODING + TestParameters = namedtuple( "TestParameters", [ @@ -138,10 +141,6 @@ def validate_group_setup( else: self.output_data = {} - # For consistency set the character encoding ASCII which is - # the lowest common denominator available on all systems. - settings.SYSTEM_CHARACTER_ENCODING = "ASCII" - if self.session.definitions is None: self.print_and_log("Definitions are not initialized.") return INVALID_TEST_GROUP_SETUP @@ -195,7 +194,9 @@ def show_test(self, test: DocTest, index: int, subindex: int): """Show the current test""" test_str = test.test if not self.quiet: - print(f"{index:4d} ({subindex:2d}): TEST {test_str}") + print( + f"{index:4d} ({subindex:2d}): TEST {string_to_invertible_ascii(test_str)}" + ) def test_case( @@ -224,7 +225,7 @@ def test_case( return False time_start = datetime.now() - comparison_result = test.compare_result(result) + comparison_result = test.compare_result(result, encoding=CHARACTER_ENCODING) if test_parameters.check_partial_elapsed_time: test_pipeline.print_and_log( @@ -498,10 +499,7 @@ def test_tests( """ test_status: TestStatus = test_pipeline.status test_parameters: TestParameters = test_pipeline.parameters - # For consistency set the character encoding ASCII which is - # the lowest common denominator available on all systems. - settings.SYSTEM_CHARACTER_ENCODING = "ASCII" test_pipeline.reset_user_definitions() output_data, names = test_pipeline.validate_group_setup( diff --git a/mathics/eval/encoding.py b/mathics/eval/encoding.py new file mode 100644 index 000000000..ebc4d6497 --- /dev/null +++ b/mathics/eval/encoding.py @@ -0,0 +1,76 @@ +""" +Functions to format strings in a given encoding. +""" + +from typing import Dict + +from mathics_scanner.characters import UNICODE_CHARACTER_TO_ASCII + +from mathics.core.convert.op import operator_to_ascii, operator_to_unicode + +# Map WMA encoding names to Python encoding names +ENCODING_WMA_TO_PYTHON = { + "WindowsEastEurope": "cp1250", + "WindowsCyrillic": "cp1251", + "WindowsANSI": "cp1252", + "WindowsGreek": "cp1252", + "WindowsTurkish": "cp1254", +} + + +# These characters are used in encoding +# in WMA, and differs from what we have +# in Mathics3-scanner tables: +UNICODE_CHARACTER_TO_ASCII.update( + { + operator_to_unicode["Times"]: r" x ", + "": r"\[DifferentialD]", + } +) +# Some printable ASCII characters appears in the name +# table. We should remove them: +for raw_char_code in range(32, 127): + char = chr(raw_char_code) + if char in UNICODE_CHARACTER_TO_ASCII: + del UNICODE_CHARACTER_TO_ASCII[char] + + +class EncodingNameError(Exception): + pass + + +def get_encoding_table(encoding: str) -> Dict[str, str]: + """ + Return a dictionary with a map from + character codes in the internal (Unicode) + representation to the request encoding. + """ + if encoding == "Unicode": + return {} + + # In the final implementation, this should load the corresponding + # json table or an encoding file as in WMA + # SystemFiles/CharacterEncodings/*.m + # If the encoding is not available, raise an EncodingError + try: + return { + "ASCII": UNICODE_CHARACTER_TO_ASCII, + "UTF-8": {}, + }[encoding] + except KeyError: + raise EncodingNameError + + +def encode_string_value(value: str, encoding: str) -> str: + """Convert an Unicode string `value` to the required `encoding`""" + + # In WMA, encodings are readed from SystemFiles/CharacterEncodings/*.m + # on the fly. We should load them from Mathics3-Scanner tables. + encoding_table = get_encoding_table(encoding) + if not encoding_table: + return value + result = "" + for ch in value: + ch = encoding_table.get(ch, ch) + result += ch + return result diff --git a/mathics/eval/strings.py b/mathics/eval/strings.py index 4e83e2848..56f27815a 100644 --- a/mathics/eval/strings.py +++ b/mathics/eval/strings.py @@ -17,15 +17,23 @@ from mathics.core.expression_predefined import MATHICS3_INFINITY from mathics.core.list import ListExpression from mathics.core.symbols import Symbol, SymbolTrue +from mathics.eval.encoding import EncodingNameError from mathics.format.box import format_element def eval_ToString( expr: BaseElement, form: Symbol, encoding: String, evaluation: Evaluation ) -> String: - boxes = format_element(expr, evaluation, form, encoding=encoding) - text = boxes.to_text(evaluation=evaluation) - return String(text) + + boxes = format_element(expr, evaluation, form) + try: + return String(boxes.to_text(evaluation=evaluation, encoding=encoding)) + except EncodingNameError: + # Mimic the WMA behavior. In the future, we can implement the mechanism + # with encodings stored in .m files, and give a chance with it. + evaluation.message("Get", "noopen", String("encodings/" + encoding + "." + "m")) + + return String(boxes.to_text(evaluation=evaluation, encoding="Unicode")) def eval_StringContainsQ(name, string, patt, evaluation, options, matched): diff --git a/mathics/format/form/inputform.py b/mathics/format/form/inputform.py index 5fd1b2564..233198f97 100644 --- a/mathics/format/form/inputform.py +++ b/mathics/format/form/inputform.py @@ -39,7 +39,6 @@ SymbolRight, ) from mathics.format.box.formatvalues import do_format # , format_element -from mathics.settings import SYSTEM_CHARACTER_ENCODING from .util import ( ARITHMETIC_OPERATOR_STRINGS, @@ -154,7 +153,6 @@ def _infix_expression_to_inputform_text( # has a head that matches with a symbol associated to an infix # operator, WMA builds its inputform without passing through # its "Infix" form. - kwargs["encoding"] = kwargs.get("encoding", SYSTEM_CHARACTER_ENCODING) operands, ops_lst, precedence, group = collect_in_pre_post_arguments( expr, evaluation, **kwargs ) @@ -198,7 +196,6 @@ def _prefix_expression_to_inputform_text( """ Convert Prefix[...] into a OutputForm string. """ - kwargs["encoding"] = kwargs.get("encoding", SYSTEM_CHARACTER_ENCODING) operands, op_head, precedence, group = collect_in_pre_post_arguments( expr, evaluation, **kwargs ) @@ -206,7 +203,6 @@ def _prefix_expression_to_inputform_text( if len(operands) != 1: raise _WrongFormattedExpression operand = operands[0] - kwargs["encoding"] = kwargs.get("encoding", SYSTEM_CHARACTER_ENCODING) target_txt = render_input_form(operand, evaluation, **kwargs) parenthesized = group in (None, SymbolRight, SymbolNonAssociative) target_txt = parenthesize(precedence, operand, target_txt, parenthesized) @@ -220,7 +216,6 @@ def _postfix_expression_to_inputform_text( """ Convert Postfix[...] into a OutputForm string. """ - kwargs["encoding"] = kwargs.get("encoding", SYSTEM_CHARACTER_ENCODING) operands, op_head, precedence, group = collect_in_pre_post_arguments( expr, evaluation, **kwargs ) @@ -295,7 +290,6 @@ def _rule_to_inputform_text(expr, evaluation: Evaluation, **kwargs) -> str: """Rule|RuleDelayed[{...}]""" head = expr.head elements = expr.elements - kwargs["encoding"] = kwargs.get("encoding", SYSTEM_CHARACTER_ENCODING) if len(elements) != 2: return _generic_to_inputform_text(expr, evaluation, **kwargs) pat, rule = (render_input_form(elem, evaluation, **kwargs) for elem in elements) diff --git a/mathics/format/form/outputform.py b/mathics/format/form/outputform.py index 8f67b34ce..a158e8299 100644 --- a/mathics/format/form/outputform.py +++ b/mathics/format/form/outputform.py @@ -54,7 +54,6 @@ get_numberform_parameters, numberform_to_boxes, ) -from mathics.settings import SYSTEM_CHARACTER_ENCODING from .inputform import render_input_form from .util import ( @@ -345,7 +344,7 @@ def other_forms(expr, evaluation, **kwargs): raise _WrongFormattedExpression result = format_element(expr, evaluation, SymbolStandardForm, **kwargs) - return result.to_text() + return result.to_text(evaluation=evaluation, **kwargs) @register_outputform("System`Integer") @@ -364,7 +363,7 @@ def integer_outputform(n, evaluation, **kwargs): result = numberform_to_boxes(n, digits, padding, evaluation, py_options) if isinstance(result, String): return result.value - return result.to_text() + return result.to_text(**kwargs) @register_outputform("System`Image") @@ -388,7 +387,6 @@ def _infix_outputform_text(expr: Expression, evaluation: Evaluation, **kwargs) - # has a head that matches with a symbol associated to an infix # operator, WMA builds its inputform without passing through # its "Infix" form. - kwargs["encoding"] = kwargs.get("encoding", SYSTEM_CHARACTER_ENCODING) operands, ops_lst, precedence, group = collect_in_pre_post_arguments( expr, evaluation, **kwargs ) @@ -688,7 +686,6 @@ def _prefix_output_text(expr: Expression, evaluation: Evaluation, **kwargs) -> s if not isinstance(expr.head, Symbol): raise _WrongFormattedExpression - kwargs["encoding"] = kwargs.get("encoding", SYSTEM_CHARACTER_ENCODING) operands, op_head, precedence, group = collect_in_pre_post_arguments( expr, evaluation, **kwargs ) @@ -698,7 +695,6 @@ def _prefix_output_text(expr: Expression, evaluation: Evaluation, **kwargs) -> s if not isinstance(op_head, str): raise _WrongFormattedExpression operand = operands[0] - kwargs["encoding"] = kwargs.get("encoding", SYSTEM_CHARACTER_ENCODING) target_txt = render_output_form(operand, evaluation, **kwargs) parenthesized = group in (None, SymbolRight, SymbolNonAssociative) target_txt = parenthesize(precedence, operand, target_txt, parenthesized) @@ -713,7 +709,6 @@ def _postfix_output_text(expr: Expression, evaluation: Evaluation, **kwargs) -> if not isinstance(expr.head, Symbol): raise _WrongFormattedExpression - kwargs["encoding"] = kwargs.get("encoding", SYSTEM_CHARACTER_ENCODING) operands, op_head, precedence, group = collect_in_pre_post_arguments( expr, evaluation, **kwargs ) @@ -783,7 +778,6 @@ def rule_to_outputform_text(expr, evaluation: Evaluation, **kwargs): raise _WrongFormattedExpression elements = expr.elements - kwargs["encoding"] = kwargs.get("encoding", SYSTEM_CHARACTER_ENCODING) if len(elements) != 2: return _default_render_output_form(expr, evaluation, **kwargs) pat, rule = (render_output_form(elem, evaluation, **kwargs) for elem in elements) diff --git a/mathics/format/form/util.py b/mathics/format/form/util.py index a3a7a7584..1160396ac 100644 --- a/mathics/format/form/util.py +++ b/mathics/format/form/util.py @@ -145,7 +145,6 @@ def collect_in_pre_post_arguments( def get_operator_str(head, evaluation, **kwargs) -> str: - encoding = kwargs["encoding"] if isinstance(head, String): op_str = head.value elif isinstance(head, Symbol): @@ -154,10 +153,7 @@ def get_operator_str(head, evaluation, **kwargs) -> str: render_function = kwargs["_render_function"] return render_function(head, evaluation, **kwargs) - if encoding == "ASCII": - operator = operator_to_ascii.get(op_str, op_str) - else: - operator = operator_to_unicode.get(op_str, op_str) + operator = operator_to_unicode.get(op_str, op_str) return operator diff --git a/mathics/format/render/text.py b/mathics/format/render/text.py index 11ea8045a..9231e93b8 100644 --- a/mathics/format/render/text.py +++ b/mathics/format/render/text.py @@ -2,7 +2,6 @@ """ Mathics3 box rendering to plain text. """ - from mathics.builtin.box.graphics import GraphicsBox from mathics.builtin.box.graphics3d import Graphics3DBox from mathics.builtin.box.layout import ( @@ -27,7 +26,8 @@ convert_box_to_format, convert_inner_box_field, ) -from mathics.core.symbols import Atom, SymbolFalse, SymbolTrue +from mathics.core.symbols import Atom, SymbolTrue,SymbolFalse +from mathics.eval.encoding import encode_string_value from mathics.format.box.graphics import prepare_elements as prepare_elements2d from mathics.format.box.graphics3d import prepare_elements as prepare_elements3d from mathics.format.form.util import _WrongFormattedExpression, text_cells_to_grid @@ -167,7 +167,7 @@ def string(s: String, **options) -> str: if value.startswith('"') and value.endswith('"'): # nopep8 if not show_string_characters: value = value[1:-1] - return value + return encode_string_value(value, options["encoding"]) add_render_function(String, string) diff --git a/test/builtin/files_io/test_files.py b/test/builtin/files_io/test_files.py index 7cf17f603..91a49fa91 100644 --- a/test/builtin/files_io/test_files.py +++ b/test/builtin/files_io/test_files.py @@ -231,28 +231,28 @@ def test_close(): ( 'stream = StringToStream["1.523E-19"]; Read[stream, Real]', None, - "1.523×10^-19", + "1.523 x 10^-19", "", ), ("Close[stream];", None, "Null", ""), ( 'stream = StringToStream["-1.523e19"]; Read[stream, Real]', None, - "-1.523×10^19", + "-1.523 x 10^19", "", ), ("Close[stream];", None, "Null", ""), ( 'stream = StringToStream["3*^10"]; Read[stream, Real]', None, - "3.×10^10", + "3. x 10^10", "", ), ("Close[stream];", None, "Null", ""), ( 'stream = StringToStream["3.*^10"]; Read[stream, Real]', None, - "3.×10^10", + "3. x 10^10", "", ), ("Close[stream];", None, "Null", ""), diff --git a/test/builtin/files_io/test_importexport.py b/test/builtin/files_io/test_importexport.py index 64b09973b..a19b13930 100644 --- a/test/builtin/files_io/test_importexport.py +++ b/test/builtin/files_io/test_importexport.py @@ -310,7 +310,7 @@ def test_inividually(): ( r'System`Convert`B64Dump`B64Decode["4oirIGYg752MIHg="]', None, - r"∫ f  x", + r"\[Integral] f \[DifferentialD] x", None, ), ], diff --git a/test/builtin/numbers/test_trig.py b/test/builtin/numbers/test_trig.py index 89d4d7100..c24226608 100644 --- a/test/builtin/numbers/test_trig.py +++ b/test/builtin/numbers/test_trig.py @@ -42,9 +42,9 @@ def test_ArcCos(): ("ArcTan[-1, 0]", None, "Pi", None), ("ArcTan[0, 1]", None, "Pi / 2", None), ("ArcTan[0, -1]", None, "-Pi / 2", None), - ("Cos[1.5 Pi]", None, "-1.83697×10^-16", None), + ("Cos[1.5 Pi]", None, "-1.83697 x 10^-16", None), ("N[Sin[1], 40]", None, "0.8414709848078965066525023216302989996226", None), - ("Tan[0.5 Pi]", None, "1.63312×10^16", None), + ("Tan[0.5 Pi]", None, "1.63312 x 10^16", None), ], ) def test_trig(str_expr, msgs, str_expected, fail_msg): diff --git a/test/builtin/test_binary.py b/test/builtin/test_binary.py index 0aebbb817..2506b054d 100644 --- a/test/builtin/test_binary.py +++ b/test/builtin/test_binary.py @@ -236,8 +236,12 @@ ('WbR[{1, 0, 0, 0, 0, 0, 240, 255}, "Real64"]', "Indeterminate", None), ## Real128 ## 0x0000 - ('WbR[{0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0}, "Real128"]', "0.×10^-4965", None), - ('WbR[{0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,128}, "Real128"]', "0.×10^-4965", None), + ('WbR[{0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0}, "Real128"]', "0. x 10^-4965", None), + ( + 'WbR[{0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,128}, "Real128"]', + "0. x 10^-4965", + None, + ), ## 0x0001 - 0x7FFE ( 'WbR[{0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,255,63}, "Real128"]', @@ -256,12 +260,12 @@ ), ( 'WbR[{135, 62, 233, 137, 22, 208, 233, 210, 133, 82, 251, 92, 220, 216, 207, 72}, "Real128"]', - "2.45563355727491021879689747166252×10^679", + "2.45563355727491021879689747166252 x 10^679", None, ), ( 'z=WbR[{74, 95, 30, 234, 116, 130, 1, 84, 20, 133, 245, 221, 113, 110, 219, 212}, "Real128"]', - "-4.52840681592341879518366539335138×10^1607", + "-4.52840681592341879518366539335138 x 10^1607", None, ), ("z // Precision", "33.", None), @@ -427,8 +431,8 @@ def test_ByteOrdering(str_expr, str_expected, fail_msg): @pytest.mark.parametrize( ("str_expr", "str_expected"), [ - ("NumericArray[{{1,2},{3,4}}]", ""), - ("ToString[NumericArray[{{1,2},{3,4}}]]", ""), + ("NumericArray[{{1,2},{3,4}}]", ""), + ("ToString[NumericArray[{{1,2},{3,4}}]]", ""), ("Head[NumericArray[{1,2}]]", "NumericArray"), ("AtomQ[NumericArray[{1,2}]]", "True"), ("First[NumericArray[{1,2,3}]]", "1"), diff --git a/test/builtin/test_forms.py b/test/builtin/test_forms.py index 16650ebcb..ff61794ce 100644 --- a/test/builtin/test_forms.py +++ b/test/builtin/test_forms.py @@ -46,8 +46,8 @@ def test_makeboxes_form(expr, form, head, subhead): ("NumberForm[14310983091809]", None, "14310983091809", None), ## Zero case ("z0 = 0.0;z1 = 0.0000000000000000000000000000;", None, "Null", None), - ("NumberForm[{z0, z1}, 10]", None, "{0., 0.×10^-28}", None), - ("NumberForm[{z0, z1}, {10, 4}]", None, "{0.0000, 0.0000×10^-28}", None), + ("NumberForm[{z0, z1}, 10]", None, "{0., 0. x 10^-28}", None), + ("NumberForm[{z0, z1}, {10, 4}]", None, "{0.0000, 0.0000 x 10^-28}", None), ("z0=.;z1=.;", None, "Null", None), ## Trailing zeros ("NumberForm[1.0, 10]", None, "1.", None), @@ -67,7 +67,7 @@ def test_makeboxes_form(expr, form, head, subhead): ( "NumberForm[{2^123, 2^123.}, 4, ExponentFunction -> ((#1) &)]", None, - "{10633823966279326983230456482242756608, 1.063×10^37}", + "{10633823966279326983230456482242756608, 1.063 x 10^37}", None, ), ("NumberForm[{0, 10, -512}, {10, 3}]", None, "{0.000, 10.000, -512.000}", None), @@ -178,7 +178,7 @@ def test_makeboxes_form(expr, form, head, subhead): ( "NumberForm[12345.123456789, 14, ExponentFunction -> ((#) &)]", None, - "1.2345123456789×10^4", + "1.2345123456789 x 10^4", None, ), ( @@ -191,7 +191,7 @@ def test_makeboxes_form(expr, form, head, subhead): ( "NumberForm[y, 10, ExponentFunction -> (3 Quotient[#, 3] &)]", None, - "{114.0256472×10^-12, 3.267763643×10^-3, 93.64804748×10^3, 2.683779414×10^12, 76.91214221×10^18}", + "{114.0256472 x 10^-12, 3.267763643 x 10^-3, 93.64804748 x 10^3, 2.683779414 x 10^12, 76.91214221 x 10^18}", None, ), ( @@ -207,7 +207,7 @@ def test_makeboxes_form(expr, form, head, subhead): ( "NumberForm[10^8 N[Pi], 10, ExponentStep -> 3]", None, - "314.1592654×10^6", + "314.1592654 x 10^6", None, ), ( @@ -225,7 +225,7 @@ def test_makeboxes_form(expr, form, head, subhead): ( "NumberForm[y, 10, ExponentStep -> 6]", None, - "{114.0256472×10^-12, 3267.763643×10^-6, 93648.04748, 2.683779414×10^12, 76.91214221×10^18}", + "{114.0256472 x 10^-12, 3267.763643 x 10^-6, 93648.04748, 2.683779414 x 10^12, 76.91214221 x 10^18}", None, ), ## NumberFormat @@ -318,7 +318,7 @@ def test_makeboxes_form(expr, form, head, subhead): ( 'NumberForm[N[10^ 7 Pi], 15, DigitBlock -> 3, NumberSeparator -> {",", " "}]', None, - "3.141 592 653 589 79×10^7", + "3.141 592 653 589 79 x 10^7", None, ), ( diff --git a/test/builtin/test_numeric.py b/test/builtin/test_numeric.py index 78d3a25a4..1ca38e27c 100644 --- a/test/builtin/test_numeric.py +++ b/test/builtin/test_numeric.py @@ -76,19 +76,19 @@ def test_realvalued(): ( "N[3^200]", None, - "2.65614×10^95", + "2.65614 x 10^95", "Numeric converts a large integer to a MachineReal without losing precision", ), ( "N[2^1023]", None, - "8.98847×10^307", + "8.98847 x 10^307", "Numeric display digits for value under 2^1024 is DefaultPrintDisplay value 6", ), ( "N[2^1024]", None, - "1.797693134862316×10^308", + "1.797693134862316 x 10^308", "Numeric display digits for value on or over= 2^1024 is $MachineDigits", ), ( diff --git a/test/format/format_tests.yaml b/test/format/format_tests.yaml index a954c6670..a3733efae 100644 --- a/test/format/format_tests.yaml +++ b/test/format/format_tests.yaml @@ -101,7 +101,7 @@ mathml: {} text: System`InputForm: -1.*^-6 - System`OutputForm: "-1.\xD710^-6" + System`OutputForm: "-1. x 10^-6" -1. 10^5: @@ -125,9 +125,9 @@ mathml: {} text: System`InputForm: -1.*^6 - System`OutputForm: "-1.\xD710^6" + System`OutputForm: "-1. x 10^6" System`StandardForm: -1.`*^6 - System`TraditionalForm: "-1.`\xD710^6" + System`TraditionalForm: "-1.` x 10^6" '-4': @@ -225,7 +225,7 @@ mathml: {} text: System`InputForm: 1.*^-6 - System`OutputForm: "1.\xD710^-6" + System`OutputForm: "1. x 10^-6" 1. 10^5: @@ -253,9 +253,9 @@ mathml: {} text: System`InputForm: 1.*^6 - System`OutputForm: "1.\xD710^6" + System`OutputForm: "1. x 10^6" System`StandardForm: 1.`*^6 - System`TraditionalForm: "1.`\xD710^6" + System`TraditionalForm: "1.` x 10^6" 1/(1+1/(1+1/a)): @@ -288,13 +288,13 @@ <|a -> x, b -> y, c -> <|d -> t|>|>: msg: Association latex: - System`InputForm: '\text{$<$$\vert$a -$>$ x, b -$>$ y, c -$>$ $<$$\vert$d -$>$ t$\vert$$>$$\vert$$>$}' - System`OutputForm: '\text{$<$$\vert$a -$>$ x, b -$>$ y, c -$>$ $<$$\vert$d -$>$ t$\vert$$>$$\vert$$>$}' + System`InputForm: '\text{$<$$\vert$a $\to$ x, b $\to$ y, c $\to$ $<$$\vert$d $\to$ t$\vert$$>$$\vert$$>$}' + System`OutputForm: '\text{$<$$\vert$a $\to$ x, b $\to$ y, c $\to$ $<$$\vert$d $\to$ t$\vert$$>$$\vert$$>$}' System`StandardForm: '\langle\vert a->x, b->y, c->\langle\vert d->t\vert\rangle \vert\rangle' System`TraditionalForm: '\langle\vert a->x, b->y, c->\langle\vert d->t\vert\rangle \vert\rangle' mathml: - System`InputForm: <|a -> x, b -> y, c -> <|d -> t|>|> - System`OutputForm: '<|a -> x, b -> y, c -> <|d -> t|>|>' + System`InputForm: <|a ⇾ x, b ⇾ y, c ⇾ <|d ⇾ t|>|> + System`OutputForm: '<|a ⇾ x, b ⇾ y, c ⇾ <|d ⇾ t|>|>' System`StandardForm: "\n <|\n \n \n a\n ->\n x\n \n ,\n \n b\n ->\n y\n \n ,\n \n c\n ->\n \n <|\n \n d\n ->\n t\n \n |>\n \n \n \n |>\n" System`TraditionalForm: "\n <|\n \n \n a\n ->\n x\n \n ,\n \n b\n ->\n y\n \n ,\n \n c\n ->\n \n <|\n \n d\n ->\n t\n \n |>\n \n \n \n |>\n" text: @@ -307,13 +307,13 @@ Association[a -> x, b -> y, c -> Association[d -> t, Association[e -> u]]]: msg: Nested Association latex: - System`InputForm: '\text{$<$$\vert$a -$>$ x, b -$>$ y, c -$>$ $<$$\vert$d -$>$ t, e -$>$ u$\vert$$>$$\vert$$>$}' - System`OutputForm: '\text{$<$$\vert$a -$>$ x, b -$>$ y, c -$>$ $<$$\vert$d -$>$ t, e -$>$ u$\vert$$>$$\vert$$>$}' + System`InputForm: '\text{$<$$\vert$a $\to$ x, b $\to$ y, c $\to$ $<$$\vert$d $\to$ t, e $\to$ u$\vert$$>$$\vert$$>$}' + System`OutputForm: '\text{$<$$\vert$a $\to$ x, b $\to$ y, c $\to$ $<$$\vert$d $\to$ t, e $\to$ u$\vert$$>$$\vert$$>$}' System`StandardForm: '\langle\vert a->x, b->y, c->\langle\vert d->t, e->u\vert\rangle \vert\rangle' System`TraditionalForm: '\langle\vert a->x, b->y, c->\langle\vert d->t, e->u\vert\rangle \vert\rangle' mathml: - System`InputForm: "<|a -> x, b -> y, c -> <|d -> t, e -> u|>|>" - System`OutputForm: '<|a -> x, b -> y, c -> <|d -> t, e -> u|>|>' + System`InputForm: "<|a ⇾ x, b ⇾ y, c ⇾ <|d ⇾ t, e ⇾ u|>|>" + System`OutputForm: '<|a ⇾ x, b ⇾ y, c ⇾ <|d ⇾ t, e ⇾ u|>|>' System`StandardForm: "\n <|\n \n \n a\n ->\n x\n \n ,\n \n b\n ->\n y\n \n ,\n \n c\n ->\n \n <|\n \n \n d\n ->\n t\n \n ,\n \n e\n ->\n u\n \n \n |>\n \n \n \n |>\n" System`TraditionalForm: "\n <|\n \n \n a\n ->\n x\n \n ,\n \n b\n ->\n y\n \n ,\n \n c\n ->\n \n <|\n \n \n d\n ->\n t\n \n ,\n \n e\n ->\n u\n \n \n |>\n \n \n \n |>\n" text: @@ -337,9 +337,9 @@ Complex[1.09*^12, 3.]: System`TraditionalForm: "\n \n 1.09\n ×\n \n 10\n 12\n \n \n +\n \n 3.\n \n I\n \n" text: System`InputForm: 1.09*^12 + 3.*I - System`OutputForm: "1.09\xD710^12 + 3. I" + System`OutputForm: "1.09 x 10^12 + 3. I" System`StandardForm: 1.09`*^12+3.` I - System`TraditionalForm: "1.09`\xD710^12+3.`\u2062I" + System`TraditionalForm: "1.09` x 10^12+3.`\u2062I" Graphics[{Text[a^b,{0,0}]}]: @@ -530,8 +530,8 @@ Integrate[F[x], {x, a, g[b]}]: text: System`InputForm: Integrate[F[x], {x, a, g[b]}] System`OutputForm: Integrate[F[x], {x, a, g[b]}] - System`StandardForm: 'Subsuperscript[∫, a, g[b]]⁢F[x]⁢𝑑x' - System`TraditionalForm: 'Subsuperscript[∫, a, g(b)]⁢F(x)⁢𝑑x' + System`StandardForm: 'Subsuperscript[\[Integral], a, g[b]]⁢F[x]⁢\[DifferentialD]x' + System`TraditionalForm: 'Subsuperscript[\[Integral], a, g(b)]⁢F(x)⁢\[DifferentialD]x' MatrixForm[{{a,b},{c,d}}]: msg: GridBox in a matrix diff --git a/test/format/test_format.py b/test/format/test_format.py index f6aeea269..557f4fde6 100644 --- a/test/format/test_format.py +++ b/test/format/test_format.py @@ -40,7 +40,7 @@ PATH = os.path.dirname(__file__) + os.path.sep -with open(PATH + "format_tests.yaml", "r") as src: +with open(PATH + "format_tests.yaml", "r", encoding="utf-8") as src: all_test = yaml.safe_load(src) @@ -115,10 +115,13 @@ def test_makeboxes_text_fragile(str_expr, str_expected, form, msg): format_result = result.format(session.evaluation, form) if msg: assert ( - format_result.to_text(evaluation=session.evaluation) == str_expected + format_result.to_text(evaluation=session.evaluation, encoding="ASCII") + == str_expected ), msg else: - strresult = format_result.to_text(evaluation=session.evaluation) + strresult = format_result.to_text( + evaluation=session.evaluation, encoding="ASCII" + ) assert strresult == str_expected @@ -130,9 +133,14 @@ def test_makeboxes_text(str_expr, str_expected, form, msg): result = session.evaluate(str_expr) format_result = result.format(session.evaluation, form) if msg: - assert format_result.to_text(evaluation=session.evaluation) == str_expected, msg + assert ( + format_result.to_text(evaluation=session.evaluation, encoding="ASCII") + == str_expected + ), msg else: - strresult = format_result.to_text(evaluation=session.evaluation) + strresult = format_result.to_text( + evaluation=session.evaluation, encoding="ASCII" + ) assert strresult == str_expected @@ -175,7 +183,9 @@ def test_makeboxes_tex(str_expr, str_expected, form, msg): == str_expected.strip() ), msg else: - strresult = format_result.to_text(evaluation=session.evaluation).strip() + strresult = format_result.to_text( + evaluation=session.evaluation, encoding="ASCII" + ).strip() assert strresult == str_expected @@ -209,7 +219,8 @@ def test_makeboxes_mathml(str_expr, str_expected, form, msg): format_result = result.format(session.evaluation, form) if msg: assert ( - format_result.to_mathml(evaluation=session.evaluation) == str_expected + format_result.to_mathml(evaluation=session.evaluation, encoding="ASCII") + == str_expected ), msg else: strresult = format_result.to_mathml(evaluation=session.evaluation) @@ -221,7 +232,7 @@ def test_makeboxes_mathml(str_expr, str_expected, form, msg): [ ( "OutputForm[Complex[2.0 ^ 40, 3]]", - "1.09951×10^12 + 3. I", + "1.09951 x 10^12 + 3. I", "OutputForm Complex", ), ( diff --git a/test/helper.py b/test/helper.py index dc0900eb4..7407920da 100644 --- a/test/helper.py +++ b/test/helper.py @@ -189,7 +189,7 @@ def check_evaluation_as_in_cli( if failure_message: assert res.result == str_expected, failure_message - assert res.result == str_expected + assert res.result == str_expected, f"'{res.result}'!='{str_expected}'" # List below could be a Tuple, but List looks better in the tests