Help converting function to Vim9 script

182 views
Skip to first unread message

Lifepillar

unread,
Jul 4, 2021, 2:42:10 PM7/4/21
to vim...@googlegroups.com
The MetaPost plugin in Vim contains the following definition to replace
the n-th occurrence of `beginfig(...)` with `beginfig(n)`:

function! s:fix_beginfigs()
let i = 1
g/^beginfig(\d*);$/s//\='beginfig('.i.');'/ | let i = i + 1
endfunction

command -nargs=0 FixBeginfigs call s:fix_beginfigs()

On attempting to convert it to Vim9 script, I came up with this:

def FixBeginfig()
i = 1
g/^beginfig(\d*);$/s//\='beginfig(' .. i .. ');'/ | i += 1
enddef

command! -buffer -nargs=0 -bar FixBeginfigs call <sid>FixBeginfig()

But this results in 'Undefined variable: i', likely because the command
is not evaluated in Vim9 script context.

Any idea what would be the best way to convert the snippet above?

Thanks,
Life.


Bram Moolenaar

unread,
Jul 4, 2021, 5:30:16 PM7/4/21
to vim...@googlegroups.com, Lifepillar
Well, this works:

def Fix_beginfigs()
g:index = 1
g/^beginfig(\d*);$/s//\='beginfig(' .. g:index .. ');'/ | g:index = g:index + 1
unlet g:index
enddef

However, using a legacy function would not be worse. The global command
does most of the work, what function it's in doesn't really matter.

--
From "know your smileys":
(:-# Said something he shouldn't have

/// Bram Moolenaar -- Br...@Moolenaar.net -- http://www.Moolenaar.net \\\
/// \\\
\\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ ///
\\\ help me help AIDS victims -- http://ICCF-Holland.org ///

Steve Litt

unread,
Jul 4, 2021, 5:52:29 PM7/4/21
to vim...@googlegroups.com
Bram Moolenaar said on Sun, 04 Jul 2021 23:30:01 +0200


>Well, this works:
>
>def Fix_beginfigs()
> g:index = 1
> g/^beginfig(\d*);$/s//\='beginfig(' .. g:index .. ');'/ | g:index =
> g:index + 1 unlet g:index
>enddef
>
>However, using a legacy function would not be worse. The global
>command does most of the work, what function it's in doesn't really
>matter.

Hi Bram,

I try not to use global variables within functions. Is there an
alternative enabling non-use of global variables?

Also, I thought the new Vim9 Vimscript was getting rid of the need for
g: and l: and a: etc. That stuff used to kill my brain.

Thanks,

SteveT

Steve Litt
Spring 2021 featured book: Troubleshooting Techniques of the Successful
Technologist http://www.troubleshooters.com/techniques

Bram Moolenaar

unread,
Jul 4, 2021, 6:07:49 PM7/4/21
to vim...@googlegroups.com, Steve Litt

> >Well, this works:
> >
> >def Fix_beginfigs()
> > g:index = 1
> > g/^beginfig(\d*);$/s//\='beginfig(' .. g:index .. ');'/ | g:index =
> > g:index + 1 unlet g:index
> >enddef
> >
> >However, using a legacy function would not be worse. The global
> >command does most of the work, what function it's in doesn't really
> >matter.
>
> Hi Bram,
>
> I try not to use global variables within functions. Is there an
> alternative enabling non-use of global variables?
>
> Also, I thought the new Vim9 Vimscript was getting rid of the need for
> g: and l: and a: etc. That stuff used to kill my brain.

Right, using global variables is not the best way. The thing is that
with a compiled function the local variables are on the stack, which
makes them unreachable for expressions that are not compiled.

Over time we have added these expressions in various places, which will
be evaluated in whatever context they are used. For a legacy function
the context is available, with dictionaries containing the local
variables. With compiled functions we have no such dictionaries, since
these add a lot of overhead.

The basic thing is, once you are in a compiled function, keep using
compiled expressions. When you escape to commands not using compiled
expressions, you run into trouble.

--
From "know your smileys":
*<|:-) Santa Claus (Ho Ho Ho)

lgc

unread,
Jul 7, 2021, 1:35:02 PM7/7/21
to vim_use
> Any idea what would be the best way to convert the snippet above?

Move the `i` variable in the script-local namespace.  That is, don't declare it inside the function, but at the script-level.  You can still reset it to a default value whenever the function is invoked:

    vim9script
    var i: number

    def FixBeginfig()
        i = 1
        global /^beginfig(\d*);$/ substitute//\='beginfig(' .. i .. ');'/ | ++i
    enddef
    command -buffer -nargs=0 -bar FixBeginfigs FixBeginfig()

This works because – in a Vim9 script – all commands are executed in the Vim9 script context.  As a result, they have access to any Vim9 syntax, and to all items defined in the script-local namespace.

Whether they're executed from a global command, an autocmd, a user command, a simple `:execute`, ... it doesn't matter.

For this reason, in your user command definition, you don't need `:call` nor `<sid>`.  You don't need a bang after `:command` either since the patch v8.1.0573.

---

However, as mentioned earlier, there won't be a speed increase because the command is not compiled, just executed.  Like most (all?) commands executed by another command.  This is confirmed by the output of `:disassemble`:

    <SNR>1_FixBeginfig
        i = 1
       0 PUSHNR 1
       1 STORESCRIPT i-0 in /tmp/t.vim

        global /^beginfig(\d*);$/ substitute//\='beginfig(' .. i .. ');'/ | ++i
       2 EXEC     global /^beginfig(\d*);$/ substitute//\='beginfig(' .. i .. ');'/ | ++i
       3 RETURN void

Notice that the second generated instruction is a simple `EXEC`.

FWIW, I would still refactor the code in Vim9.  Mainly because of type checking, and for better readability.

lgc

unread,
Jul 7, 2021, 2:06:53 PM7/7/21
to vim_use
> I try not to use global variables within functions. Is there an
> alternative enabling non-use of global variables?

Yes, use block-local, function-local, or script-local variables.  Those don't need any explicit scope.


> Also, I thought the new Vim9 Vimscript was getting rid of the need for
> g: and l: and a: etc. That stuff used to kill my brain.

`l:` and `a:` are invalid now.  You don't need them anymore.

`s:` is still valid, but in practice, you never need it.

`v:` is no longer needed either in front of some common values: `true`, `false`, `null`.

As for `g:`, how else would you tell Vim that the variable is global and not local to the current script/function/block?  Same thing for buffer-local, window-local and tab-local variables.  `b:`, `w:`, `t:` might look weird, but we need a way to specify in which namespace such variable names should be looked for.  There is no equivalent in other programming languages, because there is no concept of "current buffer", "current window", "current tab page"... in them.  Anyway, in practice, you rarely need those scopes.  The vast majority of your variables are script-local, function-local, or block-local.  And for all of those, no explicit scope is required any longer.

Lifepillar

unread,
Jul 8, 2021, 9:21:17 AM7/8/21
to vim...@googlegroups.com
On 2021-07-07, lgc <jdupo...@gmail.com> wrote:
>> Any idea what would be the best way to convert the snippet above?
>
> Move the `i` variable in the script-local namespace.

Thanks for the thorough explanation. For now, I am keeping a function
instead of a def. But, as you say:

> FWIW, I would still refactor the code in Vim9. Mainly because of type
> checking, and for better readability.

I'll likely do that, using a script variable as you suggest.

Thanks,
Life.

Reply all
Reply to author
Forward
0 new messages