Complete sim config boundary coverage

This commit is contained in:
2026-06-11 06:12:06 +08:00
parent 5bd8f12872
commit 1ab5571ae1
76 changed files with 18975 additions and 146 deletions

View File

@@ -1,6 +1,7 @@
export { createLinuxCncIniSdk } from "./linuxcnc-ini.js";
export { createLinuxCncInterpSdk } from "./linuxcnc-interp.js";
export {
analyzeIniRuntimeBoundaries,
planIniFileContextStaging,
planSimConfigStaging,
} from "./sim-config-staging.js";

View File

@@ -172,6 +172,197 @@ function remapNgcNames(iniValues) {
return names;
}
function sectionValues(iniValues, section) {
const prefix = `${section.toUpperCase()}.`;
const values = [];
for (const [key, entries] of iniValues.entries()) {
if (!key.startsWith(prefix)) {
continue;
}
for (const value of entries) {
values.push({ key: key.slice(prefix.length), value });
}
}
return values;
}
function valuesMatching(iniValues, section, keys) {
const wanted = new Set(keys.map((key) => key.toUpperCase()));
return sectionValues(iniValues, section)
.filter((entry) => wanted.has(entry.key))
.map((entry) => entry.value);
}
function looksLikePythonReference(value) {
return /(^|\s|=|:)["']?[^"'\s]*\.py(["'\s]|$)/i.test(value);
}
function userMCodesInText(text) {
const codes = new Set();
for (const match of text.matchAll(/(?<![A-Za-z0-9_])M\s*(1\d\d)(?![0-9])/gi)) {
codes.add(`M${match[1]}`);
}
return [...codes].sort();
}
function vendoredUserMCodes({
manifestEntries,
sourceDir,
normalizedSearchRoot,
userMPathValues,
}) {
const manifestSet = new Set(manifestEntries);
const executableCodes = new Set();
for (const dirEntry of userMPathValues.flatMap(splitSearchPath)) {
if (dirEntry.startsWith("/")) {
continue;
}
const sourceUserMDir = normalizeRel(`${sourceDir}/${dirEntry}`);
const prefix = sourceUserMDir ? `${sourceUserMDir}/` : "";
for (const sourceRel of manifestEntries) {
if (
sourceRel.startsWith(prefix) &&
!sourceRel.slice(prefix.length).includes("/") &&
isUserMCodePath(sourceRel)
) {
executableCodes.add(basename(sourceRel).toUpperCase());
}
}
}
for (const dirEntry of userMPathValues.flatMap(splitSearchPath)) {
if (dirEntry.startsWith("/")) {
continue;
}
const sourceUserMDir = normalizeRel(`${sourceDir}/${dirEntry}`);
for (let code = 100; code <= 199; code += 1) {
const candidate = findUpwardByBasename(
manifestSet,
sourceUserMDir,
`M${code}`,
normalizedSearchRoot,
);
if (candidate) {
executableCodes.add(`M${code}`);
}
}
}
return [...executableCodes].sort();
}
export function analyzeIniRuntimeBoundaries({
manifestText = "",
sourceRootRel,
sourceSearchRootRel = sourceRootRel,
iniFile = "",
iniText,
executionTexts = [],
}) {
const manifestEntries = cleanManifest(manifestText);
const iniValues = parseIni(iniText);
const normalizedSourceRoot = normalizeRel(sourceRootRel);
const normalizedSearchRoot = normalizeRel(sourceSearchRootRel);
const sourceDir = normalizeRel(`${normalizedSourceRoot}/${dirname(iniFile)}`);
const userMPathValues = allIniValues(iniValues, "RS274NGC", "USER_M_PATH");
const halValues = valuesMatching(iniValues, "HAL", [
"HALFILE",
"HALCMD",
"POSTGUI_HALFILE",
"HALUI",
]);
const displayValues = valuesMatching(iniValues, "DISPLAY", [
"DISPLAY",
"PYVCP",
"GLADEVCP",
"EMBED_TAB_COMMAND",
]);
const halUiMdiCommands = allIniValues(iniValues, "HALUI", "MDI_COMMAND");
const dbProgram = firstIniValue(iniValues, "EMCIO", "DB_PROGRAM");
const remapValues = allIniValues(iniValues, "RS274NGC", "REMAP");
const pythonRemapReferences = [
...sectionValues(iniValues, "PYTHON").map((entry) => entry.value),
...remapValues.filter((value) => /(?:^|\s)python=/i.test(value)),
];
const pythonUiReferences = [
...displayValues.filter(looksLikePythonReference),
...(dbProgram && looksLikePythonReference(dbProgram) ? [dbProgram] : []),
];
const pythonReferences = [...pythonRemapReferences, ...pythonUiReferences];
const vendoredUserMCodeList = vendoredUserMCodes({
manifestEntries,
sourceDir,
normalizedSearchRoot,
userMPathValues,
});
const vendoredUserMCodeSet = new Set(vendoredUserMCodeList);
const hasUserMPath = userMPathValues.length > 0;
const executionUserMCodes = [...new Set(executionTexts.flatMap(userMCodesInText))].sort();
const unstagedExecutionUserMCodes = executionUserMCodes
.filter((code) => !vendoredUserMCodeSet.has(code));
const hasExternalUserMUse = hasUserMPath && unstagedExecutionUserMCodes.length > 0;
const dependencies = [];
if (dbProgram) {
dependencies.push("tool_database_process");
}
if (halValues.length > 0) {
dependencies.push("hal_process");
}
if (displayValues.some((value) => !/^axis$/i.test(value))) {
dependencies.push("ui_process");
}
if (halUiMdiCommands.length > 0) {
dependencies.push("halui_mdi_process");
}
if (pythonReferences.length > 0) {
dependencies.push("python_runtime");
}
if (hasExternalUserMUse) {
dependencies.push("external_user_m_process");
}
let recommendedBlockedKind = "-";
if (dbProgram) {
recommendedBlockedKind = "L4-TOOL-DB";
} else if (hasExternalUserMUse) {
recommendedBlockedKind = "L4-USER-M-PROCESS";
} else if (pythonRemapReferences.length > 0) {
recommendedBlockedKind = "L4-PYTHON-REMAP";
}
return {
dependencies: [...new Set(dependencies)].sort(),
recommendedBlockedKind,
toolDatabaseProgram: dbProgram ?? "",
halRuntime: {
values: halValues,
requiresProcess: halValues.length > 0,
},
uiRuntime: {
values: displayValues,
requiresProcess: displayValues.some((value) => !/^axis$/i.test(value)),
},
haluiRuntime: {
mdiCommands: halUiMdiCommands,
requiresProcess: halUiMdiCommands.length > 0,
},
userMRuntime: {
paths: userMPathValues,
vendoredExecutableCount: vendoredUserMCodeList.length,
executionCodes: executionUserMCodes,
unstagedExecutionCodes: unstagedExecutionUserMCodes,
requiresExternalProcess: hasExternalUserMUse,
},
pythonRuntime: {
references: pythonReferences,
remapReferences: pythonRemapReferences,
uiReferences: pythonUiReferences,
requiresProcess: pythonReferences.length > 0,
},
};
}
export function planSimConfigStaging({
manifestText,
machineRel,