2

Looking to preserve inner nested brackets when replacing brackets. If possible to make many nested layers work that would be great, if not just nested once is fine.

(if money>5 and (times + total_cash >266))[something]
(if times + total_cash >266)[something]

{if money>5 and (times + total_cash >266)}[something]
{if times + total_cash >266}[something]

A naive attempt doesn't seem to work that well:

str.replace(/\(if(.*?)\)]/gi, '{if $1}')
Harry
  • 52,711
  • 71
  • 177
  • 261
  • 1
    Not possible to deal with several levels of nested brackets with javascript regex. You have to write a state machine. – Casimir et Hippolyte Jan 17 '22 at 23:18
  • @CasimiretHippolyte okay, but 1 is still possible right? Do you know what the limit is? – Harry Jan 17 '22 at 23:19
  • If you know that 1 is the max, yes it's possible. – Casimir et Hippolyte Jan 17 '22 at 23:20
  • The limit is the limit you know in advance, but the pattern grows with the nesting levels (at least with javascript because you can't build recursive patterns). – Casimir et Hippolyte Jan 17 '22 at 23:30
  • 1
    If the outer left parenthesis is the first left parenthesis in the string and the outer right parenthesis is the last right parenthesis in the string you can match the regex `^[^\(]*(\().*(\))[^\)]*$` and convert the content of capture group 1 (a left parenthesis) to `'{'` and and convert the content of capture group 2 (a right parenthesis) to `'}'`. [Demo](https://regex101.com/r/uuGALV/1) – Cary Swoveland Jan 17 '22 at 23:45
  • @CarySwoveland how do you replace only the captured group? I thought you had to capture everything else to do replace it. https://stackoverflow.com/questions/3954927/how-to-replace-captured-groups-only – Harry Jan 18 '22 at 00:59
  • 1
    Each match will have two captures; capture group 1 holds `'('` , capture group 2 holds `')'` . You should be able to programmatically get the index in the string for each of those captures and make the substitution. I don't know Javascript but to give you an idea of how to do it, here is how you would do that in Ruby (which resembles pseudo-code): `s= "cat (if times + total_cash >266)[something] dog"; m = /^[^\(]*(\().*(\))[^\)]*$/.match(s); i = m.begin(1) #=> 4; j = m.begin(2) #=> 31; s[4] = '{'; s[31] = '}'; s #=> "cat {if times + total_cash >266}[something] dog"`. – Cary Swoveland Jan 18 '22 at 03:12
  • 1
    Similar @CarySwoveland's suggestion, if input matches this pattern, even [`\((.*)\)`](https://regex101.com/r/a9CtGd/1) might suffice (replace with capture `{$1}`). – bobble bubble Jan 18 '22 at 07:29
  • @bobble bubble isn't that the same as what I had in the question, that doesn't capture the last closing parenthesis it captures the one before it. – Harry Jan 18 '22 at 18:23
  • @Harry Oh, yours was very similar indeed - with a minor difference - your `.*?` is [lazy](https://stackoverflow.com/questions/2301285/what-do-lazy-and-greedy-mean-in-the-context-of-regular-expressions) whereas my `.*` is greedy (hungry/consumes as much as possible). – bobble bubble Jan 19 '22 at 11:02

1 Answers1

3

for zero level of nesting:

str.replace(/\(if\s*([^()]*)\)/gi, '{if $1}')

for one level (or less):

str.replace(/\(if\s*([^()]*(?:\([^()]*\)[^()]*)*)\)/gi, '{if $1}')

for two levels (or less):

str.replace(/\(if\s*([^()]*(?:\([^()]*(?:\([^()]*\)[^()]*)*\)[^()]*)*)\)/gi, '{if $1}')

etc. This method becomes quickly limited.

Javascript regexes don't have features like recursive patterns (perl, pcre), or a counter system like in .net languages. That's why, the best option is to build a state machine to count the number of opening and closing parenthesis (note that you can use a regex to split your string to interesting parts to do it, for example: str.split(/(\bif\b|[()])/i)).


Note that [^()]*(?:\([^()]*\)[^()]*)* is an optimized way to write: (?:[^()]|\([^()]*\))* (that is shorter but inefficient). This subpattern is unrolled.

Casimir et Hippolyte
  • 88,009
  • 5
  • 94
  • 125