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 | 585656x 585656x 585656x 585633x 585633x 585624x 585624x 585615x 585615x 585603x 585603x 585589x 585589x 585582x 585582x 585576x 585576x 585568x 585590x 585590x 585504x 585442x 585442x 584867x 584867x 584743x 585588x 585588x 584626x 2x 66x 66x 64x 64x 368x 368x 357x 357x 287x 287x 287x 2x 39x 39x 38x 38x 7x 35x 35x 34x 34x 5x 5x 5x 4x | /**
* 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 "../PARSE/2-Parse/grammar/CNextParser";
/**
* An identifier with exactly one subscript applied, and that subscript's
* index expressions.
*
* Declared here rather than in `src/types/`: it names a parse
* context, and a type file that does so JOINS the population
* `parse-tree-confined-to-parser` gates (#1317). This module is already in it,
* so the shape costs nothing where it is.
*/
interface ISubscriptedIdentifier {
readonly name: string;
/**
* One expression for `s[i]`, two for `s[i, n]` -- the grammar allows no
* other arity.
*/
readonly indexes: readonly Parser.ExpressionContext[];
}
/**
* 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;
}
/**
* The two operand texts of a two-operand `+`, or null for anything else.
*
* #1445: lifted out of `StringOperationsHelper`, whose ADR-045 concatenation
* check was the only caller and which now names no parse type. The operands
* come back as SOURCE TEXT because that is what its question needs -- a
* string literal's length and a declared string's capacity are both answered
* from the name, not from generated code.
*
* Subtraction is rejected on the MINUS token rather than on the text. An
* identifier or a string literal may contain a hyphen, so a
* `getText().includes("-")` test reads `str + "hello-world"` as a
* subtraction and silently declines to concatenate it.
*/
static getAdditionOperandTexts(
ctx: Parser.ExpressionContext,
): readonly [string, string] | null {
const add = this.getAdditiveExpression(ctx);
if (!add) return null;
const operands = add.multiplicativeExpression();
if (operands.length !== 2 || add.MINUS().length > 0) return null;
return [operands[0].getText(), operands[1].getText()];
}
/**
* An identifier with exactly one subscript applied: `s[i]` or `s[i, n]`.
*
* The sibling of `getSimpleIdentifier`, which answers the no-suffix case.
*
* `postfixOp` has four shapes and only the two subscripts carry expressions
* -- a member access carries an IDENTIFIER and a call carries an
* argumentList -- so an empty index list is an exact discriminator rather
* than a heuristic, and `f()[0]` is excluded by the primary not being an
* identifier.
*
* The indexes come back as NODES, not generated code, because generating one
* is not free: it can allocate a C++ temp and queue its declaration, so a
* caller that may discard them has to decide before it pays. ADR-045's
* substring extraction asks the source's capacity first for exactly that
* reason.
*/
static getSubscriptedIdentifier(
ctx: Parser.ExpressionContext,
): ISubscriptedIdentifier | null {
const postfix = this.getPostfixExpression(ctx);
if (!postfix) return null;
const ops = postfix.postfixOp();
if (ops.length !== 1) return null;
const id = postfix.primaryExpression().IDENTIFIER();
const indexes = ops[0].expression();
if (!id || indexes.length === 0) return null;
return { name: id.getText(), indexes };
}
}
export default ExpressionUnwrapper;
|