# 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 } } ```