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 | 25x 25x 25x 25x 25x 760x 1268x 634x 634x 634x 638x 638x 634x 634x 122x 122x 122x 122x 122x 122x 122x 122x 275x 122x 122x 634x 634x 531x 531x 531x 634x 5943x 2453x 131x 3359x | import { stripComments } from "@shared/parser-patterns";
import type { FactRequirement } from "./curriculum-schema";
export type SketchType = "char" | "byte" | "int" | "long" | "float";
export interface SketchFacts {
readonly typesUsed: readonly SketchType[];
readonly arrays: readonly { name: string; elementType: SketchType; elementCount: number }[];
readonly serialCalls: readonly ("print" | "write")[];
}
export interface SketchFactExtractor {
extract(code: string): SketchFacts;
}
const TYPE_ALIASES: Readonly<Record<string, SketchType>> = {
char: "char",
byte: "byte",
uint8_t: "byte",
int: "int",
"unsigned int": "int",
long: "long",
"unsigned long": "long",
float: "float",
};
const TYPE_PATTERN = /\b(?:(?:unsigned|signed)\s+)?(?:char|byte|uint8_t|int|long|float)\b/g;
const ARRAY_PATTERN = /\b(char|byte|uint8_t|int|long|float)\s+([A-Za-z_]\w*)\s*\[([^\]]*)\]/g;
const INITIALIZER_PATTERN = /^\s*=\s*\{([^}]*)\}/;
const SERIAL_PATTERN = /\bSerial\s*\.\s*(print|println|write)\s*\(/gi;
function canonicalType(value: string): SketchType | undefined {
return TYPE_ALIASES[value.trim().toLowerCase()];
}
function distinct<T>(values: readonly T[]): readonly T[] {
return [...new Set(values)];
}
export class DefaultSketchFactExtractor implements SketchFactExtractor {
extract(code: string): SketchFacts {
const clean = stripComments(code);
const types: SketchType[] = [];
for (const match of clean.matchAll(TYPE_PATTERN)) {
const type = canonicalType(match[0]);
Eif (type) types.push(type);
}
const arrays: Array<{ name: string; elementType: SketchType; elementCount: number }> = [];
for (const match of clean.matchAll(ARRAY_PATTERN)) {
const elementType = canonicalType(match[1] ?? "");
const name = match[2];
Iif (!elementType || !name) continue;
const initializerMatch = INITIALIZER_PATTERN.exec(clean.slice((match.index ?? 0) + match[0].length));
const initializer = initializerMatch?.[1]?.trim();
const declaredSize = Number.parseInt(match[3]?.trim() ?? "", 10);
let elementCount = 0;
if (initializer) {
elementCount = initializer.split(",").map((item) => item.trim()).filter(Boolean).length;
} else Eif (Number.isSafeInteger(declaredSize) && declaredSize > 0) {
elementCount = declaredSize;
}
arrays.push({ name, elementType, elementCount });
types.push(elementType);
}
const serialCalls: ("print" | "write")[] = [];
for (const match of clean.matchAll(SERIAL_PATTERN)) {
const operation = match[1]?.toLowerCase();
if (operation === "print" || operation === "println") serialCalls.push("print");
if (operation === "write") serialCalls.push("write");
}
return {
typesUsed: distinct(types),
arrays,
serialCalls: distinct(serialCalls),
};
}
}
export function matchesFactRequirement(facts: SketchFacts, requirement: FactRequirement): boolean {
switch (requirement.fact) {
case "type-used":
return facts.typesUsed.some((type) => requirement.values?.includes(type) ?? false);
case "array-declared":
return facts.arrays.some((array) => requirement.elementTypes?.includes(array.elementType) ?? true);
case "serial-call":
return facts.serialCalls.some((operation) => requirement.values?.includes(operation) ?? false);
}
}
|