Open-Source Wikis

/

Neovim

/

Systems

/

Vimscript / eval

neovim/neovim

Vimscript / eval

Purpose

Vimscript (also called "VimL" or just "eval") is Vim's original scripting language. Neovim still supports the full language but treats it as a legacy path: new functionality should be in Lua, and existing Vimscript built-ins are increasingly thin wrappers that delegate to Lua. The interpreter, the value type, and the function dispatch all live in src/nvim/eval/.

Directory layout

src/nvim/
├── eval.c (~194k bytes)       Top-level: parser, statements, control flow
├── eval.h, eval_defs.h
├── eval.lua (~510k bytes)     Metadata for every built-in function
├── eval/
│   ├── typval.c (~135k bytes)        The typval_T variant + ops
│   ├── typval_defs.h, typval.h
│   ├── typval_encode.c.h, .h         Generic encoder template
│   ├── funcs.c (~216k bytes)         Built-in function implementations
│   ├── userfunc.c (~124k bytes)      :function user-defined functions
│   ├── vars.c (~110k bytes)          Variable storage (local/script/global)
│   ├── window.c (~28k bytes)         Window-related vimfns
│   ├── executor.c                    Run a script file
│   ├── decode.c, encode.c (~38/34k)  msgpack codec for typvals
│   ├── fs.c (~53k bytes)             readfile, writefile, glob, etc.
│   ├── buffer.c (~26k bytes)         Buffer-related vimfns
│   ├── list.c (~24k bytes)           Lists
│   ├── deprecated.c
│   └── gc.c                          Garbage-collection roots
└── viml/                             VimL grammar (parser experiment)

Key abstractions

Type / function File Description
typval_T eval/typval_defs.h The variant. Tagged union of: number, float, string, list, dict, blob, funcref, partial.
list_T, dict_T eval/typval_defs.h Reference-counted compound types.
eval_to_* eval.c Evaluate an expression to a typval_T.
call_func eval.c Invoke any callable (funcref_T, builtin, partial).
f_* eval/funcs.c C implementations of built-in functions (f_strlen, f_searchpos, f_chansend, ...).
do_lua etc. (eval.c → lua) Vimscript → Lua interop helpers.

Typvals

Vimscript values are dynamically typed. typval_T is the runtime representation:

typedef struct {
    VarType v_type;     // VAR_NUMBER, VAR_FLOAT, VAR_STRING, VAR_LIST, ...
    char    v_lock;
    union {
        varnumber_T v_number;
        float_T     v_float;
        char       *v_string;
        list_T     *v_list;
        dict_T     *v_dict;
        partial_T  *v_partial;
        blob_T     *v_blob;
    } vval;
} typval_T;

typval.c (135 KB) contains the operations: copy, compare, free, deepcopy, equality, hash, conversion. typval_encode.c.h is a parametrized C file (note the .c.h extension — it's included after defining macros that customize the encoder) used to generate msgpack encoders, JSON encoders, and the API converter.

eval.c

This is the parser/interpreter for the language. It's organized roughly:

  1. Lexer + parsereval0 through eval7 are the precedence-climbing functions. eval0 is the top level; each level handles one binary operator class.
  2. Statementsdo_let, do_call, do_unlet, control-flow primitives (if, while, for, try/catch, function, return).
  3. Built-in dispatchfind_internal_func looks up f_* in the table generated from eval.lua.
  4. User functions — see userfunc.c.

The grammar is permissive and quirky. Most contributors don't change eval.c directly; bug fixes and Vim-patches are routine but new language constructs are essentially never added.

eval.lua

src/nvim/eval.lua is the metadata table for every built-in function:

funcs.searchpos = {
    args = {1, 5},
    base = 1,
    name = 'searchpos',
    params = {{'pattern', 'string'}, {'flags', 'string'}, ...},
    returns = 'integer[]',
    desc = [[Same as |search()|, but returns ...]],
}

The generator emits the dispatch table and the doc text. New built-in functions are registered here even if implemented in C (f_*) or Lua (func_lua = true). Lua-implemented vimfns live in runtime/lua/vim/_core/vimfn.lua.

userfunc.c

:function and :def (Vim9script-style — not yet supported; only Vimscript-1) user functions live here. The data structure is ufunc_T. The implementation handles:

  • Argument default values (function Foo(x = 1)).
  • Variadic args (...).
  • Closures (lexical scope capture; :closure modifier).
  • Funcrefs and partials (function('Foo') returns a funcref_T).
  • Recursion limits and the call stack.

Variables

src/nvim/eval/vars.c (~110 KB) handles every variable scope: g: global, s: script, l: function-local, b: buffer, w: window, t: tabpage, v: Vim-internal, a: function args. Each scope has its own dict (or virtual dict for a: which is dynamically generated). The dispatch in vars.c decides which dict to read/write based on the g:/b:/t:/etc. prefix.

v: variables are special — they are defined in src/nvim/vvars.lua and processed by src/gen/gen_vimvim.lua to produce the C declarations.

funcs.c

216 KB, the largest single file in eval/. It's an alphabetic list of f_* functions, one per built-in. Each is short — argument handling, the actual computation, set the result. New functions appended here are registered in eval.lua.

For functions that should be Lua-first, the convention from runtime/doc/dev_arch.txt is:

  1. Implement in runtime/lua/vim/_core/vimfn.lua.
  2. Add the entry to eval.lua with func_lua = true.
  3. Skip funcs.c entirely.

Vimscript callers go through the bridge; Lua callers (vim.fn.foo()) take a fast path that calls the Lua function directly without typval conversion (nlua_call).

msgpack codec

encode.c and decode.c implement Vimscript's msgpackdump, msgpackparse, and friends. The encoder uses typval_encode.c.h parametrized over output method; the decoder builds typvals from msgpack input. The shada subsystem also uses these.

Integration points

  • Lua bridgenlua_call_vimfn in src/nvim/lua/executor.c lets C call into Lua-implemented vimfns. The reverse, Lua → vimfn, goes through vim.fn.<name>() which nlua_calls.
  • APIvim.api.<name> from Vimscript dispatches via eval_call_provider and f_lua (eval-to-lua bridge). API calls from Lua bypass eval entirely.
  • Typval ↔ Objectsrc/nvim/eval/typval.c#object_to_vim / vim_to_object. The conversion is asymmetric — typvals carry locks and references that Object doesn't.
  • Shada — register/mark/jump persistence uses the encoder.
  • Channelschansend() and chanrecv() take typvals.

Entry points for modification

  • Add a new built-in function. Prefer Lua per dev_arch.txt. Add to eval.lua with func_lua = true, implement in runtime/lua/vim/_core/vimfn.lua. If you must do it in C, append f_<name> to funcs.c.
  • Add a new v: variable. Edit src/nvim/vvars.lua and rebuild. The Lua data file is the source of truth.
  • Touch the language itself. That's eval.c. Be prepared to update Vim-patches that touch the same area, since most parser changes are mirrored from upstream Vim.
  • Improve typval ↔ Lua/Object conversion. Look at src/nvim/eval/typval_encode.c.h (template), src/nvim/lua/converter.c (Lua), and src/nvim/eval/typval.c#object_to_vim (API). All three share invariants.

Key source files

File Purpose
src/nvim/eval.c Parser/interpreter
src/nvim/eval.lua Built-in function metadata (generates dispatch + docs)
src/nvim/eval/typval.c The variant value: ops, copy, compare
src/nvim/eval/typval_defs.h typval_T, list_T, dict_T, partial_T
src/nvim/eval/funcs.c f_* built-in implementations
src/nvim/eval/userfunc.c :function, closures, funcrefs
src/nvim/eval/vars.c Scope resolution and storage
src/nvim/eval/encode.c, decode.c msgpack codec
src/nvim/vvars.lua v: variables
runtime/lua/vim/_core/vimfn.lua Lua-implemented vimfns

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Vimscript / eval – Neovim wiki | Factory