Skip to main content

opy_rs/
cst.rs

1//! The frontend's concrete syntax tree (CST).
2//!
3//! Source-preserving syntax structure with spans on every node, produced by
4//! [`crate::parser`] and consumed by [`crate::lower`] (and, in later
5//! milestones, language services). Nodes are deliberately close to the Opy
6//! HIR contract so lowering stays a small, reviewable mapping; unresolved
7//! names and member accesses remain explicit until semantic resolution.
8
9use crate::diag::Span;
10
11/// A parsed program: declarations and rule/subroutine entries.
12#[derive(Debug, Clone)]
13pub struct Program {
14    pub declarations: Vec<Decl>,
15    pub rules: Vec<RuleEntry>,
16    /// All top-level forms in source order. The category-specific vectors are
17    /// retained for the HIR-shaped parser API, while lowering uses this list
18    /// for scope and visibility decisions.
19    pub top_level: Vec<TopLevel>,
20    /// The parsed top-of-file `settings { ... }` block, when present (#86).
21    pub settings: Option<Settings>,
22}
23
24/// A top-level declaration or rule entry in source order.
25#[derive(Debug, Clone)]
26pub enum TopLevel {
27    Declaration(Decl),
28    Rule(RuleEntry),
29}
30
31/// A parsed `settings { ... }` block (JSONC, #86).
32#[derive(Debug, Clone)]
33pub struct Settings {
34    pub span: Span,
35    pub children: Vec<SettingsNode>,
36}
37
38/// One member of a settings group.
39#[derive(Debug, Clone)]
40pub enum SettingsNode {
41    Group {
42        name: String,
43        children: Vec<SettingsNode>,
44        span: Span,
45    },
46    Number {
47        name: String,
48        value: f64,
49        span: Span,
50    },
51    Bool {
52        name: String,
53        value: bool,
54        span: Span,
55    },
56    String {
57        name: String,
58        value: String,
59        span: Span,
60    },
61    List {
62        name: String,
63        elements: Vec<SettingsListElement>,
64        span: Span,
65    },
66}
67
68/// One element of a settings list.
69#[derive(Debug, Clone)]
70pub struct SettingsListElement {
71    pub value: String,
72    pub span: Span,
73}
74
75/// A program-scope declaration.
76#[derive(Debug, Clone)]
77pub enum Decl {
78    GlobalVariable {
79        name: String,
80        /// An explicit Workshop index (`globalvar x 100`), when given.
81        index: Option<u32>,
82        span: Span,
83        /// The exact span of the declared identifier token.
84        name_span: Span,
85        initializer: Option<Expr>,
86    },
87    PlayerVariable {
88        name: String,
89        index: Option<u32>,
90        span: Span,
91        /// The exact span of the declared identifier token.
92        name_span: Span,
93        initializer: Option<Expr>,
94    },
95    Subroutine {
96        name: String,
97        span: Span,
98        /// The exact span of the declared identifier token.
99        name_span: Span,
100    },
101    /// A user-defined `enum`; members fold to numeric constants.
102    Enum {
103        name: String,
104        members: Vec<(String, Span)>,
105        span: Span,
106    },
107    /// A `macro` declaration with parameterized statement body.
108    Macro {
109        name: String,
110        args: Vec<String>,
111        body: Vec<Stmt>,
112        span: Span,
113    },
114}
115
116/// A rule or a subroutine definition.
117#[derive(Debug, Clone)]
118pub enum RuleEntry {
119    Rule(Rule),
120    SubroutineDef {
121        name: String,
122        presentation_name: Option<String>,
123        span: Span,
124        /// The exact span of the defined identifier token in `def name():`.
125        name_span: Span,
126        body: Vec<Stmt>,
127        annotations: Vec<Annotation>,
128        rule_prefix: Option<String>,
129    },
130}
131
132/// A rule with its event, conditions, and actions.
133#[derive(Debug, Clone)]
134pub struct Rule {
135    pub name: String,
136    pub span: Span,
137    /// The exact span of the rule name inside its string literal.
138    pub name_span: Span,
139    pub disabled: bool,
140    pub delimiter: bool,
141    pub new_page: Option<String>,
142    pub annotations: Vec<Annotation>,
143    pub rule_prefix: Option<String>,
144    pub event: Event,
145    pub conditions: Vec<Expr>,
146    pub actions: Vec<Stmt>,
147}
148
149/// A source annotation retained for tooling and provenance.
150#[derive(Debug, Clone)]
151pub struct Annotation {
152    pub name: String,
153    pub args: Vec<AnnotationArg>,
154    pub span: Span,
155}
156
157/// One raw annotation argument. Values such as heroes, teams, and slots stay
158/// opaque here because their canonical domains belong to workshop-rs.
159#[derive(Debug, Clone)]
160pub struct AnnotationArg {
161    pub text: String,
162    pub span: Span,
163}
164
165/// A rule event or an `@Event` directive.
166#[derive(Debug, Clone)]
167pub struct Event {
168    pub name: String,
169    pub args: Vec<Expr>,
170    pub span: Span,
171}
172
173/// A statement.
174#[derive(Debug, Clone)]
175pub enum Stmt {
176    Expr {
177        expr: Expr,
178        span: Span,
179    },
180    Assign {
181        target: Expr,
182        value: Expr,
183        span: Span,
184    },
185    If {
186        branches: Vec<IfBranch>,
187        r#else: Option<Vec<Stmt>>,
188        span: Span,
189    },
190    For {
191        variable: Expr,
192        iterable: Expr,
193        body: Vec<Stmt>,
194        span: Span,
195    },
196    While {
197        condition: Expr,
198        body: Vec<Stmt>,
199        span: Span,
200    },
201    DoWhile {
202        condition: Expr,
203        body: Vec<Stmt>,
204        span: Span,
205    },
206    Switch {
207        value: Expr,
208        arms: Vec<SwitchArm>,
209        span: Span,
210    },
211    Delete {
212        target: Expr,
213        span: Span,
214    },
215    Break {
216        span: Span,
217    },
218    Return {
219        span: Span,
220    },
221    Continue {
222        span: Span,
223    },
224    Goto {
225        label: Option<String>,
226        offset: Option<Expr>,
227        rule_start: bool,
228        span: Span,
229    },
230    Label {
231        name: String,
232        span: Span,
233    },
234    Pass {
235        span: Span,
236    },
237}
238
239/// One source-ordered arm in a switch statement.
240#[derive(Debug, Clone)]
241pub enum SwitchArm {
242    Case {
243        value: Expr,
244        body: Vec<Stmt>,
245        span: Span,
246    },
247    Default {
248        body: Vec<Stmt>,
249        span: Span,
250    },
251}
252
253/// One condition/body pair of an `if`.
254#[derive(Debug, Clone)]
255pub struct IfBranch {
256    pub condition: Expr,
257    pub body: Vec<Stmt>,
258}
259
260/// One call argument: either positional (`expr`) or keyword (`name = expr`,
261/// issue #110). Keyword arguments keep the name token's exact span so binding
262/// diagnostics are source-located on the name (unknown/duplicate keyword) or
263/// the value (enum-domain, arity of the value expression) as appropriate.
264#[derive(Debug, Clone)]
265pub struct CallArg {
266    /// The keyword name and its exact span, when this is a `name = expr`
267    /// argument.
268    pub keyword: Option<(String, Span)>,
269    /// The argument's value expression.
270    pub value: Expr,
271}
272
273/// An expression.
274#[derive(Debug, Clone)]
275pub enum Expr {
276    Number {
277        value: f64,
278        text: String,
279        span: Span,
280    },
281    String {
282        value: String,
283        span: Span,
284    },
285    Bool {
286        value: bool,
287        span: Span,
288    },
289    Null {
290        span: Span,
291    },
292    Array {
293        elements: Vec<Expr>,
294        span: Span,
295    },
296    Dict {
297        entries: Vec<DictEntry>,
298        span: Span,
299    },
300    Comprehension {
301        element: Box<Expr>,
302        variable: String,
303        variable_span: Span,
304        index: Option<(String, Span)>,
305        iterable: Box<Expr>,
306        condition: Option<Box<Expr>>,
307        span: Span,
308    },
309    Lambda {
310        params: Vec<(String, Span)>,
311        body: Box<Expr>,
312        span: Span,
313    },
314    StringModifier {
315        modifier: char,
316        value: String,
317        /// The decoded f-string template, with interpolation placeholders
318        /// normalized to `{0}`, `{1}`, …; present only for modifier `f`.
319        format_text: Option<String>,
320        /// Expressions parsed from f-string interpolation regions, in source
321        /// order. Their spans point into the original string literal.
322        interpolations: Vec<Expr>,
323        span: Span,
324    },
325    /// A plain function call.
326    Call {
327        name: String,
328        args: Vec<CallArg>,
329        span: Span,
330    },
331    /// A call on a receiver (`x.f(...)`).
332    ReceiverCall {
333        receiver: Box<Expr>,
334        name: String,
335        args: Vec<CallArg>,
336        span: Span,
337    },
338    /// An unresolved identifier (resolved during lowering).
339    Name {
340        name: String,
341        span: Span,
342    },
343    /// A member access `x.y` (resolved during lowering).
344    Member {
345        receiver: Box<Expr>,
346        member: String,
347        /// The exact span of the member identifier after `.`.
348        member_span: Span,
349        span: Span,
350    },
351    /// A source type literal used by `createWorkshopSetting`, such as
352    /// `float[0.5:10]`.
353    Type {
354        name: String,
355        args: Vec<Expr>,
356        span: Span,
357    },
358    Index {
359        array: Box<Expr>,
360        index: Box<Expr>,
361        span: Span,
362    },
363    Binary {
364        op: String,
365        left: Box<Expr>,
366        right: Box<Expr>,
367        span: Span,
368    },
369    Conditional {
370        then_value: Box<Expr>,
371        condition: Box<Expr>,
372        else_value: Box<Expr>,
373        span: Span,
374    },
375    Unary {
376        op: String,
377        operand: Box<Expr>,
378        span: Span,
379    },
380}
381
382/// One key/value pair in an OPY dictionary literal.
383#[derive(Debug, Clone)]
384pub struct DictEntry {
385    pub key: Expr,
386    pub value: Expr,
387    pub span: Span,
388}
389
390impl Expr {
391    /// The source span of this expression.
392    pub fn span(&self) -> Span {
393        match self {
394            Expr::Number { span, .. }
395            | Expr::String { span, .. }
396            | Expr::Bool { span, .. }
397            | Expr::Null { span }
398            | Expr::Array { span, .. }
399            | Expr::Dict { span, .. }
400            | Expr::Comprehension { span, .. }
401            | Expr::Lambda { span, .. }
402            | Expr::StringModifier { span, .. }
403            | Expr::Call { span, .. }
404            | Expr::ReceiverCall { span, .. }
405            | Expr::Name { span, .. }
406            | Expr::Member { span, .. }
407            | Expr::Type { span, .. }
408            | Expr::Index { span, .. }
409            | Expr::Binary { span, .. }
410            | Expr::Conditional { span, .. }
411            | Expr::Unary { span, .. } => *span,
412        }
413    }
414}
415
416impl Stmt {
417    /// The source span of this statement.
418    pub fn span(&self) -> Span {
419        match self {
420            Stmt::Expr { span, .. }
421            | Stmt::Assign { span, .. }
422            | Stmt::If { span, .. }
423            | Stmt::For { span, .. }
424            | Stmt::While { span, .. }
425            | Stmt::DoWhile { span, .. }
426            | Stmt::Switch { span, .. }
427            | Stmt::Delete { span, .. }
428            | Stmt::Break { span }
429            | Stmt::Return { span }
430            | Stmt::Continue { span }
431            | Stmt::Goto { span, .. }
432            | Stmt::Label { span, .. }
433            | Stmt::Pass { span } => *span,
434        }
435    }
436}
437
438impl CallArg {
439    /// The source span of this argument: the keyword name when keyword, the
440    /// value expression otherwise.
441    pub fn span(&self) -> Span {
442        match &self.keyword {
443            Some((_, name_span)) => {
444                let end = self.value.span().end;
445                Span::new(name_span.file, name_span.start, end)
446            }
447            None => self.value.span(),
448        }
449    }
450}