增加解释器WASM文件入口
结论:解释器核心WASM新增基于LinuxCNC Interp::open/read/execute的文件运行入口,并覆盖file_open_reset、percent_file_finish和oword_subroutine夹具。
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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"]'
|
||||
|
||||
Reference in New Issue
Block a user