All files / transform scopetraverser.js

100% Statements 12/12
77.78% Branches 7/9
100% Functions 2/2
100% Lines 11/11
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88                                                                          6x                   6x                       70x     70x             60x     14x 8x     14x       60x     60x   60x    
import Traverser from './traverser';
import t from '../parser/nodes';
 
/**
 * A traverser which pre-populates a hosited scope pass through node processing.
 * The \\((V\_n)_{n\in V'}\\) in a static scope-lookup senario. This will create
 * "intermediate scopes" \\(\left\\{v \subseteq V : \left|v\right| < n\right\\}\\)
 * which will generally be defined as \\(I\\).
 * 
 * This means that while \\(I \subseteq V\\), you can always assume:
 * 
 * $$\exists i \in I : (V : I_i) \equiv i$$
 * 
 * This means that if:
 * 
 * $$I_{n \in V \setminus I} \perp V$$
 * 
 * You know something funky is going on. In terms of cyclic dependence, this
 * would update a reference when \\(V_{n \in V \setminus I}\\) is accessed. So
 * such dependencies can be resolved.
 * 
 * $$  \neg(\Gamma\_{i \in (V \setminus I)} \wedge \Gamma\_{ i\perp \left\\{j \in I \right\\}} \rightarrow \Delta\_{(V \setminus I)\perp I}), \Gamma_{I\_j \perp \left\\{k \in \left(V \setminus I\right) \right\\}}\vdash P\_E(i),\Delta $$
 * 
 * Where \\(P_E\\) defines such a dependency.
 */
export default class ScopeTraverser extends Traverser {
    
    /**
     * Instantiates a traverser object. Note: This must be subclassed to be
     * used. See {@link Transformer} or {@link ASTGarbageCollector} for more
     * information.
     * 
     * @param {boolean} shouldProcess - Whether or not the node should be setup
     *     with special data. (Note: if false, only assume the raw node will be
     *     passed.)
     */
    constructor(shouldProcess: boolean = true) {
        super(shouldProcess);
        
        /**
         * This is basically a stack of the current scope.
         * For a normal app this would look roughy like:
         * [ STL, Libraries, Global ]
         * specify STL to provide the base STL info
         * 
         * @type {CodeBlock[]}
         */
        this.scope = [];
    }
    
    /**
     * Handles updating and application of scope
     * 
     * @param {Node | Node[]} parent - The parent node of the given node being
     *     processed
     * @param {string} name - The name of the current node being processed
     * @override
     */
    processNode(parent: Node | Node[], name: string) {
        let node = parent[name];
        
        // Ignore empty nodes
        if (node === null) return;
        
        // If the parent is a code block, we want to add it to the scope
        // This builds a stack of the scope tree, so for a typical top-level
        // class this might look like:
        //     libvsl, MyClass.vsl, MyClass
        // Each file would have it's own top-level preqeued block.
        if (node instanceof t.CodeBlock) {
            
            // If there is a parent scope, specify it
            if (this.scope.length > 0) {
                node.scope.parentScope = this.scope[this.scope.length - 1].scope;
            }
            
            this.scope.push(node);
        }
        
        // Store the current scope for brevity
        const currentScope = this.scope[this.scope.length - 1];
        
        // Set parent scope for transformers
        node.parentScope = currentScope || null;
        
        super.processNode(parent, name);
    }
}