vimlrs/viml_ast.rs
1//! ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2//! EXTENSION — NO `vendor/` COUNTERPART. Neovim's `eval.c` parses and evaluates
3//! in one pass over the source string; there is no AST. This tree is net-new,
4//! its shape dictated by the `eval1`…`eval7` precedence ladder so the compiler
5//! can lower it to fusevm bytecode.
6//! ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
7
8use crate::viml_lexer::{CaseFlag, CmpOp};
9
10/// A Vimscript expression node.
11#[derive(Debug, Clone)]
12pub enum Expr {
13 /// Integer literal.
14 Number(i64),
15 /// Float literal.
16 Float(f64),
17 /// String literal (already unescaped).
18 Str(String),
19 /// Interpolated string `$'…{expr}…'` / `$"…{expr}…"` — each segment (literal
20 /// chunk or embedded expression) is echo-stringified and the results are
21 /// concatenated left to right, always yielding a String.
22 Interp(Vec<Expr>),
23 /// List literal `[a, b, …]`.
24 List(Vec<Expr>),
25 /// Lambda `{args -> body}` — desugars to an anonymous function returning
26 /// `body`. (No closure capture of the enclosing scope yet.)
27 Lambda {
28 /// Parameter names (without `a:`).
29 params: Vec<String>,
30 /// The single body expression.
31 body: Box<Expr>,
32 },
33 /// Dict literal `{k: v, …}`.
34 Dict(Vec<(Expr, Expr)>),
35 /// Variable reference (possibly scoped).
36 Var(String),
37 /// Option reference `&name`.
38 Option(String),
39 /// Environment variable `$NAME`.
40 Env(String),
41 /// Register `@x`.
42 Register(char),
43
44 /// Unary leader: `!`, `-`, `+` (`eval7_leader`).
45 Unary {
46 /// Operator.
47 op: UnaryOp,
48 /// Operand.
49 expr: Box<Expr>,
50 },
51 /// Arithmetic / concatenation (`eval5`/`eval6`).
52 Arith {
53 /// Operator.
54 op: ArithOp,
55 /// Left operand.
56 lhs: Box<Expr>,
57 /// Right operand.
58 rhs: Box<Expr>,
59 },
60 /// Comparison (`eval4`) — carries the case flag and `is`/`isnot`.
61 Compare {
62 /// Relational operator.
63 op: CmpOp,
64 /// Case-sensitivity suffix.
65 case: CaseFlag,
66 /// Left operand.
67 lhs: Box<Expr>,
68 /// Right operand.
69 rhs: Box<Expr>,
70 },
71 /// Logical AND `&&` (`eval3`) — short-circuits, yields 0/1.
72 And(Box<Expr>, Box<Expr>),
73 /// Logical OR `||` (`eval2`) — short-circuits, yields 0/1.
74 Or(Box<Expr>, Box<Expr>),
75 /// Ternary `cond ? a : b` (`eval1`).
76 Ternary {
77 /// Condition.
78 cond: Box<Expr>,
79 /// Truthy value.
80 then: Box<Expr>,
81 /// Falsy value.
82 otherwise: Box<Expr>,
83 },
84 /// Falsy-coalesce `lhs ?? rhs` (`eval1`).
85 Coalesce(Box<Expr>, Box<Expr>),
86
87 /// Subscript `base[index]`.
88 Index {
89 /// Indexed value.
90 base: Box<Expr>,
91 /// Index expression.
92 index: Box<Expr>,
93 },
94 /// Slice `base[from:to]`.
95 Slice {
96 /// Sliced value.
97 base: Box<Expr>,
98 /// Lower bound, or None.
99 from: Option<Box<Expr>>,
100 /// Upper bound, or None.
101 to: Option<Box<Expr>>,
102 },
103 /// Dict member `base.key`.
104 Member {
105 /// Dict value.
106 base: Box<Expr>,
107 /// Literal key.
108 key: String,
109 },
110 /// Function call `name(args)`.
111 Call {
112 /// Function name.
113 name: String,
114 /// Argument expressions.
115 args: Vec<Expr>,
116 },
117 /// Direct call of a funcref-valued expression: `expr(args)` (e.g.
118 /// `function('toupper')('hi')` or `(F)(x)`).
119 CallExpr {
120 /// Expression evaluating to a Funcref/Partial.
121 callee: Box<Expr>,
122 /// Argument expressions.
123 args: Vec<Expr>,
124 },
125 /// Method call `base->name(args)`.
126 Method {
127 /// Receiver (first argument).
128 base: Box<Expr>,
129 /// Method name.
130 name: String,
131 /// Remaining arguments.
132 args: Vec<Expr>,
133 },
134}
135
136/// Unary leader operators (`eval7_leader`).
137#[derive(Debug, Clone, Copy, PartialEq, Eq)]
138pub enum UnaryOp {
139 /// `-` numeric negation.
140 Neg,
141 /// `+` numeric coercion.
142 Plus,
143 /// `!` logical not.
144 Not,
145}
146
147/// Arithmetic and concatenation operators (`eval5`/`eval6`).
148#[derive(Debug, Clone, Copy, PartialEq, Eq)]
149pub enum ArithOp {
150 /// `+`
151 Add,
152 /// `-`
153 Sub,
154 /// `*`
155 Mul,
156 /// `/`
157 Div,
158 /// `%`
159 Mod,
160 /// `.` / `..`
161 Concat,
162}
163
164/// Assignment target for `:let`.
165#[derive(Debug, Clone)]
166pub enum LetTarget {
167 /// `let x = …` / `let g:x = …`.
168 Var(String),
169 /// `let &opt = …`.
170 Option(String),
171 /// `let $ENV = …`.
172 Env(String),
173 /// `let @x = …`.
174 Register(char),
175 /// `let base[index] = …` / `let base.key = …` — index/member assignment.
176 /// `base` is the container expression (so nesting like `d['a']['b']` works).
177 Index {
178 /// The container expression.
179 base: Box<Expr>,
180 /// The index/key expression.
181 index: Box<Expr>,
182 },
183 /// `let [a, b] = list` / `let [a, b; rest] = list` — list-unpack.
184 List {
185 /// Leading target names.
186 names: Vec<String>,
187 /// Trailing `; rest` name, if present (gets the remaining items).
188 rest: Option<String>,
189 },
190 /// `let base[idx1:idx2] = list` — list range assignment. Omitted `idx1`
191 /// defaults to 0; omitted `idx2` means "to the end".
192 Range {
193 /// The container expression.
194 base: Box<Expr>,
195 /// The first index (`None` → from the start).
196 idx1: Option<Box<Expr>>,
197 /// The last index (`None` → to the end).
198 idx2: Option<Box<Expr>>,
199 },
200}
201
202/// A single `:unlet` argument: either a bare variable name or a List/Dict
203/// element target. Mirrors the two non-name branches of `do_unlet_var()`
204/// (`vendor/eval/vars.c`).
205#[derive(Debug, Clone)]
206pub enum UnletArg {
207 /// `unlet x` / `unlet g:x` / `unlet $ENV` — remove a variable by name.
208 Name(String),
209 /// `unlet l[i]` / `unlet d.key` / `unlet d['key']` — remove one List item
210 /// or Dict entry. `base` is the container expression, `index` the key/index.
211 Item {
212 /// The container expression.
213 base: Box<Expr>,
214 /// The index/key expression.
215 index: Box<Expr>,
216 },
217}
218
219/// `:for` loop variable: a single name, or a `[a, b]` unpack of each item.
220#[derive(Debug, Clone)]
221pub enum ForVars {
222 /// `:for x in …`.
223 One(String),
224 /// `:for [a, b] in …` — each item is unpacked into these names.
225 List(Vec<String>),
226}
227
228/// A Vimscript statement (one ex-command's worth of work).
229#[derive(Debug, Clone)]
230pub enum Stmt {
231 /// `:echo expr …`.
232 Echo(Vec<Expr>),
233 /// `:echon expr …`.
234 Echon(Vec<Expr>),
235 /// `:let target = expr`.
236 Let {
237 /// Assignment target.
238 target: LetTarget,
239 /// Value expression.
240 expr: Expr,
241 },
242 /// `:call funcref(args)`.
243 Call(Expr),
244 /// A bare expression (REPL / `-e`).
245 Expr(Expr),
246
247 /// `:if … :elseif … :else … :endif`. Each arm is `(condition, body)`; the
248 /// optional trailing `else` body has no condition.
249 If {
250 /// `if` / `elseif` arms in source order.
251 arms: Vec<(Expr, Vec<Stmt>)>,
252 /// `else` body, if present.
253 else_body: Option<Vec<Stmt>>,
254 },
255 /// `:while {cond} … :endwhile`.
256 While {
257 /// Loop condition.
258 cond: Expr,
259 /// Loop body.
260 body: Vec<Stmt>,
261 },
262 /// `:for {var} in {expr} … :endfor` (list iteration).
263 For {
264 /// Loop variable(s) — a single name or a `[a, b]` unpack.
265 vars: ForVars,
266 /// Iterable expression (a List in Phase 3 of this port).
267 iter: Expr,
268 /// Loop body.
269 body: Vec<Stmt>,
270 },
271 /// `:break`.
272 Break,
273 /// `:continue`.
274 Continue,
275 /// `:finish` — stop sourcing the rest of the current script/file.
276 Finish,
277 /// `:return [expr]`.
278 Return(Option<Expr>),
279 /// `:function {name}(args) … :endfunction`.
280 Function {
281 /// Function name (may be scoped / `s:` / autoload).
282 name: String,
283 /// Parameter names (without the `a:` prefix).
284 args: Vec<String>,
285 /// Default values for optional parameters: `(param index, default expr)`,
286 /// e.g. `func F(a, b = 10)` records `(1, Num(10))`. Evaluated at call time
287 /// when the argument is omitted (`:help optional-function-argument`).
288 defaults: Vec<(usize, Expr)>,
289 /// Function body.
290 body: Vec<Stmt>,
291 /// `function!` — replace an existing definition.
292 bang: bool,
293 /// `true` for a vim9 `:def` (bare names in the body resolve to
294 /// script-scope vars/functions), `false` for a legacy `:function`.
295 vim9: bool,
296 },
297 /// `:try … :catch {pat} … :finally … :endtry`.
298 Try {
299 /// Protected body.
300 body: Vec<Stmt>,
301 /// `catch` clauses: `(optional /pattern/, body)`.
302 catches: Vec<(Option<String>, Vec<Stmt>)>,
303 /// `finally` body, always run.
304 finally: Option<Vec<Stmt>>,
305 },
306 /// `:throw {expr}`.
307 Throw(Expr),
308 /// `:execute expr …` — concatenate the values (space-separated) and run the
309 /// result as an ex command line.
310 Execute(Vec<Expr>),
311 /// `:set {args}` — set options (the raw argument text).
312 Set(String),
313 /// `:source {file}` — read and run another `.vim` file in the current scope
314 /// (its functions and globals persist). The raw (unquoted) filename.
315 Source(String),
316 /// `:unlet[!] {name}…` — delete one or more variables, list items, or dict
317 /// entries. Each argument is either a bare name or a List/Dict element
318 /// target (`l[i]` / `d.key`); see [`UnletArg`].
319 Unlet(Vec<UnletArg>),
320 /// A `:map`-family command (`nmap`, `inoremap`, `vunmap`, `mapclear`, …):
321 /// the whole raw command line, re-parsed by the mapping runtime.
322 Map(String),
323 /// `:command[!] [-attrs] Name {repl}` — define a user command (raw args).
324 CommandDef(String),
325 /// `:delcommand {name}` — delete a user command.
326 CommandDel(String),
327 /// `:delfunction[!] {name}` — remove a user function from the registry.
328 /// The raw argument (optional leading `!`, then the name) is resolved at
329 /// run time, mirroring how `:call`/`exists('*…')` key the function table.
330 DelFunction(String),
331 /// Invocation of a user command (`:Name args`): the whole raw line,
332 /// resolved against the user-command table at run time.
333 UserCmd(String),
334 /// `:autocmd[!] {event} {pat} {cmd}` — register an autocommand (raw args).
335 Autocmd(String),
336 /// `:augroup {name}` / `:augroup END` — set the active autocommand group.
337 Augroup(String),
338 /// `:doautocmd {event} [{pat}]` — fire matching autocommands.
339 Doautocmd(String),
340 /// A `:`-prefixed or `%`-prefixed Ex command line with an optional line
341 /// range (`:%s/…`, `:1,3d`, `%g/…/d`): the whole raw line, parsed and run
342 /// against the current buffer at run time.
343 ExCmd(String),
344 /// `:colorscheme {name}` (`:colo`) — select a color scheme. Sources the
345 /// matching `colors/{name}.vim` from the runtime path (firing its
346 /// `:highlight` commands) and records `g:colors_name`. The raw name; empty
347 /// for the bare `:colorscheme` query.
348 Colorscheme(String),
349 /// `:highlight [default] {group} {key}={val}…` (`:hi`) — define a highlight
350 /// group. The raw argument text; parsed at run time into the highlight
351 /// registry and mirrored to an embedding editor via the highlight host hook.
352 Highlight(String),
353 /// `:syntax …` (`:syn`) — syntax-highlighting control. Recognized so real
354 /// vimrc files parse; the raw argument text is forwarded to an optional host
355 /// hook (an embedding editor may enable its own highlighter) and is
356 /// otherwise a no-op standalone.
357 Syntax(String),
358 /// `:filetype …` (`:filet`) — filetype-detection control. Recognized so real
359 /// vimrc files parse; forwarded to an optional host hook and otherwise a
360 /// no-op standalone.
361 Filetype(String),
362}