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