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        /// `true` when the lambda was written wrapped in parentheses. The
325        /// reference detects binder lambdas at the token level, so
326        /// `(lambda x: e)` never satisfies a lambda-argument position
327        /// (issue #445); the flag lets lowering enforce the same rule
328        /// without reparsing source.
329        parenthesized: bool,
330        span: Span,
331    },
332    StringModifier {
333        modifier: char,
334        value: String,
335        /// The decoded f-string template, with interpolation placeholders
336        /// normalized to `{0}`, `{1}`, …; present only for modifier `f`.
337        format_text: Option<String>,
338        /// Expressions parsed from f-string interpolation regions, in source
339        /// order. Their spans point into the original string literal.
340        interpolations: Vec<Expr>,
341        span: Span,
342    },
343    /// A plain function call.
344    Call {
345        name: String,
346        args: Vec<CallArg>,
347        debug_source: Option<String>,
348        span: Span,
349    },
350    /// A call on a receiver (`x.f(...)`).
351    ReceiverCall {
352        receiver: Box<Expr>,
353        name: String,
354        args: Vec<CallArg>,
355        span: Span,
356    },
357    /// An unresolved identifier (resolved during lowering).
358    Name {
359        name: String,
360        span: Span,
361    },
362    /// A member access `x.y` (resolved during lowering).
363    Member {
364        receiver: Box<Expr>,
365        member: String,
366        /// The exact span of the member identifier after `.`.
367        member_span: Span,
368        span: Span,
369    },
370    /// A source type literal used by `createWorkshopSetting`, such as
371    /// `float[0.5:10]`.
372    Type {
373        name: String,
374        args: Vec<Expr>,
375        span: Span,
376    },
377    Index {
378        array: Box<Expr>,
379        index: Box<Expr>,
380        span: Span,
381    },
382    Binary {
383        op: String,
384        left: Box<Expr>,
385        right: Box<Expr>,
386        span: Span,
387    },
388    Conditional {
389        then_value: Box<Expr>,
390        condition: Box<Expr>,
391        else_value: Box<Expr>,
392        span: Span,
393    },
394    Unary {
395        op: String,
396        operand: Box<Expr>,
397        span: Span,
398    },
399}
400
401/// One key/value pair in an OPY dictionary literal.
402#[derive(Debug, Clone)]
403pub struct DictEntry {
404    pub key: Expr,
405    pub value: Expr,
406    pub span: Span,
407}
408
409impl Expr {
410    /// The source span of this expression.
411    pub fn span(&self) -> Span {
412        match self {
413            Expr::Number { span, .. }
414            | Expr::String { span, .. }
415            | Expr::Bool { span, .. }
416            | Expr::Null { span }
417            | Expr::Array { span, .. }
418            | Expr::Dict { span, .. }
419            | Expr::Comprehension { span, .. }
420            | Expr::Lambda { span, .. }
421            | Expr::StringModifier { span, .. }
422            | Expr::Call { span, .. }
423            | Expr::ReceiverCall { span, .. }
424            | Expr::Name { span, .. }
425            | Expr::Member { span, .. }
426            | Expr::Type { span, .. }
427            | Expr::Index { span, .. }
428            | Expr::Binary { span, .. }
429            | Expr::Conditional { span, .. }
430            | Expr::Unary { span, .. } => *span,
431        }
432    }
433}
434
435impl Stmt {
436    /// The source span of this statement.
437    pub fn span(&self) -> Span {
438        match self {
439            Stmt::Expr { span, .. }
440            | Stmt::Assign { span, .. }
441            | Stmt::If { span, .. }
442            | Stmt::For { span, .. }
443            | Stmt::While { span, .. }
444            | Stmt::DoWhile { span, .. }
445            | Stmt::Switch { span, .. }
446            | Stmt::Delete { span, .. }
447            | Stmt::Break { span }
448            | Stmt::Return { span }
449            | Stmt::Continue { span }
450            | Stmt::Goto { span, .. }
451            | Stmt::Label { span, .. }
452            | Stmt::Pass { span } => *span,
453        }
454    }
455}
456
457impl CallArg {
458    /// The source span of this argument: the keyword name when keyword, the
459    /// value expression otherwise.
460    pub fn span(&self) -> Span {
461        match &self.keyword {
462            Some((_, name_span)) => {
463                let end = self.value.span().end;
464                Span::new(name_span.file, name_span.start, end)
465            }
466            None => self.value.span(),
467        }
468    }
469}