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                                  34x     34x                           3303x                   296x 296x                               1725x 707x   211x 211x 211x   211x   1725x 1725x 1725x                         1240x                                     1171x 425x     31x 11x     20x                           966x 966x                                   129x 660x 114x         15x 13x 13x 12x 12x         3x                       125x 125x          
/**
 * 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;