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    /// 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        span: Span,
201    },
202    For {
203        variable: Expr,
204        iterable: Expr,
205        body: Vec<Stmt>,
206        span: Span,
207    },
208    While {
209        condition: Expr,
210        body: Vec<Stmt>,
211        span: Span,
212    },
213    DoWhile {
214        condition: Expr,
215        body: Vec<Stmt>,
216        span: Span,
217    },
218    Switch {
219        value: Expr,
220        arms: Vec<SwitchArm>,
221        span: Span,
222    },
223    Delete {
224        target: Expr,
225        span: Span,
226    },
227    Break {
228        span: Span,
229    },
230    Return {
231        span: Span,
232    },
233    Continue {
234        span: Span,
235    },
236    Goto {
237        label: Option<String>,
238        offset: Option<Expr>,
239        rule_start: bool,
240        span: Span,
241    },
242    Label {
243        name: String,
244        span: Span,
245    },
246    Pass {
247        span: Span,
248    },
249}
250
251/// One source-ordered arm in a switch statement.
252#[derive(Debug, Clone)]
253pub enum SwitchArm {
254    Case {
255        value: Expr,
256        body: Vec<Stmt>,
257        span: Span,
258    },
259    Default {
260        body: Vec<Stmt>,
261        span: Span,
262    },
263}
264
265/// One condition/body pair of an `if`.
266#[derive(Debug, Clone)]
267pub struct IfBranch {
268    pub condition: Expr,
269    pub body: Vec<Stmt>,
270}
271
272/// One call argument: either positional (`expr`) or keyword (`name = expr`,
273/// issue #110). Keyword arguments keep the name token's exact span so binding
274/// diagnostics are source-located on the name (unknown/duplicate keyword) or
275/// the value (enum-domain, arity of the value expression) as appropriate.
276#[derive(Debug, Clone)]
277pub struct CallArg {
278    /// The keyword name and its exact span, when this is a `name = expr`
279    /// argument.
280    pub keyword: Option<(String, Span)>,
281    /// The argument's value expression.
282    pub value: Expr,
283}
284
285/// An expression.
286#[derive(Debug, Clone)]
287pub enum Expr {
288    Number {
289        value: f64,
290        text: String,
291        span: Span,
292    },
293    String {
294        value: String,
295        span: Span,
296    },
297    Bool {
298        value: bool,
299        span: Span,
300    },
301    Null {
302        span: Span,
303    },
304    Array {
305        elements: Vec<Expr>,
306        span: Span,
307    },
308    Dict {
309        entries: Vec<DictEntry>,
310        span: Span,
311    },
312    Comprehension {
313        element: Box<Expr>,
314        variable: String,
315        variable_span: Span,
316        index: Option<(String, Span)>,
317        iterable: Box<Expr>,
318        condition: Option<Box<Expr>>,
319        span: Span,
320    },
321    Lambda {
322        params: Vec<(String, Span)>,
323        body: Box<Expr>,
324        span: Span,
325    },
326    StringModifier {
327        modifier: char,
328        value: String,
329        /// The decoded f-string template, with interpolation placeholders
330        /// normalized to `{0}`, `{1}`, …; present only for modifier `f`.
331        format_text: Option<String>,
332        /// Expressions parsed from f-string interpolation regions, in source
333        /// order. Their spans point into the original string literal.
334        interpolations: Vec<Expr>,
335        span: Span,
336    },
337    /// A plain function call.
338    Call {
339        name: String,
340        args: Vec<CallArg>,
341        span: Span,
342    },
343    /// A call on a receiver (`x.f(...)`).
344    ReceiverCall {
345        receiver: Box<Expr>,
346        name: String,
347        args: Vec<CallArg>,
348        span: Span,
349    },
350    /// An unresolved identifier (resolved during lowering).
351    Name {
352        name: String,
353        span: Span,
354    },
355    /// A member access `x.y` (resolved during lowering).
356    Member {
357        receiver: Box<Expr>,
358        member: String,
359        /// The exact span of the member identifier after `.`.
360        member_span: Span,
361        span: Span,
362    },
363    /// A source type literal used by `createWorkshopSetting`, such as
364    /// `float[0.5:10]`.
365    Type {
366        name: String,
367        args: Vec<Expr>,
368        span: Span,
369    },
370    Index {
371        array: Box<Expr>,
372        index: Box<Expr>,
373        span: Span,
374    },
375    Binary {
376        op: String,
377        left: Box<Expr>,
378        right: Box<Expr>,
379        span: Span,
380    },
381    Conditional {
382        then_value: Box<Expr>,
383        condition: Box<Expr>,
384        else_value: Box<Expr>,
385        span: Span,
386    },
387    Unary {
388        op: String,
389        operand: Box<Expr>,
390        span: Span,
391    },
392}
393
394/// One key/value pair in an OPY dictionary literal.
395#[derive(Debug, Clone)]
396pub struct DictEntry {
397    pub key: Expr,
398    pub value: Expr,
399    pub span: Span,
400}
401
402impl Expr {
403    /// The source span of this expression.
404    pub fn span(&self) -> Span {
405        match self {
406            Expr::Number { span, .. }
407            | Expr::String { span, .. }
408            | Expr::Bool { span, .. }
409            | Expr::Null { span }
410            | Expr::Array { span, .. }
411            | Expr::Dict { span, .. }
412            | Expr::Comprehension { span, .. }
413            | Expr::Lambda { span, .. }
414            | Expr::StringModifier { span, .. }
415            | Expr::Call { span, .. }
416            | Expr::ReceiverCall { span, .. }
417            | Expr::Name { span, .. }
418            | Expr::Member { span, .. }
419            | Expr::Type { span, .. }
420            | Expr::Index { span, .. }
421            | Expr::Binary { span, .. }
422            | Expr::Conditional { span, .. }
423            | Expr::Unary { span, .. } => *span,
424        }
425    }
426}
427
428impl Stmt {
429    /// The source span of this statement.
430    pub fn span(&self) -> Span {
431        match self {
432            Stmt::Expr { span, .. }
433            | Stmt::Assign { span, .. }
434            | Stmt::If { span, .. }
435            | Stmt::For { span, .. }
436            | Stmt::While { span, .. }
437            | Stmt::DoWhile { span, .. }
438            | Stmt::Switch { span, .. }
439            | Stmt::Delete { span, .. }
440            | Stmt::Break { span }
441            | Stmt::Return { span }
442            | Stmt::Continue { span }
443            | Stmt::Goto { span, .. }
444            | Stmt::Label { span, .. }
445            | Stmt::Pass { span } => *span,
446        }
447    }
448}
449
450impl CallArg {
451    /// The source span of this argument: the keyword name when keyword, the
452    /// value expression otherwise.
453    pub fn span(&self) -> Span {
454        match &self.keyword {
455            Some((_, name_span)) => {
456                let end = self.value.span().end;
457                Span::new(name_span.file, name_span.start, end)
458            }
459            None => self.value.span(),
460        }
461    }
462}