vimlrs 0.2.1

Faithful Rust port of the Vimscript (VimL) interpreter, from the Neovim C eval engine
Documentation
" command_abbreviations.vim — every statement below is an Ex command a real
" .vimrc puts inside a :function body. Before these were recognized, an
" unrecognized one fell through to the expression parser, aborted the enclosing
" :function, and leaked its body (`a:`/`l:` refs) into global scope. Each test
" here proves the function DEFINES and its scoped variables resolve — i.e. the
" body was parsed as the function's, not leaked. Self-test: asserts into
" v:errors, throws at the end if anything failed.

" --- :execute accepts every abbreviation down to :exe (exec/execu/execut). The
"     command word before them (`let l:x`) must stay inside the function.
function! F_Exec(n) abort
  let l:x = a:n * 2
  exec 'let l:x = l:x + 1'
  execu 'let l:x = l:x + 10'
  return l:x
endfunction
call assert_equal(1, exists('*F_Exec'))
call assert_equal(15, F_Exec(2))

" --- block-keyword abbreviations: fun/endfun (=:function/:endfunction), wh/endw
"     (=:while/:endwhile). A body defined with them must close correctly.
fun! F_Block(limit)
  let l:sum = 0
  let l:i = 0
  wh l:i < a:limit
    let l:sum += l:i
    let l:i += 1
  endw
  return l:sum
endfun
call assert_equal(1, exists('*F_Block'))
call assert_equal(6, F_Block(4))

" --- :echohl {group} sets a highlight group (no-op editor-less); its argument is
"     a group NAME, not an expression. A body using it must still define.
function! F_Echohl(v) abort
  echohl ErrorMsg
  let l:r = a:v
  echohl None
  return l:r
endfunction
call assert_equal(1, exists('*F_Echohl'))
call assert_equal(7, F_Echohl(7))

" --- :normal, :! (shell), :redraw!, :redir, :mark, and a bare mark-address
"     (`'>`) are recognized editor commands; each is a no-op here but must parse.
function! F_Editor(v) abort
  let l:r = a:v
  normal! gg
  silent !true > /dev/null 2>&1
  redraw!
  mark z
  '>
  return l:r
endfunction
call assert_equal(1, exists('*F_Editor'))
call assert_equal(9, F_Editor(9))

" --- :echoerr (abbrev echoe/echoer) parses like :echomsg. In a never-taken
"     branch it must not abort the definition.
function! F_Echoerr(bad) abort
  let l:r = 3
  if a:bad
    echoerr 'should not run'
  endif
  return l:r
endfunction
call assert_equal(1, exists('*F_Echoerr'))
call assert_equal(3, F_Echoerr(0))

" --- bare :function / :let are listing commands (no parens / no `=`), not a
"     nested definition / a broken assignment. A body listing them still defines.
function! F_Listing(v) abort
  let l:r = a:v
  silent function
  silent let
  return l:r
endfunction
call assert_equal(1, exists('*F_Listing'))
call assert_equal(5, F_Listing(5))

" --- a:000 varargs must resolve inside a `...` function (regression: the body
"     leaked and a:000 escaped to global).
function! F_Varargs(...)
  return len(a:000)
endfunction
call assert_equal(3, F_Varargs('a', 'b', 'c'))
call assert_equal(0, F_Varargs())

" --- :autocmd absorbs a trailing `|` as part of its command; the splitter must
"     keep the line whole (else the `| let` breaks off as a stray statement and,
"     worse, the trailing word leaks). Registering it must not run the command.
let g:ac_ran = 0
autocmd User AbbrevTest call add([], 1) | let g:ac_ran = g:ac_ran
call assert_equal(1, exists('#User#AbbrevTest'))
call assert_equal(0, g:ac_ran)

" --- :source expands $VAR/~ in its path (Vim's do_source), like glob() does.
let $VIMLRS_SRCDIR = tempname()
call mkdir($VIMLRS_SRCDIR, 'p')
call writefile(['let g:sourced_ok = 123'], $VIMLRS_SRCDIR . '/inc.vim')
let g:sourced_ok = 0
execute 'source ' . $VIMLRS_SRCDIR . '/inc.vim'
call assert_equal(123, g:sourced_ok)
" the env var is expanded from an unquoted path too
let g:sourced_ok = 0
source $VIMLRS_SRCDIR/inc.vim
call assert_equal(123, g:sourced_ok)

if len(v:errors) > 0
  for err in v:errors
    echo err
  endfor
  throw 'command_abbreviations.vim: ' . len(v:errors) . ' assertion(s) failed'
endif
echo 'command_abbreviations.vim: all assertions passed'