All files / utils ConstExprPrinter.ts

92.85% Statements 13/14
92.3% Branches 12/13
100% Functions 2/2
92.3% Lines 12/13

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                                  105x       32x 32x 22x     11x   1x   1x       7x   1x   1x                 17x 17x          
/**
 * The C for a constant expression only C can evaluate (#1175): an array
 * dimension naming a macro from an included header (`u8[BUF_SIZE + 1]`).
 *
 * Written from the expression's STRUCTURE, never from its source text. Every
 * part that has a value is written as that value, so a C-Next const (which C
 * cannot see) never reaches the C by name; operators are C's (`=` is `==`);
 * and a negative value is parenthesized, so no two tokens can join into one
 * (`1 - -1` must never become `1--1`). The .c and the .h both print a
 * dimension here, so they cannot disagree about it.
 */
import ConstantEvaluator from "./ConstantEvaluator";
import invariant from "./invariant";
import type IConstantEnvironment from "./types/IConstantEnvironment";
import type TConstExpr from "../types/TConstExpr";
 
/** The C-Next operators C spells differently */
const C_OPERATOR: Readonly<Record<string, string>> = { "=": "==" };
 
class ConstExprPrinter {
  static toC(expr: TConstExpr, env: IConstantEnvironment): string {
    const result = ConstantEvaluator.evaluate(expr, env);
    if (result.kind === "value") return result.value.toString();
    switch (expr.kind) {
      case "name":
        // Only a header's name is left unvalued here, and C spells it as is
        return expr.path.join(".");
      case "sizeof":
        return `sizeof(${env.cTypeName(expr.typeName, expr.at)})`;
      case "cast":
        return `(${env.cTypeName(expr.typeName, expr.at)})${ConstExprPrinter.operand(expr.operand, env)}`;
      case "unary":
        return `${expr.op}${ConstExprPrinter.operand(expr.operand, env)}`;
      case "binary":
        return `${ConstExprPrinter.operand(expr.left, env)} ${C_OPERATOR[expr.op] ?? expr.op} ${ConstExprPrinter.operand(expr.right, env)}`;
      case "ternary":
        return `(${ConstExprPrinter.toC(expr.condition, env)}) ? ${ConstExprPrinter.operand(expr.whenTrue, env)} : ${ConstExprPrinter.operand(expr.whenFalse, env)}`;
      default:
        invariant(
          false,
          `only an expression C can evaluate is printed, not a '${expr.kind}' -- 2.1 rejects one with no value first`,
        );
    }
  }
 
  /** An operand, parenthesized unless it is one token that cannot join another */
  private static operand(expr: TConstExpr, env: IConstantEnvironment): string {
    const text = ConstExprPrinter.toC(expr, env);
    return /^\w+$/.test(text) ? text : `(${text})`;
  }
}
 
export default ConstExprPrinter;