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 `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    /// An unquoted settings expression preserved without assigning it a type.
62    Raw {
63        name: String,
64        value: String,
65        span: Span,
66    },
67    List {
68        name: String,
69        elements: Vec<SettingsListElement>,
70        span: Span,
71    },
72}
73
74/// One element of a settings list.
75#[derive(Debug, Clone)]
76pub struct SettingsListElement {
77    /// Literal text (string content, normalized number) or, when `expr` is
78    /// set, expression source to be evaluated like the pinned settings
79    /// evaluator does (#512).
80    pub value: String,
81    /// `value` is an expression to evaluate, not literal text.
82    pub expr: bool,
83    pub span: Span,
84}
85
86/// A program-scope declaration.
87#[derive(Debug, Clone)]
88pub enum Decl {
89    GlobalVariable {
90        name: String,
91        /// An explicit Workshop index (`globalvar x 100`), when given.
92        index: Option<u32>,
93        span: Span,
94        /// The exact span of the declared identifier token.
95        name_span: Span,
96        initializer: Option<Expr>,
97    },
98    PlayerVariable {
99        name: String,
100        index: Option<u32>,
101        span: Span,
102        /// The exact span of the declared identifier token.
103        name_span: Span,
104        initializer: Option<Expr>,
105    },
106    Subroutine {
107        name: String,
108        span: Span,
109        /// The exact span of the declared identifier token.
110        name_span: Span,
111    },
112    /// A user-defined `enum`; members fold to numeric constants.
113    Enum {
114        name: String,
115        members: Vec<(String, Span)>,
116        span: Span,
117    },
118    /// A source-level constant declared with `macro name = expression`.
119    Constant {
120        name: String,
121        value: Expr,
122        span: Span,
123    },
124    /// A `macro` declaration with parameterized statement body.
125    Macro {
126        name: String,
127        args: Vec<String>,
128        body: Vec<Stmt>,
129        span: Span,
130    },
131}
132
133/// A rule or a subroutine definition.
134#[derive(Debug, Clone)]
135pub enum RuleEntry {
136    Rule(Rule),
137    SubroutineDef {
138        name: String,
139        presentation_name: Option<String>,
140        span: Span,
141        /// The exact span of the defined identifier token in `def name():`.
142        name_span: Span,
143        body: Vec<Stmt>,
144        annotations: Vec<Annotation>,
145        rule_prefix: Option<String>,
146    },
147}
148
149/// A rule with its event, conditions, and actions.
150#[derive(Debug, Clone)]
151pub struct Rule {
152    pub name: String,
153    pub span: Span,
154    /// The exact span of the rule name inside its string literal.
155    pub name_span: Span,
156    pub disabled: bool,
157    pub delimiter: bool,
158    pub new_page: Option<String>,
159    pub annotations: Vec<Annotation>,
160    pub rule_prefix: Option<String>,
161    pub event: Event,
162    pub conditions: Vec<Expr>,
163    pub actions: Vec<Stmt>,
164}
165
166/// A source annotation retained for tooling and provenance.
167#[derive(Debug, Clone)]
168pub struct Annotation {
169    pub name: String,
170    pub args: Vec<AnnotationArg>,
171    pub span: Span,
172}
173
174/// One raw annotation argument. Values such as heroes, teams, and slots stay
175/// opaque here because their canonical domains belong to workshop-rs.
176#[derive(Debug, Clone)]
177pub struct AnnotationArg {
178    pub text: String,
179    pub span: Span,
180}
181
182/// A rule event or an `@Event` directive.
183#[derive(Debug, Clone)]
184pub struct Event {
185    pub name: String,
186    pub args: Vec<Expr>,
187    pub span: Span,
188}
189
190/// A statement.
191#[derive(Debug, Clone)]
192pub enum Stmt {
193    Expr {
194        expr: Expr,
195        span: Span,
196    },
197    Assign {
198        target: Expr,
199        value: Expr,
200        span: Span,
201    },
202    If {
203        branches: Vec<IfBranch>,
204        r#else: Option<Vec<Stmt>>,
205        /// The span of the `else` keyword, when the chain has an else branch.
206        else_span: Option<Span>,
207        /// Zero-width span at the first token past the chain (dedent or EOF):
208        /// the position a Workshop `End` marker would occupy.
209        end_span: Span,
210        /// The chain began with an `elif`/`else` in statement position (no
211        /// preceding `if`): the reference emits `Else If`/`Else` markers
212        /// rather than `If` (#516).
213        orphan: bool,
214        span: Span,
215    },
216    For {
217        variable: Expr,
218        iterable: Expr,
219        body: Vec<Stmt>,
220        span: Span,
221    },
222    While {
223        condition: Expr,
224        body: Vec<Stmt>,
225        span: Span,
226    },
227    DoWhile {
228        condition: Expr,
229        body: Vec<Stmt>,
230        span: Span,
231    },
232    Switch {
233        value: Expr,
234        arms: Vec<SwitchArm>,
235        span: Span,
236    },
237    Delete {
238        target: Expr,
239        span: Span,
240    },
241    Break {
242        span: Span,
243    },
244    Return {
245        span: Span,
246    },
247    Continue {
248        span: Span,
249    },
250    Goto {
251        label: Option<String>,
252        offset: Option<Expr>,
253        rule_start: bool,
254        span: Span,
255    },
256    Label {
257        name: String,
258        span: Span,
259    },
260    Pass {
261        span: Span,
262    },
263}
264
265/// One source-ordered arm in a switch statement.
266#[derive(Debug, Clone)]
267pub enum SwitchArm {
268    Case {
269        value: Expr,
270        body: Vec<Stmt>,
271        span: Span,
272    },
273    Default {
274        body: Vec<Stmt>,
275        span: Span,
276    },
277}
278
279/// One condition/body pair of an `if`.
280#[derive(Debug, Clone)]
281pub struct IfBranch {
282    /// The span of the `if`, `elif`, or `else` keyword that opens this branch.
283    pub marker: Span,
284    pub condition: Expr,
285    pub body: Vec<Stmt>,
286}
287
288/// One call argument: either positional (`expr`) or keyword (`name = expr`,
289/// issue #110). Keyword arguments keep the name token's exact span so binding
290/// diagnostics are source-located on the name (unknown/duplicate keyword) or
291/// the value (enum-domain, arity of the value expression) as appropriate.
292#[derive(Debug, Clone)]
293pub struct CallArg {
294    /// The keyword name and its exact span, when this is a `name = expr`
295    /// argument.
296    pub keyword: Option<(String, Span)>,
297    /// The argument's value expression.
298    pub value: Expr,
299}
300
301/// An expression.
302#[derive(Debug, Clone)]
303pub enum Expr {
304    Number {
305        value: f64,
306        text: String,
307        span: Span,
308    },
309    String {
310        value: String,
311        span: Span,
312    },
313    Bool {
314        value: bool,
315        span: Span,
316    },
317    Null {
318        span: Span,
319    },
320    Array {
321        elements: Vec<Expr>,
322        span: Span,
323    },
324    Dict {
325        entries: Vec<DictEntry>,
326        span: Span,
327    },
328    Comprehension {
329        element: Box<Expr>,
330        variable: String,
331        variable_span: Span,
332        index: Option<(String, Span)>,
333        iterable: Box<Expr>,
334        condition: Option<Box<Expr>>,
335        span: Span,
336    },
337    Lambda {
338        params: Vec<(String, Span)>,
339        body: Box<Expr>,
340        /// `true` when the lambda was written wrapped in parentheses. The
341        /// reference detects binder lambdas at the token level, so
342        /// `(lambda x: e)` never satisfies a lambda-argument position
343        /// (issue #445); the flag lets lowering enforce the same rule
344        /// without reparsing source.
345        parenthesized: bool,
346        span: Span,
347    },
348    StringModifier {
349        modifier: char,
350        value: String,
351        /// The decoded f-string template, with interpolation placeholders
352        /// normalized to `{0}`, `{1}`, …; present only for modifier `f`.
353        format_text: Option<String>,
354        /// Expressions parsed from f-string interpolation regions, in source
355        /// order. Their spans point into the original string literal.
356        interpolations: Vec<Expr>,
357        span: Span,
358    },
359    /// A plain function call.
360    Call {
361        name: String,
362        args: Vec<CallArg>,
363        debug_source: Option<String>,
364        span: Span,
365    },
366    /// A call on a receiver (`x.f(...)`).
367    ReceiverCall {
368        receiver: Box<Expr>,
369        name: String,
370        args: Vec<CallArg>,
371        span: Span,
372    },
373    /// An unresolved identifier (resolved during lowering).
374    Name {
375        name: String,
376        span: Span,
377    },
378    /// A member access `x.y` (resolved during lowering).
379    Member {
380        receiver: Box<Expr>,
381        member: String,
382        /// The exact span of the member identifier after `.`.
383        member_span: Span,
384        span: Span,
385    },
386    /// A source type literal used by `createWorkshopSetting`, such as
387    /// `float[0.5:10]`.
388    Type {
389        name: String,
390        args: Vec<Expr>,
391        span: Span,
392    },
393    Index {
394        array: Box<Expr>,
395        index: Box<Expr>,
396        span: Span,
397    },
398    Binary {
399        op: String,
400        left: Box<Expr>,
401        right: Box<Expr>,
402        span: Span,
403    },
404    Conditional {
405        then_value: Box<Expr>,
406        condition: Box<Expr>,
407        else_value: Box<Expr>,
408        span: Span,
409    },
410    Unary {
411        op: String,
412        operand: Box<Expr>,
413        span: Span,
414    },
415}
416
417/// One key/value pair in an OPY dictionary literal.
418#[derive(Debug, Clone)]
419pub struct DictEntry {
420    pub key: Expr,
421    pub value: Expr,
422    pub span: Span,
423}
424
425impl Expr {
426    /// The source span of this expression.
427    pub fn span(&self) -> Span {
428        match self {
429            Expr::Number { span, .. }
430            | Expr::String { span, .. }
431            | Expr::Bool { span, .. }
432            | Expr::Null { span }
433            | Expr::Array { span, .. }
434            | Expr::Dict { span, .. }
435            | Expr::Comprehension { span, .. }
436            | Expr::Lambda { span, .. }
437            | Expr::StringModifier { span, .. }
438            | Expr::Call { span, .. }
439            | Expr::ReceiverCall { span, .. }
440            | Expr::Name { span, .. }
441            | Expr::Member { span, .. }
442            | Expr::Type { span, .. }
443            | Expr::Index { span, .. }
444            | Expr::Binary { span, .. }
445            | Expr::Conditional { span, .. }
446            | Expr::Unary { span, .. } => *span,
447        }
448    }
449}
450
451impl Stmt {
452    /// The source span of this statement.
453    pub fn span(&self) -> Span {
454        match self {
455            Stmt::Expr { span, .. }
456            | Stmt::Assign { span, .. }
457            | Stmt::If { span, .. }
458            | Stmt::For { span, .. }
459            | Stmt::While { span, .. }
460            | Stmt::DoWhile { span, .. }
461            | Stmt::Switch { span, .. }
462            | Stmt::Delete { span, .. }
463            | Stmt::Break { span }
464            | Stmt::Return { span }
465            | Stmt::Continue { span }
466            | Stmt::Goto { span, .. }
467            | Stmt::Label { span, .. }
468            | Stmt::Pass { span } => *span,
469        }
470    }
471}
472
473impl CallArg {
474    /// The source span of this argument: the keyword name when keyword, the
475    /// value expression otherwise.
476    pub fn span(&self) -> Span {
477        match &self.keyword {
478            Some((_, name_span)) => {
479                let end = self.value.span().end;
480                Span::new(name_span.file, name_span.start, end)
481            }
482            None => self.value.span(),
483        }
484    }
485}