Skip to main content

pine_ast/
lib.rs

1use serde::{Deserialize, Serialize};
2
3pub mod visitor;
4pub use visitor::{walk_block, walk_expr, walk_program, walk_stmt, Visitor};
5
6// Helper function for serde to skip false values
7fn is_false(b: &bool) -> bool {
8    !b
9}
10
11// Helper function for serde to skip None values
12fn skip_none<T>(opt: &Option<T>) -> bool {
13    opt.is_none()
14}
15
16// Helper function for serde to skip unassigned call-site ids
17fn is_zero_u32(n: &u32) -> bool {
18    *n == 0
19}
20
21/// Source location (1-based line and column) attached to select AST nodes for
22/// diagnostics.
23///
24/// `Loc` is intentionally transparent to equality and serialization: two nodes
25/// that differ only in location compare **equal**, and the position is **never**
26/// written to the serialized AST (the field carries `#[serde(skip)]`).
27#[derive(Debug, Clone, Copy, Default)]
28pub struct Loc {
29    pub line: u32,
30    pub column: u32,
31}
32
33impl Loc {
34    pub fn new(line: u32, column: u32) -> Self {
35        Self { line, column }
36    }
37
38    /// The tracked `(line, column)`, or `None` when unknown (line `0`).
39    pub fn position(&self) -> Option<(u32, u32)> {
40        (self.line != 0).then_some((self.line, self.column))
41    }
42
43    /// The tracked line, or `None` when unknown (line `0`).
44    pub fn line(&self) -> Option<u32> {
45        (self.line != 0).then_some(self.line)
46    }
47}
48
49// Location must not participate in structural equality: an AST compared against
50// a snapshot (which never stores a line) must still match.
51impl PartialEq for Loc {
52    fn eq(&self, _other: &Self) -> bool {
53        true
54    }
55}
56
57/// Type qualifier for variables and parameters
58/// Hierarchy: const < input < simple < series (const is the weakest)
59#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
60pub enum TypeQualifier {
61    Const,
62    Input,
63    Simple,
64    Series,
65}
66
67/// How a variable declaration behaves across bars.
68#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
69pub enum VarKind {
70    /// `x = expr` — the initializer is re-evaluated on every bar.
71    #[default]
72    Plain,
73    /// `var x = expr` — the initializer runs once; the value persists across bars.
74    Var,
75    /// `varip x = expr` — like `Var`, but also updates intrabar in realtime.
76    Varip,
77}
78
79impl VarKind {
80    /// `var`/`varip`: initialize once and retain the value across bars.
81    pub fn is_persistent(self) -> bool {
82        !matches!(self, VarKind::Plain)
83    }
84
85    /// Used by serde to omit the field for plain declarations.
86    fn is_plain(&self) -> bool {
87        matches!(self, VarKind::Plain)
88    }
89}
90
91/// Function argument - can be positional or named
92#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
93pub enum Argument {
94    Positional(Expr),
95    Named { name: String, value: Expr },
96}
97
98// AST nodes
99#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
100pub enum Expr {
101    Literal(Literal),
102    Variable {
103        name: String,
104        #[serde(skip)]
105        loc: Loc,
106    },
107    Binary {
108        left: Box<Expr>,
109        op: BinOp,
110        right: Box<Expr>,
111        #[serde(skip)]
112        loc: Loc,
113    },
114    Unary {
115        op: UnOp,
116        expr: Box<Expr>,
117    },
118    Call {
119        callee: Box<Expr>,
120        #[serde(default, skip_serializing_if = "Vec::is_empty")]
121        type_args: Vec<String>, // Type arguments like <int>, <float>
122        args: Vec<Argument>,
123        #[serde(default, skip_serializing_if = "is_zero_u32")]
124        id: u32,
125        #[serde(skip)]
126        loc: Loc,
127    },
128    Index {
129        expr: Box<Expr>,
130        index: Box<Expr>,
131        #[serde(default, skip_serializing_if = "is_zero_u32")]
132        id: u32,
133    },
134    MemberAccess {
135        object: Box<Expr>,
136        member: String,
137        #[serde(skip)]
138        member_loc: Loc,
139    },
140    Ternary {
141        condition: Box<Expr>,
142        then_expr: Box<Expr>,
143        else_expr: Box<Expr>,
144    },
145    Function {
146        params: Vec<FunctionParam>,
147        body: Vec<Stmt>,
148    },
149    Array(Vec<Expr>),
150    Switch {
151        value: Box<Expr>,
152        cases: Vec<(Expr, Expr)>, // (pattern, result)
153    },
154    IfExpr {
155        condition: Box<Expr>,
156        then_expr: Box<Expr>,
157        else_if_branches: Vec<(Expr, Expr)>, // Vec of (condition, expression) for else if
158        else_expr: Option<Box<Expr>>,        // None means return na if no branch matches
159    },
160}
161
162impl Expr {
163    /// A variable reference with no recorded position — for tests and desugaring
164    /// where the use has no distinct source location.
165    pub fn var(name: impl Into<String>) -> Self {
166        Expr::Variable {
167            name: name.into(),
168            loc: Loc::default(),
169        }
170    }
171}
172
173#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
174pub enum Literal {
175    Int(i64),
176    Number(f64),
177    String(String),
178    Bool(bool),
179    Na,               // PineScript's N/A value
180    HexColor(String), // Hex color: #RRGGBB or #RRGGBBAA
181}
182
183#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
184pub enum BinOp {
185    Add,
186    Sub,
187    Mul,
188    Div,
189    Mod,
190    Eq,
191    NotEq,
192    Less,
193    Greater,
194    LessEq,
195    GreaterEq,
196    And,
197    Or,
198}
199
200#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
201pub enum UnOp {
202    Neg,
203    Not,
204}
205
206#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
207pub enum Stmt {
208    VarDecl {
209        name: String,
210        #[serde(skip_serializing_if = "skip_none")]
211        type_qualifier: Option<TypeQualifier>,
212        type_annotation: Option<String>,
213        initializer: Option<Expr>,
214        #[serde(default, skip_serializing_if = "VarKind::is_plain")]
215        var_kind: VarKind,
216        #[serde(skip)]
217        loc: Loc,
218    },
219    Assignment {
220        target: Expr, // Can be Variable or MemberAccess
221        value: Expr,
222    },
223    TupleAssignment {
224        names: Vec<String>,
225        value: Expr,
226        #[serde(skip)]
227        loc: Loc,
228    },
229    Expression(Expr),
230    If {
231        condition: Expr,
232        then_branch: Vec<Stmt>,
233        else_if_branches: Vec<(Expr, Vec<Stmt>)>, // Vec of (condition, statements) for else if
234        else_branch: Option<Vec<Stmt>>,
235    },
236    For {
237        var_name: String,
238        from: Expr,
239        to: Expr,
240        #[serde(default, skip_serializing_if = "skip_none")]
241        step: Option<Expr>,
242        body: Vec<Stmt>,
243        #[serde(skip)]
244        loc: Loc,
245    },
246    ForIn {
247        // For single item: for item in collection
248        // For tuple: for [index, item] in collection
249        index_var: Option<String>, // None for simple form, Some(name) for tuple form
250        item_var: String,
251        collection: Expr,
252        body: Vec<Stmt>,
253        #[serde(skip)]
254        loc: Loc,
255    },
256    While {
257        condition: Expr,
258        body: Vec<Stmt>,
259    },
260    Break {
261        #[serde(skip)]
262        loc: Loc,
263    },
264    Continue {
265        #[serde(skip)]
266        loc: Loc,
267    },
268    TypeDecl {
269        name: String,
270        fields: Vec<TypeField>,
271        #[serde(default, skip_serializing_if = "is_false")]
272        export: bool,
273        #[serde(skip)]
274        loc: Loc,
275    },
276    MethodDecl {
277        name: String,
278        params: Vec<MethodParam>,
279        body: Vec<Stmt>,
280        #[serde(default, skip_serializing_if = "is_false")]
281        export: bool,
282        #[serde(skip)]
283        loc: Loc,
284    },
285    EnumDecl {
286        name: String,
287        fields: Vec<EnumField>,
288        #[serde(default, skip_serializing_if = "is_false")]
289        export: bool,
290        #[serde(skip)]
291        loc: Loc,
292    },
293    FunctionDecl {
294        name: String,
295        params: Vec<FunctionParam>,
296        body: Vec<Stmt>,
297        #[serde(default, skip_serializing_if = "is_false")]
298        export: bool,
299        #[serde(skip)]
300        loc: Loc,
301    },
302    Export {
303        item: ExportItem,
304    },
305    Import {
306        path: String,  // e.g., "userName/Point/1"
307        alias: String, // e.g., "pt"
308        #[serde(skip)]
309        loc: Loc,
310    },
311}
312
313/// An item that can be exported from a library
314#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
315pub enum ExportItem {
316    Type(String),     // export type typename
317    Function(String), // export functionname
318}
319
320/// A field in an enum declaration
321#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
322pub struct EnumField {
323    pub name: String,
324    pub title: Option<String>, // Optional title for the enum field
325    #[serde(skip)]
326    pub loc: Loc,
327}
328
329/// A parameter in a method declaration
330#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
331pub struct MethodParam {
332    #[serde(skip_serializing_if = "skip_none")]
333    pub type_qualifier: Option<TypeQualifier>,
334    pub type_annotation: Option<String>, // e.g., "InfoLabel"
335    pub name: String,
336    pub default_value: Option<Expr>,
337    #[serde(skip)]
338    pub loc: Loc,
339}
340
341/// A parameter in a function declaration
342#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
343pub struct FunctionParam {
344    #[serde(skip_serializing_if = "skip_none")]
345    pub type_qualifier: Option<TypeQualifier>,
346    #[serde(skip_serializing_if = "skip_none")]
347    pub type_annotation: Option<String>,
348    pub name: String,
349    #[serde(skip_serializing_if = "skip_none")]
350    pub default_value: Option<Expr>,
351    #[serde(skip)]
352    pub loc: Loc,
353}
354
355/// A field in a user-defined type
356#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
357pub struct TypeField {
358    pub name: String,
359    #[serde(skip_serializing_if = "skip_none")]
360    pub type_qualifier: Option<TypeQualifier>,
361    pub type_annotation: String,
362    pub default_value: Option<Expr>,
363    #[serde(skip)]
364    pub loc: Loc,
365}
366
367/// A program is a collection of statements
368#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
369pub struct Program {
370    pub statements: Vec<Stmt>,
371}
372
373impl Program {
374    pub fn new(statements: Vec<Stmt>) -> Self {
375        Self { statements }
376    }
377}