Skip to main content

opy_rs/hir/
types.rs

1//! Serde protocol types for the `wright/opy-hir` protocol, major version 2.
2//!
3//! These types mirror the Opy HIR v2 specification (`docs/hir/opy-hir-v2.md`,
4//! `wright/opy-hir` v2.0.0 wire payloads). Unknown
5//! fields on known nodes are tolerated so an additive producer change inside
6//! the same major version does not break the consumer; unknown node *kinds*
7//! are rejected during validation (see [`super::validate`]).
8
9use serde::{Deserialize, Serialize};
10
11/// The `wright/opy-hir` protocol name.
12pub const PROTOCOL_NAME: &str = "wright/opy-hir";
13/// The protocol major version this consumer understands.
14pub const PROTOCOL_MAJOR: u32 = 2;
15/// The protocol version emitted by this producer.
16pub const PROTOCOL_VERSION: &str = "2.0.0";
17
18/// The number of Workshop variable slots per variable set.
19///
20/// OverPy's `defaultVarNames` table covers exactly these slots: the 128
21/// uppercase letter spellings `A`–`Z`, `AA`–`AZ`, …, `DA`–`DX` (bijective
22/// base-26, Excel-style, zero-based). The pinned OverPy 9.7.10 reference
23/// accepts these names as *implicit* global variables anywhere a variable may
24/// appear — including as a `for ... in range(...)` loop binder — and as
25/// `eventPlayer.<name>` player variables, without declarations, assigning each
26/// namespace its fixed slot.
27/// Names outside the table (lowercase, mixed case, longer spellings) stay
28/// ordinary unresolved identifiers (see `docs/opy/support-matrix.md`).
29const DEFAULT_VAR_SLOTS: u32 = 128;
30
31/// The fixed Workshop slot for an OverPy default variable name (`A`–`Z`,
32/// `AA`–`AZ`, …, `DA`–`DX`), or `None` for any other spelling.
33///
34/// The index is the zero-based bijective base-26 value of the uppercase
35/// spelling (`A` = 0, `Z` = 25, `AA` = 26, `DX` = 127); spellings beyond the
36/// 128-slot table (`DY`, `EA`, …, three-letter names, lowercase) return
37/// `None`, matching the pinned reference's `defaultVarNames` table exactly.
38pub fn default_var_index(name: &str) -> Option<u32> {
39    if name.is_empty() || name.len() > 2 {
40        return None;
41    }
42    let mut value: u32 = 0;
43    for byte in name.bytes() {
44        if !byte.is_ascii_uppercase() {
45            return None;
46        }
47        value = value * 26 + u32::from(byte - b'A' + 1);
48    }
49    let index = value - 1;
50    (index < DEFAULT_VAR_SLOTS).then_some(index)
51}
52
53/// Protocol envelope identity.
54#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
55pub struct Protocol {
56    pub name: String,
57    pub version: String,
58}
59
60/// Producer identity.
61#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
62pub struct Generator {
63    pub name: String,
64    pub version: String,
65    pub frontend: String,
66}
67
68/// A source file in the protocol's file registry.
69#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
70pub struct SourceFile {
71    pub id: u32,
72    pub path: String,
73}
74
75/// A preprocessing define recorded for provenance.
76#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
77pub struct Define {
78    pub name: String,
79    #[serde(default)]
80    pub is_function: bool,
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub span: Option<Span>,
83}
84
85/// A 1-based, half-open source interval.
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
87pub struct Span {
88    pub file: u32,
89    pub start: Position,
90    pub end: Position,
91}
92
93/// A 1-based line/column position.
94#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
95pub struct Position {
96    pub line: u32,
97    pub col: u32,
98}
99
100/// A top-level program payload.
101#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
102pub struct Program {
103    pub protocol: Protocol,
104    pub generator: Generator,
105    pub files: Vec<SourceFile>,
106    #[serde(default)]
107    pub defines: Vec<Define>,
108    #[serde(default)]
109    pub declarations: Vec<Declaration>,
110    #[serde(default)]
111    pub rules: Vec<RuleEntry>,
112    /// The typed custom-game-settings block, when the source had one (#86).
113    #[serde(default, skip_serializing_if = "Option::is_none")]
114    pub settings: Option<Settings>,
115    /// Frontend preprocessing state. Workshop execution of optimizer,
116    /// translation, and replacement choices remains lowering-dependent.
117    #[serde(default)]
118    pub preprocessing: PreprocessingState,
119}
120
121/// The source-level preprocessing state observed by the frontend.
122#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
123pub struct PreprocessingState {
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub main_file: Option<DirectiveValue>,
126    pub allow_macro_redeclaration: bool,
127    #[serde(default)]
128    pub rule_prefix: Option<DirectiveValue>,
129    #[serde(default)]
130    pub rule_prefix_template: Option<DirectiveValue>,
131    #[serde(default)]
132    pub translations: Option<TranslationState>,
133    #[serde(default)]
134    pub optimization: OptimizationState,
135    #[serde(default)]
136    pub replacements: Vec<DirectiveValue>,
137    #[serde(default)]
138    pub suppressed_warnings: Vec<String>,
139    #[serde(default)]
140    pub directives: Vec<DirectiveRecord>,
141}
142
143/// A directive value plus its source provenance.
144#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
145pub struct DirectiveValue {
146    pub value: String,
147    #[serde(skip_serializing_if = "Option::is_none")]
148    pub span: Option<Span>,
149}
150
151/// Translation language selection. Locale/catalog data is intentionally not
152/// represented here.
153#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
154pub struct TranslationState {
155    pub languages: Vec<String>,
156    #[serde(skip_serializing_if = "Option::is_none")]
157    pub span: Option<Span>,
158}
159
160/// Frontend-visible optimization controls. The optimizer itself is outside
161/// this repository and remains lowering-dependent.
162#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
163pub struct OptimizationState {
164    pub enabled: bool,
165    pub for_size: bool,
166    pub for_size_aggressive: bool,
167    pub strict: bool,
168}
169
170impl Default for OptimizationState {
171    fn default() -> Self {
172        Self {
173            enabled: true,
174            for_size: false,
175            for_size_aggressive: false,
176            strict: false,
177        }
178    }
179}
180
181/// One preprocessing event, retained so block-scoped state transitions remain
182/// inspectable without executing a backend optimizer.
183#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
184pub struct DirectiveRecord {
185    pub name: String,
186    #[serde(skip_serializing_if = "Option::is_none")]
187    pub value: Option<String>,
188    pub scope_col: u32,
189    #[serde(default)]
190    pub scope_depth: u32,
191    #[serde(default)]
192    pub state: PreprocessingSnapshot,
193    #[serde(skip_serializing_if = "Option::is_none")]
194    pub span: Option<Span>,
195}
196
197#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
198pub struct PreprocessingSnapshot {
199    pub allow_macro_redeclaration: bool,
200    pub optimization: OptimizationState,
201    #[serde(default)]
202    pub rule_prefix: Option<String>,
203    #[serde(default)]
204    pub rule_prefix_template: Option<String>,
205    #[serde(default)]
206    pub translations: Option<Vec<String>>,
207    #[serde(default)]
208    pub replacements: Vec<String>,
209}
210
211/// A custom-game-settings block (`settings { ... }`, #86).
212#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
213pub struct Settings {
214    #[serde(skip_serializing_if = "Option::is_none")]
215    pub span: Option<Span>,
216    #[serde(default)]
217    pub children: Vec<SettingsNode>,
218}
219
220/// One member of a settings group.
221#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
222#[serde(tag = "kind", rename_all = "camelCase")]
223pub enum SettingsNode {
224    Group {
225        name: String,
226        #[serde(default)]
227        children: Vec<SettingsNode>,
228        #[serde(skip_serializing_if = "Option::is_none")]
229        span: Option<Span>,
230    },
231    Number {
232        name: String,
233        value: f64,
234        #[serde(skip_serializing_if = "Option::is_none")]
235        span: Option<Span>,
236    },
237    Bool {
238        name: String,
239        value: bool,
240        #[serde(skip_serializing_if = "Option::is_none")]
241        span: Option<Span>,
242    },
243    String {
244        name: String,
245        value: String,
246        #[serde(skip_serializing_if = "Option::is_none")]
247        span: Option<Span>,
248    },
249    List {
250        name: String,
251        #[serde(default)]
252        elements: Vec<SettingsListElement>,
253        #[serde(skip_serializing_if = "Option::is_none")]
254        span: Option<Span>,
255    },
256}
257
258impl SettingsNode {
259    /// The source span of this node, if any.
260    pub fn span(&self) -> Option<&Span> {
261        match self {
262            SettingsNode::Group { span, .. }
263            | SettingsNode::Number { span, .. }
264            | SettingsNode::Bool { span, .. }
265            | SettingsNode::String { span, .. }
266            | SettingsNode::List { span, .. } => span.as_ref(),
267        }
268    }
269}
270
271/// One element of a settings list (corpus lists are all strings).
272#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
273pub struct SettingsListElement {
274    pub value: String,
275    #[serde(skip_serializing_if = "Option::is_none")]
276    pub span: Option<Span>,
277}
278
279/// A program-scope symbol declaration.
280#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
281#[serde(tag = "kind", rename_all = "camelCase")]
282pub enum Declaration {
283    GlobalVariable {
284        name: String,
285        #[serde(default)]
286        index: Option<u32>,
287        #[serde(skip_serializing_if = "Option::is_none")]
288        span: Option<Span>,
289        /// The exact span of the declared identifier token.
290        #[serde(default, skip_serializing_if = "Option::is_none")]
291        name_span: Option<Span>,
292        #[serde(default)]
293        initializer: Option<Box<Expr>>,
294    },
295    PlayerVariable {
296        name: String,
297        #[serde(default)]
298        index: Option<u32>,
299        #[serde(skip_serializing_if = "Option::is_none")]
300        span: Option<Span>,
301        /// The exact span of the declared identifier token.
302        #[serde(default, skip_serializing_if = "Option::is_none")]
303        name_span: Option<Span>,
304        #[serde(default)]
305        initializer: Option<Box<Expr>>,
306    },
307    Subroutine {
308        name: String,
309        #[serde(default)]
310        index: Option<u32>,
311        #[serde(skip_serializing_if = "Option::is_none")]
312        span: Option<Span>,
313        /// The exact span of the declared identifier token.
314        #[serde(default, skip_serializing_if = "Option::is_none")]
315        name_span: Option<Span>,
316    },
317    Constant {
318        name: String,
319        #[serde(skip_serializing_if = "Option::is_none")]
320        span: Option<Span>,
321        value: Box<Expr>,
322    },
323    Macro {
324        name: String,
325        #[serde(default)]
326        args: Vec<String>,
327        #[serde(skip_serializing_if = "Option::is_none")]
328        span: Option<Span>,
329        #[serde(default)]
330        body: Vec<Stmt>,
331    },
332}
333
334/// An entry in `rules`: a rule or a subroutine definition.
335#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
336#[serde(untagged)]
337pub enum RuleEntry {
338    /// A rule: an object without a `kind` tag.
339    Rule(Rule),
340    /// A subroutine definition: `{ "kind": "subroutineDef", ... }`.
341    SubroutineDef {
342        #[serde(rename = "kind")]
343        kind: String,
344        name: String,
345        #[serde(default, skip_serializing_if = "String::is_empty")]
346        source_name: String,
347        #[serde(skip_serializing_if = "Option::is_none")]
348        span: Option<Span>,
349        /// The exact span of the defined identifier token in `def name():`.
350        #[serde(default, skip_serializing_if = "Option::is_none")]
351        name_span: Option<Span>,
352        #[serde(default)]
353        body: Vec<Stmt>,
354        #[serde(default)]
355        annotations: Vec<Annotation>,
356    },
357}
358
359/// A rule with its event, conditions, and actions.
360#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
361pub struct Rule {
362    pub name: String,
363    #[serde(skip_serializing_if = "Option::is_none")]
364    pub span: Option<Span>,
365    /// The exact span of the rule name inside its string literal.
366    #[serde(default, skip_serializing_if = "Option::is_none")]
367    pub name_span: Option<Span>,
368    #[serde(default)]
369    pub disabled: bool,
370    #[serde(default)]
371    pub delimiter: bool,
372    #[serde(default, skip_serializing_if = "Option::is_none")]
373    pub new_page: Option<String>,
374    #[serde(default)]
375    pub annotations: Vec<Annotation>,
376    pub event: Event,
377    #[serde(default)]
378    pub conditions: Vec<Expr>,
379    #[serde(default)]
380    pub actions: Vec<Stmt>,
381}
382
383/// A source annotation retained on a rule or subroutine definition.
384#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
385pub struct Annotation {
386    pub name: String,
387    #[serde(default)]
388    pub args: Vec<AnnotationArg>,
389    #[serde(skip_serializing_if = "Option::is_none")]
390    pub span: Option<Span>,
391}
392
393#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
394pub struct AnnotationArg {
395    pub text: String,
396    #[serde(skip_serializing_if = "Option::is_none")]
397    pub span: Option<Span>,
398}
399
400/// A rule event.
401#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
402pub struct Event {
403    pub name: String,
404    #[serde(default)]
405    pub args: Vec<Expr>,
406    #[serde(skip_serializing_if = "Option::is_none")]
407    pub span: Option<Span>,
408}
409
410/// A statement.
411#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
412#[serde(tag = "kind", rename_all = "camelCase")]
413pub enum Stmt {
414    Expr {
415        expr: Box<Expr>,
416        #[serde(skip_serializing_if = "Option::is_none")]
417        span: Option<Span>,
418    },
419    Assign {
420        target: Box<Expr>,
421        value: Box<Expr>,
422        #[serde(skip_serializing_if = "Option::is_none")]
423        span: Option<Span>,
424    },
425    If {
426        branches: Vec<IfBranch>,
427        #[serde(default)]
428        r#else: Option<Vec<Stmt>>,
429        #[serde(skip_serializing_if = "Option::is_none")]
430        span: Option<Span>,
431    },
432    For {
433        variable: Box<Expr>,
434        iterable: Box<Expr>,
435        #[serde(default)]
436        body: Vec<Stmt>,
437        #[serde(skip_serializing_if = "Option::is_none")]
438        span: Option<Span>,
439    },
440    While {
441        condition: Box<Expr>,
442        #[serde(default)]
443        body: Vec<Stmt>,
444        #[serde(skip_serializing_if = "Option::is_none")]
445        span: Option<Span>,
446    },
447    DoWhile {
448        condition: Box<Expr>,
449        #[serde(default)]
450        body: Vec<Stmt>,
451        #[serde(skip_serializing_if = "Option::is_none")]
452        span: Option<Span>,
453    },
454    Switch {
455        value: Box<Expr>,
456        #[serde(default)]
457        arms: Vec<SwitchArm>,
458        #[serde(skip_serializing_if = "Option::is_none")]
459        span: Option<Span>,
460    },
461    Break {
462        #[serde(skip_serializing_if = "Option::is_none")]
463        span: Option<Span>,
464    },
465    CallSubroutine {
466        name: String,
467        #[serde(skip_serializing_if = "Option::is_none")]
468        span: Option<Span>,
469    },
470    Pass {
471        #[serde(skip_serializing_if = "Option::is_none")]
472        span: Option<Span>,
473    },
474}
475
476/// One condition/body pair of an `if` statement.
477#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
478pub struct IfBranch {
479    pub condition: Box<Expr>,
480    #[serde(default)]
481    pub body: Vec<Stmt>,
482}
483
484impl Stmt {
485    /// The source span of this statement, if any.
486    pub fn span(&self) -> Option<&Span> {
487        match self {
488            Stmt::Expr { span, .. }
489            | Stmt::Assign { span, .. }
490            | Stmt::If { span, .. }
491            | Stmt::For { span, .. }
492            | Stmt::While { span, .. }
493            | Stmt::DoWhile { span, .. }
494            | Stmt::Switch { span, .. }
495            | Stmt::Break { span }
496            | Stmt::CallSubroutine { span, .. }
497            | Stmt::Pass { span } => span.as_ref(),
498        }
499    }
500}
501
502/// One source-ordered arm in the OPY HIR. Arms execute in source order and
503/// fall through to subsequent arms until a `break` statement is encountered.
504#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
505#[serde(tag = "kind", rename_all = "camelCase")]
506pub enum SwitchArm {
507    Case {
508        value: Box<Expr>,
509        #[serde(default)]
510        body: Vec<Stmt>,
511        #[serde(skip_serializing_if = "Option::is_none")]
512        span: Option<Span>,
513    },
514    Default {
515        #[serde(default)]
516        body: Vec<Stmt>,
517        #[serde(skip_serializing_if = "Option::is_none")]
518        span: Option<Span>,
519    },
520}
521
522/// An expression.
523#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
524#[serde(tag = "kind", rename_all = "camelCase")]
525pub enum Expr {
526    Number {
527        value: f64,
528        text: String,
529        #[serde(skip_serializing_if = "Option::is_none")]
530        span: Option<Span>,
531    },
532    String {
533        value: String,
534        #[serde(skip_serializing_if = "Option::is_none")]
535        span: Option<Span>,
536    },
537    Bool {
538        value: bool,
539        #[serde(skip_serializing_if = "Option::is_none")]
540        span: Option<Span>,
541    },
542    Null {
543        #[serde(skip_serializing_if = "Option::is_none")]
544        span: Option<Span>,
545    },
546    Array {
547        #[serde(default)]
548        elements: Vec<Expr>,
549        #[serde(skip_serializing_if = "Option::is_none")]
550        span: Option<Span>,
551    },
552    Dict {
553        #[serde(default)]
554        entries: Vec<DictEntry>,
555        #[serde(skip_serializing_if = "Option::is_none")]
556        span: Option<Span>,
557    },
558    Comprehension {
559        element: Box<Expr>,
560        variable: String,
561        #[serde(skip_serializing_if = "Option::is_none")]
562        variable_span: Option<Span>,
563        #[serde(default, skip_serializing_if = "Option::is_none")]
564        index: Option<String>,
565        #[serde(default, skip_serializing_if = "Option::is_none")]
566        index_span: Option<Span>,
567        iterable: Box<Expr>,
568        #[serde(default, skip_serializing_if = "Option::is_none")]
569        condition: Option<Box<Expr>>,
570        #[serde(skip_serializing_if = "Option::is_none")]
571        span: Option<Span>,
572    },
573    Lambda {
574        #[serde(default)]
575        params: Vec<String>,
576        #[serde(default)]
577        param_spans: Vec<Option<Span>>,
578        body: Box<Expr>,
579        #[serde(skip_serializing_if = "Option::is_none")]
580        span: Option<Span>,
581    },
582    StringModifier {
583        modifier: String,
584        value: String,
585        #[serde(skip_serializing_if = "Option::is_none")]
586        span: Option<Span>,
587    },
588    Local {
589        name: String,
590        #[serde(skip_serializing_if = "Option::is_none")]
591        span: Option<Span>,
592    },
593    Vector {
594        x: Box<Expr>,
595        y: Box<Expr>,
596        z: Box<Expr>,
597        #[serde(skip_serializing_if = "Option::is_none")]
598        span: Option<Span>,
599    },
600    Enum {
601        #[serde(rename = "type")]
602        value_type: String,
603        value: String,
604        #[serde(skip_serializing_if = "Option::is_none")]
605        span: Option<Span>,
606    },
607    GlobalVar {
608        name: String,
609        #[serde(skip_serializing_if = "Option::is_none")]
610        span: Option<Span>,
611    },
612    PlayerVar {
613        player: Box<Expr>,
614        name: String,
615        #[serde(skip_serializing_if = "Option::is_none")]
616        span: Option<Span>,
617    },
618    /// An OPY member expression whose canonical Workshop meaning is deferred
619    /// to the integration catalog. The receiver and source member identity
620    /// remain available to tooling and lowering.
621    Member {
622        receiver: Box<Expr>,
623        member: String,
624        #[serde(default, skip_serializing_if = "Option::is_none")]
625        member_span: Option<Span>,
626        #[serde(skip_serializing_if = "Option::is_none")]
627        span: Option<Span>,
628    },
629    EventPlayer {
630        #[serde(skip_serializing_if = "Option::is_none")]
631        span: Option<Span>,
632    },
633    Constant {
634        name: String,
635        #[serde(skip_serializing_if = "Option::is_none")]
636        span: Option<Span>,
637    },
638    Call {
639        name: String,
640        #[serde(default)]
641        args: Vec<Expr>,
642        #[serde(skip_serializing_if = "Option::is_none")]
643        span: Option<Span>,
644    },
645    ReceiverCall {
646        receiver: Box<Expr>,
647        name: String,
648        #[serde(default)]
649        args: Vec<Expr>,
650        #[serde(skip_serializing_if = "Option::is_none")]
651        span: Option<Span>,
652    },
653    MacroCall {
654        name: String,
655        #[serde(default)]
656        args: Vec<Expr>,
657        #[serde(skip_serializing_if = "Option::is_none")]
658        span: Option<Span>,
659    },
660    MacroParam {
661        name: String,
662        #[serde(skip_serializing_if = "Option::is_none")]
663        span: Option<Span>,
664    },
665    Binary {
666        op: String,
667        left: Box<Expr>,
668        right: Box<Expr>,
669        #[serde(skip_serializing_if = "Option::is_none")]
670        span: Option<Span>,
671    },
672    Unary {
673        op: String,
674        operand: Box<Expr>,
675        #[serde(skip_serializing_if = "Option::is_none")]
676        span: Option<Span>,
677    },
678    Index {
679        array: Box<Expr>,
680        index: Box<Expr>,
681        #[serde(skip_serializing_if = "Option::is_none")]
682        span: Option<Span>,
683    },
684    Format {
685        text: String,
686        #[serde(default)]
687        args: Vec<Expr>,
688        #[serde(skip_serializing_if = "Option::is_none")]
689        span: Option<Span>,
690    },
691}
692
693/// One key/value pair in an OPY dictionary.
694#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
695pub struct DictEntry {
696    pub key: Box<Expr>,
697    pub value: Box<Expr>,
698    #[serde(skip_serializing_if = "Option::is_none")]
699    pub span: Option<Span>,
700}
701
702impl Expr {
703    /// The source span of this expression, if any.
704    pub fn span(&self) -> Option<&Span> {
705        match self {
706            Expr::Number { span, .. }
707            | Expr::String { span, .. }
708            | Expr::Bool { span, .. }
709            | Expr::Null { span }
710            | Expr::Array { span, .. }
711            | Expr::Dict { span, .. }
712            | Expr::Comprehension { span, .. }
713            | Expr::Lambda { span, .. }
714            | Expr::StringModifier { span, .. }
715            | Expr::Local { span, .. }
716            | Expr::Vector { span, .. }
717            | Expr::Enum { span, .. }
718            | Expr::GlobalVar { span, .. }
719            | Expr::PlayerVar { span, .. }
720            | Expr::Member { span, .. }
721            | Expr::EventPlayer { span }
722            | Expr::Constant { span, .. }
723            | Expr::Call { span, .. }
724            | Expr::ReceiverCall { span, .. }
725            | Expr::MacroCall { span, .. }
726            | Expr::MacroParam { span, .. }
727            | Expr::Binary { span, .. }
728            | Expr::Unary { span, .. }
729            | Expr::Index { span, .. }
730            | Expr::Format { span, .. } => span.as_ref(),
731        }
732    }
733
734    /// The protocol `kind` of this expression.
735    pub fn kind_name(&self) -> &'static str {
736        match self {
737            Expr::Number { .. } => "number",
738            Expr::String { .. } => "string",
739            Expr::Bool { .. } => "bool",
740            Expr::Null { .. } => "null",
741            Expr::Array { .. } => "array",
742            Expr::Dict { .. } => "dict",
743            Expr::Comprehension { .. } => "comprehension",
744            Expr::Lambda { .. } => "lambda",
745            Expr::StringModifier { .. } => "stringModifier",
746            Expr::Local { .. } => "local",
747            Expr::Vector { .. } => "vector",
748            Expr::Enum { .. } => "enum",
749            Expr::GlobalVar { .. } => "globalVar",
750            Expr::PlayerVar { .. } => "playerVar",
751            Expr::Member { .. } => "member",
752            Expr::EventPlayer { .. } => "eventPlayer",
753            Expr::Constant { .. } => "constant",
754            Expr::Call { .. } => "call",
755            Expr::ReceiverCall { .. } => "receiverCall",
756            Expr::MacroCall { .. } => "macroCall",
757            Expr::MacroParam { .. } => "macroParam",
758            Expr::Binary { .. } => "binary",
759            Expr::Unary { .. } => "unary",
760            Expr::Index { .. } => "index",
761            Expr::Format { .. } => "format",
762        }
763    }
764}