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 | 169x 49x 80x 80x 80x 14x 21x 21x 21x 8x 13519x 13519x 294x 5224x 5224x 5093x 5093x 15x 15x 15x 5078x 5078x 116x 4962x 4885x 77x 223x 223x 133x 90x 28x 62x 20x 42x 101x 96x 96x 3137x 43x 40x 40x 25x | /**
* Utility class for analyzing literal values in the parse tree.
*
* Extracted from DivisionByZeroAnalyzer and FloatModuloAnalyzer
* to eliminate duplicate literal checking code.
*/
import * as Parser from "../PARSE/2-Parse/grammar/CNextParser";
/**
* A float literal's shape as the grammar spells one: digits, an optional
* fraction, an optional exponent and an optional width suffix, captured. The
* grammar also requires a fraction or an exponent, which `[.eE]` asserts
* beside it -- in the pattern, that requirement doubled every arm.
*/
const FLOAT_LITERAL = /^\d+(?:\.\d+)?(?:[eE][+-]?\d+)?(?:[fF](32|64))?$/;
/**
* Static utility methods for literal analysis
*/
class LiteralUtils {
/**
* Check if a literal represents zero, in any C-Next literal format
* (decimal, hex, binary, float, each with or without a suffix).
*
* @param ctx - The literal context from the parse tree
* @returns true if the literal is zero
*/
static isZero(ctx: Parser.LiteralContext): boolean {
return LiteralUtils.isZeroText(ctx.getText());
}
/**
* Whether a numeric literal's text is zero, by its VALUE rather than its
* spelling -- for a node's text, and for a const's initializer, which
* arrives as text from a declaration that may be in another file (#1664
* box 7). The spelling test this replaced missed `00`, `0x00`, `0x00u8`
* and every suffixed float (`0.0f32`). Anything that is not a numeric
* literal (a string, a char, `false`) is not zero.
*/
static isZeroText(text: string): boolean {
const trimmed = text.trim();
const integer = LiteralUtils.parseIntegerLiteral(
trimmed.replace(/[uUiI](?:8|16|32|64)$/, ""),
);
if (integer !== undefined) return integer === 0;
return (
LiteralUtils.floatLiteralWidth(trimmed) !== null &&
LiteralUtils.isFloatZero(trimmed.replace(/[fF](?:32|64)$/, ""))
);
}
/**
* Check if a float literal string represents zero.
* Issue #1010: Detect float zero for division-by-zero checking.
*
* Handles: 0.0, .0, 0., 0.0f, 0.0F, 0.0e0, 0.0E0, etc.
*
* @param text - The float literal text
* @returns true if the float is zero
*/
static isFloatZero(text: string): boolean {
// Remove optional float suffix (f, F)
const withoutSuffix = text.replace(/[fF]$/, "");
// Parse to number and check if zero
const value = Number.parseFloat(withoutSuffix);
return value === 0;
}
/**
* Check if a literal is a floating-point number.
*
* @param ctx - The literal context from the parse tree
* @returns true if the literal is a float
*/
static isFloat(ctx: Parser.LiteralContext): boolean {
return LiteralUtils.floatLiteralWidth(ctx.getText()) !== null;
}
/**
* The width of a floating literal's type, read from its text: 32 for
* `2.5f32`, 64 for `2.5f64` and for an unsuffixed `2.5` (a C `double`).
* Null when the text is not a floating literal.
*
* #1668: the one decision of whether a literal is floating. It used to be
* made three ways, each by a partial test that some other literal also
* passes. A trailing `f32` also ends the hex integer `0xFF32`, which the
* render layer then emitted as `0xFf`. A `.` also occurs in the char literal
* `'.'`, which E0804 then rejected as a floating modulo operand. So the text
* must match the grammar's FLOAT_LITERAL / SUFFIXED_FLOAT shape as a whole.
*/
static floatLiteralWidth(text: string): 32 | 64 | null {
const match = FLOAT_LITERAL.exec(text);
if (!match || !/[.eE]/.test(text)) return null;
return match[1] === "32" ? 32 : 64;
}
/**
* ADR-024: Get the type from a literal (suffixed or unsuffixed).
*
* #1668: moved here from 2.2's ExpressionTypeResolver so that 2.1 can type a
* composite's literal operand with the same rule 2.2 uses. Composite typing is
* one decision (`CompositeType`) that both layers read, and it treats a
* floating operand as a veto, so both have to agree on which literal operands
* are floating.
*/
static typeOf(ctx: Parser.LiteralContext): string | null {
const text = ctx.getText();
if (text === "true" || text === "false") return "bool";
const suffixMatch = /([uUiI])(8|16|32|64)$/.exec(text);
if (suffixMatch) {
const signChar = suffixMatch[1].toLowerCase();
const width = suffixMatch[2];
return (signChar === "u" ? "u" : "i") + width;
}
// A plain float literal (no suffix) has type double in C
const floatWidth = LiteralUtils.floatLiteralWidth(text);
if (floatWidth !== null) {
return `f${floatWidth}`;
}
// Plain integer literals (no suffix) have type int in C
// Check for integer: starts with digit, no decimal point
if (/^\d+$/.test(text) || /^0[xXbBoO][\da-fA-F]+$/.test(text)) {
return "int";
}
return null;
}
/**
* Parse an integer literal string to a numeric value.
*
* Handles all C-Next integer formats:
* - Decimal: 42, -17
* - Hex: 0x2A, 0X2a
* - Binary: 0b101010, 0B101010
*
* Issue #455: Used for resolving const values in array dimensions.
*
* @param text - The literal text to parse
* @returns The numeric value, or undefined if not a valid integer literal
*/
static parseIntegerLiteral(text: string): number | undefined {
const trimmed = text.trim();
// Decimal integer (including negative)
if (/^-?\d+$/.test(trimmed)) {
return Number.parseInt(trimmed, 10);
}
// Hex literal (0x or 0X prefix)
if (/^0[xX][0-9a-fA-F]+$/.test(trimmed)) {
return Number.parseInt(trimmed, 16);
}
// Binary literal (0b or 0B prefix)
if (/^0[bB][01]+$/.test(trimmed)) {
return Number.parseInt(trimmed.substring(2), 2);
}
return undefined;
}
/**
* Whether a folded value is exact (#1760 review). A double holds every
* integer up to 2^53 - 1 and rounds past it, so `9007199254740993` parses
* to 2^53. A fold that computed with the rounded value folded
* `BIG - 9007199254740992` to 0, a false E0800, where C computes 1. Every
* fold asks this of each literal it reads and each value it computes, and
* gives no value otherwise: an unknown is never reported against.
*/
static isExactInteger(value: number | undefined): value is number {
return value !== undefined && Number.isSafeInteger(value);
}
/** An integer literal's value for a fold, when that value is exact */
static exactIntegerLiteral(text: string): number | undefined {
const value = LiteralUtils.parseIntegerLiteral(text);
return LiteralUtils.isExactInteger(value) ? value : undefined;
}
/**
* ADR-044 "Integer Literals" (owner ruling, 2026-10-03, #1728): whether a
* decimal literal, with or without its type suffix, has a leading zero.
* C-Next has no octal literal, so 2.1 reports one (E0912), and no reading
* gives it a value before then -- not the decimal one, which C, reading
* octal, would disagree with. The one rule every reading asks.
*/
static hasLeadingZero(decimalText: string): boolean {
return /^0\d/.test(decimalText);
}
/**
* The value of an integer literal as written in C-Next source, with any
* width suffix (`9u8`, `3i32`): decimal, hex or binary. Null for anything
* else, and for a leading-zero literal (`010`, E0912).
*/
static integerValue(text: string): number | null {
if (LiteralUtils.hasLeadingZero(text)) return null;
const match = /^(0[xX][\da-fA-F]+|0[bB][01]+|\d+)([uUiI]\d+)?$/.exec(text);
if (match === null) return null;
return LiteralUtils.parseIntegerLiteral(match[1]) ?? null;
}
}
export default LiteralUtils;
|