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:
- Lexer + parser —
eval0througheval7are the precedence-climbing functions.eval0is the top level; each level handles one binary operator class. - Statements —
do_let,do_call,do_unlet, control-flow primitives (if,while,for,try/catch,function,return). - Built-in dispatch —
find_internal_funclooks upf_*in the table generated fromeval.lua. - 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;
:closuremodifier). - Funcrefs and partials (
function('Foo')returns afuncref_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:
- Implement in
runtime/lua/vim/_core/vimfn.lua. - Add the entry to
eval.luawithfunc_lua = true. - Skip
funcs.centirely.
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 bridge —
nlua_call_vimfninsrc/nvim/lua/executor.clets C call into Lua-implemented vimfns. The reverse, Lua → vimfn, goes throughvim.fn.<name>()whichnlua_calls. - API —
vim.api.<name>from Vimscript dispatches viaeval_call_providerandf_lua(eval-to-lua bridge). API calls from Lua bypass eval entirely. - Typval ↔ Object —
src/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.
- Channels —
chansend()andchanrecv()take typvals.
Entry points for modification
- Add a new built-in function. Prefer Lua per
dev_arch.txt. Add toeval.luawithfunc_lua = true, implement inruntime/lua/vim/_core/vimfn.lua. If you must do it in C, appendf_<name>tofuncs.c. - Add a new
v:variable. Editsrc/nvim/vvars.luaand 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), andsrc/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.