All files / transpiler/state SymbolRegistry.ts

97.36% Statements 37/38
90.9% Branches 20/22
100% Functions 9/9
100% Lines 33/33

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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 168 169 170 171 172 173 174 175 176 177 178 179 180                                  31x     31x                           3070x                   248x 248x                               1545x 599x   196x 196x 196x   196x   1545x 1545x 1545x                         1120x                                     936x 336x     13x 3x     10x                           949x 949x                                   96x 449x 89x         7x 5x 5x 4x 4x         3x                       92x 92x          
/**
 * SymbolRegistry - Central registry for C-Next symbol management
 *
 * Provides centralized storage and lookup for all symbols in the C-Next transpiler.
 *
 * Design decisions:
 * - Static class with global state (reset between transpilation runs)
 * - `getOrCreateScope` handles scope merging across files (same scope name = same object)
 * - `resolveFunction` walks scope chain (current -> parent -> global)
 * - String keys in Maps for lookup, but values are proper symbol objects
 */
import ScopeUtils from "../../utils/ScopeUtils";
import type IScopeSymbol from "../types/symbols/IScopeSymbol";
import type IFunctionSymbol from "../types/symbols/IFunctionSymbol";
 
class SymbolRegistry {
  /** The global scope singleton (recreated on reset) */
  private static globalScope: IScopeSymbol = ScopeUtils.createGlobalScope();
 
  /** Map from scope path (e.g., "Outer.Inner") to scope object */
  private static readonly scopes: Map<string, IScopeSymbol> = new Map();
 
  // ============================================================================
  // Scope Management
  // ============================================================================
 
  /**
   * Get the global scope singleton.
   *
   * The global scope has:
   * - name: "" (empty string)
   * - parent: points to itself (self-reference)
   */
  static getGlobalScope(): IScopeSymbol {
    return this.globalScope;
  }
 
  /**
   * Get a scope by its dotted path without creating it.
   *
   * Use this for read-only lookups where you don't want to create
   * orphaned scopes. Returns null if the scope doesn't exist.
   */
  static getScope(path: string): IScopeSymbol | null {
    Iif (path === "") return this.globalScope;
    return this.scopes.get(path) ?? null;
  }
 
  /**
   * Get or create a scope by its dotted path.
   *
   * For simple names (e.g., "Test"), creates scope with global parent.
   * For dotted paths (e.g., "Outer.Inner"), creates nested scopes.
   *
   * If the scope already exists, returns the existing scope.
   * This enables scope merging across files.
   *
   * Note: This creates scopes that don't exist. For read-only lookups,
   * use getScope() instead to avoid creating orphaned scopes.
   */
  static getOrCreateScope(path: string): IScopeSymbol {
    if (path === "") return this.globalScope;
    if (this.scopes.has(path)) return this.scopes.get(path)!;
 
    const parts = path.split(".");
    const name = parts.pop()!;
    const parentPath = parts.join(".");
    const parent =
      parentPath === "" ? this.globalScope : this.getOrCreateScope(parentPath);
 
    const scope = ScopeUtils.createScope(name, parent);
    this.scopes.set(path, scope);
    return scope;
  }
 
  // ============================================================================
  // Function Management
  // ============================================================================
 
  /**
   * Register a function in its scope.
   *
   * The function is added to the scope's functions array.
   */
  static registerFunction(func: IFunctionSymbol): void {
    func.scope.functions.push(func);
  }
 
  /**
   * Resolve a function by name, walking the scope chain.
   *
   * Searches in order:
   * 1. Current scope
   * 2. Parent scope
   * 3. Parent's parent (recursively)
   * 4. Global scope
   *
   * Returns null if the function is not found.
   */
  static resolveFunction(
    name: string,
    fromScope: IScopeSymbol,
  ): IFunctionSymbol | null {
    // Search in current scope
    const found = fromScope.functions.find((f) => f.name === name);
    if (found) return found;
 
    // Walk up the scope chain (stop when we reach global scope's self-reference)
    if (fromScope !== this.globalScope && fromScope.parent !== fromScope) {
      return this.resolveFunction(name, fromScope.parent);
    }
 
    return null;
  }
 
  // ============================================================================
  // Reset
  // ============================================================================
 
  /**
   * Reset all registry state.
   *
   * Creates a fresh global scope and clears all registered scopes.
   * Call this between transpilation runs.
   */
  static reset(): void {
    this.globalScope = ScopeUtils.createGlobalScope();
    this.scopes.clear();
  }
 
  // ============================================================================
  // Bridge Methods (for gradual migration from string-based lookups)
  // ============================================================================
 
  /**
   * Find a function by its transpiled C name (e.g., "Test_fillData").
   *
   * This is a bridge method for gradual migration. New code should use
   * resolveFunction() with bare names and scope references instead.
   *
   * @param cName Transpiled C function name (e.g., "Test_fillData", "main")
   * @returns The function symbol, or null if not found
   */
  static findByCName(cName: string): IFunctionSymbol | null {
    // Check global scope first (no underscore = global function)
    for (const func of this.globalScope.functions) {
      if (func.name === cName) {
        return func;
      }
    }
 
    // Check all scopes - the C name should match scope_name pattern
    for (const [scopePath, scope] of this.scopes) {
      const prefix = scopePath.replaceAll(".", "_") + "_";
      for (const func of scope.functions) {
        Eif (prefix + func.name === cName) {
          return func;
        }
      }
    }
 
    return null;
  }
 
  /**
   * Get the scope of a function given its transpiled C name.
   *
   * This is a bridge method for gradual migration.
   *
   * @param cName Transpiled C function name
   * @returns The scope the function belongs to, or null if not found
   */
  static getScopeByCFunctionName(cName: string): IScopeSymbol | null {
    const func = this.findByCName(cName);
    return func?.scope ?? null;
  }
}
 
export default SymbolRegistry;