All files / vsl/scope scope.js

6.38% Statements 3/47
4.76% Branches 1/21
11.11% Functions 1/9
7.5% Lines 3/40
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 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167      1x                                                                                       597x             597x                                                                                                                                                                                                                                
/** @private */
// This counts the amount of unique IDs. This is suffixes to always ensure a
// unique name which makes life much easier.
let ID_COUNTER = 0;
 
/**
 * Wraps a scope, encapsulates mangling, get/set, and overloading. Check if what
 * you want can be done with an ASTTool and this is a lightweight wrapper and
 * does not implement any recursion/traversing.
 * 
 * ### Implementation
 * This is implemented with two primary fields:
 *  - ids: [String: Id]
 *  - types: [Type]
 * 
 * An ID is used so an O(1) lookup time can be obtained for variables and
 * identifiers. However for types, such are complex and therefore a simple string
 * lookup won't suffice, a sequential search must be done as this would also be
 * used for checking type and declarations. For example `a: Int -> Void` and
 * `a: Double -> Void` are both unique and must be disambiguated.
 * 
 * The generated IR does not specifically create a unique identifier for each,
 * fields are still mangles and due to compile-time inheritance. `super` would
 * result in conflicts when overriding certain classes. Therefore `super.foo`
 * if a `self.foo` exists would refer to the `super` field.
 * 
 * Operators are also compiled to functions. That said, the transformation pass
 * converts the desired unmangled `Int.==` to a `add` instruction directly.
 * 
 * ### Hashing
 * 
 * Essentially we have a `HashTable<ID, LinkedList<T: ScopeItem>>` where we can
 * obtain a `O(1)` lookup time to obtain all possible candidates. Might actually
 * not be a linked list but hash table always use linked list so why not.
 * 
 */
export default class Scope {
    /**
     * Initalizes an empty scope. 
     * @param {Scope} [parent=null] - The parent scope of this. Use `hoistTo` to
     *     join two scopes.
     */
    constructor(parentScope: Scope = null) {
        /**
         * Contains all scope data. DO NOT directly modify.
         * @type {Map<string, ScopeItem>}
         */
        this.ids = new Map();
        
        /**
         * The scope that is the immediate parent of this.
         * @protected
         * @type {?Scope}
         */
        this.parentScope = parentScope;
    }
    
    /**
     * Returns all {@link ScopeItem}s in the scope with the given root ID. Used
     * for obtaining all candidate items for some processes.
     * 
     * @param {string} id - Returns the matching item from the scope.
     * @return {?ScopeItem[]} null if the item could not be obtained
     */
    getAll(id: string): ?ScopeItem[] {
        let items = this.ids.get(id);
        if (this.parentScope !== null) {
            let res = this.parentScope && this.parentScope.getAll(id);
            if (res !== null) items = items.concat(res);
        }
        return items;
    }
    
    /**
     * Matches an associated `ScopeItem`. Returns null if it can't find that 
     * reference.
     * 
     * @param {ScopeItem} item - An item to lookup for an applicable matching
     *     scope item. 
     * @return {?ScopeItem} A reference to the matching scope item
     */
    get(item: ScopeItem): ?ScopeItem {
        let candidates = this.getAll(item.rootId);
        if (!candidates) return null;
        
        for (let i = 0; i < candidates.length; i++) {
            if (candidates[i].equal(item)) return candidates[i];
        }
        
        return null;
    }
    
    /**
     * Determines whether the **current** scope has a candidate for a given
     * template.
     * 
     * @param {ScopeItem} item - The item to check if a candidate exists for it
     *     remember, this only checks in the current scope!
     * @return {boolean} Whether or not it exists
     */
    has(item: ScopeItem): boolean {
        let candidates = this.getAll(item.rootId);
        if (!candidates) return false;
        
        for (let i = 0; i < candidates.length; i++) {
            if (candidates[i].equal(item)) return true;
        }
        
        return false;
    }
    
    /**
     * Sets a key in this scope to the {@link ScopeItem}. Note: This DOES NOT
     * update an existing key, this will return `false` is there is a duplicate.
     * 
     * @param {ScopeItem} item - The item to add to this scope.
     * @return {boolean} this will return `false` if the item has already been
     *     declared in the scope. In that case you should throw an error
     *     otherwise major borks could happen.
     */
    set(item: ScopeItem): boolean {
        let candidates = this.get(item.rootId);
        if (candidates) {
            candidates.push(item);
            return candidates.get(item) === item;
        } else {
            this.ids.set(item.rootId, [item])
            return true;
        }
    }
    
    /**
     * Helper function which generates a scope's visualization. Used for
     * debugging and reference
     * 
     * @return {string} The visualized scope. 
     */
    toString() {
        let res = "";
        const format = (name) => name.constructor.name.replace(/^Scope(.+)Item$/, "$1");
        
        for (let [id, candidates] of this.ids) {
            let str = `├ ${id}`;
            
            if (candidates.length > 1) {
                candidates.forEach(candidate => {
                    str += `\n   ├ ${format(candidate)}`;
                    str += `\n     ├ ${candidate.toString()}`
                });
            } else {
                str += ` (${format(candidates[0])})`;
                str += `\n   ├ ${candidates[0].toString()}`;
            }
            
            res += "\n" + str;
        }
        
        let prefix = "";
        if (this.parentScope !== null) {
            prefix = parentScope.toString();
        } else {
            prefix = "Root";
        }
        
        return prefix + res.split("\n").map(line => " " + line).join("\n");
    }
}