0

I would like to define a new command for C++ files/buffers (ft=cpp).

I shall explain the requirements through the example below. The newly defined command :stripdbg should strip the surrounding dbg() text and should accept an address range. e.g :10,20stripdbg, :.,$stripdbg, :%stripdbg etc should be possible and work as expected.

example.cpp

#include "dbg.h"
#include <vector>


int factorial(int n) {
  if (dbg(n <= 1)) {
    return dbg(1);
  } else {
    return dbg(n * factorial(n - 1));
  }
}

int main() {
  std::string message = "hello";
  dbg(message); // [example.cpp:15 (main)] message = "hello" (std::string)

  const int a = 2;
  const int b = dbg(3 * a) + 1; // [example.cpp:18 (main)] 3 * a = 6 (int)

  std::vector<int> numbers{b, 13, 42};
  dbg(numbers); // [example.cpp:21 (main)] numbers = {7, 13, 42} (size: 3)
                // (std::vector<int>)

  dbg("this line is executed"); // [example.cpp:23 (main)] this line is executed

  factorial(4);

  return 0;
}

How can we achieve this through .vimrc settings?

Update: Desired output after %StripDbg()

Background

The dbg.h project is extremely helpful for quick debugging without the need for setting up/configuring and stepping through a command-line gdb process for a C++ codebase. For instance, this proves to be extremely helpful for one-off debugging in the 'coding test' round of an interview for a software development position. Quite a few tech companies use Codility for timed coding tasks. For such a use, offline development and quick debugging using dbg works perfectly. When the code works as expected, then we just need to strip out the dbg() calls and copy it into the browser, so that the online compiler in codility does not error out during grading.

So, my expected output after running :%StripDbg shall be,

example_strippeddbg.cpp (just an illustrative filename for distinction. The proposed command does not affect the filename)

#include "dbg.h"
#include <vector>


int factorial(int n) {
  if (n <= 1) {           //  __stripped debug__
    return 1;             //  __stripped debug__
  } else {
    return n * factorial(n - 1);   //  __stripped debug__
  }
}

int main() {
  std::string message = "hello";
  message;  //  __stripped debug__

  const int a = 2;
  const int b = 3 * a + 1; //  __stripped debug__

  std::vector<int> numbers{b, 13, 42};
  numbers; //  __stripped debug__

  dbg("this line is executed");  // __Ideally, this line must be deleted__ This might be hard.

  factorial(4);

  return 0;
}

The only hard to tackle case might be dbg("this line is executed"); which should ideally be deleted , but if that is not possible, should be left untouched. I don't wish to consider nested dbg() cases.

In summary, I am looking for the opposite of surround.vim for the specific dbg() regex sequence, but available as a standard ex command.

  • 1
    Depending on what you want the end result to be, the command itself could be a simple :g/dbg(/d – D. Ben Knoble Jul 14 at 20:52
  • @D.BenKnoble I doubt it. With that command if (dbg( would be deleted and I assume this is not what OP wants. – Maxim Kim Jul 15 at 6:31
  • @MaximKim fair enough—but a substitute could reasonably do it , and those take ranges too – D. Ben Knoble Jul 15 at 13:01
2

Unlike Maxin's answer, I'd recommend to use a ftplugin. They're really made to store the definition of filetype related mappings, commands, abbreviations, options, variables...

While we shall use <buffer> with mappings and abbreviations, it's -b with commands.

Now the problem is that you want to implement is quite complex. Vim main tool is regex. And regex don't support recursive patterns. Unfortunately, in your case, we need them to handle a variety of situations like dbg(f(x, g(y, z))) + dbg(u+v): we have parenthesis within calls to dbg, and several uses of dbg on a same line.

I'd say at best we could start from tricks like this one: https://old.reddit.com/r/vim/comments/5nf4pb/some_help_needed_about_a_vim_regex_for_a_plugin/dcbhavc/

If we simplify it (a little), it becomes.

   " ~/.vimrc
   filetype plugin on " at least!


    " ~/.vim/ftplugin/cpp/StripDebug.vim
    command! -bang -buffer -range StripDbg <line1>,<line2>call s:StripDbg("<bang>")
    
    let s:SINGLEQUOTED = '''\%(\\.\|[^\\'']\+\)*'''
    let s:DOUBLEQUOTED = '"\%(\\.\|[^\\"]\+\)*"'
    let s:NEUTRAL      = '[^''"\[\](){}]\+'
    let s:TERMINAL     = s:NEUTRAL . '\|' . s:SINGLEQUOTED . '\|' . s:DOUBLEQUOTED
    let s:BALANCED     = '\%(' . s:TERMINAL . '\)*'
    
    for n in range(5)
      let s:PARENTHESIZED   = '(' . s:BALANCED . ')'
      let s:BALANCED        = '\%(' . s:TERMINAL . '\|' . s:PARENTHESIZED . '\)*'
    endfor
    
    function! s:StripDbg(bang) range abort
      let confirm = a:bang == '!' ? '' : 'c'
      exe a:firstline.','.a:lastline.'s/\<dbg(\('.s:BALANCED.'\))/\1/g'.confirm
    endfunction

Unfortunately, it doesn't handle cases where you use gdb to log variables. I guess you'd need to write a refactoring plugin for clang-tidy & co in order to detect that the expression returned by gdb is used elsewhere.

I'm not sure whether the solution should not be to use macro at code level. Something like

template <typename T>
inline
decltype(auto) dbg(T && v) { // not sure about the best parameters and return type to use
#if ! defined(DEBUG)
    spdlog::debug("DGB: {}", v);
#endif
    return std::forward<T>(v);
}
| improve this answer | |
  • 1
    There are also cases when dbg is commented, in a string or it might be multilined. So clarification is still needed on what exactly OP wants. Also if you strip dbg("bla bla") then it would make cpp compiler unhappy. – Maxim Kim Jul 15 at 11:52
  • @MaximKim, you're right. I wanted to add a note about the multi-lines case as well but forgot about it. My solution doesn't handle this case. Regarding "bla bla";, C++ compiler will ignore it. At best we have a warning: warning: statement has no effect [-Wunused-value] – Luc Hermitte Jul 15 at 12:28
  • @LucHermitte Well, I am thankful for your in-depth answer, and the technical depth of your explanation and the effort you took in helping me out. My case is far too simple (atleast initially). I just need to strip out dbg so that the code become portable and works on any development environment that only has the standard library (after commenting out dbg.h). I have now updated the question to clarify what exactly I am looking for. Would you be happy to refine/revise your answer to fine-tune to my requirements? – Dr Krishnakumar Gopalakrishnan Jul 15 at 22:53
  • @Krishna, have you tried my solution? It exactly does what you're looking for (and a little bit more as it also supports side cases like dbg(f(x, g(y, z))) or dbg(x) + dbg(u+v) -- we need recursive regex for the first, and :s and not :g for the second . The only thing it cannot do: remove unused values and expressions. You'll have to use your linter or your compiler warning for this last part. – Luc Hermitte Jul 16 at 8:35
  • @LucHermitte I have tried your solution. I get an error E118: Too many arguments for function: <SNR>112_StripDbg, any thoughts? Also, this seems to not work with neovim? – Dr Krishnakumar Gopalakrishnan Jul 21 at 17:04
1
  1. You cannot have a command started with lowercase letter, use :Stripdbg or :StripDbg
  2. To define a command available only in specific filetype you can define it in autocommand event with -buffer parameter:
augroup cpp_group | au!
    au FileType cpp command! -buffer StripDbg call StripDbg()
augroup end
  1. You have to implement StripDbg() function to do actual stripping
  2. To add a range to your command add -range parameter and prepend <line1>,<line2> range to a function call:
augroup cpp_group | au!
    au FileType cpp command! -buffer -range StripDbg <line1>,<line2>call StripDbg()
augroup end
  1. You can use implicit a:firstline, a:lastline variables to go through the range and use help of vim-surround plugin (you have to install it).
func! StripDbg() range
    for lnr in range(a:firstline, a:lastline)
        if getline(lnr) =~ 'dbg\s*('
            call search('dbg\s*(', "W", line(lnr))
            " delete dbg leaving cursor on a (
            " here we use normal with ! to call default mappings
            normal! dw
            " use vim-surround mapping to remove ( )
            " note, here we use normal without !, to call mapping of vim-surround
            normal dsb
        endif
    endfor 
endfunc

The result of :%StripDbg is:

#include "dbg.h"
#include <vector>


int factorial(int n) {
  if (n <= 1) {           //  __stripped debug__
    return 1;             //  __stripped debug__
  } else {
    return n * factorial(n - 1);   //  __stripped debug__
  }
}

int main() {
  std::string message = "hello";
  message;  //  __stripped debug__

  const int a = 2;
  const int b = 3 * a + 1; //  __stripped debug__

  std::vector<int> numbers{b, 13, 42};
  numbers; //  __stripped debug__

  "this line is executed";  // __Ideally, this line must be deleted__ This might be hard.

  factorial(4);

  return 0;
}

NOTE, it doesn't take into account dbg("this line is executed") should be removed. But you can add this kind of checks to the function yourself. Also, if there are more then 1 dbg calls on the same line, only first one would be stripped.

UPDATE

Simpler solution using help of the :global command:

func! StripDbg()
    " place cursor on dbg
    call search('dbg\s*(', "W")
    " delete dbg with normal vim dw
    " here we use normal with ! to call default mappings
    normal! dw
    " use vim-surround mapping to remove ( )
    " note, here we use normal without !, to call mapping of vim-surround
    normal dsb
endfunc

" Search every line that has dbg( or dbg (  and exectute StripDbg() function on that line
command! -buffer -range StripDbg <line1>,<line2>g/dbg\s*(/call StripDbg()
| improve this answer | |
  • 1
    Ad 2. Why not autocmd filetype cpp ... ? – Jorengarenar Jul 14 at 20:26
  • Probably filetype cpp would be better, here – Maxim Kim Jul 14 at 20:32
  • @Maxim Kim Thanks a lot for your answer. Isn't the current question self-standing? Even if I make a separate question, I would have to repeat the full example as well as the majority of the wording. Can you please expand your answer to complete the StripDbg() function? – Dr Krishnakumar Gopalakrishnan Jul 15 at 9:29
  • @Krishna, I don't know what it should do. I can only assume but again it is not clear what you want to achieve. – Maxim Kim Jul 15 at 9:43
  • @MaximKim I have edited my question to describe the desired output after the strip operation. – Dr Krishnakumar Gopalakrishnan Jul 15 at 22:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.