# JavaScript (ES6), 177 bytes <sub><sub><sup><sup>(LL1 rotated 177°)</sup></sup></sub></sub>

```js
s=>(f=(x,i)=>x<"A"||x>"Z"?v=s[i]==x&&[i+1,x]:s.split`
`.some((r,a,j)=>v=r[0]==x&&[j=i,a=[a],...r].slice(3).every(y=>f(y,j)&&a.push(([j]=v)[1]))&&[j,a]))("S",0)&&s[v[0]]==0&&v[1]
```

[Try it online!](https://tio.run/##LY@xasQwEER7f4YKscKK8JEmhKxDGhdXJIW6CIGFkS8yvrPR@oQM9@@OAplqB@YNO5NLjoYY1u0pvRzDcqNl9mpeLnDWX5@KthhulzDuAAdhCyNClkFgm9/YB3s8csu@2XtCMsEiZs5NqE8y21dStM5h66te0XL1AFE6ORUwYTTNf3bCIB0aZ6VSKlpFcxg8PAvlk4877NiOsBeKc6fWO/0AmMliEuZkhfjjpSsHMM1kUzyZVKpLd8N5KplDQF@Bq52odFdp0HUnqs6Vl8qqK4ii4xc "JavaScript (V8) – Try It Online")

Outputs `false` if the string does not match, otherwise outputs a nested array of rule indices and terminals:
```js
[2,"(",[1,[3,"a"]],"+",[3,"a"],")"]
```

## Ungolfed

```js
str =>
  (applyRule = (ruleName, index) =>
    ruleName < "A" || ruleName > "Z"
      ? result = str[index] == ruleName && [index + 1, ruleName]
      : str.split("\n").some(
        (rule, appliedRules, jndex) => result =
          rule[0] == ruleName &&
          (
            jndex = index,
            appliedRules = [appliedRules],
            [...rule].slice(1).every(char =>
              applyRule(char, jndex)
              && appliedRules.push(([jndex] = result)[1])
            )
            && [jndex, appliedRules]
          )
      )
  )("S", 0)
  && str[result[0]] == 0 // "\n" == 0
  && result[1]
```

```js
function main(input) {
  const [str, ...rules] = input.split("\n")

  const result = applyRule("S", 0)

  if(result && result[0] === str.length)
    return result[1]

  function applyRule(ruleName, index) {
    if (ruleName === ruleName.toLowerCase())
      return str[index] === ruleName ? [index + 1, ruleName] : null

    main: for (const [ruleIndex, rule] of rules.entries()) {
      if (!rule.startsWith(ruleName))
        continue
      let curIndex = index
      let appliedRules = [ruleIndex + 1]
      for (const char of rule.slice(1)) {
        let result = applyRule(char, curIndex)
        if (!result) continue main
        curIndex = result[0]
        appliedRules.push(result[1])
      }
      return [curIndex, appliedRules]
    }

    return null
  }
}
```