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