1

I am working on a C project an I want to change all bool-variable checking from

if(!a)

to

if(a == false)

in order to make the code easier to read(I want to do the same with while statements). Anyway I'm using the following regex, which searches for an exclamation mark followed by a lowercase character and for the last closing parenthesis on the line.

%s/\(.*\)!\([a-z]\)\(.*\))\([^)]+\)/\1\2\3 == false)\4/g

I'm sorry for asking you to look over it but i can't understand why it would fail.

Also, is there an easier way of solving this problem and of using vim regex in general?

bsky
  • 19,326
  • 49
  • 155
  • 270
  • 1
    if the cases are always as simple as the examples, it is doable. otherwise, it is hard to change it flawless. you have `for, if, switch, while ...` also you have `if (!blah&&!(foo(bar)))` – Kent Mar 25 '14 at 13:18
  • 1
    Also what do you expect to happen when changing `if (!ptr) {` changing it to `if (ptr == false) {` doesn't really make a lot of sense as the if is a not NULL check. – FDinoff Mar 25 '14 at 14:20

4 Answers4

1

One solution should be this one:

%s/\(.*\)(\(\s*\)!\(\w\+\))/\1(\3 == false)/gc

Here, we do the following:

%s/\(.*\)(\(\s*\)!\(\w\+\))/\1(\3 == false)/gc

   \--+-/|\--+--/|\---+--/|
      |  |   |   |    |   finally test for a single `)`.
      |  |   |   |    (\3): then for one or more word characters (the var name).
      |  |   |   the single `!`
      |  |   (\2): then for any amount of white space before the `!`
      |  the single `(`
      (\1): test for any characters before a single `(`

Then, it's replaced by the first, third pattern, and then appends the text == false, opening and closing the parentheses as needed.

Nicolás Ozimica
  • 9,481
  • 5
  • 38
  • 51
  • What is the point of the first capture group if it is just `.*`? Or the second capture group? Why account for white-space after the `(`, but not before the `)`. Something like this, `%s/(\s*!\(\w\+\)\s*)/(\1 == false)/gc`, maybe? – Peter Rincker Mar 25 '14 at 13:51
  • The first capture group gets anything before `(`, so in this case it should be `ìf`, `for`, `switch`, `while`, as asked by OP. The second capture group is if any whitespace was added (we don't know the coding practices of the OP). Before the `)` could be some space as well, but I didn't put it, and I didn't say I did. Anything else that is not clear after my answer? – Nicolás Ozimica Mar 25 '14 at 14:56
  • `%s/\(.*\)(\(\s*\)!\(\w\+\))/\1(\3 == false)/gc` is equivalent to `%s/(\s*!\(\w\+\))/(\1 == false)/gc`. Your first and second capture groups are irrelevant – Peter Rincker Mar 25 '14 at 15:12
  • Indeed they are not necessary for this question, but I put them (and also explained them here) in order to give an answer more like what the OP was doing, so, it could be worth for him. Another thing is (prematurely) optimize it. – Nicolás Ozimica Mar 25 '14 at 16:13
1

To do this in vim, you could use the following:

%s/\(if(\)!\([^)]\+\)/\1\2==false/c

  • make sure that only if(!var)-constructs are matched, you could change that to while for the next task
  • c asks for confirmation for every occurence
Jasper
  • 3,939
  • 1
  • 18
  • 35
  • 1
    The `!` does not need to be escaped. However I feel like this would fail for cases like `if(!a && b)`. Is there any reason you are doing `[^)]\+` instead of `\k\+`? This is what I have came up with: `%s/\ – Peter Rincker Mar 25 '14 at 13:43
  • I usually use something like [^{boundary}] instead of directly specifying what I want. I changed the part about the exclamation mark. – Jasper Mar 25 '14 at 13:50
  • I understand using the negated bracketed character class for finding the boundary. My issue with it is that it will capture too much, e.g. `if(!a && b)`. In this case it will capture `a && b` which is too much – Peter Rincker Mar 25 '14 at 13:57
  • You're right, but according to OP's question, my regex is sufficient. Why don't you post your regex as answer? – Jasper Mar 25 '14 at 14:09
1

As @Kent said this is not a small undertaking. However for the simple case of just if(!a) it can be done.

:%s/\<if(\zs!\(\k\+\)\ze)/\1 == false/c

Explanation:

  • Start by making sure if is at a word bound by \<. This ensures it isn't part of some function name.
  • \zs and \ze set the start and end of the match respectively.
  • Capture the variable via the keyword class \k (\w works too) ending up with \(\k\+\)
  • For extra safety use the c flag to confirm each substation.

Thoughts:

  • This will need to be updated for other constructs, e.g. while
  • May need to make alterations for extra white-space, e.g. \<if\s*(\s*\zs!\(\k\+\)\ze\s*)
  • May want to use [a-z0-9_] instead of \k or \w to avoid capturing macros
  • There are instances where you may not have a construct: foo = !a && b;
  • This only handles the false cases. Doing a == true may be far trickier

Depending on your case it might be safest to just do the following:

:%s/!\([a-z0-9]\+\)/\1 == false/gc
Peter Rincker
  • 43,539
  • 9
  • 74
  • 101
1

On top of the answers already presented, I would say that the code does not smell like it needs refactoring. For a global regex replacement, the primary problem is to find

all bool-variables

and distinguish them from pointers, etc.

Community
  • 1
  • 1
Peter Petrik
  • 9,701
  • 5
  • 41
  • 65