Skip to content

Commit

Permalink
RowBox parsing corrections:
Browse files Browse the repository at this point in the history
* Implicit multiplication inside a RowBox is concatenation.
* Some refactoring
  • Loading branch information
rocky committed Jan 6, 2025
1 parent 274d22c commit e9bd446
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 6 deletions.
58 changes: 52 additions & 6 deletions mathics/core/parser/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand All @@ -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)
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -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()
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"]
Expand Down
10 changes: 10 additions & 0 deletions test/core/parser/test_box_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]',
Expand All @@ -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)

0 comments on commit e9bd446

Please sign in to comment.