按规划继续工作

结论:补齐 INI 派生参数文件和刀具表 OPFS 会话加载,扩大浏览器解释器 file-path fixture 覆盖,并通过 host/WASM/browser 聚合验证。
This commit is contained in:
2026-06-08 09:00:54 +08:00
parent cf0887e0e7
commit e21f11d69f
7 changed files with 214 additions and 25 deletions

View File

@@ -120,7 +120,8 @@ exported C ABI backed by vendored LinuxCNC `Interp::execute()` and
named-parameter file path and negative interpreter fixtures with expected
error text plus absent canonical motion output. It also uses real browser OPFS
storage plus the interpreter SDK to restore and save a LinuxCNC parameter file
through vendored `Interp::restore_parameters()` and `Interp::save_parameters()`.
through vendored `Interp::restore_parameters()` and `Interp::save_parameters()`,
and to load/save LinuxCNC tool tables through vendored `tooldata_common.cc`.
The aggregate host smoke script builds the INI and interpreter-core WASM
artifacts once, then runs the Node WASM smokes, the Node OPFS mock smoke, and
@@ -254,10 +255,11 @@ the raw canonical-event display fed directly by LinuxCNC interpreter WASM
output, the Node parameter/tool-table bridges that copy OPFS text through the
SDK into vendored LinuxCNC file APIs, the Node machine-session bridge that
groups INI, parameter, and tool-table loading, the random-toolchanger flag
derived from vendored LinuxCNC INI boolean parsing and passed through to the
LinuxCNC-backed tool-table ABI, and a browser interpreter smoke that uses the
same session bridge before saving OPFS-backed parameter and tool-table text
through vendored LinuxCNC file APIs.
derived from vendored LinuxCNC INI boolean parsing, INI-derived
`[RS274NGC]PARAMETER_FILE` and `[EMCIO]TOOL_TABLE` file names mapped to OPFS
machine files, and a browser interpreter smoke that uses the same session
bridge before saving OPFS-backed parameter and tool-table text through
vendored LinuxCNC file APIs.
Full browser coverage, full SDK coverage, and richer machine-state validation
remain future work.
@@ -367,12 +369,36 @@ Browser interpreter negative coverage currently includes every fixture under
- `tool_length_offset_not_found`
- `tool_not_found`
Browser interpreter file-path coverage currently includes:
Browser interpreter file-path coverage currently includes the same
`INTERP_FILE_FIXTURES` list as the Node WASM smoke:
- `position_params` through the dedicated `canon_file/` expectation
- `minimal_linear`
- `arc_semantics`
- `length_units`
- `modal_incremental`
- `plane_selection`
- `coordinate_offsets`
- `g53_machine_coordinates`
- `feed_control_modes`
- `probe_semantics`
- `spindle_orient`
- `comment_logging`
- `numbered_params`
- `tool_semantics`
- `tool_table_setup`
- `tool_reload`
- `canned_cycles`
- `cutter_comp_motion`
- `threading_sync`
- `nurbs_g5_semantics`
- `nurbs_g6_semantics`
- `state_tag_motion`
- `canon_runtime_edges`
- `program_end_modal_reset`
- `file_open_reset`
- `percent_file_finish`
- `oword_subroutine`
- `position_params` through the dedicated `canon_file/` expectation
- `namedparam_semantics` through the INI-aware file ABI
All current positive G-code fixtures have browser interpreter smoke coverage

View File

@@ -24,7 +24,7 @@ semantic rewrites:
| --- | --- |
| RTAPI | Minimal compatibility shim in `runtime/core/shims/rtapi.h`. |
| HAL lookup | Standalone HAL adapter for `_hal[...]` named parameter reads. |
| INI lookup | Standalone INI adapter around vendored LinuxCNC INI parser behavior, including boolean conversion through LinuxCNC `iniFindBool()`. |
| INI lookup | Standalone INI adapter around vendored LinuxCNC INI parser behavior, including boolean conversion through LinuxCNC `iniFindBool()` and string lookup for INI-declared machine file names. |
| Canonical output | Canonical calls are captured as test events instead of driving hardware. |
| Feed-rate state | `SET_FEED_RATE` updates standalone canonical runtime state so vendored `convert_length_units()` can read it back through `GET_EXTERNAL_FEED_RATE()` during G20/G21 changes. |
| Tool-data reload | LinuxCNC `RELOAD_TOOLDATA` is captured as a canonical test event; browser/native tool-table reload plumbing remains a future host/runtime adapter. |
@@ -44,7 +44,7 @@ semantic rewrites:
| Kinematics component lifecycle | Kinematics modules are initialized through LinuxCNC module entry points where native runtime probes exist, while HAL component init/ready/exit, HAL pin allocation, and RTAPI module metadata are handled by standalone shims. |
| Go math C/C++ linkage | `genserkins` runtime probing compiles vendored `gomath.c` through a narrow C++ wrapper so LinuxCNC `genserfuncs.c` can link to the upstream Go math symbols without editing vendored source. |
| Switchkins iterative forward | `genhexkins` runtime probing follows LinuxCNC switchkins iterative-forward behavior, including the first-call warmup path before asserting roundtrip convergence. |
| Browser storage | OPFS remains outside the native core; `runtime/opfs/file-service.js` owns browser text-file persistence, `runtime/opfs/path-model.js` owns host-side storage paths for INI, tool table, parameter, G-code, preview-cache, and session-snapshot content, `runtime/opfs/snapshot-store.js` owns generic JSON session snapshot persistence, `runtime/opfs/machine-file-store.js` owns pure-text machine-file and G-code persistence, `runtime/opfs/linuxcnc-parameter-bridge.js` only copies parameter files between OPFS text storage and the LinuxCNC-backed WASM parameter-file ABI, `runtime/opfs/linuxcnc-tool-table-bridge.js` only copies tool tables between OPFS text storage and the LinuxCNC-backed WASM tool-table ABI, and `runtime/opfs/linuxcnc-machine-session-bridge.js` groups INI, parameter, and tool-table loading while using the LinuxCNC-backed INI SDK for `[EMCIO]RANDOM_TOOLCHANGER` when available. |
| Browser storage | OPFS remains outside the native core; `runtime/opfs/file-service.js` owns browser text-file persistence, `runtime/opfs/path-model.js` owns host-side storage paths for INI, tool table, parameter, G-code, preview-cache, and session-snapshot content, `runtime/opfs/snapshot-store.js` owns generic JSON session snapshot persistence, `runtime/opfs/machine-file-store.js` owns pure-text machine-file and G-code persistence, `runtime/opfs/linuxcnc-parameter-bridge.js` only copies parameter files between OPFS text storage and the LinuxCNC-backed WASM parameter-file ABI, `runtime/opfs/linuxcnc-tool-table-bridge.js` only copies tool tables between OPFS text storage and the LinuxCNC-backed WASM tool-table ABI, and `runtime/opfs/linuxcnc-machine-session-bridge.js` groups INI, parameter, and tool-table loading while using the LinuxCNC-backed INI SDK for `[EMCIO]RANDOM_TOOLCHANGER`, `[RS274NGC]PARAMETER_FILE`, and `[EMCIO]TOOL_TABLE` when available. |
## Enforced Non-Drift Rules

View File

@@ -33,7 +33,7 @@ Current validation is intentionally mechanical:
| Capability | LinuxCNC source files | Port classification | Standalone boundary | Current validation |
| --- | --- | --- | --- | --- |
| INI parsing | `src/emc/ini/inifile.cc`, `inifile.h`, `inifile.hh` | Copy unchanged | Native file IO remains LinuxCNC-style in the vendored parser; browser OPFS integration remains outside this layer; `runtime/sdk/src/index.js` exports the INI SDK wrapper around the generated WASM C ABI, including LinuxCNC `iniFindBool()` for machine-session flags | Vendor byte sync, `linuxcnc_ini_probe`, `linuxcnc_inifile_source_probe`, `tests/wasm/node/verify_ini_wasm.sh`, `tests/browser/verify_ini_panel_browser.sh`, `tests/host/verify_host_smokes.sh` |
| INI parsing | `src/emc/ini/inifile.cc`, `inifile.h`, `inifile.hh` | Copy unchanged | Native file IO remains LinuxCNC-style in the vendored parser; browser OPFS integration remains outside this layer; `runtime/sdk/src/index.js` exports the INI SDK wrapper around the generated WASM C ABI, including LinuxCNC `iniFindBool()` for machine-session flags and LinuxCNC string lookup for INI-declared machine file names | Vendor byte sync, `linuxcnc_ini_probe`, `linuxcnc_inifile_source_probe`, `tests/wasm/node/verify_ini_wasm.sh`, `tests/browser/verify_ini_panel_browser.sh`, `tests/host/verify_host_smokes.sh` |
| RTAPI compatibility headers | `src/rtapi/rtapi_*.h` in the manifest | Copy unchanged plus standalone shim include path | `runtime/core/shims/rtapi.h` supplies the minimal standalone RTAPI surface needed by vendored code | Vendor byte sync, compile coverage through dependent source probes |
| Canon/NML-facing interpreter types | `src/emc/nml_intf/canon*.hh`, `emctool.h`, `interp_return.hh`, `motion_types.h`, `emcpose.*`, `emcpos.h`, `debugflags.h`, `src/emc/linuxcnc.h` | Copy unchanged plus narrow standalone status shim | NML transport is not ported; `runtime/core/shims/nml_intf/emc.hh` exposes only the `emcStatus` machine-units status edge currently needed by vendored interpreter conversion and initialization code | Vendor byte sync, dependent source probes, `linuxcnc_emc_status_probe`, `linuxcnc_tp_api_probe`, interpreter harnesses |
| Motion state headers | `src/emc/motion/state_tag.h`, `emcmotcfg.h`, `simple_tp.h`, `motion.h`, `mot_priv.h`, `axis.h` | Copy unchanged | Realtime motion process is not ported; standalone probes seed the small motion status/config state required by TP calls | Vendor byte sync, `linuxcnc_tp_api_probe` |
@@ -54,7 +54,7 @@ Current validation is intentionally mechanical:
| Dependency | LinuxCNC files that expose it | Standalone treatment |
| --- | --- | --- |
| Native file IO | `inifile.cc`, `rs274ngc_pre.cc`, `tooldata_common.cc`, parameter file paths, tool table paths | Allowed in native probes; the interpreter WASM C ABI validates parameter-file restore/save by calling vendored `Interp::restore_parameters()` and `Interp::save_parameters()` and tool-table load/save by calling vendored `tooldata_load()` and `tooldata_save()` against Emscripten filesystem paths; browser OPFS remains a host-side adapter under `runtime/opfs/`, with path ownership in `runtime/opfs/path-model.js`, generic snapshot persistence in `runtime/opfs/snapshot-store.js`, pure-text machine-file persistence in `runtime/opfs/machine-file-store.js`, OPFS-to-WASM parameter-file copying in `runtime/opfs/linuxcnc-parameter-bridge.js`, OPFS-to-WASM tool-table copying in `runtime/opfs/linuxcnc-tool-table-bridge.js`, and grouped INI/parameter/tool-table session loading in `runtime/opfs/linuxcnc-machine-session-bridge.js` |
| Native file IO | `inifile.cc`, `rs274ngc_pre.cc`, `tooldata_common.cc`, parameter file paths, tool table paths | Allowed in native probes; the interpreter WASM C ABI validates parameter-file restore/save by calling vendored `Interp::restore_parameters()` and `Interp::save_parameters()` and tool-table load/save by calling vendored `tooldata_load()` and `tooldata_save()` against Emscripten filesystem paths; browser OPFS remains a host-side adapter under `runtime/opfs/`, with path ownership in `runtime/opfs/path-model.js`, generic snapshot persistence in `runtime/opfs/snapshot-store.js`, pure-text machine-file persistence in `runtime/opfs/machine-file-store.js`, OPFS-to-WASM parameter-file copying in `runtime/opfs/linuxcnc-parameter-bridge.js`, OPFS-to-WASM tool-table copying in `runtime/opfs/linuxcnc-tool-table-bridge.js`, and grouped INI/parameter/tool-table session loading in `runtime/opfs/linuxcnc-machine-session-bridge.js`, including INI-derived `[RS274NGC]PARAMETER_FILE` and `[EMCIO]TOOL_TABLE` OPFS filename selection through the LinuxCNC-backed INI SDK |
| RTAPI | `rtapi_*.h`, TP, posemath, motion headers | Minimal standalone shim in `runtime/core/shims/rtapi.h` |
| NML transport | `emc.hh`, motion/NML type headers | Transport is not ported; only the status/type edges needed by vendored compute code are exposed through standalone shims and probes |
| HAL runtime | named parameter lookup, kinematics component lifecycle, and runtime status edges | Standalone HAL adapter under `runtime/core/linuxcnc_wrap/` |
@@ -93,7 +93,8 @@ Current validation is intentionally mechanical:
back the LinuxCNC-saved tool table.
`runtime/opfs/linuxcnc-machine-session-bridge.js` groups INI, parameter, and
tool-table loading into one host-side session load boundary and can derive
the random-toolchanger tooldata mode from `[EMCIO]RANDOM_TOOLCHANGER` through
the LinuxCNC-backed INI SDK. Full machine-state restoration remains future
work.
the random-toolchanger tooldata mode from `[EMCIO]RANDOM_TOOLCHANGER` plus
parameter/tool-table OPFS file names from `[RS274NGC]PARAMETER_FILE` and
`[EMCIO]TOOL_TABLE` through the LinuxCNC-backed INI SDK. Full machine-state
restoration remains future work.
- Native LinuxCNC GUI code remains out of scope for implementation.

View File

@@ -21,22 +21,35 @@ function requireIniSdk(iniSdk) {
if (!iniSdk) {
return;
}
for (const method of ["writeTextFile", "getBool"]) {
for (const method of ["writeTextFile", "getString", "getBool"]) {
if (typeof iniSdk[method] !== "function") {
throw new Error(`INI SDK is missing ${method}().`);
}
}
}
function resolveSessionPaths(machineId, options = {}) {
function iniString(iniSdk, iniWasmPath, section, tag) {
if (!iniSdk) {
return undefined;
}
const value = iniSdk.getString(iniWasmPath, section, tag);
return value === null || value === "" ? undefined : value;
}
function resolveSessionPaths(machineId, options = {}, iniValues = {}) {
return {
iniOpfsPath: options.iniOpfsPath ?? machineIniPath(machineId, options.iniFilename),
iniWasmPath: options.iniWasmPath ?? DEFAULT_WASM_INI_PATH,
parameterOpfsPath:
options.parameterOpfsPath ?? parameterFilePath(machineId, options.parameterFilename),
options.parameterOpfsPath ??
parameterFilePath(
machineId,
options.parameterFilename ?? iniValues.parameterFilename,
),
parameterWasmPath: options.parameterWasmPath ?? DEFAULT_WASM_PARAMETER_PATH,
toolTableOpfsPath:
options.toolTableOpfsPath ?? toolTablePath(machineId, options.toolTableFilename),
options.toolTableOpfsPath ??
toolTablePath(machineId, options.toolTableFilename ?? iniValues.toolTableFilename),
toolTableWasmPath: options.toolTableWasmPath ?? DEFAULT_WASM_TOOL_TABLE_PATH,
};
}
@@ -53,8 +66,18 @@ function randomToolChangerFromIni(iniSdk, iniWasmPath, iniText) {
export async function loadMachineSessionFromOpfs(interp, machineId, options = {}) {
requireSessionSdk(interp);
requireIniSdk(options.iniSdk);
const paths = resolveSessionPaths(machineId, options);
const iniText = await loadTextFile(paths.iniOpfsPath, options.storage);
const iniPaths = resolveSessionPaths(machineId, options);
const iniText = await loadTextFile(iniPaths.iniOpfsPath, options.storage);
options.iniSdk?.writeTextFile(iniPaths.iniWasmPath, iniText);
const paths = resolveSessionPaths(machineId, options, {
parameterFilename: iniString(
options.iniSdk,
iniPaths.iniWasmPath,
"RS274NGC",
"PARAMETER_FILE",
),
toolTableFilename: iniString(options.iniSdk, iniPaths.iniWasmPath, "EMCIO", "TOOL_TABLE"),
});
interp.writeTextFile(paths.iniWasmPath, iniText);
const randomToolChanger =
typeof options.randomToolChanger === "boolean"

View File

@@ -14,6 +14,9 @@
import {
saveMachineParametersToOpfs,
} from "../../runtime/opfs/linuxcnc-parameter-bridge.js";
import {
saveTextFile,
} from "../../runtime/opfs/file-service.js";
import {
saveMachineToolTableToOpfs,
} from "../../runtime/opfs/linuxcnc-tool-table-bridge.js";
@@ -323,6 +326,80 @@
].join("\n"),
);
await saveMachineTextFiles("browser-ini-file-session", {
ini: [
"[EMC]",
"MACHINE = browser-ini-file-session",
"",
"[RS274NGC]",
"PARAMETER_FILE = browser-custom.var",
"",
"[EMCIO]",
"TOOL_TABLE = browser-custom-tool.tbl",
"",
].join("\n"),
});
await saveTextFile(
"linuxcnc/machines/browser-ini-file-session/browser-custom.var",
[
"5161 31.25",
"5162 62.5",
"5220 1",
"5221 4.5",
"5399 99",
"",
].join("\n"),
);
await saveTextFile(
"linuxcnc/machines/browser-ini-file-session/browser-custom-tool.tbl",
"T8 P8 Z4.5 D0.5 ;browser custom tool\n",
);
const loadedIniFileSession = await loadMachineSessionFromOpfs(
interp,
"browser-ini-file-session",
{
iniSdk: ini,
iniWasmPath: "/work/browser-ini-file-session.ini",
parameterWasmPath: "/work/browser-custom.var",
toolTableWasmPath: "/work/browser-custom-tool.tbl",
},
);
if (
loadedIniFileSession.parameters.opfsPath !==
"linuxcnc/machines/browser-ini-file-session/browser-custom.var"
) {
throw new Error(`unexpected custom parameter path: ${loadedIniFileSession.parameters.opfsPath}`);
}
if (
loadedIniFileSession.toolTable.opfsPath !==
"linuxcnc/machines/browser-ini-file-session/browser-custom-tool.tbl"
) {
throw new Error(`unexpected custom tool path: ${loadedIniFileSession.toolTable.opfsPath}`);
}
verifyExpectedOutput(
"opfs_load_ini_named_parameter_file",
loadedIniFileSession.parameters.result,
[
"restore_parameters=0",
"parameter_5161=31.25",
"parameter_5162=62.5",
"parameter_5221=4.5",
"parameter_5399=99",
].join("\n"),
);
verifyExpectedOutput(
"opfs_load_ini_named_tool_table",
loadedIniFileSession.toolTable.result,
[
"tooldata_load=0",
"tool_1.toolno=8",
"tool_1.pocketno=8",
"tool_1.z=4.5",
"tool_1.diameter=0.5",
"tool_1.comment=browser custom tool",
].join("\n"),
);
status.textContent = "browser_interp_smoke=ok";
} catch (error) {
status.textContent = `browser_interp_smoke=fail ${error.stack || error.message}`;

View File

@@ -70,11 +70,7 @@ export const INTERP_FILE_FIXTURES = [
"oword_subroutine",
];
export const INTERP_BROWSER_FILE_FIXTURES = [
"file_open_reset",
"percent_file_finish",
"oword_subroutine",
];
export const INTERP_BROWSER_FILE_FIXTURES = INTERP_FILE_FIXTURES;
export const INTERP_INI_FIXTURE = "namedparam_semantics";
export const INTERP_POSITION_PARAMS_FILE_FIXTURE = "position_params";

View File

@@ -201,6 +201,23 @@ const bridgeIniSdk = {
writeTextFile(path, text) {
bridgeFiles.set(`ini:${path}`, text);
},
getString(path, section, tag) {
if (
path === "/work/ini-file-session.ini" &&
section === "RS274NGC" &&
tag === "PARAMETER_FILE"
) {
return "custom.var";
}
if (
path === "/work/ini-file-session.ini" &&
section === "EMCIO" &&
tag === "TOOL_TABLE"
) {
return "custom-tool.tbl";
}
return null;
},
getBool(path, section, tag) {
if (
path === "/work/ini-derived-session.ini" &&
@@ -350,6 +367,55 @@ assert.equal(
"tooldata_load=0\nload_tool_path=/work/ini-derived-session-tool.tbl\nrandom_toolchanger=1\n",
);
await saveMachineTextFiles("ini-file-session", {
ini: [
"[EMC]",
"MACHINE = ini-file-session",
"",
"[RS274NGC]",
"PARAMETER_FILE = custom.var",
"",
"[EMCIO]",
"TOOL_TABLE = custom-tool.tbl",
"",
].join("\n"),
}, { storage });
await saveTextFile(
"linuxcnc/machines/ini-file-session/custom.var",
"5161 77.0\n5162 88.0\n",
storage,
);
await saveTextFile(
"linuxcnc/machines/ini-file-session/custom-tool.tbl",
"T8 P8 Z4.5 D0.5\n",
storage,
);
const loadedIniFileSession = await loadMachineSessionFromOpfs(
bridgeInterp,
"ini-file-session",
{
storage,
iniSdk: bridgeIniSdk,
iniWasmPath: "/work/ini-file-session.ini",
parameterWasmPath: "/work/ini-file-session.var",
toolTableWasmPath: "/work/ini-file-session-tool.tbl",
},
);
assert.equal(
loadedIniFileSession.parameters.opfsPath,
"linuxcnc/machines/ini-file-session/custom.var",
);
assert.equal(
loadedIniFileSession.toolTable.opfsPath,
"linuxcnc/machines/ini-file-session/custom-tool.tbl",
);
assert.equal(bridgeFiles.get("/work/ini-file-session.var"), "5161 77.0\n5162 88.0\n");
assert.equal(bridgeFiles.get("/work/ini-file-session-tool.tbl"), "T8 P8 Z4.5 D0.5\n");
assert.equal(
loadedIniFileSession.toolTable.result,
"tooldata_load=0\nload_tool_path=/work/ini-file-session-tool.tbl\nrandom_toolchanger=0\n",
);
await assert.rejects(
() => loadTextFile("linuxcnc/machines/missing.ini", storage),
/missing file/,