diff --git a/CHANGES.rst b/CHANGES.rst index 0665179f6..5c31d9e7c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,7 +1,8 @@ .. contents:: CHANGES -======= +======== + 5.0.3dev0 --------- @@ -42,6 +43,8 @@ Bugs # ``0`` with a given precision (like in ```0`3```) is now parsed as ``0``, an integer number. #. ``RandomSample`` with one list argument now returns a random ordering of the list items. Previously it would return just one item. +#. Improving parsing ``RowBox`` expressions including ``FormBox`` tags (``` \` ```) inside. + Enhancements diff --git a/mathics/builtin/makeboxes.py b/mathics/builtin/makeboxes.py index 290eb2e1f..ff58d5cea 100644 --- a/mathics/builtin/makeboxes.py +++ b/mathics/builtin/makeboxes.py @@ -339,38 +339,38 @@ class BoxForms_(Predefined): class MakeBoxes(Builtin): - """ + r"""
'MakeBoxes[$expr$]' -
is a low-level formatting primitive that converts $expr$ - to box form, without evaluating it. -
'\\( ... \\)' +
is a low-level formatting primitive that converts $expr$ to box form, without evaluating it. + +
'\( ... \)'
directly inputs box objects.
String representation of boxes - >> \\(x \\^ 2\\) + >> \(x \^ 2\) = SuperscriptBox[x, 2] - >> \\(x \\_ 2\\) + >> \(x \_ 2\) = SubscriptBox[x, 2] - >> \\( a \\+ b \\% c\\) + >> \( a \+ b \% c\) = UnderoverscriptBox[a, b, c] - >> \\( a \\& b \\% c\\) + >> \( a \& b \% c\) = UnderoverscriptBox[a, c, b] - #> \\( \\@ 5 \\) + #> \( \@ 5 \) = SqrtBox[5] - >> \\(x \\& y \\) + >> \(x \& y \) = OverscriptBox[x, y] - >> \\(x \\+ y \\) + >> \(x \+ y \) = UnderscriptBox[x, y] - #> \\( x \\^ 2 \\_ 4 \\) + #> \( x \^ 2 \_ 4 \) = SuperscriptBox[x, SubscriptBox[2, 4]] ## Tests for issue 151 (infix operators in heads) diff --git a/mathics/core/parser/parser.py b/mathics/core/parser/parser.py index 7e681b63c..e8a916629 100644 --- a/mathics/core/parser/parser.py +++ b/mathics/core/parser/parser.py @@ -308,13 +308,53 @@ def p_RawLeftAssociation(self, token): def p_LeftRowBox(self, token): self.consume() children = [] - self.box_depth += 1 - self.bracket_depth += 1 + # If this does not happen, it would be because + # it was called when a `FormBox` (or any other) + # was found. More generally, we could use + # ``token.tag == "LeftRowBox" + # if there were other Tokens with a + # similar behaviour (see bellow). + + if token.tag != "FormBox": + self.box_depth += 1 + self.bracket_depth += 1 + token = self.next() - while token.tag not in ("RightRowBox", "OtherscriptBox"): + while token.tag not in ("RightRowBox", "OtherscriptBox", "FormBox"): newnode = self.parse_box(0) children.append(newnode) token = self.next() + + # FormBox token has a particular behaviour: if it is found inside + # a RowBox, it splits the Rowbox in two pieces: the part at the + # left is taken as a "Format" and the part at the right is parsed + # as the Box to be formatted, in a way that + # \(a_1, a_2 ... \` b_1 b_2 ... \) is parsed as + # FormBox[RowBox[{b_1, b_2,...}], RowBox[a_1, a_2, ...]] + # This kind of parsing is not supported by the standard mechanism, + # so we need to processing it here instead of using a p_FormBox + # method. + # The strategy to deal with is that, when a "FormBox" tag is found, + # the collected elements are used to build the second argument of the + # output, and then the method is called again to parse the rest of the + # box as it had started at the "FormBox" tag. In this new call, + # neither `self.box_depth` or `self.bracket_depth` are going to be + # incremented, but are decremented at the end of the child expression. + if token.tag == "FormBox": + if len(children) == 0: + fmt = Symbol("StandardForm") + elif len(children) == 1: + fmt = children[0] + if type(fmt) is String: + fmt_name = fmt.value + if is_symbol_name(fmt_name): + fmt = Symbol(fmt_name) + else: + fmt = Node("Removed", String("$$Failure")) + else: + fmt = Node("RowBox", Node("List", *children)) + rest = self.p_LeftRowBox(token) + return Node("FormBox", rest, fmt) if len(children) == 0: result = String("") elif len(children) == 1: @@ -840,20 +880,6 @@ def b_FractionBox(self, box1, token, p): box2 = self.parse_box(q + 1) return Node("FractionBox", box1, box2) - def b_FormBox(self, box1, token, p): - q = misc_ops["FormBox"] - if q < p: - return None - if box1 is None: - box1 = Symbol("StandardForm") # RawForm - elif is_symbol_name(box1.value): - box1 = Symbol(box1.value, context=None) - else: - box1 = Node("Removed", String("$$Failure")) - self.consume() - box2 = self.parse_box(q) - return Node("FormBox", box2, box1) - def b_OverscriptBox(self, box1, token, p): q = misc_ops["OverscriptBox"] if q < p: diff --git a/test/core/parser/test_parser.py b/test/core/parser/test_parser.py index 629f2a1ce..bce249c4c 100644 --- a/test/core/parser/test_parser.py +++ b/test/core/parser/test_parser.py @@ -694,10 +694,14 @@ def testFraction(self): self.check("\\( \\/ \\)", 'FractionBox["", ""]') def testFormBox(self): - self.check("\\( 1 \\` b \\)", 'FormBox["b", Removed["$$Failure"]]') - self.check("\\( \\` b \\)", 'FormBox["b", StandardForm]') - self.check("\\( a \\` b \\)", 'FormBox["b", a]') - self.check("\\( a \\` \\)", 'FormBox["", a]') + self.check(r"\( \` b \)", 'FormBox["b", StandardForm]') + self.check(r"\( a \` b \)", 'FormBox["b", a]') + self.check(r"\( a \` \)", 'FormBox["", a]') + self.check(r"\( a \` b + c \)", 'FormBox[RowBox[{"b", "+", "c"}], a]') + self.check(r"\( a \` b \` c \)", 'FormBox[FormBox["c", b], a]') + self.check(r'\( "a" \` b \)', 'FormBox["b", Removed["$$Failure"]]') + self.check(r"\( 3.2 \` b \)", 'FormBox["b", Removed["$$Failure"]]') + self.check(r"\( 3.2 + a \` b \)", 'FormBox["b", RowBox[{"3.2", "+", "a"}]]') def testRow(self): self.check("\\( \\)", String(""))