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