{"record":{"id":"9589620867adbe33","repo":"jax-ml/jax","slug":"unmatched-parenthesis-in-s-r","errorCode":null,"errorMessage":"Unmatched parenthesis in {s!r}","messagePattern":"Unmatched parenthesis in (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"jax/_src/pallas/einshape.py","lineNumber":144,"sourceCode":"    \"(ab)c\" -> [['a', 'b'], ['c']]\n\n  Args:\n    s: One side of an einshape equation string.\n\n  Returns:\n    A list of lists of characters, where each inner list represents a group of\n    dimensions.\n  \"\"\"\n  # Remove spaces\n  s = s.replace(\" \", \"\")\n  groups = []\n  i = 0\n  while i < len(s):\n    if s[i] == \"(\":\n      # Start of a group\n      j = s.find(\")\", i)\n      if j == -1:\n        raise ValueError(f\"Unmatched parenthesis in {s!r}\")\n      group = list(s[i + 1 : j])\n      groups.append(group)\n      i = j + 1\n    elif s[i] == \")\":\n      raise ValueError(f\"Unmatched parenthesis in {s!r}\")\n    else:\n      # distinct dimension\n      groups.append([s[i]])\n      i += 1\n  return groups\n\n\ndef _parse_equation(equation: str) -> tuple[list[list[str]], list[list[str]]]:\n  \"\"\"Parses an einshape equation.\"\"\"\n  if equation.count(\"->\") != 1:\n    raise ValueError(\"Equation must contain exactly one '->'\")\n  lhs_str, rhs_str = equation.split(\"->\")\n  return _parse_side(lhs_str), _parse_side(rhs_str)","sourceCodeStart":126,"sourceCodeEnd":162,"githubUrl":"https://github.com/jax-ml/jax/blob/1e1c6a8fc06dfcd1247076ec5cae4640cea5d7bb/jax/_src/pallas/einshape.py#L126-L162","documentation":"einshape (used by pallas's jax._src.pallas.einshape) parses each side of an einsum-like reshape equation. When an opening parenthesis has no matching ')' anywhere later in the string, _parse_side raises ValueError 'Unmatched parenthesis'. Grouping parentheses like '(ab)c' declare merged/split dimensions and must be balanced.","triggerScenarios":"Passing an equation like '...((ab -> ...' or 'a(b' to einshape/reshape helpers — any side string containing '(' with no subsequent ')'. For example jax._src.pallas.einshape.get_einshape_transforms('a(b -> abc', shape).","commonSituations":"Hand-writing einshape equations with a typo (missing ')'); dynamically building equation strings that drop a closing paren; porting numpy reshape logic to einshape notation incorrectly.","solutions":["Balance the parentheses: every '(' needs a matching ')' on the same side of the equation, e.g. 'a(bc) -> abc'","Programmatically validate with s.count('(') == s.count(')') before calling einshape","Review einshape docs: parentheses denote dimension groups whose sizes multiply; they cannot span the '->'"],"exampleFix":"# before\ntransforms = get_einshape_transforms('a(b -> ab', x.shape)  # unmatched '('\n\n# after\ntransforms = get_einshape_transforms('a(b) -> ab', x.shape)","handlingStrategy":"validation","validationCode":"def eq_side_ok(side: str) -> bool:\n    depth = 0\n    for ch in side:\n        if ch == '(':\n            depth += 1\n        elif ch == ')':\n            depth -= 1\n            if depth < 0:\n                return False\n    return depth == 0","typeGuard":"null","tryCatchPattern":"try:\n    t = get_einshape_transforms(eq, shape)\nexcept ValueError as e:\n    if 'Unmatched parenthesis' in str(e):\n        raise ValueError(f'fix parentheses in einshape equation {eq!r}') from None\n    raise","preventionTips":["Lint equation strings with the depth-scanner helper before calling einshape","Build equations from structured parts (dim lists) rather than string concatenation","Add unit tests for every equation constant in your codebase"],"tags":["jax","einshape","einsum-notation","parse-error","parentheses"],"backgroundTag":"equation-string-parse-error","analyzedSha":"1e1c6a8fc06dfcd1247076ec5cae4640cea5d7bb","analyzedAt":"2026-08-27T09:53:25.647Z","schemaVersion":2},"datasetVersion":"2026-08-27T13:17:12.746Z"}