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