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    Index {
321        array: Box<Expr>,
322        index: Box<Expr>,
323        span: Span,
324    },
325    Binary {
326        op: String,
327        left: Box<Expr>,
328        right: Box<Expr>,
329        span: Span,
330    },
331    Unary {
332        op: String,
333        operand: Box<Expr>,
334        span: Span,
335    },
336}
337
338/// One key/value pair in an OPY dictionary literal.
339#[derive(Debug, Clone)]
340pub struct DictEntry {
341    pub key: Expr,
342    pub value: Expr,
343    pub span: Span,
344}
345
346impl Expr {
347    /// The source span of this expression.
348    pub fn span(&self) -> Span {
349        match self {
350            Expr::Number { span, .. }
351            | Expr::String { span, .. }
352            | Expr::Bool { span, .. }
353            | Expr::Null { span }
354            | Expr::Array { span, .. }
355            | Expr::Dict { span, .. }
356            | Expr::Comprehension { span, .. }
357            | Expr::Lambda { span, .. }
358            | Expr::StringModifier { span, .. }
359            | Expr::Call { span, .. }
360            | Expr::ReceiverCall { span, .. }
361            | Expr::Name { span, .. }
362            | Expr::Member { span, .. }
363            | Expr::Index { span, .. }
364            | Expr::Binary { span, .. }
365            | Expr::Unary { span, .. } => *span,
366        }
367    }
368}
369
370impl Stmt {
371    /// The source span of this statement.
372    pub fn span(&self) -> Span {
373        match self {
374            Stmt::Expr { span, .. }
375            | Stmt::Assign { span, .. }
376            | Stmt::If { span, .. }
377            | Stmt::For { span, .. }
378            | Stmt::While { span, .. }
379            | Stmt::DoWhile { span, .. }
380            | Stmt::Switch { span, .. }
381            | Stmt::Break { span }
382            | Stmt::Pass { span } => *span,
383        }
384    }
385}
386
387impl CallArg {
388    /// The source span of this argument: the keyword name when keyword, the
389    /// value expression otherwise.
390    pub fn span(&self) -> Span {
391        match &self.keyword {
392            Some((_, name_span)) => {
393                let end = self.value.span().end;
394                Span::new(name_span.file, name_span.start, end)
395            }
396            None => self.value.span(),
397        }
398    }
399}