All files / transpiler/output/codegen/utils ExpressionUnwrapper.ts

98.3% Statements 58/59
97.22% Branches 35/36
100% Functions 7/7
100% Lines 42/42

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                                                                    559x 559x   559x   550x 550x   548x 548x   547x 547x   546x 546x   544x 544x   541x 541x   538x 538x   534x                       531x 531x   507x   499x 499x   470x 470x   454x                             529x 529x 441x                       2x                       28x 28x   27x   27x                             8x 8x   6x   6x   3x 3x 3x               2x          
/**
 * Expression Unwrapper Utility
 *
 * Navigates through the C-Next expression tree hierarchy to extract
 * inner expressions. The C-Next grammar has a deep expression hierarchy:
 *
 * expression -> ternary -> or -> and -> equality -> relational ->
 * bitwiseOr -> bitwiseXor -> bitwiseAnd -> shift -> additive ->
 * multiplicative -> unary -> postfix
 *
 * This utility extracts inner expressions when they are "simple" (single term
 * at each level), returning null if any level has multiple terms (indicating
 * a binary operation at that level).
 *
 * Issue #707: Extracted from CodeGenerator.ts and TypeResolver.ts to
 * eliminate code duplication.
 */
 
import * as Parser from "../../../logic/parser/grammar/CNextParser";
 
/**
 * Utility class for navigating expression tree hierarchy
 */
class ExpressionUnwrapper {
  /**
   * Navigate from ExpressionContext to ShiftExpressionContext.
   * This is the common navigation path shared by getPostfixExpression,
   * getUnaryExpression, and getAdditiveExpression.
   *
   * Returns null if expression has multiple terms at any level above shift.
   */
  private static navigateToShift(
    ctx: Parser.ExpressionContext,
  ): Parser.ShiftExpressionContext | null {
    const ternary = ctx.ternaryExpression();
    const orExprs = ternary.orExpression();
    // If it's a ternary (3 orExpressions), we can't get a single expression
    if (orExprs.length !== 1) return null;
 
    const or = orExprs[0];
    if (or.andExpression().length !== 1) return null;
 
    const and = or.andExpression()[0];
    if (and.equalityExpression().length !== 1) return null;
 
    const eq = and.equalityExpression()[0];
    if (eq.relationalExpression().length !== 1) return null;
 
    const rel = eq.relationalExpression()[0];
    if (rel.bitwiseOrExpression().length !== 1) return null;
 
    const bor = rel.bitwiseOrExpression()[0];
    if (bor.bitwiseXorExpression().length !== 1) return null;
 
    const bxor = bor.bitwiseXorExpression()[0];
    if (bxor.bitwiseAndExpression().length !== 1) return null;
 
    const band = bxor.bitwiseAndExpression()[0];
    if (band.shiftExpression().length !== 1) return null;
 
    return band.shiftExpression()[0];
  }
 
  /**
   * Navigate from ExpressionContext to UnaryExpressionContext.
   * Common helper for getPostfixExpression and getUnaryExpression.
   *
   * Returns null if expression has multiple terms at any level.
   */
  private static navigateToUnary(
    ctx: Parser.ExpressionContext,
  ): Parser.UnaryExpressionContext | null {
    const shift = this.navigateToShift(ctx);
    if (!shift) return null;
 
    if (shift.additiveExpression().length !== 1) return null;
 
    const add = shift.additiveExpression()[0];
    if (add.multiplicativeExpression().length !== 1) return null;
 
    const mult = add.multiplicativeExpression()[0];
    if (mult.unaryExpression().length !== 1) return null;
 
    return mult.unaryExpression()[0];
  }
 
  /**
   * Navigate from ExpressionContext to PostfixExpressionContext.
   * Returns null if the expression has multiple terms at any level
   * (indicating binary operations).
   *
   * Use this when you need to access the postfix expression for:
   * - Getting the primary expression (identifier, literal)
   * - Checking postfix operators (member access, array indexing)
   */
  static getPostfixExpression(
    ctx: Parser.ExpressionContext,
  ): Parser.PostfixExpressionContext | null {
    const unary = this.navigateToUnary(ctx);
    if (!unary?.postfixExpression()) return null;
    return unary.postfixExpression()!;
  }
 
  /**
   * Navigate from ExpressionContext to UnaryExpressionContext.
   * Returns null if the expression has multiple terms at any level.
   *
   * Use this when you need access to unary operators (!, -, ~, etc.)
   */
  static getUnaryExpression(
    ctx: Parser.ExpressionContext,
  ): Parser.UnaryExpressionContext | null {
    return this.navigateToUnary(ctx);
  }
 
  /**
   * Navigate from ExpressionContext to AdditiveExpressionContext.
   * Returns null if the expression has multiple terms at outer levels.
   *
   * Use this when you need to check for additive operations (+, -)
   */
  static getAdditiveExpression(
    ctx: Parser.ExpressionContext,
  ): Parser.AdditiveExpressionContext | null {
    const shift = this.navigateToShift(ctx);
    if (!shift) return null;
 
    Iif (shift.additiveExpression().length !== 1) return null;
 
    return shift.additiveExpression()[0];
  }
 
  /**
   * Extract a simple identifier from an expression.
   * Returns the identifier name if the expression is a simple variable
   * reference with no postfix operators (member access, indexing).
   * Returns null for complex expressions.
   *
   * Use this for cases like:
   * - Checking if an expression is a specific variable
   * - Parameter lookup
   * - Simple variable references
   */
  static getSimpleIdentifier(ctx: Parser.ExpressionContext): string | null {
    const postfix = this.getPostfixExpression(ctx);
    if (!postfix) return null;
 
    const ops = postfix.postfixOp();
    // Must have no postfix operations (no member access, no indexing)
    if (ops.length !== 0) return null;
 
    const primary = postfix.primaryExpression();
    const id = primary.IDENTIFIER();
    return id ? id.getText() : null;
  }
 
  /**
   * Check if an expression is a simple identifier (variable reference).
   * Convenience method for boolean checks.
   */
  static isSimpleIdentifier(ctx: Parser.ExpressionContext): boolean {
    return this.getSimpleIdentifier(ctx) !== null;
  }
}
 
export default ExpressionUnwrapper;