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