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;
|