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 | 24x 24x 24x 24x 24x 6x 6x 6x 6x 260x 6x 6x 254x 6x 6x 6x 248x 246x 243x 248x 31x 31x 31x 288x 288x 288x 288x 3x 285x 3x 282x 8x 23x 22x 260x 22x 25x 3x 6x 6x 3x 46x 46x 46x 37x 45x 45x 45x 45x 45x 45x 84x 84x 84x 84x 84x 84x 20x 20x 20x 20x 20x 45x 40x 40x 10x 10x | /**
* Arduino-IDE-compatible function prototypes for the local g++ build.
*
* The Arduino IDE declares every sketch function before the first function
* definition, so a helper may be called before it is defined. The prototypes
* go there, not at the top of the file, because their signatures may use
* types and constants that the sketch defines first (a `typedef`, or `M` in
* `double mat[M][N]`). Default arguments are left out: C++ allows them only
* once, and the definition already carries them.
*/
import { maskCommentsPreservingLines } from "@shared/source-project";
const FUNCTION_DEFINITION = /^((?:\w[\w*&]*\s+)*\w[\w*&]*)\s+(\w+)\s*(\([^)]*\))\s*\{/gm;
const NOT_A_FUNCTION = new Set(["if", "else", "while", "for", "do", "switch", "case", "return", "break", "continue", "goto", "class", "struct", "union", "enum", "namespace", "typedef"]);
const ENTRYPOINTS = new Set(["setup", "loop", "main"]);
const OPENING_BRACKETS = "([{";
const CLOSING_BRACKETS = ")]}";
export interface ForwardDeclarationPlan {
/** Newline-separated prototypes, empty when the sketch needs none. */
declarations: string;
/** Offset in the code where the prototypes belong; -1 when there is no function definition. */
insertAt: number;
}
interface ParameterScan {
parts: string[];
current: string;
depth: number;
quote: string | null;
escaped: boolean;
}
function scanQuoted(scan: ParameterScan, character: string): void {
scan.current += character;
Iif (scan.escaped) scan.escaped = false;
else Iif (character === "\\") scan.escaped = true;
else if (character === scan.quote) scan.quote = null;
}
function scanParameterCharacter(scan: ParameterScan, character: string): void {
if (scan.quote) {
scanQuoted(scan, character);
return;
}
if (character === "," && scan.depth === 0) {
scan.parts.push(scan.current);
scan.current = "";
return;
}
if (character === '"' || character === "'") scan.quote = character;
else if (OPENING_BRACKETS.includes(character)) scan.depth++;
else if (CLOSING_BRACKETS.includes(character)) scan.depth--;
scan.current += character;
}
/** Index of the `=` that starts a default argument, ignoring `=` inside quotes or brackets. */
function defaultArgumentStart(parameter: string): number {
let depth = 0;
let quote: string | null = null;
for (let index = 0; index < parameter.length; index++) {
const character = parameter[index];
Iif (quote) {
if (character === "\\") index++;
else if (character === quote) quote = null;
} else Iif (character === '"' || character === "'") {
quote = character;
} else if (OPENING_BRACKETS.includes(character)) {
depth++;
} else if (CLOSING_BRACKETS.includes(character)) {
depth--;
} else if (character === "=" && depth === 0) {
return index;
}
}
return -1;
}
/** Removes default arguments from a parenthesized parameter list such as `(int a, byte b = 2)`. */
export function stripDefaultArguments(parameterList: string): string {
const scan: ParameterScan = { parts: [], current: "", depth: 0, quote: null, escaped: false };
for (const character of parameterList.slice(1, -1)) scanParameterCharacter(scan, character);
scan.parts.push(scan.current);
if (!scan.parts.some((part) => defaultArgumentStart(part) >= 0)) return parameterList;
const parameters = scan.parts.map((part) => {
const start = defaultArgumentStart(part);
return (start >= 0 ? part.slice(0, start) : part).trim();
});
return `(${parameters.join(", ")})`;
}
function lineIndexAt(code: string, offset: number): number {
let line = 0;
for (let index = code.indexOf("\n"); index >= 0 && index < offset; index = code.indexOf("\n", index + 1)) line++;
return line;
}
/**
* Plans the prototypes for every function defined in `code`.
*
* `isSketchLine` tells which lines belong to the sketch itself rather than to
* an inlined header; the prototypes go before the first function the sketch
* defines, like in the Arduino IDE. Without a sketch function they go before
* the first function at all.
*/
export function planForwardDeclarations(
code: string,
isSketchLine: (lineIndex: number) => boolean = () => true,
): ForwardDeclarationPlan {
const masked = maskCommentsPreservingLines(code);
const seen = new Set<string>();
const declarations: string[] = [];
let firstDefinition = -1;
let firstSketchDefinition = -1;
for (const match of masked.matchAll(FUNCTION_DEFINITION)) {
const name = match[2];
Iif (NOT_A_FUNCTION.has(name)) continue;
const offset = match.index ?? 0;
if (firstDefinition < 0) firstDefinition = offset;
if (firstSketchDefinition < 0 && isSketchLine(lineIndexAt(masked, offset))) firstSketchDefinition = offset;
if (ENTRYPOINTS.has(name)) continue;
const parameters = stripDefaultArguments(match[3]);
const signature = `${name}${parameters}`;
Iif (seen.has(signature)) continue;
seen.add(signature);
declarations.push(`${match[1].trim()} ${signature};`);
}
return {
declarations: declarations.join("\n"),
insertAt: firstSketchDefinition >= 0 ? firstSketchDefinition : firstDefinition,
};
}
/** Inserts the planned prototypes into `code`; returns `code` unchanged when none are needed. */
export function insertForwardDeclarations(
code: string,
isSketchLine?: (lineIndex: number) => boolean,
): string {
const plan = planForwardDeclarations(code, isSketchLine);
if (!plan.declarations || plan.insertAt < 0) return code;
const section = `// --- Forward declarations (auto-generated, mirrors Arduino IDE behaviour) ---\n${plan.declarations}\n\n`;
return `${code.slice(0, plan.insertAt)}${section}${code.slice(plan.insertAt)}`;
}
|