Search code examples
textvimautomationeditor

How to automate the modification of code?


I have a large piece of code consisting of thousands of equations. Example of two connective lines, corresponding to lines 998 and 999 is

sum(x[i]*y[i]for i in 1:n) == 15;
sum(z[i]*x[i]for i in 1:n) == 30;

I would like to replace such lines with the following

sum(x[i]*y[i]for i in 1:n) - 15 >= -s[998];
sum(x[i]*y[i]for i in 1:n) - 15 <=  s[998];

sum(z[i]*x[i]for i in 1:n) - 30 >= -s[999];
sum(z[i]*x[i]for i in 1:n) - 30 <=  s[999];

How to automate this process?


Solution

  • Substitutions

    Our first step is to transform each individual line:

    sum(x[i]*y[i]for i in 1:n) == 15;
    

    into this:

    sum(x[i]*y[i]for i in 1:n) - 15 >= -s[998];
    

    While doing it in one command would be a nice parlor trick, we will do it in several easier to follow steps.

    1. == to -

      Here is our first command, it is very simple:

      :%s/==/-<CR>
      
      • : enters command-line mode.
      • % is the range of lines on which to execute the following command. Here, % is a shortcut for 1,$ (from line 1 to last line), thus "every line".
      • s is the "substitute" command, see :help :s.
      • /== is what you want to substitute.
      • /- is what you want to substitute it with.
      • Press <CR> (ENTER) to execute the command.

      In plain english: "substitute every == with -".

      We should get something like:

      sum(x[i]*y[i]for i in 1:n) - 15;
      sum(z[i]*x[i]for i in 1:n) - 30;
      
    2. ; to >= -s[XXX];

      Here is our second command:

      :%s/;/ >= -s[XXX];
      

      Where we substitute every ; with a generic >= -s[XXX];. This is also quite simple.

      In plain english: "substitute every ; with >= -s[XXX];.

      We should get something like:

      sum(x[i]*y[i]for i in 1:n) - 15 >= -s[XXX];
      sum(z[i]*x[i]for i in 1:n) - 30 >= -s[XXX];
      
    3. XXX to line number

      Here is our third command:

      :%s/XXX/\=line('.')
      

      The big change between this command and the other ones is that the replacement part is dynamic. With \=, we use an expression that is evaluated during each execution instead of a fixed string. line('.') is a vimscript function that returns a line number, which is exactly what we want between those brackets.

      In plain english: "substitute every XXX with the current line number".

      We should get something like:

      sum(x[i]*y[i]for i in 1:n) - 15 >= -s[998];
      sum(z[i]*x[i]for i in 1:n) - 30 >= -s[999];
      

    Duplication

    Here we duplicate each line with a single command:

    :g/^/t.|s/-s/ s
    
    • :g is the :help :global command.
    • /^/ matches every line so the following command will be executed on every line.
    • t is the :help :t command.
    • . represents the current line.
    • | separates two commands.
    • s/-s/ s removes the - before the s on the duplicated line.

    In plain english: "mark every line, then copy each marked line below itself, then remove that leading - before the s".

    We should get something like:

    sum(x[i]*y[i]for i in 1:n) - 15 >= -s[998];
    sum(x[i]*y[i]for i in 1:n) - 15 >=  s[998];
    sum(z[i]*x[i]for i in 1:n) - 30 >= -s[999];
    sum(z[i]*x[i]for i in 1:n) - 30 >=  s[999];
    

    One last thing

    We use one last command to add a line between our blocks:

    :g/ s[/put=''
    
    • Every line matching s[ is marked.
    • We use :help :put to append an empty line.

    In plain english: "put an empty line after each line with s[".

    We should get something like:

    sum(x[i]*y[i]for i in 1:n) - 15 >= -s[998];
    sum(x[i]*y[i]for i in 1:n) - 15 >=  s[998];
    
    sum(z[i]*x[i]for i in 1:n) - 30 >= -s[999];
    sum(z[i]*x[i]for i in 1:n) - 30 >=  s[999];
    

    What did we learn?

    • how to perform simple substitutions with :help :substitute,
    • the notion of :help :range,
    • how to use an expression in the replacement part (the usual gateway drug to vimscript) with :help sub-replace-\=,
    • how to execute arbitrary commands on lines matching a specific pattern with :help :global,
    • how to copy a given line to a given address with :help :t,
    • how to put some text, here an empty string, below the current line with :help :put,
    • how to separate commands with :help :|.