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    },
132    MemberAccess {
133        object: Box<Expr>,
134        member: String,
135        #[serde(skip)]
136        member_loc: Loc,
137    },
138    Ternary {
139        condition: Box<Expr>,
140        then_expr: Box<Expr>,
141        else_expr: Box<Expr>,
142    },
143    Function {
144        params: Vec<FunctionParam>,
145        body: Vec<Stmt>,
146    },
147    Array(Vec<Expr>),
148    Switch {
149        value: Box<Expr>,
150        cases: Vec<(Expr, Expr)>, // (pattern, result)
151    },
152    IfExpr {
153        condition: Box<Expr>,
154        then_expr: Box<Expr>,
155        else_if_branches: Vec<(Expr, Expr)>, // Vec of (condition, expression) for else if
156        else_expr: Option<Box<Expr>>,        // None means return na if no branch matches
157    },
158}
159
160impl Expr {
161    /// A variable reference with no recorded position — for tests and desugaring
162    /// where the use has no distinct source location.
163    pub fn var(name: impl Into<String>) -> Self {
164        Expr::Variable {
165            name: name.into(),
166            loc: Loc::default(),
167        }
168    }
169}
170
171#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
172pub enum Literal {
173    Int(i64),
174    Number(f64),
175    String(String),
176    Bool(bool),
177    Na,               // PineScript's N/A value
178    HexColor(String), // Hex color: #RRGGBB or #RRGGBBAA
179}
180
181#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
182pub enum BinOp {
183    Add,
184    Sub,
185    Mul,
186    Div,
187    Mod,
188    Eq,
189    NotEq,
190    Less,
191    Greater,
192    LessEq,
193    GreaterEq,
194    And,
195    Or,
196}
197
198#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
199pub enum UnOp {
200    Neg,
201    Not,
202}
203
204#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
205pub enum Stmt {
206    VarDecl {
207        name: String,
208        #[serde(skip_serializing_if = "skip_none")]
209        type_qualifier: Option<TypeQualifier>,
210        type_annotation: Option<String>,
211        initializer: Option<Expr>,
212        #[serde(default, skip_serializing_if = "VarKind::is_plain")]
213        var_kind: VarKind,
214        #[serde(skip)]
215        loc: Loc,
216    },
217    Assignment {
218        target: Expr, // Can be Variable or MemberAccess
219        value: Expr,
220    },
221    TupleAssignment {
222        names: Vec<String>,
223        value: Expr,
224        #[serde(skip)]
225        loc: Loc,
226    },
227    Expression(Expr),
228    If {
229        condition: Expr,
230        then_branch: Vec<Stmt>,
231        else_if_branches: Vec<(Expr, Vec<Stmt>)>, // Vec of (condition, statements) for else if
232        else_branch: Option<Vec<Stmt>>,
233    },
234    For {
235        var_name: String,
236        from: Expr,
237        to: Expr,
238        body: Vec<Stmt>,
239        #[serde(skip)]
240        loc: Loc,
241    },
242    ForIn {
243        // For single item: for item in collection
244        // For tuple: for [index, item] in collection
245        index_var: Option<String>, // None for simple form, Some(name) for tuple form
246        item_var: String,
247        collection: Expr,
248        body: Vec<Stmt>,
249        #[serde(skip)]
250        loc: Loc,
251    },
252    While {
253        condition: Expr,
254        body: Vec<Stmt>,
255    },
256    Break,
257    Continue,
258    TypeDecl {
259        name: String,
260        fields: Vec<TypeField>,
261        #[serde(default, skip_serializing_if = "is_false")]
262        export: bool,
263        #[serde(skip)]
264        loc: Loc,
265    },
266    MethodDecl {
267        name: String,
268        params: Vec<MethodParam>,
269        body: Vec<Stmt>,
270        #[serde(default, skip_serializing_if = "is_false")]
271        export: bool,
272        #[serde(skip)]
273        loc: Loc,
274    },
275    EnumDecl {
276        name: String,
277        fields: Vec<EnumField>,
278        #[serde(default, skip_serializing_if = "is_false")]
279        export: bool,
280        #[serde(skip)]
281        loc: Loc,
282    },
283    FunctionDecl {
284        name: String,
285        params: Vec<FunctionParam>,
286        body: Vec<Stmt>,
287        #[serde(default, skip_serializing_if = "is_false")]
288        export: bool,
289        #[serde(skip)]
290        loc: Loc,
291    },
292    Export {
293        item: ExportItem,
294    },
295    Import {
296        path: String,  // e.g., "userName/Point/1"
297        alias: String, // e.g., "pt"
298        #[serde(skip)]
299        loc: Loc,
300    },
301}
302
303/// An item that can be exported from a library
304#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
305pub enum ExportItem {
306    Type(String),     // export type typename
307    Function(String), // export functionname
308}
309
310/// A field in an enum declaration
311#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
312pub struct EnumField {
313    pub name: String,
314    pub title: Option<String>, // Optional title for the enum field
315    #[serde(skip)]
316    pub loc: Loc,
317}
318
319/// A parameter in a method declaration
320#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
321pub struct MethodParam {
322    #[serde(skip_serializing_if = "skip_none")]
323    pub type_qualifier: Option<TypeQualifier>,
324    pub type_annotation: Option<String>, // e.g., "InfoLabel"
325    pub name: String,
326    pub default_value: Option<Expr>,
327    #[serde(skip)]
328    pub loc: Loc,
329}
330
331/// A parameter in a function declaration
332#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
333pub struct FunctionParam {
334    #[serde(skip_serializing_if = "skip_none")]
335    pub type_qualifier: Option<TypeQualifier>,
336    #[serde(skip_serializing_if = "skip_none")]
337    pub type_annotation: Option<String>,
338    pub name: String,
339    #[serde(skip_serializing_if = "skip_none")]
340    pub default_value: Option<Expr>,
341    #[serde(skip)]
342    pub loc: Loc,
343}
344
345/// A field in a user-defined type
346#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
347pub struct TypeField {
348    pub name: String,
349    #[serde(skip_serializing_if = "skip_none")]
350    pub type_qualifier: Option<TypeQualifier>,
351    pub type_annotation: String,
352    pub default_value: Option<Expr>,
353    #[serde(skip)]
354    pub loc: Loc,
355}
356
357/// A program is a collection of statements
358#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
359pub struct Program {
360    pub statements: Vec<Stmt>,
361}
362
363impl Program {
364    pub fn new(statements: Vec<Stmt>) -> Self {
365        Self { statements }
366    }
367}