Complete sim config boundary coverage
This commit is contained in:
@@ -23,6 +23,7 @@ Use `src/index.js` for stable imports:
|
||||
|
||||
```js
|
||||
import {
|
||||
analyzeIniRuntimeBoundaries,
|
||||
createLinuxCncIniSdk,
|
||||
createLinuxCncInterpSdk,
|
||||
planIniFileContextStaging,
|
||||
@@ -104,6 +105,20 @@ source manifest text, machine relative path, INI file name, and INI text. It
|
||||
uses `planIniFileContextStaging()` with a `configs/sim/<machine>` source root
|
||||
and `configs/sim` upward-search boundary.
|
||||
|
||||
`analyzeIniRuntimeBoundaries()` is a host-boundary classifier for LinuxCNC
|
||||
INI-driven runs. It reads INI text plus optional execution text and manifest
|
||||
text, then reports declared HAL, UI, HALUI MDI, Python, tool-database, and
|
||||
external user-M process dependencies. User-M accounting is per execution code:
|
||||
`executionCodes` lists `M100..M199` codes seen in the supplied execution text,
|
||||
while `unstagedExecutionCodes` lists the subset not backed by vendored
|
||||
`USER_M_PATH` files. Python accounting keeps UI/DB references separate from
|
||||
Python remap runtime references, so a UI handler or DB program does not imply
|
||||
Python-remap coverage. It also returns the currently recommended Layer 4
|
||||
blocked kind for hard process boundaries such as `L4-TOOL-DB` and
|
||||
`L4-USER-M-PROCESS`. The classifier is policy/accounting only: it does not
|
||||
execute HAL, task, UI, Python, user-M, or tool-database behavior and does not
|
||||
change interpreter semantics.
|
||||
|
||||
`runFileWithIniContinueOnError()` uses the same LinuxCNC-backed file execution
|
||||
path but keeps the runner loop going after LinuxCNC reports an error, matching
|
||||
upstream `rs274 -n 0` regression tests such as `tests/interp/oword-unwind`.
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user