1use std::collections::HashMap;
11
12use pine_ast::{Argument, Expr, Literal, Program, Stmt};
13use pine_interpreter::{BuiltinSignature, PineOutput, Value};
14
15use crate::scope::{is_global_only, ScopeStack, SymbolKind};
16use pine_diagnostics::Diagnostic;
17
18pub struct Analyzer<'a, O: PineOutput> {
19 scopes: ScopeStack,
20 diagnostics: Vec<Diagnostic>,
21 loop_depth: u32,
24 builtins: &'a HashMap<String, Value<O>>,
29 declarations: u32,
32}
33
34const SCRIPT_DECLARATIONS: &[&str] = &["study", "indicator", "strategy", "library"];
36
37fn callee_name(callee: &Expr) -> String {
39 match callee {
40 Expr::Variable(name) => name.clone(),
41 Expr::MemberAccess { object, member } => match object.as_ref() {
42 Expr::Variable(namespace) => format!("{namespace}.{member}"),
43 _ => member.clone(),
44 },
45 _ => String::new(),
46 }
47}
48
49fn describe_literal(literal: &Literal) -> &'static str {
51 match literal {
52 Literal::Int(_) | Literal::Number(_) => "a number",
53 Literal::String(_) => "a string",
54 Literal::Bool(_) => "a bool",
55 Literal::HexColor(_) => "a color",
56 Literal::Na => "na",
57 }
58}
59
60impl<'a, O: PineOutput> Analyzer<'a, O> {
61 pub fn new(builtins: &'a HashMap<String, Value<O>>) -> Self {
62 Self {
63 scopes: ScopeStack::new(),
64 diagnostics: Vec::new(),
65 loop_depth: 0,
66 builtins,
67 declarations: 0,
68 }
69 }
70
71 fn is_builtin(&self, name: &str) -> bool {
72 self.builtins.contains_key(name)
73 }
74
75 fn builtin_signature(&self, callee: &Expr) -> Option<BuiltinSignature> {
82 let value = match callee {
83 Expr::Variable(name) => {
84 if self.scopes.resolve(name).is_some() {
85 return None;
86 }
87 self.builtins.get(name)?.clone()
88 }
89 Expr::MemberAccess { object, member } => {
90 let Expr::Variable(namespace) = object.as_ref() else {
91 return None;
92 };
93 if self.scopes.resolve(namespace).is_some() {
94 return None;
95 }
96 match self.builtins.get(namespace)? {
97 Value::Object { fields, .. } => fields.borrow().get(member)?.clone(),
98 _ => return None,
99 }
100 }
101 _ => return None,
102 };
103
104 match value {
105 Value::BuiltinFunction(builtin) if !builtin.signature.params.is_empty() => {
106 Some(builtin.signature)
107 }
108 _ => None,
109 }
110 }
111
112 fn check_builtin_args(
116 &mut self,
117 name: &str,
118 signature: &BuiltinSignature,
119 args: &[Argument],
120 pos: Option<(u32, u32)>,
121 ) {
122 let positional = args
123 .iter()
124 .filter(|arg| matches!(arg, Argument::Positional(_)))
125 .count();
126
127 if let Some(max) = signature.max_positional() {
128 if positional > max {
129 self.emit(
130 "too-many-arguments",
131 pos,
132 format!("`{name}` takes at most {max} arguments, found {positional}"),
133 );
134 }
135 }
136
137 let mut index = 0;
138 for arg in args {
139 let (param, value) = match arg {
140 Argument::Positional(value) => {
141 let param = signature.positional(index);
142 index += 1;
143 (param, value)
144 }
145 Argument::Named { name: label, value } => match signature.named(label) {
146 Some(param) => (Some(param), value),
147 None => {
148 self.emit(
149 "unknown-argument",
150 pos,
151 format!("`{name}` has no argument named `{label}`"),
152 );
153 continue;
154 }
155 },
156 };
157
158 let (Some(param), Expr::Literal(literal)) = (param, value) else {
161 continue;
162 };
163 if !param.ty.accepts(literal) {
164 let found = describe_literal(literal);
165 let expected = param.ty.describe();
166 let label = param.name.clone();
167 self.emit(
168 "argument-type",
169 pos,
170 format!("`{name}` expects {expected} for `{label}`, found {found}"),
171 );
172 }
173 }
174 }
175
176 pub fn analyze(mut self, program: &Program) -> Vec<Diagnostic> {
178 for stmt in &program.statements {
179 self.check_stmt(stmt);
180 }
181 self.diagnostics
182 }
183
184 fn emit(&mut self, rule: &'static str, pos: Option<(u32, u32)>, message: impl Into<String>) {
185 self.diagnostics.push(Diagnostic::error(rule, pos, message));
186 }
187
188 fn declare(&mut self, name: &str, kind: SymbolKind) {
192 if self.scopes.declare(name, kind).is_some() {
193 self.emit(
194 "duplicate-declaration",
195 None,
196 format!("`{name}` is already declared in this scope"),
197 );
198 }
199 }
200
201 fn block(&mut self, body: &[Stmt]) {
203 self.scopes.push();
204 for stmt in body {
205 self.check_stmt(stmt);
206 }
207 self.scopes.pop();
208 }
209
210 fn loop_body(&mut self, body: &[Stmt]) {
213 self.loop_depth += 1;
214 for stmt in body {
215 self.check_stmt(stmt);
216 }
217 self.loop_depth -= 1;
218 }
219
220 fn function_body<'p>(
223 &mut self,
224 params: impl Iterator<Item = (&'p str, Option<&'p Expr>)>,
225 body: &[Stmt],
226 ) {
227 self.scopes.push();
228 let saved_loop_depth = self.loop_depth;
229 self.loop_depth = 0;
230 for (name, default) in params {
231 if let Some(default) = default {
232 self.check_expr(default);
233 }
234 self.scopes.declare(name, SymbolKind::Var);
235 }
236 for stmt in body {
237 self.check_stmt(stmt);
238 }
239 self.loop_depth = saved_loop_depth;
240 self.scopes.pop();
241 }
242
243 fn check_stmt(&mut self, stmt: &Stmt) {
244 match stmt {
245 Stmt::VarDecl {
246 name, initializer, ..
247 } => {
248 if let Some(init) = initializer {
251 self.check_expr(init);
252 }
253 if self.scopes.declare(name, SymbolKind::Var).is_some() {
254 self.emit(
255 "duplicate-declaration",
256 None,
257 format!(
258 "`{name}` is already declared in this scope (use `:=` to reassign)"
259 ),
260 );
261 }
262 }
263 Stmt::Assignment { target, value } => {
264 self.check_expr(value);
265 self.check_assign_target(target);
266 }
267 Stmt::TupleAssignment { names, value } => {
268 self.check_expr(value);
269 for name in names {
270 if self.scopes.declare(name, SymbolKind::Var).is_some() {
271 self.emit(
272 "duplicate-declaration",
273 None,
274 format!("`{name}` is already declared in this scope"),
275 );
276 }
277 }
278 }
279 Stmt::Expression(expr) => self.check_expr(expr),
280 Stmt::If {
281 condition,
282 then_branch,
283 else_if_branches,
284 else_branch,
285 } => {
286 self.check_expr(condition);
287 self.block(then_branch);
288 for (cond, body) in else_if_branches {
289 self.check_expr(cond);
290 self.block(body);
291 }
292 if let Some(body) = else_branch {
293 self.block(body);
294 }
295 }
296 Stmt::For {
297 var_name,
298 from,
299 to,
300 body,
301 } => {
302 self.check_expr(from);
303 self.check_expr(to);
304 self.scopes.push();
305 self.scopes.declare(var_name, SymbolKind::Var);
306 self.loop_body(body);
307 self.scopes.pop();
308 }
309 Stmt::ForIn {
310 index_var,
311 item_var,
312 collection,
313 body,
314 } => {
315 self.check_expr(collection);
316 self.scopes.push();
317 if let Some(idx) = index_var {
318 self.scopes.declare(idx, SymbolKind::Var);
319 }
320 self.scopes.declare(item_var, SymbolKind::Var);
321 self.loop_body(body);
322 self.scopes.pop();
323 }
324 Stmt::While { condition, body } => {
325 self.check_expr(condition);
326 self.scopes.push();
327 self.loop_body(body);
328 self.scopes.pop();
329 }
330 Stmt::Break => self.check_loop_keyword("break"),
331 Stmt::Continue => self.check_loop_keyword("continue"),
332 Stmt::FunctionDecl {
333 name, params, body, ..
334 } => {
335 self.declare(name, SymbolKind::Function);
337 self.function_body(
338 params
339 .iter()
340 .map(|p| (p.name.as_str(), p.default_value.as_ref())),
341 body,
342 );
343 }
344 Stmt::MethodDecl { params, body, .. } => {
345 self.function_body(
348 params
349 .iter()
350 .map(|p| (p.name.as_str(), p.default_value.as_ref())),
351 body,
352 );
353 }
354 Stmt::TypeDecl { name, .. } => self.declare(name, SymbolKind::Type),
355 Stmt::EnumDecl { name, .. } => self.declare(name, SymbolKind::Enum),
356 Stmt::Import { alias, .. } => self.declare(alias, SymbolKind::Import),
357 Stmt::Export { .. } => {}
359 }
360 }
361
362 fn check_loop_keyword(&mut self, keyword: &str) {
363 if self.loop_depth == 0 {
364 self.emit(
365 "break-outside-loop",
366 None,
367 format!("`{keyword}` is only valid inside a loop"),
368 );
369 }
370 }
371
372 fn check_assign_target(&mut self, target: &Expr) {
374 match target {
375 Expr::Variable(name) => match self.scopes.resolve(name) {
376 Some(SymbolKind::Var) => {}
377 Some(other) => self.emit(
378 "invalid-assignment",
379 None,
380 format!("cannot assign to `{name}`, it is a {}", other.noun()),
381 ),
382 None if self.is_builtin(name) => self.emit(
383 "reassign-builtin",
384 None,
385 format!("cannot reassign built-in `{name}`"),
386 ),
387 None => self.emit(
388 "invalid-assignment",
389 None,
390 format!(
391 "cannot assign to undeclared variable `{name}` (declare it with `=` first)"
392 ),
393 ),
394 },
395 other => self.check_expr(other),
397 }
398 }
399
400 fn check_expr(&mut self, expr: &Expr) {
401 match expr {
402 Expr::Variable(name) => {
403 if self.scopes.resolve(name).is_none() && !self.is_builtin(name) {
404 self.emit(
405 "undeclared-variable",
406 None,
407 format!("undeclared variable `{name}`"),
408 );
409 }
410 }
411 Expr::Call {
412 callee, args, loc, ..
413 } => {
414 if let Expr::Variable(fname) = callee.as_ref() {
415 let pos = loc.position();
416 if is_global_only(fname) && !self.scopes.at_global() {
417 self.emit(
418 "global-scope-required",
419 pos,
420 format!("`{fname}` may only be called in the global scope"),
421 );
422 }
423 if SCRIPT_DECLARATIONS.contains(&fname.as_str()) {
424 self.declarations += 1;
425 if self.declarations > 1 {
426 self.emit(
427 "duplicate-declaration",
428 pos,
429 "a script may only have one indicator/strategy/library declaration",
430 );
431 }
432 }
433 if self.scopes.resolve(fname).is_none() && !self.is_builtin(fname) {
434 self.emit(
435 "unknown-function",
436 pos,
437 format!("unknown function `{fname}`"),
438 );
439 }
440 } else {
441 self.check_expr(callee);
442 }
443 if let Some(signature) = self.builtin_signature(callee) {
444 let name = callee_name(callee);
445 self.check_builtin_args(&name, &signature, args, loc.position());
446 }
447 for arg in args {
448 match arg {
449 Argument::Positional(e) => self.check_expr(e),
450 Argument::Named { value, .. } => self.check_expr(value),
451 }
452 }
453 }
454 Expr::Binary { left, right, .. } => {
455 self.check_expr(left);
456 self.check_expr(right);
457 }
458 Expr::Unary { expr, .. } => self.check_expr(expr),
459 Expr::Index { expr, index } => {
460 self.check_expr(expr);
461 self.check_expr(index);
462 }
463 Expr::MemberAccess { object, .. } => self.check_expr(object),
466 Expr::Ternary {
467 condition,
468 then_expr,
469 else_expr,
470 } => {
471 self.check_expr(condition);
472 self.check_expr(then_expr);
473 self.check_expr(else_expr);
474 }
475 Expr::IfExpr {
476 condition,
477 then_expr,
478 else_if_branches,
479 else_expr,
480 } => {
481 self.check_expr(condition);
482 self.check_expr(then_expr);
483 for (cond, e) in else_if_branches {
484 self.check_expr(cond);
485 self.check_expr(e);
486 }
487 if let Some(e) = else_expr {
488 self.check_expr(e);
489 }
490 }
491 Expr::Switch { value, cases } => {
492 self.check_expr(value);
493 for (pattern, result) in cases {
494 self.check_expr(pattern);
495 self.check_expr(result);
496 }
497 }
498 Expr::Array(elements) => {
499 for e in elements {
500 self.check_expr(e);
501 }
502 }
503 Expr::Function { params, body } => {
505 self.function_body(
506 params
507 .iter()
508 .map(|p| (p.name.as_str(), p.default_value.as_ref())),
509 body,
510 );
511 }
512 Expr::Literal(_) => {}
513 }
514 }
515}