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(String),
103    Binary {
104        left: Box<Expr>,
105        op: BinOp,
106        right: Box<Expr>,
107        #[serde(skip)]
108        loc: Loc,
109    },
110    Unary {
111        op: UnOp,
112        expr: Box<Expr>,
113    },
114    Call {
115        callee: Box<Expr>,
116        #[serde(default, skip_serializing_if = "Vec::is_empty")]
117        type_args: Vec<String>, // Type arguments like <int>, <float>
118        args: Vec<Argument>,
119        #[serde(default, skip_serializing_if = "is_zero_u32")]
120        id: u32,
121        #[serde(skip)]
122        loc: Loc,
123    },
124    Index {
125        expr: Box<Expr>,
126        index: Box<Expr>,
127    },
128    MemberAccess {
129        object: Box<Expr>,
130        member: String,
131    },
132    Ternary {
133        condition: Box<Expr>,
134        then_expr: Box<Expr>,
135        else_expr: Box<Expr>,
136    },
137    Function {
138        params: Vec<FunctionParam>,
139        body: Vec<Stmt>,
140    },
141    Array(Vec<Expr>),
142    Switch {
143        value: Box<Expr>,
144        cases: Vec<(Expr, Expr)>, // (pattern, result)
145    },
146    IfExpr {
147        condition: Box<Expr>,
148        then_expr: Box<Expr>,
149        else_if_branches: Vec<(Expr, Expr)>, // Vec of (condition, expression) for else if
150        else_expr: Option<Box<Expr>>,        // None means return na if no branch matches
151    },
152}
153
154#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
155pub enum Literal {
156    Int(i64),
157    Number(f64),
158    String(String),
159    Bool(bool),
160    Na,               // PineScript's N/A value
161    HexColor(String), // Hex color: #RRGGBB or #RRGGBBAA
162}
163
164#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
165pub enum BinOp {
166    Add,
167    Sub,
168    Mul,
169    Div,
170    Mod,
171    Eq,
172    NotEq,
173    Less,
174    Greater,
175    LessEq,
176    GreaterEq,
177    And,
178    Or,
179}
180
181#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
182pub enum UnOp {
183    Neg,
184    Not,
185}
186
187#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
188pub enum Stmt {
189    VarDecl {
190        name: String,
191        #[serde(skip_serializing_if = "skip_none")]
192        type_qualifier: Option<TypeQualifier>,
193        type_annotation: Option<String>,
194        initializer: Option<Expr>,
195        #[serde(default, skip_serializing_if = "VarKind::is_plain")]
196        var_kind: VarKind,
197    },
198    Assignment {
199        target: Expr, // Can be Variable or MemberAccess
200        value: Expr,
201    },
202    TupleAssignment {
203        names: Vec<String>,
204        value: Expr,
205    },
206    Expression(Expr),
207    If {
208        condition: Expr,
209        then_branch: Vec<Stmt>,
210        else_if_branches: Vec<(Expr, Vec<Stmt>)>, // Vec of (condition, statements) for else if
211        else_branch: Option<Vec<Stmt>>,
212    },
213    For {
214        var_name: String,
215        from: Expr,
216        to: Expr,
217        body: Vec<Stmt>,
218    },
219    ForIn {
220        // For single item: for item in collection
221        // For tuple: for [index, item] in collection
222        index_var: Option<String>, // None for simple form, Some(name) for tuple form
223        item_var: String,
224        collection: Expr,
225        body: Vec<Stmt>,
226    },
227    While {
228        condition: Expr,
229        body: Vec<Stmt>,
230    },
231    Break,
232    Continue,
233    TypeDecl {
234        name: String,
235        fields: Vec<TypeField>,
236        #[serde(default, skip_serializing_if = "is_false")]
237        export: bool,
238    },
239    MethodDecl {
240        name: String,
241        params: Vec<MethodParam>,
242        body: Vec<Stmt>,
243        #[serde(default, skip_serializing_if = "is_false")]
244        export: bool,
245    },
246    EnumDecl {
247        name: String,
248        fields: Vec<EnumField>,
249        #[serde(default, skip_serializing_if = "is_false")]
250        export: bool,
251    },
252    FunctionDecl {
253        name: String,
254        params: Vec<FunctionParam>,
255        body: Vec<Stmt>,
256        #[serde(default, skip_serializing_if = "is_false")]
257        export: bool,
258    },
259    Export {
260        item: ExportItem,
261    },
262    Import {
263        path: String,  // e.g., "userName/Point/1"
264        alias: String, // e.g., "pt"
265    },
266}
267
268/// An item that can be exported from a library
269#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
270pub enum ExportItem {
271    Type(String),     // export type typename
272    Function(String), // export functionname
273}
274
275/// A field in an enum declaration
276#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
277pub struct EnumField {
278    pub name: String,
279    pub title: Option<String>, // Optional title for the enum field
280}
281
282/// A parameter in a method declaration
283#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
284pub struct MethodParam {
285    #[serde(skip_serializing_if = "skip_none")]
286    pub type_qualifier: Option<TypeQualifier>,
287    pub type_annotation: Option<String>, // e.g., "InfoLabel"
288    pub name: String,
289    pub default_value: Option<Expr>,
290}
291
292/// A parameter in a function declaration
293#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
294pub struct FunctionParam {
295    #[serde(skip_serializing_if = "skip_none")]
296    pub type_qualifier: Option<TypeQualifier>,
297    #[serde(skip_serializing_if = "skip_none")]
298    pub type_annotation: Option<String>,
299    pub name: String,
300    #[serde(skip_serializing_if = "skip_none")]
301    pub default_value: Option<Expr>,
302}
303
304/// A field in a user-defined type
305#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
306pub struct TypeField {
307    pub name: String,
308    #[serde(skip_serializing_if = "skip_none")]
309    pub type_qualifier: Option<TypeQualifier>,
310    pub type_annotation: String,
311    pub default_value: Option<Expr>,
312}
313
314/// A program is a collection of statements
315#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
316pub struct Program {
317    pub statements: Vec<Stmt>,
318}
319
320impl Program {
321    pub fn new(statements: Vec<Stmt>) -> Self {
322        Self { statements }
323    }
324}