All files / utils ConstExprLowering.ts

94.11% Statements 96/102
92.3% Branches 96/104
100% Functions 17/17
93.25% Lines 83/89

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                                                108x         108x                                           108x                       563x       563x           2425x               197x 23x   174x     174x     174x     174x     174x       2425x 2425x 1x             2424x                 28418x 3237x   25181x 25181x 25181x 636x 636x       636x             25181x       636x       3268x 3268x 22x 21x 21x 2x 21x                         3246x 3246x 3246x     76x 3246x 3246x   91x   3246x 8x         68x 68x 3246x     68x           83x                 5x                   12x 12x                       868x 866x 857x       3170x 3170x 3158x 3158x 10x             3148x 29x   3119x 3119x 817x 817x   800x                 2302x 15x               2287x       5x   2282x 8x 8x       8x           2274x 2251x           23x 3x   20x 1x         67x                         67x 67x              
/**
 * An expression's parse tree as a `TConstExpr` (#1175, #1669), so a value can
 * be computed from it after the tree is gone.
 *
 * Lowered from the TREE, never from `getText()`: ANTLR joins tokens with no
 * separator, and the joined text re-lexes as different tokens -- `1 - -1`
 * becomes `1--1`, `(A < -1)` becomes `(A<-1)`. The tree already has the
 * operators and operands apart, so no text is ever re-read.
 *
 * Lowering decides nothing about values. A name stays a name, a call stays a
 * call, and `ConstantEvaluator` says what each is worth.
 */
import * as Parser from "../PARSE/2-Parse/grammar/CNextParser";
import ParserUtils from "./ParserUtils";
import invariant from "./invariant";
import ConstantEvaluator from "./ConstantEvaluator";
import LiteralUtils from "./LiteralUtils";
import LengthProperty from "./LengthProperty";
import ELEMENT_STEP from "../types/ELEMENT_STEP";
import type IConstantEnvironment from "./types/IConstantEnvironment";
import type { ParserRuleContext } from "antlr4ng";
import type TConstExpr from "../types/TConstExpr";
 
/** A name with no member, subscript or operator: C reads it as written */
const BARE_NAME = /^[A-Za-z_]\w*$/;
 
type TBinaryOp = Extract<TConstExpr, { kind: "binary" }>["op"];
type TOtherWhat = Extract<TConstExpr, { kind: "other" }>["what"];
 
const BINARY_OPS: ReadonlySet<string> = new Set<TBinaryOp>([
  "*",
  "/",
  "%",
  "+",
  "-",
  "<<",
  ">>",
  "&",
  "^",
  "|",
  "<",
  ">",
  "<=",
  ">=",
  "=",
  "!=",
  "&&",
  "||",
]);
 
/** A suffixed literal: its value, and the type its suffix names */
const SUFFIXED = /^(.+?)([uUiI](?:8|16|32|64))$/;
 
class ConstExprLowering {
  /**
   * What an expression written here is worth, as an integer: the 2.x entry,
   * where the tree is in hand. Undefined when it has no value -- a runtime
   * operand, a C macro -- which is a real answer, not a failure.
   */
  static valueOf(
    ctx: Parser.ExpressionContext,
    env: IConstantEnvironment,
  ): number | undefined {
    const result = ConstantEvaluator.evaluate(
      ConstExprLowering.lower(ctx),
      env,
    );
    return result.kind === "value"
      ? ConstantEvaluator.toNumber(result.value)
      : undefined;
  }
 
  static lower(ctx: Parser.ExpressionContext): TConstExpr {
    return ConstExprLowering.ternary(ctx.ternaryExpression());
  }
 
  /**
   * Any expression-level node: what a caller holding an operand rather than
   * an `expression` lowers (a shift amount, a slice bound, a subscript)
   */
  static lowerNode(node: ParserRuleContext): TConstExpr {
    if (node instanceof Parser.ExpressionContext) {
      return ConstExprLowering.lower(node);
    }
    Iif (node instanceof Parser.TernaryExpressionContext) {
      return ConstExprLowering.ternary(node);
    }
    Iif (node instanceof Parser.PostfixExpressionContext) {
      return ConstExprLowering.postfix(node);
    }
    Iif (node instanceof Parser.PrimaryExpressionContext) {
      return ConstExprLowering.primary(node);
    }
    Iif (node instanceof Parser.LiteralContext) {
      return ConstExprLowering.literal(node);
    }
    return ConstExprLowering.chain(node);
  }
 
  private static ternary(ternary: Parser.TernaryExpressionContext): TConstExpr {
    const parts = ternary.orExpression();
    if (parts.length === 3) {
      return {
        kind: "ternary",
        condition: ConstExprLowering.chain(parts[0]),
        whenTrue: ConstExprLowering.chain(parts[1]),
        whenFalse: ConstExprLowering.chain(parts[2]),
      };
    }
    return ConstExprLowering.chain(parts[0]);
  }
 
  /**
   * One left-associative binary level, `a op b op c` as `(a op b) op c`. Every
   * level from `||` down to `*` has this shape: its operands are rule contexts
   * and its operators the terminals between them.
   */
  private static chain(ctx: ParserRuleContext): TConstExpr {
    if (ctx instanceof Parser.UnaryExpressionContext) {
      return ConstExprLowering.unary(ctx);
    }
    const children = ctx.children;
    let result = ConstExprLowering.chain(children[0] as ParserRuleContext);
    for (let i = 1; i + 1 < children.length; i += 2) {
      const op = children[i].getText();
      invariant(
        ConstExprLowering.isBinaryOp(op),
        `every operator of a binary expression level is a C-Next binary operator, not '${op}'`,
      );
      result = {
        kind: "binary",
        op,
        left: result,
        right: ConstExprLowering.chain(children[i + 1] as ParserRuleContext),
      };
    }
    return result;
  }
 
  private static isBinaryOp(op: string): op is TBinaryOp {
    return BINARY_OPS.has(op);
  }
 
  private static unary(ctx: Parser.UnaryExpressionContext): TConstExpr {
    const postfix = ctx.postfixExpression();
    if (postfix) return ConstExprLowering.postfix(postfix);
    if (ctx.BITAND()) return ConstExprLowering.other("address", ctx);
    let op: "-" | "~" | "!" = "!";
    if (ctx.MINUS()) op = "-";
    else if (ctx.BITNOT()) op = "~";
    return {
      kind: "unary",
      op,
      operand: ConstExprLowering.unary(ctx.unaryExpression()!),
    };
  }
 
  /**
   * A name, possibly qualified (`this.N`, `Scope.N`, `EColor.COUNT`,
   * `buf.element_count`). A subscript or a call anywhere in the chain makes it
   * something no constant contains.
   */
  private static postfix(ctx: Parser.PostfixExpressionContext): TConstExpr {
    const primary = ctx.primaryExpression();
    const ops = ctx.postfixOp();
    if (ops.length === 0) return ConstExprLowering.primary(primary);
    // ADR-058: a length property is the same for every element, so before
    // one a subscript is a step into the element, whatever its index
    const last = ops.at(-1)!.IDENTIFIER()?.getText();
    const measured = last !== undefined && LengthProperty.isLength(last);
    const blocking = ops.find(
      (op) =>
        op.DOT() === null && !(measured && ConstExprLowering.isElement(op)),
    );
    if (blocking) {
      return ConstExprLowering.other(
        blocking.LBRACKET() ? "subscript" : "call",
        ctx,
      );
    }
    const root = ConstExprLowering.root(primary);
    const head = primary.IDENTIFIER()?.getText();
    Iif (root === null && head === undefined) {
      return ConstExprLowering.other("member", ctx);
    }
    return {
      kind: "name",
      root,
      path: [
        ...(head === undefined ? [] : [head]),
        ...ops.map((op) =>
          op.DOT() === null ? ELEMENT_STEP : op.IDENTIFIER()!.getText(),
        ),
      ],
      at: ParserUtils.getPosition(ctx),
    };
  }
 
  /** `[i]`, not a bit range `[start, width]` */
  private static isElement(op: Parser.PostfixOpContext): boolean {
    return op.LBRACKET() !== null && op.expression().length === 1;
  }
 
  /**
   * `sizeof` of a type, or of a bare name C reads the same way, is C's to
   * size. Any other expression has no value here: its text joins tokens
   * (`sizeof(word - -1)` read back as `word--1`), and C-Next does not write an
   * expression for C structurally inside `sizeof` (#1863 review).
   */
  private static sizeOf(ctx: Parser.SizeofExpressionContext): TConstExpr {
    const named = ctx.type() ?? ctx.expression()!;
    return ctx.type() || BARE_NAME.test(named.getText())
      ? {
          kind: "sizeof",
          typeName: named.getText(),
          at: ParserUtils.getPosition(ctx),
        }
      : ConstExprLowering.other("sizeofExpression", ctx);
  }
 
  private static root(
    ctx: Parser.PrimaryExpressionContext,
  ): "this" | "global" | null {
    if (ctx.THIS()) return "this";
    if (ctx.GLOBAL()) return "global";
    return null;
  }
 
  private static primary(ctx: Parser.PrimaryExpressionContext): TConstExpr {
    const sizeOf = ctx.sizeofExpression();
    if (sizeOf) return ConstExprLowering.sizeOf(sizeOf);
    const cast = ctx.castExpression();
    if (cast) {
      return {
        kind: "cast",
        typeName: cast.type().getText(),
        operand: ConstExprLowering.unary(cast.unaryExpression()),
        at: ParserUtils.getPosition(cast),
      };
    }
    if (ctx.structInitializer() || ctx.arrayInitializer()) {
      return ConstExprLowering.other("initializer", ctx);
    }
    const literal = ctx.literal();
    if (literal) return ConstExprLowering.literal(literal);
    const expression = ctx.expression();
    if (expression) return ConstExprLowering.lower(expression);
    // A bare name, or `this` / `global` alone, which name no value
    return {
      kind: "name",
      root: ConstExprLowering.root(ctx),
      path: ctx.IDENTIFIER() ? [ctx.IDENTIFIER()!.getText()] : [],
      at: ParserUtils.getPosition(ctx),
    };
  }
 
  private static literal(ctx: Parser.LiteralContext): TConstExpr {
    if (ctx.TRUE() || ctx.FALSE()) {
      return {
        kind: "literal",
        digits: ctx.TRUE() ? "1" : "0",
        typeName: "bool",
      };
    }
    // ADR-044: there is no octal literal, so a leading zero is E0912 in 2.1;
    // until then it has no value, suffixed (`010u8`) or not
    if (
      (ctx.INTEGER_LITERAL() || ctx.SUFFIXED_DECIMAL()) &&
      LiteralUtils.hasLeadingZero(ctx.getText())
    ) {
      return ConstExprLowering.other("leadingZero", ctx);
    }
    if (ctx.SUFFIXED_DECIMAL() || ctx.SUFFIXED_HEX() || ctx.SUFFIXED_BINARY()) {
      const match = SUFFIXED.exec(ctx.getText());
      invariant(
        match,
        `a suffixed integer literal ends in its suffix: ${ctx.getText()}`,
      );
      return {
        kind: "literal",
        digits: BigInt(match[1]).toString(),
        typeName: match[2].toLowerCase(),
      };
    }
    if (ctx.INTEGER_LITERAL() || ctx.HEX_LITERAL() || ctx.BINARY_LITERAL()) {
      return {
        kind: "literal",
        digits: BigInt(ctx.getText()).toString(),
        typeName: null,
      };
    }
    if (ctx.FLOAT_LITERAL() || ctx.SUFFIXED_FLOAT()) {
      return ConstExprLowering.other("float", ctx);
    }
    if (ctx.STRING_LITERAL()) return ConstExprLowering.other("string", ctx);
    Eif (ctx.CHAR_LITERAL()) return ConstExprLowering.other("character", ctx);
    return ConstExprLowering.other("address", ctx);
  }
 
  private static other(what: TOtherWhat, ctx: ParserRuleContext): TConstExpr {
    return {
      kind: "other",
      what,
      spelling: ConstExprLowering.asWritten(ctx),
      at: ParserUtils.getPosition(ctx),
    };
  }
 
  /**
   * A node's source text, spaces and all, for a message: `getText()` joins
   * tokens, so `word - -1` would read back as `word--1`
   */
  private static asWritten(ctx: ParserRuleContext): string {
    const stream = ctx.start?.inputStream;
    return stream && ctx.start && ctx.stop
      ? stream.getTextFromRange(ctx.start.start, ctx.stop.stop)
      : ctx.getText();
  }
}
 
export default ConstExprLowering;