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