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        #[serde(default, skip_serializing_if = "skip_none")]
239        step: Option<Expr>,
240        body: Vec<Stmt>,
241        #[serde(skip)]
242        loc: Loc,
243    },
244    ForIn {
245        // For single item: for item in collection
246        // For tuple: for [index, item] in collection
247        index_var: Option<String>, // None for simple form, Some(name) for tuple form
248        item_var: String,
249        collection: Expr,
250        body: Vec<Stmt>,
251        #[serde(skip)]
252        loc: Loc,
253    },
254    While {
255        condition: Expr,
256        body: Vec<Stmt>,
257    },
258    Break {
259        #[serde(skip)]
260        loc: Loc,
261    },
262    Continue {
263        #[serde(skip)]
264        loc: Loc,
265    },
266    TypeDecl {
267        name: String,
268        fields: Vec<TypeField>,
269        #[serde(default, skip_serializing_if = "is_false")]
270        export: bool,
271        #[serde(skip)]
272        loc: Loc,
273    },
274    MethodDecl {
275        name: String,
276        params: Vec<MethodParam>,
277        body: Vec<Stmt>,
278        #[serde(default, skip_serializing_if = "is_false")]
279        export: bool,
280        #[serde(skip)]
281        loc: Loc,
282    },
283    EnumDecl {
284        name: String,
285        fields: Vec<EnumField>,
286        #[serde(default, skip_serializing_if = "is_false")]
287        export: bool,
288        #[serde(skip)]
289        loc: Loc,
290    },
291    FunctionDecl {
292        name: String,
293        params: Vec<FunctionParam>,
294        body: Vec<Stmt>,
295        #[serde(default, skip_serializing_if = "is_false")]
296        export: bool,
297        #[serde(skip)]
298        loc: Loc,
299    },
300    Export {
301        item: ExportItem,
302    },
303    Import {
304        path: String,  // e.g., "userName/Point/1"
305        alias: String, // e.g., "pt"
306        #[serde(skip)]
307        loc: Loc,
308    },
309}
310
311/// An item that can be exported from a library
312#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
313pub enum ExportItem {
314    Type(String),     // export type typename
315    Function(String), // export functionname
316}
317
318/// A field in an enum declaration
319#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
320pub struct EnumField {
321    pub name: String,
322    pub title: Option<String>, // Optional title for the enum field
323    #[serde(skip)]
324    pub loc: Loc,
325}
326
327/// A parameter in a method declaration
328#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
329pub struct MethodParam {
330    #[serde(skip_serializing_if = "skip_none")]
331    pub type_qualifier: Option<TypeQualifier>,
332    pub type_annotation: Option<String>, // e.g., "InfoLabel"
333    pub name: String,
334    pub default_value: Option<Expr>,
335    #[serde(skip)]
336    pub loc: Loc,
337}
338
339/// A parameter in a function declaration
340#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
341pub struct FunctionParam {
342    #[serde(skip_serializing_if = "skip_none")]
343    pub type_qualifier: Option<TypeQualifier>,
344    #[serde(skip_serializing_if = "skip_none")]
345    pub type_annotation: Option<String>,
346    pub name: String,
347    #[serde(skip_serializing_if = "skip_none")]
348    pub default_value: Option<Expr>,
349    #[serde(skip)]
350    pub loc: Loc,
351}
352
353/// A field in a user-defined type
354#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
355pub struct TypeField {
356    pub name: String,
357    #[serde(skip_serializing_if = "skip_none")]
358    pub type_qualifier: Option<TypeQualifier>,
359    pub type_annotation: String,
360    pub default_value: Option<Expr>,
361    #[serde(skip)]
362    pub loc: Loc,
363}
364
365/// A program is a collection of statements
366#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
367pub struct Program {
368    pub statements: Vec<Stmt>,
369}
370
371impl Program {
372    pub fn new(statements: Vec<Stmt>) -> Self {
373        Self { statements }
374    }
375}