1082815dcSEvan Baconconst LEFT_BRACKETS = ['(', '{'] as const; 2082815dcSEvan Baconconst RIGHT_BRACKETS = [')', '}'] as const; 3082815dcSEvan Bacon 4*8a424bebSJames Idetype LeftBracket = (typeof LEFT_BRACKETS)[number]; 5*8a424bebSJames Idetype RightBracket = (typeof RIGHT_BRACKETS)[number]; 6082815dcSEvan Bacontype Bracket = LeftBracket | RightBracket; 7082815dcSEvan Bacon 8082815dcSEvan Baconexport function findMatchingBracketPosition( 9082815dcSEvan Bacon contents: string, 10082815dcSEvan Bacon bracket: Bracket, 11082815dcSEvan Bacon offset: number = 0 12082815dcSEvan Bacon): number { 13082815dcSEvan Bacon // search first occurrence of `bracket` 14082815dcSEvan Bacon const firstBracketPos = contents.indexOf(bracket, offset); 15082815dcSEvan Bacon if (firstBracketPos < 0) { 16082815dcSEvan Bacon return -1; 17082815dcSEvan Bacon } 18082815dcSEvan Bacon 19082815dcSEvan Bacon let stackCounter = 0; 20082815dcSEvan Bacon const matchingBracket = getMatchingBracket(bracket); 21082815dcSEvan Bacon 22082815dcSEvan Bacon if (isLeftBracket(bracket)) { 23082815dcSEvan Bacon const contentsLength = contents.length; 24082815dcSEvan Bacon // search forward 25082815dcSEvan Bacon for (let i = firstBracketPos + 1; i < contentsLength; ++i) { 26082815dcSEvan Bacon const c = contents[i]; 27082815dcSEvan Bacon if (c === bracket) { 28082815dcSEvan Bacon stackCounter += 1; 29082815dcSEvan Bacon } else if (c === matchingBracket) { 30082815dcSEvan Bacon if (stackCounter === 0) { 31082815dcSEvan Bacon return i; 32082815dcSEvan Bacon } 33082815dcSEvan Bacon stackCounter -= 1; 34082815dcSEvan Bacon } 35082815dcSEvan Bacon } 36082815dcSEvan Bacon } else { 37082815dcSEvan Bacon // search backward 38082815dcSEvan Bacon for (let i = firstBracketPos - 1; i >= 0; --i) { 39082815dcSEvan Bacon const c = contents[i]; 40082815dcSEvan Bacon if (c === bracket) { 41082815dcSEvan Bacon stackCounter += 1; 42082815dcSEvan Bacon } else if (c === matchingBracket) { 43082815dcSEvan Bacon if (stackCounter === 0) { 44082815dcSEvan Bacon return i; 45082815dcSEvan Bacon } 46082815dcSEvan Bacon stackCounter -= 1; 47082815dcSEvan Bacon } 48082815dcSEvan Bacon } 49082815dcSEvan Bacon } 50082815dcSEvan Bacon 51082815dcSEvan Bacon return -1; 52082815dcSEvan Bacon} 53082815dcSEvan Bacon 54082815dcSEvan Baconfunction isLeftBracket(bracket: Bracket): boolean { 55082815dcSEvan Bacon const leftBracketList: readonly Bracket[] = LEFT_BRACKETS; 56082815dcSEvan Bacon return leftBracketList.includes(bracket); 57082815dcSEvan Bacon} 58082815dcSEvan Bacon 59082815dcSEvan Baconfunction getMatchingBracket(bracket: Bracket): Bracket { 60082815dcSEvan Bacon switch (bracket) { 61082815dcSEvan Bacon case '(': 62082815dcSEvan Bacon return ')'; 63082815dcSEvan Bacon case ')': 64082815dcSEvan Bacon return '('; 65082815dcSEvan Bacon case '{': 66082815dcSEvan Bacon return '}'; 67082815dcSEvan Bacon case '}': 68082815dcSEvan Bacon return '{'; 69082815dcSEvan Bacon default: 70082815dcSEvan Bacon throw new Error(`Unsupported bracket - ${bracket}`); 71082815dcSEvan Bacon } 72082815dcSEvan Bacon} 73