Repository: sympy/sympy

Issue:
# `parse_latex_lark` cannot treat square brackets or curly parentheses

`sympy.parsing.latex.parse_latex` can interpret `[x+y]z` correctly, but `sympy.parsing.latex.parse_latex_lark` cannot,
and similarly `\\{x+y\\}z`.

```python
from sympy.parsing.latex import parse_latex, parse_latex_lark
parse_latex_lark('[x+y]z')
```

```
---------------------------------------------------------------------------
UnexpectedCharacters                      Traceback (most recent call last)
Cell In[7], [line 1](vscode-notebook-cell:?execution_count=7&line=1)
----> [1](vscode-notebook-cell:?execution_count=7&line=1) parse_latex_lark(latex)

File ~/parse_latex_lark/.venv/lib/python3.14/site-packages/sympy/parsing/latex/lark/latex_parser.py:110, in parse_latex_lark(s)
    108 if _lark is None:
    109     raise ImportError("Lark is probably not installed")
--> [110](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/volat/parse_latex_lark/~/parse_latex_lark/.venv/lib/python3.14/site-packages/sympy/parsing/latex/lark/latex_parser.py:110) return _lark_latex_parser.doparse(s)

File ~/parse_latex_lark/.venv/lib/python3.14/site-packages/sympy/parsing/latex/lark/latex_parser.py:74, in LarkLaTeXParser.doparse(self, s)
     71 if self.print_debug_output:
     72     _lark.logger.setLevel(logging.DEBUG)
---> [74](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/volat/parse_latex_lark/~/parse_latex_lark/.venv/lib/python3.14/site-packages/sympy/parsing/latex/lark/latex_parser.py:74) parse_tree = self.parser.parse(s)
     76 if not self.transform_expr:
     77     # exit early and return the parse tree
     78     _lark.logger.debug("expression = %s", s)

File ~/parse_latex_lark/.venv/lib/python3.14/site-packages/lark/lark.py:677, in Lark.parse(self, text, start, on_error)
    675 if on_error is not None and self.options.parser != 'lalr':
    676     raise NotImplementedError("The on_error option is only implemented for the LALR(1) parser.")
--> [677](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/volat/parse_latex_lark/~/parse_latex_lark/.venv/lib/python3.14/site-packages/lark/lark.py:677) return self.parser.parse(text, start=start, on_error=on_error)

File ~/parse_latex_lark/.venv/lib/python3.14/site-packages/lark/parser_frontends.py:131, in ParsingFrontend.parse(self, text, start, on_error)
    129 kw = {} if on_error is None else {'on_error': on_error}
    130 stream = self._make_lexer_thread(text)
--> [131](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/volat/parse_latex_lark/~/parse_latex_lark/.venv/lib/python3.14/site-packages/lark/parser_frontends.py:131) return self.parser.parse(stream, chosen_start, **kw)

File ~/parse_latex_lark/.venv/lib/python3.14/site-packages/lark/parsers/earley.py:280, in Parser.parse(self, lexer, start)
    277     else:
    278         columns[0].add(item)
--> [280](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/volat/parse_latex_lark/~/parse_latex_lark/.venv/lib/python3.14/site-packages/lark/parsers/earley.py:280) to_scan = self._parse(lexer, columns, to_scan, start_symbol)
    282 # If the parse was successful, the start
    283 # symbol should have been completed in the last step of the Earley cycle, and will be in
    284 # this column. Find the item for the start_symbol, which is the root of the SPPF tree.
    285 solutions = dedup_list(n.node for n in columns[-1] if n.is_complete and n.node is not None and n.s == start_symbol and n.start == 0)

File ~/parse_latex_lark/.venv/lib/python3.14/site-packages/lark/parsers/xearley.py:153, in Parser._parse(self, stream, columns, to_scan, start_symbol)
    150 for token in stream:
    151     self.predict_and_complete(i, to_scan, columns, transitives, node_cache)
--> [153](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/volat/parse_latex_lark/~/parse_latex_lark/.venv/lib/python3.14/site-packages/lark/parsers/xearley.py:153)     to_scan, node_cache = scan(i, to_scan)
    155     if token == '\n':
    156         text_line += 1

File ~/parse_latex_lark/.venv/lib/python3.14/site-packages/lark/parsers/xearley.py:125, in Parser._parse.<locals>.scan(i, to_scan)
    123 if not next_set and not delayed_matches and not next_to_scan:
    124     considered_rules = list(sorted(to_scan, key=lambda key: key.rule.origin.name))
--> [125](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/volat/parse_latex_lark/~/parse_latex_lark/.venv/lib/python3.14/site-packages/lark/parsers/xearley.py:125)     raise UnexpectedCharacters(stream, i, text_line, text_column, {item.expect.name for item in to_scan},
    126                                set(to_scan), state=frozenset(i.s for i in to_scan),
    127                                considered_rules=considered_rules
    128                                )
    130 return next_to_scan, node_cache

UnexpectedCharacters: No terminal matches '[' in the current parser context, at line 1 col 1

[x+y]z
^
Expected one of: 
	* FUNC_ARCOSH
	* L_FLOOR
	* FUNC_DETERMINANT
	* CMD_INFTY
	* L_BRACE
	* FUNC_MAX
	* FUNC_ARCSIN
	* FUNC_MATRIX_ADJUGATE
	* FUNC_TANH
	* FUNC_LIM
	* FUNC_PROD
	* FUNC_ARSINH
	* CMD_DETERMINANT_BEGIN
	* SUB
	* L_PAREN
	* CMD_OVERLINE
	* GREEK_SYMBOL_WITH_LATIN_SUBSCRIPT
	* GREEK_SYMBOL_WITH_GREEK_SUBSCRIPT
	* FUNC_SQRT
	* FUNC_COSH
	* CMD_MATRIX_BEGIN
	* FUNC_ARTANH
	* BAR
	* FUNC_SEC
	* FUNC_INT
	* FUNC_ARCSEC
	* FUNC_ARCTAN
	* FUNC_LOG
	* FUNC_ARCCOT
	* __ANON_1
	* L_CEIL
	* FUNC_SIN
	* FUNC_ARCCSC
	* CMD_LANGLE
	* FUNC_COT
	* FUNC_ARCCOS
	* FUNC_SINH
	* ADD
	* FUNC_LG
	* SYMBOL
	* FUNC_EXP
	* FUNC_TAN
	* GREEK_SYMBOL_WITH_PRIMES
	* FUNC_LN
	* CMD_MATHIT
	* FUNC_MATRIX_TRACE
	* FUNC_COS
	* LATIN_SYMBOL_WITH_LATIN_SUBSCRIPT
	* CMD_FRAC
	* FUNC_CSC
	* CMD_IMAGINARY_UNIT
	* CMD_BINOM
	* FUNC_SUM
	* FUNC_MIN
	* LATIN_SYMBOL_WITH_GREEK_SUBSCRIPT
```

this is probably an error on latex.lark's part.

```
group_round_parentheses: L_PAREN _expression R_PAREN
group_square_brackets: L_BRACKET _expression R_BRACKET
group_curly_parentheses: L_BRACE _expression R_BRACE

...

adjacent_expressions: (_one_letter_symbol | number) _expression_mul
    | group_round_parentheses (group_round_parentheses | _one_letter_symbol)
    | _function _function
    | fraction _expression_mul

_expression_func: _expression_core
    | group_round_parentheses
    | fraction
    | binomial
    | _function
    | _integral// | derivative
    | limit
    | matrix
```

should be

```
group_round_parentheses: L_PAREN _expression R_PAREN
group_square_brackets: L_BRACKET _expression R_BRACKET
group_curly_parentheses: L_BRACE _expression R_BRACE
group_curly_literal_parentheses: L_BRACE_LITERAL _expression R_BRACE_LITERAL

...

adjacent_expressions: (_one_letter_symbol | number) _expression_mul
    | (group_round_parentheses | group_square_brackets | group_curly_literal_parentheses) (group_round_parentheses | group_square_brackets | group_curly_literal_parentheses | _one_letter_symbol)
    | _function _function
    | fraction _expression_mul

_expression_func: _expression_core
    | group_round_parentheses
    | group_square_brackets
    | group_curly_literal_parentheses
    | fraction
    | binomial
    | _function
    | _integral// | derivative
    | limit
    | matrix
```

and insert the following method in `transformer.py`.
```python
    def group_curly_literal_parentheses(self, tokens):
        return tokens[1]
```



Implementation patch:
diff --git a/sympy/parsing/latex/lark/transformer.py b/sympy/parsing/latex/lark/transformer.py
index cbd514b65173..69b3f25d9bc6 100644
--- a/sympy/parsing/latex/lark/transformer.py
+++ b/sympy/parsing/latex/lark/transformer.py
@@ -126,6 +126,9 @@ def group_square_brackets(self, tokens):
     def group_curly_parentheses(self, tokens):
         return tokens[1]
 
+    def group_curly_literal_parentheses(self, tokens):
+        return tokens[1]
+
     def eq(self, tokens):
         return sympy.Eq(tokens[0], tokens[2])
 
@@ -219,7 +222,7 @@ def iscmdstar(x):
         base = tokens[0]
         if len(tokens) == 3: # a^b OR a^\prime OR a^\ast
             sup = tokens[2]
-        if len(tokens) == 5:
+        elif len(tokens) == 5:
             # a^{'}, a^{''}, ... OR
             # a^{*}, a^{**}, ... OR
             # a^{\prime}, a^{\prime\prime}, ... OR


Test patch:
diff --git a/sympy/parsing/tests/test_latex_lark.py b/sympy/parsing/tests/test_latex_lark.py
index ead2dfa26962..03b0102c9157 100644
--- a/sympy/parsing/tests/test_latex_lark.py
+++ b/sympy/parsing/tests/test_latex_lark.py
@@ -530,6 +530,38 @@ def _MatMul(a, b):
     (r"\left(  x + y\right ) z", _Mul(_Add(x, y), z)),
 ]
 
+SQUARE_BRACKET_EXPRESSION_PAIRS = [
+    (r"[x+y]z", _Mul(_Add(x, y), z)),
+    (r"[a+b]c", _Mul(_Add(a, b), c)),
+    (r"[x]y", _Mul(x, y)),
+    (r"[a+b][c+d]", _Mul(_Add(a, b), _Add(c, d))),
+    (r"[x][y]", _Mul(x, y)),
+    (r"[x+y]a", _Mul(_Add(x, y), a)),
+    (r"[a]b", _Mul(a, b)),
+]
+
+EVALUATED_SQUARE_BRACKET_EXPRESSION_PAIRS = [
+    (r"[x+y]z", (x + y) * z),
+    (r"[a+b]c", (a + b) * c),
+    (r"[x]y", x * y),
+    (r"[a+b][c+d]", (a + b) * (c + d)),
+    (r"[x][y]", x * y),
+    (r"[2+3]x", 5 * x),
+]
+
+LITERAL_BRACE_EXPRESSION_PAIRS = [
+    (r"\{x+y\}z", _Mul(_Add(x, y), z)),
+    (r"\{a\}b", _Mul(a, b)),
+    (r"\{x+y\}\{a+b\}", _Mul(_Add(x, y), _Add(a, b))),
+]
+
+MIXED_BRACKET_BRACE_EXPRESSION_PAIRS = [
+    (r"[x+y]\{a+b\}", _Mul(_Add(x, y), _Add(a, b))),
+    (r"\{a+b\}[x+y]", _Mul(_Add(a, b), _Add(x, y))),
+    (r"[x]\{y\}", _Mul(x, y)),
+    (r"\{a\}[b]", _Mul(a, b)),
+]
+
 UNEVALUATED_LITERAL_COMPLEX_NUMBER_EXPRESSION_PAIRS = [
     (r"\imaginaryunit^2", _Pow(I, 2)),
     (r"|\imaginaryunit|", _Abs(I)),
@@ -857,6 +889,32 @@ def test_miscellaneous_expressions():
             assert parse_latex_lark(latex_str) == sympy_expr, latex_str
 
 
+def test_square_bracket_multiplication():
+    for latex_str, sympy_expr in SQUARE_BRACKET_EXPRESSION_PAIRS:
+        with evaluate(False):
+            result = parse_latex_lark(latex_str)
+            assert result == sympy_expr, f"Failed for {latex_str}: got {result}, expected {sympy_expr}"
+
+    for latex_str, sympy_expr in EVALUATED_SQUARE_BRACKET_EXPRESSION_PAIRS:
+        result = parse_latex_lark(latex_str)
+        assert result == sympy_expr, f"Failed for {latex_str}: got {result}, expected {sympy_expr}"
+
+
+def test_literal_brace_multiplication():
+    for latex_str, sympy_expr in LITERAL_BRACE_EXPRESSION_PAIRS:
+        with evaluate(False):
+            result = parse_latex_lark(latex_str)
+            assert result == sympy_expr, f"Failed for {latex_str}"
+
+
+def test_mixed_bracket_brace_multiplication():
+    for latex_str, sympy_expr in MIXED_BRACKET_BRACE_EXPRESSION_PAIRS:
+        with evaluate(False):
+            result = parse_latex_lark(latex_str)
+            assert result == sympy_expr, \
+                f"Failed for {latex_str}: got {result}, expected {sympy_expr}"
+
+
 def test_literal_complex_number_expressions():
     for latex_str, sympy_expr in UNEVALUATED_LITERAL_COMPLEX_NUMBER_EXPRESSION_PAIRS:
         with evaluate(False):