All files / utils ExpressionUtils.ts

96.82% Statements 122/126
93.24% Branches 69/74
100% Functions 29/29
100% Lines 97/97

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 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333                                                              24x 24x   15x                           274x 274x   229x 229x       227x 227x 72x     155x                             278x 278x     278x 278x     276x 276x     275x 275x     274x 274x     271x 271x     267x 267x     266x 266x     266x 266x     266x 266x     265x 265x     242x 242x   231x                       244x 244x   138x 138x                             2x                     2326x 2326x 2334x 2344x 2407x 2501x 2505x 2509x 2515x                             2324x 2525x 2624x                         84x 84x   83x 83x   66x                       93x 94x   73x           94x 97x   74x           97x 122x   77x           122x 164x   102x           164x 165x   144x           165x 166x   145x           166x 167x   146x           167x 167x   147x           167x 169x   147x           169x 171x   149x               173x 173x 2x       171x 171x   171x 40x 20x     151x          
/**
 * Utility class for traversing expression trees in the parse tree.
 *
 * C-Next's expression grammar is deeply nested:
 *   expression -> ternaryExpression -> orExpression -> andExpression ->
 *   equalityExpression -> relationalExpression -> bitwiseOrExpression ->
 *   bitwiseXorExpression -> bitwiseAndExpression -> shiftExpression ->
 *   additiveExpression -> multiplicativeExpression -> unaryExpression ->
 *   postfixExpression -> primaryExpression -> literal/identifier
 *
 * This utility extracts common traversal patterns used by multiple analyzers.
 */
 
import * as Parser from "../transpiler/logic/parser/grammar/CNextParser";
 
/**
 * Static utility methods for expression tree traversal
 */
class ExpressionUtils {
  /**
   * Extract the literal from a simple expression (if it's just a literal).
   *
   * Returns null if the expression is complex (has operators, function calls, etc.)
   * Only returns a value if the expression resolves to a single literal.
   *
   * @param ctx - The expression context from the parse tree
   * @returns The literal context, or null if not a simple literal expression
   */
  static extractLiteral(
    ctx: Parser.ExpressionContext,
  ): Parser.LiteralContext | null {
    const primary = ExpressionUtils.extractPrimaryExpression(ctx);
    if (!primary) return null;
 
    return primary.literal() ?? null;
  }
 
  /**
   * Extract the primary expression from an expression (if it's simple).
   *
   * Returns null if the expression is complex (has operators, function calls, etc.)
   *
   * @param ctx - The expression context from the parse tree
   * @returns The primary expression context, or null if complex
   */
  static extractPrimaryExpression(
    ctx: Parser.ExpressionContext,
  ): Parser.PrimaryExpressionContext | null {
    const unary = ExpressionUtils.extractUnaryExpression(ctx);
    if (!unary) return null;
 
    const postfix = unary.postfixExpression();
    if (!postfix) return null;
 
    // If there are postfix operations (array access, member access, function call),
    // this is not a simple primary expression
    const postfixOps = postfix.postfixOp();
    if (postfixOps && postfixOps.length > 0) {
      return null;
    }
 
    return postfix.primaryExpression() ?? null;
  }
 
  /**
   * Extract the unary expression from an expression (if it's simple).
   *
   * Returns null if the expression has binary operators at any level.
   *
   * @param ctx - The expression context from the parse tree
   * @returns The unary expression context, or null if complex
   */
  static extractUnaryExpression(
    ctx: Parser.ExpressionContext,
  ): Parser.UnaryExpressionContext | null {
    // expression -> ternaryExpression
    const ternary = ctx.ternaryExpression();
    Iif (!ternary) return null;
 
    // ternaryExpression has multiple orExpressions if it's a ternary (condition ? a : b)
    const orExprs = ternary.orExpression();
    if (orExprs?.length !== 1) return null;
 
    // orExpression -> andExpression (multiple = has || operator)
    const andExprs = orExprs[0].andExpression();
    if (andExprs?.length !== 1) return null;
 
    // andExpression -> equalityExpression (multiple = has && operator)
    const eqExprs = andExprs[0].equalityExpression();
    if (eqExprs?.length !== 1) return null;
 
    // equalityExpression -> relationalExpression (multiple = has = or != operator)
    const relExprs = eqExprs[0].relationalExpression();
    if (relExprs?.length !== 1) return null;
 
    // relationalExpression -> bitwiseOrExpression (multiple = has <, >, <=, >= operator)
    const bitorExprs = relExprs[0].bitwiseOrExpression();
    if (bitorExprs?.length !== 1) return null;
 
    // bitwiseOrExpression -> bitwiseXorExpression (multiple = has | operator)
    const bitxorExprs = bitorExprs[0].bitwiseXorExpression();
    if (bitxorExprs?.length !== 1) return null;
 
    // bitwiseXorExpression -> bitwiseAndExpression (multiple = has ^ operator)
    const bitandExprs = bitxorExprs[0].bitwiseAndExpression();
    Iif (bitandExprs?.length !== 1) return null;
 
    // bitwiseAndExpression -> shiftExpression (multiple = has & operator)
    const shiftExprs = bitandExprs[0].shiftExpression();
    Iif (shiftExprs?.length !== 1) return null;
 
    // shiftExpression -> additiveExpression (multiple = has << or >> operator)
    const addExprs = shiftExprs[0].additiveExpression();
    if (addExprs?.length !== 1) return null;
 
    // additiveExpression -> multiplicativeExpression (multiple = has + or - operator)
    const multExprs = addExprs[0].multiplicativeExpression();
    if (multExprs?.length !== 1) return null;
 
    // multiplicativeExpression -> unaryExpression (multiple = has *, /, % operator)
    const unaryExprs = multExprs[0].unaryExpression();
    if (unaryExprs?.length !== 1) return null;
 
    return unaryExprs[0];
  }
 
  /**
   * Extract the identifier from a simple expression (if it's just an identifier).
   *
   * Returns null if the expression is complex or not a simple identifier.
   *
   * @param ctx - The expression context from the parse tree
   * @returns The identifier text, or null if not a simple identifier expression
   */
  static extractIdentifier(ctx: Parser.ExpressionContext): string | null {
    const primary = ExpressionUtils.extractPrimaryExpression(ctx);
    if (!primary) return null;
 
    const identifier = primary.IDENTIFIER();
    return identifier?.getText() ?? null;
  }
 
  /**
   * Collect all additive expressions from a ternary expression.
   *
   * Uses flatMap chains to traverse the expression grammar efficiently.
   * Useful for analyzers that need to examine operands at the additive level.
   *
   * @param ctx - The ternary expression context
   * @returns Array of all additive expression contexts in the tree
   */
  static collectAdditiveExpressions(
    ctx: Parser.TernaryExpressionContext,
  ): Parser.AdditiveExpressionContext[] {
    return ExpressionUtils.collectAdditiveFromOrExprs(ctx.orExpression());
  }
 
  /**
   * Collect additive expressions from an array of orExpression contexts.
   *
   * Internal helper that performs the actual flatMap traversal.
   */
  private static collectAdditiveFromOrExprs(
    orExprs: Parser.OrExpressionContext[],
  ): Parser.AdditiveExpressionContext[] {
    return orExprs
      .flatMap((or) => or.andExpression())
      .flatMap((and) => and.equalityExpression())
      .flatMap((eq) => eq.relationalExpression())
      .flatMap((rel) => rel.bitwiseOrExpression())
      .flatMap((bor) => bor.bitwiseXorExpression())
      .flatMap((bxor) => bxor.bitwiseAndExpression())
      .flatMap((band) => band.shiftExpression())
      .flatMap((shift) => shift.additiveExpression());
  }
 
  /**
   * Collect all unary expressions from an orExpression context.
   *
   * Traverses through the entire expression hierarchy to find all unary expressions.
   * Useful for analyzers that need to examine leaf operands.
   *
   * @param orExpr - The orExpression context
   * @returns Array of all unary expression contexts in the tree
   */
  static collectUnaryFromOrExpr(
    orExpr: Parser.OrExpressionContext,
  ): Parser.UnaryExpressionContext[] {
    return ExpressionUtils.collectAdditiveFromOrExprs([orExpr])
      .flatMap((add) => add.multiplicativeExpression())
      .flatMap((mul) => mul.unaryExpression());
  }
 
  /**
   * ADR-023: Check if an expression contains a function call anywhere in its tree.
   *
   * This traverses the full expression hierarchy to find any postfix expression
   * with an argument list (function call syntax).
   *
   * @param ctx - The expression context to check
   * @returns true if a function call is found anywhere in the expression
   */
  static hasFunctionCall(ctx: Parser.ExpressionContext): boolean {
    const ternary = ctx.ternaryExpression();
    if (!ternary) return false;
 
    for (const or of ternary.orExpression()) {
      if (ExpressionUtils.hasFunctionCallInOr(or)) return true;
    }
    return false;
  }
 
  /**
   * Issue #254: Check if an orExpression contains a function call.
   *
   * Used for ternary conditions where the condition is an OrExpressionContext.
   *
   * @param ctx - The orExpression context to check
   * @returns true if a function call is found anywhere in the expression
   */
  static hasFunctionCallInOr(ctx: Parser.OrExpressionContext): boolean {
    for (const and of ctx.andExpression()) {
      if (ExpressionUtils.hasFunctionCallInAnd(and)) return true;
    }
    return false;
  }
 
  private static hasFunctionCallInAnd(
    ctx: Parser.AndExpressionContext,
  ): boolean {
    for (const eq of ctx.equalityExpression()) {
      if (ExpressionUtils.hasFunctionCallInEquality(eq)) return true;
    }
    return false;
  }
 
  private static hasFunctionCallInEquality(
    ctx: Parser.EqualityExpressionContext,
  ): boolean {
    for (const rel of ctx.relationalExpression()) {
      if (ExpressionUtils.hasFunctionCallInRelational(rel)) return true;
    }
    return false;
  }
 
  private static hasFunctionCallInRelational(
    ctx: Parser.RelationalExpressionContext,
  ): boolean {
    for (const bor of ctx.bitwiseOrExpression()) {
      if (ExpressionUtils.hasFunctionCallInBitwiseOr(bor)) return true;
    }
    return false;
  }
 
  private static hasFunctionCallInBitwiseOr(
    ctx: Parser.BitwiseOrExpressionContext,
  ): boolean {
    for (const bxor of ctx.bitwiseXorExpression()) {
      if (ExpressionUtils.hasFunctionCallInBitwiseXor(bxor)) return true;
    }
    return false;
  }
 
  private static hasFunctionCallInBitwiseXor(
    ctx: Parser.BitwiseXorExpressionContext,
  ): boolean {
    for (const band of ctx.bitwiseAndExpression()) {
      if (ExpressionUtils.hasFunctionCallInBitwiseAnd(band)) return true;
    }
    return false;
  }
 
  private static hasFunctionCallInBitwiseAnd(
    ctx: Parser.BitwiseAndExpressionContext,
  ): boolean {
    for (const shift of ctx.shiftExpression()) {
      if (ExpressionUtils.hasFunctionCallInShift(shift)) return true;
    }
    return false;
  }
 
  private static hasFunctionCallInShift(
    ctx: Parser.ShiftExpressionContext,
  ): boolean {
    for (const add of ctx.additiveExpression()) {
      if (ExpressionUtils.hasFunctionCallInAdditive(add)) return true;
    }
    return false;
  }
 
  private static hasFunctionCallInAdditive(
    ctx: Parser.AdditiveExpressionContext,
  ): boolean {
    for (const mult of ctx.multiplicativeExpression()) {
      if (ExpressionUtils.hasFunctionCallInMultiplicative(mult)) return true;
    }
    return false;
  }
 
  private static hasFunctionCallInMultiplicative(
    ctx: Parser.MultiplicativeExpressionContext,
  ): boolean {
    for (const unary of ctx.unaryExpression()) {
      if (ExpressionUtils.hasFunctionCallInUnary(unary)) return true;
    }
    return false;
  }
 
  private static hasFunctionCallInUnary(
    ctx: Parser.UnaryExpressionContext,
  ): boolean {
    // Issue #366: Handle nested unary operators (!, -, ~, &) that wrap function calls
    // e.g., !!isReady() or -getValue()
    const nestedUnary = ctx.unaryExpression();
    if (nestedUnary) {
      return ExpressionUtils.hasFunctionCallInUnary(nestedUnary);
    }
 
    // Base case: check postfixExpression
    const postfix = ctx.postfixExpression();
    Iif (!postfix) return false;
 
    for (const op of postfix.postfixOp()) {
      if (op.argumentList() || op.getText().startsWith("(")) {
        return true;
      }
    }
    return false;
  }
}
 
export default ExpressionUtils;