Skip to main content

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}