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