Skip to main content

pine_sema/
scope.rs

1//! The scope/symbol table — Tier 0.
2//!
3//! A stack of lexical scopes. The global program is the bottom scope; every
4//! function body and every `if`/`for`/`while` block pushes a new scope (Pine
5//! locals are visible only within their block). Name resolution walks the stack
6//! from innermost to outermost.
7
8use std::collections::HashMap;
9
10/// What a declared name refers to. This drives rules like "you can't reassign a
11/// function" — only [`SymbolKind::Var`] is a reassignable value.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum SymbolKind {
14    /// A variable (`x = …`, loop variable, tuple binding, parameter).
15    Var,
16    Function,
17    Type,
18    Enum,
19    /// An import alias (`import foo/bar/1 as alias`).
20    Import,
21}
22
23impl SymbolKind {
24    /// A human-readable noun for diagnostics.
25    pub fn noun(self) -> &'static str {
26        match self {
27            SymbolKind::Var => "variable",
28            SymbolKind::Function => "function",
29            SymbolKind::Type => "type",
30            SymbolKind::Enum => "enum",
31            SymbolKind::Import => "import",
32        }
33    }
34}
35
36/// A stack of scopes; the last element is the innermost (current) scope.
37pub struct ScopeStack {
38    scopes: Vec<HashMap<String, SymbolKind>>,
39}
40
41impl ScopeStack {
42    /// Create a stack with a single (global) scope already open.
43    pub fn new() -> Self {
44        Self {
45            scopes: vec![HashMap::new()],
46        }
47    }
48
49    pub fn push(&mut self) {
50        self.scopes.push(HashMap::new());
51    }
52
53    pub fn pop(&mut self) {
54        // The global scope is never popped.
55        debug_assert!(self.scopes.len() > 1, "attempted to pop the global scope");
56        self.scopes.pop();
57    }
58
59    /// True when the current scope is the global one.
60    pub fn at_global(&self) -> bool {
61        self.scopes.len() == 1
62    }
63
64    /// Declare `name` in the current scope. Returns the previously declared
65    /// kind if `name` already exists *in this same scope* (a redeclaration),
66    /// otherwise `None`.
67    pub fn declare(&mut self, name: &str, kind: SymbolKind) -> Option<SymbolKind> {
68        let scope = self
69            .scopes
70            .last_mut()
71            .expect("scope stack always has the global scope");
72        scope.insert(name.to_string(), kind)
73    }
74
75    /// Resolve `name` against all enclosing scopes, innermost first.
76    pub fn resolve(&self, name: &str) -> Option<SymbolKind> {
77        self.scopes
78            .iter()
79            .rev()
80            .find_map(|scope| scope.get(name).copied())
81    }
82}
83
84impl Default for ScopeStack {
85    fn default() -> Self {
86        Self::new()
87    }
88}
89
90/// Functions Pine only permits at **global** scope (never inside `if`, loops, or
91/// function bodies).
92const GLOBAL_ONLY_FUNCTIONS: &[&str] = &[
93    "plot",
94    "plotshape",
95    "plotchar",
96    "plotcandle",
97    "plotbar",
98    "plotarrow",
99    "fill",
100];
101
102/// May `name` only be called at global scope?
103pub fn is_global_only(name: &str) -> bool {
104    GLOBAL_ONLY_FUNCTIONS.contains(&name)
105}