diff --git a/mathics/core/parser/parser.py b/mathics/core/parser/parser.py index f0010db28..48cd0e503 100644 --- a/mathics/core/parser/parser.py +++ b/mathics/core/parser/parser.py @@ -128,6 +128,14 @@ def incomplete(self, pos: int): self.tokeniser.incomplete() self.backtrack(pos) + @property + def is_inside_rowbox(self) -> bool: + r""" + Return True iff we parsing inside a RowBox, i.e. RowBox[...] + or \( ... \) + """ + return self.box_depth > 0 + def next(self) -> Token: if self.current_token is None: self.current_token = self.tokeniser.next() @@ -244,7 +252,7 @@ def parse_box_expr(self, precedence: int) -> Union[String, Node]: result = None new_result = None while True: - if self.box_depth > 0: + if self.is_inside_rowbox: token = self.next_noend() else: token = self.next() @@ -260,6 +268,15 @@ def parse_box_expr(self, precedence: int) -> Union[String, Node]: self.incomplete(token.pos) elif result is None and tag != "END": self.consume() + # TODO: handle non-box expressions inside RowBox + # new_result = self.parse_expr(precedence) + # if new_result is None: + # self.consume() + # new_result = String(token.text) + # if new_result.value == r"\(": + # new_result = self.p_LeftRowBox(token) + # elif isinstance(new_result, (Symbol, Number)): + # new_result = String(new_result.value) new_result = String(token.text) if new_result.value == r"\(": new_result = self.p_LeftRowBox(token) @@ -415,11 +432,23 @@ def parse_expr(self, precedence: int) -> Optional[Node]: """ result = self.parse_p() + # Note: Number and String belo ware the mathics.core.parser's Number, String and Symbol, + # not mathics.core.atom's Number and String, and Symbol. + if self.is_inside_rowbox and isinstance(result, (Number, Symbol)): + result = String(result.value) + while True: - if self.bracket_depth > 0: + if self.bracket_depth > 0 or self.is_inside_rowbox: token = self.next_noend() + if token.tag in ("OtherscriptBox", "RightRowBox"): + if self.is_inside_rowbox: + break + else: + self.tokeniser.sntx_message(token.pos) + raise InvalidSyntaxError() else: token = self.next() + tag = token.tag method = getattr(self, "e_" + tag, None) if method is not None: @@ -436,10 +465,17 @@ def parse_expr(self, precedence: int) -> Optional[Node]: tag not in self.halt_tags and flat_binary_operators["Times"] >= precedence ): - # implicit multiplication - q = flat_binary_operators["Times"] - child = self.parse_expr(q + 1) - new_result = Node("Times", result, child).flatten() + if self.is_inside_rowbox: + # Inside a RowBox, implicit multiplication is treated as + # concatentation. + child = self.parse_expr(precedence) + children = [result, child] + new_result = Node("RowBox", Node("List", *children)) + else: + # There is an implicit multiplication. + operator_precedence = flat_binary_operators["Times"] + child = self.parse_expr(operator_precedence + 1) + new_result = Node("Times", result, child).flatten() else: new_result = None if new_result is None: @@ -464,6 +500,8 @@ def parse_p(self): operator_precedence = prefix_operators[tag] child = self.parse_expr(operator_precedence) return Node(tag, child) + elif self.is_inside_rowbox: + return None else: self.tokeniser.sntx_message(token.pos) raise InvalidSyntaxError() @@ -873,6 +911,13 @@ def e_RawLeftBracket(self, expr, token: Token, p: int) -> Optional[Node]: seq = self.parse_seq() self.expect("RawRightBracket") self.bracket_depth -= 1 + + # TODO: something like this may be needed for function application + # inside a RowBox. + # if self.is_inside_rowbox: + # result = Node("List", expr, String("["), *seq, String("]")) + # else: + result = Node(expr, *seq) result.parenthesised = True return result @@ -997,6 +1042,7 @@ def e_Unset(self, expr1, token: Token, p: int) -> Optional[Node]: # Used for prefix operators, brackets and tokens which # can uniquely identified by a prefix character or string. + # FIXME DRY with pre_Decrement def p_Decrement(self, token: Token) -> Node: self.consume() q = prefix_operators["PreDecrement"] diff --git a/test/core/parser/test_box_parser.py b/test/core/parser/test_box_parser.py index b113fc5b5..fe0c3ca90 100644 --- a/test/core/parser/test_box_parser.py +++ b/test/core/parser/test_box_parser.py @@ -39,6 +39,11 @@ def test_box_parsing(): '"1"', "Box parsing a non-box expression should strip boxing and convert to String", ), + ( + r"\( 2 x \)", + 'RowBox[{"2", "x"}]', + "Box parsing of implicit multiplication is concatenation", + ), ( r"\( 2 \^ n \)", 'SuperscriptBox["2", "n"]', @@ -64,5 +69,10 @@ def test_box_parsing(): 'SuperscriptBox["x", RowBox[{FractionBox["i", "2"], "+", "5"}]]', "Box parsing using FractionBox and parenthesis should work", ), + # ( + # r"\(1 F[\(Q\)]\)", + # 'RowBox[{"1", RowBox[{"F", "[", "Q", "]"}]}]', + # "Box parsing with a function expression", + # ), ): check_evaluation(str_expr, str_expected, assert_message)