增加解释器WASM文件入口

结论:解释器核心WASM新增基于LinuxCNC Interp::open/read/execute的文件运行入口,并覆盖file_open_reset、percent_file_finish和oword_subroutine夹具。
This commit is contained in:
2026-06-08 05:21:39 +08:00
parent 80f23ea0a1
commit fd680f7515
4 changed files with 135 additions and 15 deletions

View File

@@ -76,7 +76,10 @@ vendored LinuxCNC interpreter source set used by the native minimal
interpreter harness. It loads the module in Node, runs the first WASM
interpreter fixture group through `Interp::execute()`, and compares emitted
canonical events plus required LinuxCNC `_setup` state readback with the
matching files in `tests/fixtures/canon/`.
matching files in `tests/fixtures/canon/`. It also writes selected G-code
fixtures into the Emscripten filesystem and runs them through LinuxCNC
`Interp::open()`, `Interp::read()`, and `Interp::execute()` to validate the
file execution path.
The OPFS host-boundary script validates the JavaScript file-service adapter
with a Node mock of the browser File System Access handles. It covers nested
@@ -144,7 +147,7 @@ The validation fails if:
| Harness | Purpose |
| --- | --- |
| `tests/wasm/node/verify_ini_wasm.sh` | Validates the browser-facing INI WASM module can be built from vendored LinuxCNC `inifile.cc`, loaded through the JS SDK in Node, and queried through the exported C ABI. |
| `tests/wasm/node/verify_interp_wasm.sh` | Validates the initial interpreter-core WASM module can be built from vendored LinuxCNC interpreter source, run the first fixture group through `Interp::execute()`, and match the native canonical event plus required state readback fixtures. |
| `tests/wasm/node/verify_interp_wasm.sh` | Validates the initial interpreter-core WASM module can be built from vendored LinuxCNC interpreter source, run the first fixture group through `Interp::execute()` and selected file fixtures through `Interp::open()`/`read()`/`execute()`, and match the native canonical event plus required state readback fixtures. |
| `tests/opfs/node/verify_file_service.sh` | Validates the host-owned OPFS text-file adapter, path model, session snapshot store, machine file store, and G-code text store used by the browser INI panel without moving file persistence into the WASM core. |
| `tests/browser/verify_ini_panel_browser.sh` | Validates the INI SDK, WASM module loading, OPFS text-file round trip, generic session snapshot round trip, machine file text round trip, and G-code text round trip in a real browser runtime. |
| `tests/host/verify_host_smokes.sh` | Runs the current host-side Node, WASM interpreter-core, OPFS, and browser smoke validation with shared WASM builds. |
@@ -197,9 +200,11 @@ for `minimal_linear`, `arc_semantics`, `length_units`, `modal_incremental`,
`feed_control_modes`, `position_params`, `probe_semantics`, `spindle_orient`,
`comment_logging`, and `numbered_params`, plus the `g1_zero_feed`,
`arc_radius_mismatch`, `arc_zero_radius`, and `g53_incremental` negative
fixtures. OPFS validation is limited to the JavaScript host-boundary adapter
plus the INI browser smoke harness. Full browser coverage, full SDK coverage,
and full machine-session validation remain future work.
fixtures. The WASM interpreter file path additionally covers
`file_open_reset`, `percent_file_finish`, and `oword_subroutine`. OPFS
validation is limited to the JavaScript host-boundary adapter plus the INI
browser smoke harness. Full browser coverage, full SDK coverage, and full
machine-session validation remain future work.
The current fixture expectations validate standalone behavior against both the
vendored LinuxCNC source path and an upstream `rs274` side-by-side baseline for

View File

@@ -113,6 +113,19 @@ char *copy_result(const std::string &result)
return out;
}
void append_events_and_state(std::ostringstream &output, const Interp &interp)
{
for (const auto &event : standalone::canon_events()) {
output << "canon_event=" << event << "\n";
}
output << "setup.current_x=" << interp._setup.current_x << "\n";
output << "setup.current_y=" << interp._setup.current_y << "\n";
output << "setup.current_z=" << interp._setup.current_z << "\n";
output << "setup.parameter_5420=" << interp._setup.parameters[5420] << "\n";
output << "setup.parameter_5421=" << interp._setup.parameters[5421] << "\n";
output << "setup.parameter_5422=" << interp._setup.parameters[5422] << "\n";
}
} // namespace
extern "C" {
@@ -143,15 +156,71 @@ char *lcinterp_run_program(const char *program_text)
}
}
for (const auto &event : standalone::canon_events()) {
output << "canon_event=" << event << "\n";
append_events_and_state(output, interp);
return copy_result(output.str());
}
EMSCRIPTEN_KEEPALIVE
char *lcinterp_run_file(const char *path)
{
Interp interp;
standalone::reset_canon_events();
initialize_minimal_interp(interp);
std::ostringstream output;
const int open_rc = interp.open(path);
output << "file_open=" << open_rc << "\n";
if (open_rc != INTERP_OK) {
if (open_rc > INTERP_MIN_ERROR) {
char error_buf[LINELEN] = {0};
interp.error_text(open_rc, error_buf, sizeof(error_buf));
output << "file_error_text=" << error_buf << "\n";
}
append_events_and_state(output, interp);
return copy_result(output.str());
}
output << "setup.current_x=" << interp._setup.current_x << "\n";
output << "setup.current_y=" << interp._setup.current_y << "\n";
output << "setup.current_z=" << interp._setup.current_z << "\n";
output << "setup.parameter_5420=" << interp._setup.parameters[5420] << "\n";
output << "setup.parameter_5421=" << interp._setup.parameters[5421] << "\n";
output << "setup.parameter_5422=" << interp._setup.parameters[5422] << "\n";
int file_read_count = 0;
int file_execute_count = 0;
while (true) {
const int read_rc = interp.read();
if (read_rc == INTERP_ENDFILE) {
output << "file_read_eof=" << read_rc << "\n";
break;
}
++file_read_count;
output << "file_read_" << file_read_count << "=" << read_rc << "\n";
if ((read_rc != INTERP_OK) && (read_rc != INTERP_EXECUTE_FINISH)) {
if (read_rc > INTERP_MIN_ERROR) {
char error_buf[LINELEN] = {0};
interp.error_text(read_rc, error_buf, sizeof(error_buf));
output << "file_error_text=" << error_buf << "\n";
}
break;
}
const int execute_rc = interp.execute();
++file_execute_count;
output << "file_execute_" << file_execute_count << "=" << execute_rc << "\n";
if (execute_rc > INTERP_MIN_ERROR) {
char error_buf[LINELEN] = {0};
interp.error_text(execute_rc, error_buf, sizeof(error_buf));
output << "file_error_text=" << error_buf << "\n";
}
if ((execute_rc != INTERP_OK) &&
(execute_rc != INTERP_EXECUTE_FINISH) &&
(execute_rc != INTERP_EXIT)) {
break;
}
if (execute_rc == INTERP_EXIT) {
break;
}
}
output << "file_read_count=" << file_read_count << "\n";
output << "file_execute_count=" << file_execute_count << "\n";
append_events_and_state(output, interp);
return copy_result(output.str());
}

View File

@@ -27,6 +27,29 @@ function runProgram(mod, programText) {
}
}
function ensureDir(mod, path) {
try {
mod.FS.mkdir(path);
} catch {
// Directory already exists.
}
}
function runFile(mod, path) {
const pathPtr = allocCString(mod, path);
let resultPtr = 0;
try {
resultPtr = mod._lcinterp_run_file(pathPtr);
assert.notEqual(resultPtr, 0);
return mod.UTF8ToString(resultPtr);
} finally {
if (resultPtr) {
mod._lcinterp_free_string(resultPtr);
}
mod._free(pathPtr);
}
}
function verifyExpectedOutput(fixtureName, output, expectedText) {
const expectedLines = expectedText.split("\n").filter(Boolean);
@@ -110,4 +133,26 @@ for (const fixtureName of errorFixtureNames) {
verifyExpectedOutput(fixtureName, runProgram(interp, programText), expectedOutput);
}
const fileFixtureNames = [
"file_open_reset",
"percent_file_finish",
"oword_subroutine",
];
ensureDir(interp, "/work");
for (const fixtureName of fileFixtureNames) {
const programPath = `/work/${fixtureName}.ngc`;
const programText = readFileSync(
resolve(rootDir, `tests/fixtures/gcode/${fixtureName}.ngc`),
"utf8",
);
const expectedEvents = readFileSync(
resolve(rootDir, `tests/fixtures/canon/${fixtureName}.events`),
"utf8",
).trimEnd();
interp.FS.writeFile(programPath, programText, { encoding: "utf8" });
verifyExpectedOutput(fixtureName, runFile(interp, programPath), expectedEvents);
}
console.log("interp_wasm_node_smoke=ok");

View File

@@ -94,5 +94,6 @@ link_wasm_module \
-s ALLOW_MEMORY_GROWTH=1 \
-s STACK_SIZE=2MB \
-s NO_EXIT_RUNTIME=1 \
-s EXPORTED_FUNCTIONS='["_malloc","_free","_lcinterp_run_program","_lcinterp_free_string"]' \
-s EXPORTED_RUNTIME_METHODS='["UTF8ToString","stringToUTF8","lengthBytesUTF8"]'
-s FORCE_FILESYSTEM=1 \
-s EXPORTED_FUNCTIONS='["_malloc","_free","_lcinterp_run_program","_lcinterp_run_file","_lcinterp_free_string"]' \
-s EXPORTED_RUNTIME_METHODS='["FS","UTF8ToString","stringToUTF8","lengthBytesUTF8"]'