ripex 0.2.0

Multi-language structural parsing and fact extraction.
Documentation
use crate::arena::Arena;
use crate::js::ast::*;
use crate::js::semantic::bindings::BindingKind;
use crate::js::semantic::symbols::SymbolTable;
use crate::js::visit::visitor::Visitor;
use crate::js::visit::walk::*;
use crate::span::Span;

#[derive(Debug, Clone)]
pub struct Scope {
    pub id: usize,
    pub parent: Option<usize>,
    pub children: Vec<usize>,
    pub kind: ScopeKind,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScopeKind {
    Global,
    Function,
    Block,
    Module,
    Class,
}

#[derive(Debug, Clone)]
pub struct ScopeTree {
    scopes: Vec<Scope>,
    symbols: SymbolTable,
}

impl ScopeTree {
    pub fn new() -> Self {
        ScopeTree {
            scopes: Vec::new(),
            symbols: SymbolTable::new(),
        }
    }

    pub fn build(program: &Program, ast: &Arena<Expr>) -> Self {
        let mut builder = ScopeBuilder::new();
        walk_program(&mut builder, program, ast);
        builder.tree
    }

    pub fn scopes(&self) -> &[Scope] {
        &self.scopes
    }

    pub fn symbols(&self) -> &SymbolTable {
        &self.symbols
    }

    pub fn root(&self) -> Option<&Scope> {
        self.scopes.first()
    }
}

impl Default for ScopeTree {
    fn default() -> Self {
        ScopeTree::new()
    }
}

struct ScopeBuilder {
    tree: ScopeTree,
    current_scope: usize,
}

impl ScopeBuilder {
    fn new() -> Self {
        let mut tree = ScopeTree::new();
        tree.scopes.push(Scope {
            id: 0,
            parent: None,
            children: Vec::new(),
            kind: ScopeKind::Global,
        });
        ScopeBuilder {
            tree,
            current_scope: 0,
        }
    }

    fn enter_scope(&mut self, kind: ScopeKind) -> usize {
        let id = self.tree.scopes.len();
        self.tree.scopes.push(Scope {
            id,
            parent: Some(self.current_scope),
            children: Vec::new(),
            kind,
        });
        self.tree.scopes[self.current_scope].children.push(id);
        let prev = self.current_scope;
        self.current_scope = id;
        prev
    }

    fn leave_scope(&mut self, prev: usize) {
        self.current_scope = prev;
    }

    fn declare(&mut self, name: &str, kind: BindingKind, span: Span) {
        self.tree
            .symbols
            .insert(name.to_string(), kind, span, self.current_scope);
    }
}

impl Visitor for ScopeBuilder {
    fn visit_fn_decl(&mut self, decl: &FnDecl, ast: &Arena<Expr>) {
        self.declare(&decl.id.name, BindingKind::Function, decl.span);
        let prev = self.enter_scope(ScopeKind::Function);
        for param in &decl.params {
            self.visit_pat(param, ast);
        }
        if let Some(body) = &decl.body {
            self.visit_block_stmt(body, ast);
        }
        self.leave_scope(prev);
    }

    fn visit_fn_expr(&mut self, expr: &FnExpr, ast: &Arena<Expr>) {
        let prev = self.enter_scope(ScopeKind::Function);
        if let Some(id) = &expr.id {
            self.declare(&id.name, BindingKind::Function, expr.span);
        }
        for param in &expr.params {
            self.visit_pat(param, ast);
        }
        if let Some(body) = &expr.body {
            self.visit_block_stmt(body, ast);
        }
        self.leave_scope(prev);
    }

    fn visit_arrow_expr(&mut self, expr: &ArrowExpr, ast: &Arena<Expr>) {
        let prev = self.enter_scope(ScopeKind::Function);
        for param in &expr.params {
            self.visit_pat(param, ast);
        }
        match &expr.body {
            ArrowBody::Block(block) => self.visit_block_stmt(block, ast),
            ArrowBody::Expr(e) => self.visit_expr(*e, ast),
        }
        self.leave_scope(prev);
    }

    fn visit_class_decl(&mut self, decl: &ClassDecl, ast: &Arena<Expr>) {
        self.declare(&decl.id.name, BindingKind::Class, decl.span);
        let prev = self.enter_scope(ScopeKind::Class);
        if let Some(super_class) = &decl.super_class {
            self.visit_expr(*super_class, ast);
        }
        self.leave_scope(prev);
    }

    fn visit_class_expr(&mut self, expr: &ClassExpr, ast: &Arena<Expr>) {
        let prev = self.enter_scope(ScopeKind::Class);
        if let Some(super_class) = &expr.super_class {
            self.visit_expr(*super_class, ast);
        }
        self.leave_scope(prev);
    }

    fn visit_block_stmt(&mut self, stmt: &BlockStmt, ast: &Arena<Expr>) {
        let prev = self.enter_scope(ScopeKind::Block);
        for s in &stmt.stmts {
            self.visit_stmt(s, ast);
        }
        self.leave_scope(prev);
    }

    fn visit_var_decl(&mut self, decl: &VarDecl, ast: &Arena<Expr>) {
        let kind = match decl.kind {
            VarKind::Var => BindingKind::Var,
            VarKind::Let => BindingKind::Let,
            VarKind::Const => BindingKind::Const,
            VarKind::Using => BindingKind::Let,
        };
        for d in &decl.decls {
            self.declare_from_pat(&d.name, kind, ast);
        }
    }

    fn visit_import_decl(&mut self, decl: &ImportDecl, _ast: &Arena<Expr>) {
        for spec in &decl.specifiers {
            match spec {
                ImportSpecifier::Named(n) => {
                    self.declare(&n.local.name, BindingKind::Import, n.span);
                }
                ImportSpecifier::Default(d) => {
                    self.declare(&d.local.name, BindingKind::Import, d.span);
                }
                ImportSpecifier::Namespace(ns) => {
                    self.declare(&ns.local.name, BindingKind::Import, ns.span);
                }
            }
        }
    }
}

impl ScopeBuilder {
    fn declare_from_pat(&mut self, pat: &Pat, kind: BindingKind, _ast: &Arena<Expr>) {
        match pat {
            Pat::Ident(bi) => {
                self.declare(&bi.id.name, kind, bi.span);
            }
            Pat::Array(ap) => {
                for e in ap.elements.iter().flatten() {
                    self.declare_from_pat(e, kind, _ast);
                }
                if let Some(rest) = &ap.rest {
                    self.declare_from_pat(&rest.arg, kind, _ast);
                }
            }
            Pat::Object(op) => {
                for prop in &op.props {
                    match prop {
                        ObjectPatProp::KeyValue(kv) => self.declare_from_pat(&kv.value, kind, _ast),
                        ObjectPatProp::Shorthand(id) => {
                            self.declare(&id.id.name, kind, id.span);
                        }
                        ObjectPatProp::Rest(rp) => self.declare_from_pat(&rp.arg, kind, _ast),
                    }
                }
                if let Some(rest) = &op.rest {
                    self.declare_from_pat(&rest.arg, kind, _ast);
                }
            }
            Pat::Rest(rp) => self.declare_from_pat(&rp.arg, kind, _ast),
            Pat::Assign(ap) => self.declare_from_pat(&ap.left, kind, _ast),
            _ => {}
        }
    }

    #[allow(dead_code)]
    fn visit_pat_binding(&mut self, pat: &Pat, kind: BindingKind, ast: &Arena<Expr>) {
        self.declare_from_pat(pat, kind, ast);
    }
}