All files / transpiler/logic/symbols/cnext/collectors FunctionCollector.ts

100% Statements 31/31
88.88% Branches 16/18
100% Functions 4/4
100% Lines 31/31

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                                                                    1123x 1123x     1123x 1123x 1123x 1123x     1123x 1123x   1123x                                                                           1110x     1110x                 1110x   1110x                     1123x 300x 300x 300x 300x 300x     300x 300x     300x 300x 37x 43x 43x 40x 40x   40x     3x         300x                          
/**
 * FunctionCollector - Extracts function declarations from parse trees.
 * Handles return types, parameters, visibility, and signature generation.
 *
 * Produces TType-based IFunctionSymbol with proper IScopeSymbol references.
 */
 
import * as Parser from "../../../parser/grammar/CNextParser";
import ESourceLanguage from "../../../../../utils/types/ESourceLanguage";
import IFunctionSymbol from "../../../../types/symbols/IFunctionSymbol";
import IParameterInfo from "../../../../types/symbols/IParameterInfo";
import IScopeSymbol from "../../../../types/symbols/IScopeSymbol";
import TypeResolver from "../../../../../utils/TypeResolver";
import TypeUtils from "../utils/TypeUtils";
import SymbolRegistry from "../../../../state/SymbolRegistry";
 
class FunctionCollector {
  /**
   * Collect a function declaration and return an IFunctionSymbol.
   *
   * @param ctx The function declaration context
   * @param sourceFile Source file path
   * @param scope The scope this function belongs to (IScopeSymbol)
   * @param body AST reference for the function body
   * @param visibility Visibility for scope functions (default "private")
   * @returns The function symbol with TType-based types and scope reference
   */
  static collect(
    ctx: Parser.FunctionDeclarationContext,
    sourceFile: string,
    scope: IScopeSymbol,
    body: Parser.BlockContext | null,
    visibility: "public" | "private" = "private",
  ): IFunctionSymbol {
    const name = ctx.IDENTIFIER().getText();
    const line = ctx.start?.line ?? 0;
 
    // Get return type string and convert to TType
    const returnTypeCtx = ctx.type();
    const scopeName = scope.name === "" ? undefined : scope.name;
    const returnTypeStr = TypeUtils.getTypeName(returnTypeCtx, scopeName);
    const returnType = TypeResolver.resolve(returnTypeStr);
 
    // Collect parameters with TType
    const params = ctx.parameterList()?.parameter() ?? [];
    const parameters = FunctionCollector.collectParameters(params, scopeName);
 
    return {
      kind: "function",
      name,
      scope,
      parameters,
      returnType,
      visibility,
      body,
      sourceFile,
      sourceLine: line,
      sourceLanguage: ESourceLanguage.CNext,
      isExported: visibility === "public",
    };
  }
 
  /**
   * Collect a function declaration and register it in SymbolRegistry.
   *
   * This method:
   * 1. Gets or creates the appropriate scope in SymbolRegistry
   * 2. Collects the function with TType-based types
   * 3. Registers the function in that scope
   *
   * @param ctx The function declaration context
   * @param sourceFile Source file path
   * @param scopeName Optional scope name for nested functions
   * @param body AST reference for the function body
   * @param visibility Visibility for scope functions (default "private")
   * @returns The function symbol
   */
  static collectAndRegister(
    ctx: Parser.FunctionDeclarationContext,
    sourceFile: string,
    scopeName: string | undefined,
    body: Parser.BlockContext,
    visibility: "public" | "private" = "private",
  ): IFunctionSymbol {
    // 1. Get or create the scope in SymbolRegistry
    const scope = SymbolRegistry.getOrCreateScope(scopeName ?? "");
 
    // 2. Collect function with TType-based types and scope reference
    const symbol = FunctionCollector.collect(
      ctx,
      sourceFile,
      scope,
      body,
      visibility,
    );
 
    // 3. Register in SymbolRegistry
    SymbolRegistry.registerFunction(symbol);
 
    return symbol;
  }
 
  /**
   * Extract parameter information from parameter contexts.
   * Converts type strings to TType.
   */
  private static collectParameters(
    params: Parser.ParameterContext[],
    scopeName?: string,
  ): IParameterInfo[] {
    return params.map((p) => {
      const name = p.IDENTIFIER().getText();
      const typeCtx = p.type();
      const typeStr = TypeUtils.getTypeName(typeCtx, scopeName);
      const type = TypeResolver.resolve(typeStr);
      const isConst = p.constModifier() !== null;
 
      // Check for C-Next style array type (u8[8] param, u8[4][4] param, u8[] param)
      const arrayTypeCtx = typeCtx.arrayType();
      const isArray = arrayTypeCtx !== null;
 
      // Extract array dimensions from arrayType syntax (supports multi-dimensional)
      const arrayDimensions: (number | string)[] = [];
      if (isArray) {
        for (const dim of arrayTypeCtx.arrayTypeDimension()) {
          const sizeExpr = dim.expression();
          if (sizeExpr) {
            const dimStr = sizeExpr.getText();
            const dimNum = Number.parseInt(dimStr, 10);
            // Convert numeric strings to numbers, keep others as strings
            arrayDimensions.push(Number.isNaN(dimNum) ? dimStr : dimNum);
          } else {
            // Unbounded array dimension
            arrayDimensions.push("");
          }
        }
      }
 
      return {
        name,
        type,
        isConst,
        isArray,
        arrayDimensions:
          arrayDimensions.length > 0 ? arrayDimensions : undefined,
      };
    });
  }
}
 
export default FunctionCollector;