按规划继续工作
结论:解释器 WASM、SDK 统一入口、浏览器解释器 smoke 与兼容性文档已闭环,native 和 host/WASM/browser 验证全部通过。
This commit is contained in:
@@ -70,7 +70,12 @@ expanded into a separate implementation of G-code behavior.
|
|||||||
- GUI.
|
- GUI.
|
||||||
7. Frontend code must be implemented with web technology, not migrated from
|
7. Frontend code must be implemented with web technology, not migrated from
|
||||||
native GUI code.
|
native GUI code.
|
||||||
8. All build scripts must be incremental. Native and WASM builds must reuse
|
8. Frontend, browser tests, and Node WASM tests should call generated WASM
|
||||||
|
modules through `runtime/sdk/src/index.js`. SDK code is a host-boundary
|
||||||
|
layer only: it may load modules, manage strings, write Emscripten files,
|
||||||
|
and call exported C ABI functions, but it must not implement G-code,
|
||||||
|
canonical motion, tool, parameter, kinematics, or planner semantics.
|
||||||
|
9. All build scripts must be incremental. Native and WASM builds must reuse
|
||||||
object files, dependency files, and command fingerprints, and must not
|
object files, dependency files, and command fingerprints, and must not
|
||||||
unconditionally recompile unchanged source files.
|
unconditionally recompile unchanged source files.
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ Rules:
|
|||||||
3. Port-specific code, build scripts, adapters, tests, and documentation live here.
|
3. Port-specific code, build scripts, adapters, tests, and documentation live here.
|
||||||
4. LinuxCNC source is consumed by reference, copy, generated snapshot, or scripted extraction.
|
4. LinuxCNC source is consumed by reference, copy, generated snapshot, or scripted extraction.
|
||||||
5. The browser/WASM product is managed as a separate program.
|
5. The browser/WASM product is managed as a separate program.
|
||||||
|
6. Browser UI and tests should call WASM modules through `runtime/sdk/src/index.js`.
|
||||||
|
The SDK is a thin module-loading, string-allocation, Emscripten-FS, and C
|
||||||
|
ABI wrapper; it must not implement CNC semantics.
|
||||||
|
|
||||||
Suggested layout:
|
Suggested layout:
|
||||||
|
|
||||||
@@ -119,6 +122,9 @@ Directory intent:
|
|||||||
- `vendor/linuxcnc/`: read-only copied LinuxCNC source selected for the port.
|
- `vendor/linuxcnc/`: read-only copied LinuxCNC source selected for the port.
|
||||||
- `runtime/core/`: the standalone simulation engine built around vendored LinuxCNC code.
|
- `runtime/core/`: the standalone simulation engine built around vendored LinuxCNC code.
|
||||||
- `runtime/sdk/`: JavaScript/TypeScript API for calling the WASM engine.
|
- `runtime/sdk/`: JavaScript/TypeScript API for calling the WASM engine.
|
||||||
|
Stable imports should come from `runtime/sdk/src/index.js`. SDK code may
|
||||||
|
adapt host/runtime edges, but G-code, tool, parameter, kinematics, and
|
||||||
|
planner behavior must remain in vendored LinuxCNC source.
|
||||||
- `runtime/ui/`: HTML + JavaScript CNC simulation frontend.
|
- `runtime/ui/`: HTML + JavaScript CNC simulation frontend.
|
||||||
- `runtime/opfs/`: OPFS-backed persistence layer.
|
- `runtime/opfs/`: OPFS-backed persistence layer.
|
||||||
- `tests/fixtures/`: stable simulation inputs shared by native and browser tests.
|
- `tests/fixtures/`: stable simulation inputs shared by native and browser tests.
|
||||||
|
|||||||
@@ -36,6 +36,12 @@ The current browser smoke validation command is:
|
|||||||
wasm-port/tests/browser/verify_ini_panel_browser.sh
|
wasm-port/tests/browser/verify_ini_panel_browser.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The current browser interpreter smoke validation command is:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wasm-port/tests/browser/verify_interp_browser.sh
|
||||||
|
```
|
||||||
|
|
||||||
The current aggregate host/WASM/browser smoke command is:
|
The current aggregate host/WASM/browser smoke command is:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -53,7 +59,8 @@ The native validation script runs these checks in order:
|
|||||||
Confirms every manifest file is present in `vendor/linuxcnc/`, no extra
|
Confirms every manifest file is present in `vendor/linuxcnc/`, no extra
|
||||||
vendored file exists, and every vendored file is byte-identical to upstream.
|
vendored file exists, and every vendored file is byte-identical to upstream.
|
||||||
3. `tools/verify_no_standalone_cnc_semantics.sh`
|
3. `tools/verify_no_standalone_cnc_semantics.sh`
|
||||||
Confirms standalone code has not reintroduced `Interp::convert_g()`.
|
Confirms standalone code has not introduced project-owned `Interp::...`
|
||||||
|
member definitions outside the documented Python/remap runtime-edge stubs.
|
||||||
4. `tools/verify_native_linuxcnc_fixture_baseline.sh`
|
4. `tools/verify_native_linuxcnc_fixture_baseline.sh`
|
||||||
Runs a side-by-side fixture baseline through upstream
|
Runs a side-by-side fixture baseline through upstream
|
||||||
`../linuxcnc/bin/rs274` and compares normalized canonical events for
|
`../linuxcnc/bin/rs274` and compares normalized canonical events for
|
||||||
@@ -67,17 +74,18 @@ The native validation script runs these checks in order:
|
|||||||
|
|
||||||
The WASM INI smoke script builds `runtime/ui/ini-panel/linuxcnc_ini.js` and
|
The WASM INI smoke script builds `runtime/ui/ini-panel/linuxcnc_ini.js` and
|
||||||
`linuxcnc_ini.wasm` from vendored LinuxCNC `inifile.cc`, then loads that
|
`linuxcnc_ini.wasm` from vendored LinuxCNC `inifile.cc`, then loads that
|
||||||
module through `runtime/sdk/src/linuxcnc-ini.js` in Node and verifies INI
|
module through `runtime/sdk/src/index.js` in Node and verifies INI
|
||||||
queries against a file written to the Emscripten filesystem.
|
queries against a file written to the Emscripten filesystem.
|
||||||
|
|
||||||
The WASM interpreter-core smoke script builds
|
The WASM interpreter-core smoke script builds
|
||||||
`build/wasm/core/linuxcnc_interp.js` and `linuxcnc_interp.wasm` from the same
|
`build/wasm/core/linuxcnc_interp.js` and `linuxcnc_interp.wasm` from the same
|
||||||
vendored LinuxCNC interpreter source set used by the native minimal
|
vendored LinuxCNC interpreter source set used by the native minimal
|
||||||
interpreter harness. It loads the module in Node, runs the first WASM
|
interpreter harness. It loads the module in Node through
|
||||||
interpreter fixture group through `Interp::execute()`, and compares emitted
|
`runtime/sdk/src/index.js`, runs the first WASM interpreter fixture
|
||||||
canonical events plus required LinuxCNC `_setup` state readback with the
|
group through `Interp::execute()`, and compares emitted canonical events plus
|
||||||
matching files in `tests/fixtures/canon/`. It also writes selected G-code
|
required LinuxCNC `_setup` state readback with the matching files in
|
||||||
fixtures into the Emscripten filesystem and runs them through LinuxCNC
|
`tests/fixtures/canon/`. It also writes selected G-code fixtures into the
|
||||||
|
Emscripten filesystem through the SDK and runs them through LinuxCNC
|
||||||
`Interp::open()`, `Interp::read()`, and `Interp::execute()` to validate the
|
`Interp::open()`, `Interp::read()`, and `Interp::execute()` to validate the
|
||||||
file execution path.
|
file execution path.
|
||||||
|
|
||||||
@@ -90,15 +98,24 @@ targets. It also validates the host-side session snapshot JSON envelope and
|
|||||||
round-trip store plus pure-text machine file and G-code stores without
|
round-trip store plus pure-text machine file and G-code stores without
|
||||||
defining CNC machine-state or file-format semantics.
|
defining CNC machine-state or file-format semantics.
|
||||||
|
|
||||||
The browser smoke script serves `wasm-port/` over localhost and runs Chromium
|
The browser INI/OPFS smoke script serves `wasm-port/` over localhost and runs
|
||||||
headless against a test page that imports the JS SDK, loads the INI WASM
|
Chromium headless against a test page that imports the JS SDK, loads the INI
|
||||||
module, queries vendored LinuxCNC INI parsing through the SDK, and performs an
|
WASM module, queries vendored LinuxCNC INI parsing through the SDK, and
|
||||||
OPFS text-file, generic session snapshot, machine file, and G-code text
|
performs an OPFS text-file, generic session snapshot, machine file, and G-code
|
||||||
round trip.
|
text round trip.
|
||||||
|
|
||||||
|
The browser interpreter smoke script serves `wasm-port/` over localhost and
|
||||||
|
runs Chromium headless against a test page that loads the interpreter-core
|
||||||
|
WASM module through `runtime/sdk/src/index.js`, writes no CNC
|
||||||
|
behavior in JavaScript, and verifies existing canonical fixtures through the
|
||||||
|
exported C ABI backed by vendored LinuxCNC `Interp::execute()` and
|
||||||
|
`Interp::open()`/`read()`/`execute()` paths, including the INI-aware
|
||||||
|
named-parameter file path and negative interpreter fixtures with expected
|
||||||
|
error text plus absent canonical motion output.
|
||||||
|
|
||||||
The aggregate host smoke script builds the INI and interpreter-core WASM
|
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
|
artifacts once, then runs the Node WASM smokes, the Node OPFS mock smoke, and
|
||||||
the Chromium browser smoke.
|
the Chromium browser smokes.
|
||||||
|
|
||||||
## Source Coverage
|
## Source Coverage
|
||||||
|
|
||||||
@@ -147,9 +164,10 @@ The validation fails if:
|
|||||||
| Harness | Purpose |
|
| 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_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 selected file fixtures through `Interp::open()`/`read()`/`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, loaded through the interpreter JS SDK, 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/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/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/browser/verify_interp_browser.sh` | Validates the interpreter-core WASM module loads through the interpreter JS SDK in a real browser runtime and runs selected positive and negative canonical fixtures through vendored LinuxCNC `Interp::execute()` plus `Interp::open()`/`read()`/`execute()` via the exported C ABI. |
|
||||||
| `tests/host/verify_host_smokes.sh` | Runs the current host-side Node, WASM interpreter-core, OPFS, and browser smoke validation with shared WASM builds. |
|
| `tests/host/verify_host_smokes.sh` | Runs the current host-side Node, WASM interpreter-core, OPFS, and browser smoke validation with shared WASM builds. |
|
||||||
|
|
||||||
## Fixture Coverage
|
## Fixture Coverage
|
||||||
@@ -206,12 +224,127 @@ plus the `g1_zero_feed`, `arc_radius_mismatch`, `arc_zero_radius`,
|
|||||||
`cutter_comp_plane_change`, `g53_incremental`, `namedparam_readonly`,
|
`cutter_comp_plane_change`, `g53_incremental`, `namedparam_readonly`,
|
||||||
`numbered_param_readonly`, `tool_not_found`, and
|
`numbered_param_readonly`, `tool_not_found`, and
|
||||||
`tool_length_offset_not_found` negative fixtures. The WASM interpreter file
|
`tool_length_offset_not_found` negative fixtures. The WASM interpreter file
|
||||||
path additionally covers the same canonical-event fixture group except
|
path additionally covers the same canonical-event fixture group, plus
|
||||||
`namedparam_semantics` and `position_params`, plus `file_open_reset`,
|
`namedparam_semantics` through the INI-aware file execution ABI,
|
||||||
`percent_file_finish`, and `oword_subroutine`. OPFS validation is limited to
|
`file_open_reset`, `percent_file_finish`, and `oword_subroutine`.
|
||||||
the JavaScript host-boundary adapter plus the INI browser smoke harness. Full
|
`position_params` uses a dedicated file-path expectation under
|
||||||
browser coverage, full SDK coverage, and full machine-session validation remain
|
`tests/fixtures/canon_file/` because LinuxCNC file execution advances the
|
||||||
future work.
|
post-execute position parameters differently than the line-by-line MDI smoke.
|
||||||
|
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.
|
||||||
|
|
||||||
|
## WASM/Browser Fixture Matrix
|
||||||
|
|
||||||
|
Node WASM `Interp::execute()` coverage currently includes:
|
||||||
|
|
||||||
|
- `minimal_linear`
|
||||||
|
- `arc_semantics`
|
||||||
|
- `length_units`
|
||||||
|
- `modal_incremental`
|
||||||
|
- `plane_selection`
|
||||||
|
- `coordinate_offsets`
|
||||||
|
- `g53_machine_coordinates`
|
||||||
|
- `feed_control_modes`
|
||||||
|
- `position_params`
|
||||||
|
- `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`
|
||||||
|
- `namedparam_semantics` through the INI-aware program ABI
|
||||||
|
|
||||||
|
Node WASM file-path coverage currently includes:
|
||||||
|
|
||||||
|
- `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
|
||||||
|
|
||||||
|
Browser interpreter `Interp::execute()` coverage currently includes:
|
||||||
|
|
||||||
|
- `minimal_linear`
|
||||||
|
- `arc_semantics`
|
||||||
|
- `length_units`
|
||||||
|
- `modal_incremental`
|
||||||
|
- `plane_selection`
|
||||||
|
- `coordinate_offsets`
|
||||||
|
- `g53_machine_coordinates`
|
||||||
|
- `feed_control_modes`
|
||||||
|
- `canned_cycles`
|
||||||
|
- `numbered_params`
|
||||||
|
- `comment_logging`
|
||||||
|
- `tool_semantics`
|
||||||
|
- `tool_table_setup`
|
||||||
|
- `probe_semantics`
|
||||||
|
- `spindle_orient`
|
||||||
|
- `cutter_comp_motion`
|
||||||
|
- `threading_sync`
|
||||||
|
- `nurbs_g5_semantics`
|
||||||
|
- `nurbs_g6_semantics`
|
||||||
|
- `state_tag_motion`
|
||||||
|
- `canon_runtime_edges`
|
||||||
|
- `tool_reload`
|
||||||
|
- `program_end_modal_reset`
|
||||||
|
|
||||||
|
Browser interpreter negative coverage currently includes every fixture under
|
||||||
|
`tests/fixtures/gcode_errors/`:
|
||||||
|
|
||||||
|
- `g1_zero_feed`
|
||||||
|
- `arc_radius_mismatch`
|
||||||
|
- `arc_zero_radius`
|
||||||
|
- `g53_incremental`
|
||||||
|
- `cutter_comp_plane_change`
|
||||||
|
- `namedparam_readonly`
|
||||||
|
- `numbered_param_readonly`
|
||||||
|
- `tool_length_offset_not_found`
|
||||||
|
- `tool_not_found`
|
||||||
|
|
||||||
|
Browser interpreter file-path coverage currently includes:
|
||||||
|
|
||||||
|
- `position_params` through the dedicated `canon_file/` expectation
|
||||||
|
- `file_open_reset`
|
||||||
|
- `percent_file_finish`
|
||||||
|
- `oword_subroutine`
|
||||||
|
- `namedparam_semantics` through the INI-aware file ABI
|
||||||
|
|
||||||
|
All current positive G-code fixtures have browser interpreter smoke coverage
|
||||||
|
through either `Interp::execute()`, the file-path ABI, or the INI-aware
|
||||||
|
file-path ABI.
|
||||||
|
|
||||||
The current fixture expectations validate standalone behavior against both the
|
The current fixture expectations validate standalone behavior against both the
|
||||||
vendored LinuxCNC source path and an upstream `rs274` side-by-side baseline for
|
vendored LinuxCNC source path and an upstream `rs274` side-by-side baseline for
|
||||||
|
|||||||
@@ -49,7 +49,8 @@ semantic rewrites:
|
|||||||
- Do not edit `../linuxcnc/`.
|
- Do not edit `../linuxcnc/`.
|
||||||
- Do not patch vendored files without adding a patch under `patches/` and
|
- Do not patch vendored files without adding a patch under `patches/` and
|
||||||
documenting the reason.
|
documenting the reason.
|
||||||
- Do not add standalone `Interp::convert_g()`.
|
- Do not add standalone `Interp::...` member definitions outside the
|
||||||
|
documented Python/remap runtime-edge stubs.
|
||||||
- Do not add `.c` or `.cc` manifest files without a source compile probe.
|
- Do not add `.c` or `.cc` manifest files without a source compile probe.
|
||||||
- Do not sync from a different upstream commit without updating
|
- Do not sync from a different upstream commit without updating
|
||||||
`tools/upstream-baseline.txt` and `docs/scope-and-baseline.md`.
|
`tools/upstream-baseline.txt` and `docs/scope-and-baseline.md`.
|
||||||
@@ -58,8 +59,10 @@ semantic rewrites:
|
|||||||
|
|
||||||
- No browser/full-core WASM parity tests yet. The INI parser now has Node and
|
- No browser/full-core WASM parity tests yet. The INI parser now has Node and
|
||||||
Chromium smoke harnesses against vendored LinuxCNC `inifile.cc`.
|
Chromium smoke harnesses against vendored LinuxCNC `inifile.cc`.
|
||||||
- JS SDK validation is currently limited to the INI WASM wrapper around
|
- JS SDK validation now covers the INI WASM wrapper around vendored LinuxCNC
|
||||||
vendored LinuxCNC `inifile.cc`.
|
`inifile.cc` and the interpreter-core SDK wrapper around the existing
|
||||||
|
exported C ABI. The interpreter SDK only manages strings, Emscripten file
|
||||||
|
writes, and calls into vendored LinuxCNC execution paths.
|
||||||
- OPFS validation covers a Node mock of the file-service adapter, the
|
- OPFS validation covers a Node mock of the file-service adapter, the
|
||||||
host-side path model, generic session snapshot storage, pure-text machine
|
host-side path model, generic session snapshot storage, pure-text machine
|
||||||
file and G-code storage, and a Chromium localhost round trip for those
|
file and G-code storage, and a Chromium localhost round trip for those
|
||||||
|
|||||||
@@ -888,14 +888,20 @@ Keep these documents under `wasm-port/docs/`:
|
|||||||
|
|
||||||
## Immediate Next Step
|
## Immediate Next Step
|
||||||
|
|
||||||
Continue expanding the standalone interpreter core from the verified minimal
|
Continue from the current verified extracted-core baseline without adding
|
||||||
traverse path:
|
project-authored CNC semantics:
|
||||||
|
|
||||||
1. keep `convert_g()` execution on vendored LinuxCNC interpreter conversion
|
1. Keep all `Interp::...` interpreter member behavior on vendored LinuxCNC
|
||||||
code and reject any new standalone `Interp::convert_g()` implementation.
|
source. Standalone code may only provide documented runtime-edge stubs such
|
||||||
2. identify and shim the native runtime symbols blocking direct compilation of
|
as the current Python/remap boundary.
|
||||||
`interp_convert.cc`, `interp_execute.cc`, and related interpreter files.
|
2. Expand WASM interpreter coverage by routing more existing native fixture
|
||||||
3. keep fixture coverage as regression protection while deleting temporary
|
paths through `runtime/core/linuxcnc_wrap/linuxcnc_interp_wasm.cpp`, using
|
||||||
hand-written semantics.
|
the same vendored interpreter source set as the native harness.
|
||||||
4. keep all source changes inside `wasm-port/` and leave `../linuxcnc/`
|
3. Promote remaining standalone-only fixture expectations to native LinuxCNC
|
||||||
read-only.
|
baselines where possible, especially adapter-heavy paths such as INI/HAL
|
||||||
|
named parameters, tool-change host state, and richer machine session state.
|
||||||
|
4. Move browser-facing work through SDK and OPFS adapters only after the core
|
||||||
|
behavior is validated against native LinuxCNC or vendored-source harnesses.
|
||||||
|
5. Before adding any CNC feature, update `tools/source-manifest.txt`, extract
|
||||||
|
the LinuxCNC source file, add a source probe or harness, and document the
|
||||||
|
reuse boundary in `docs/source-reuse-map.md`.
|
||||||
|
|||||||
@@ -25,13 +25,15 @@ Current validation is intentionally mechanical:
|
|||||||
- `tests/native/verify_native_probes.sh` checks that every manifest `.c` and
|
- `tests/native/verify_native_probes.sh` checks that every manifest `.c` and
|
||||||
`.cc` file has a matching source probe in `build/native/source-probes.tsv`.
|
`.cc` file has a matching source probe in `build/native/source-probes.tsv`.
|
||||||
- `tools/verify_no_standalone_cnc_semantics.sh` rejects standalone
|
- `tools/verify_no_standalone_cnc_semantics.sh` rejects standalone
|
||||||
`Interp::convert_g()` definitions outside `vendor/linuxcnc/`.
|
`Interp::...` member definitions outside `vendor/linuxcnc/`, except for the
|
||||||
|
documented Python/remap runtime-edge stubs in
|
||||||
|
`runtime/core/linuxcnc_wrap/linuxcnc_interp_edge_stubs.cpp`.
|
||||||
|
|
||||||
## Reuse Matrix
|
## Reuse Matrix
|
||||||
|
|
||||||
| Capability | LinuxCNC source files | Port classification | Standalone boundary | Current validation |
|
| 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/linuxcnc-ini.js` only wraps the exported WASM C ABI | 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 | 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 |
|
| 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 |
|
| 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` |
|
| 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` |
|
||||||
@@ -68,9 +70,11 @@ Current validation is intentionally mechanical:
|
|||||||
userspace genser flows are not yet established.
|
userspace genser flows are not yet established.
|
||||||
- Cutter compensation positive motion and negative interpreter paths are
|
- Cutter compensation positive motion and negative interpreter paths are
|
||||||
fixture-covered through vendored `interp_convert.cc` and `interp_queue.cc`.
|
fixture-covered through vendored `interp_convert.cc` and `interp_queue.cc`.
|
||||||
- Browser/WASM C ABI and JS SDK layers are not yet built for the full
|
- Browser/WASM C ABI and JS SDK layers are now present for the INI parser and
|
||||||
interpreter/planner core. The INI parser has a minimal JS SDK wrapper and
|
the current interpreter-core smoke scope. The interpreter SDK is a thin
|
||||||
Node/browser WASM smoke harnesses.
|
allocation, filesystem, and C ABI wrapper over vendored LinuxCNC execution
|
||||||
|
paths; it does not define G-code semantics. Full planner/session SDK
|
||||||
|
coverage remains future work.
|
||||||
- OPFS persistence is connected to the INI panel through the host-side
|
- OPFS persistence is connected to the INI panel through the host-side
|
||||||
`runtime/opfs/file-service.js` adapter. `runtime/opfs/path-model.js` now
|
`runtime/opfs/file-service.js` adapter. `runtime/opfs/path-model.js` now
|
||||||
defines paths for INI, tool table, parameter file, G-code program,
|
defines paths for INI, tool table, parameter file, G-code program,
|
||||||
|
|||||||
@@ -183,9 +183,13 @@ char *lcinterp_run_program(const char *program_text)
|
|||||||
}
|
}
|
||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE
|
EMSCRIPTEN_KEEPALIVE
|
||||||
char *lcinterp_run_file(const char *path)
|
char *lcinterp_run_file_with_ini(const char *path, const char *ini_path)
|
||||||
{
|
{
|
||||||
|
if (ini_path && ini_path[0] != '\0') {
|
||||||
|
setenv("INI_FILE_NAME", ini_path, 1);
|
||||||
|
} else {
|
||||||
unsetenv("INI_FILE_NAME");
|
unsetenv("INI_FILE_NAME");
|
||||||
|
}
|
||||||
|
|
||||||
Interp interp;
|
Interp interp;
|
||||||
standalone::reset_canon_events();
|
standalone::reset_canon_events();
|
||||||
@@ -248,6 +252,12 @@ char *lcinterp_run_file(const char *path)
|
|||||||
return copy_result(output.str());
|
return copy_result(output.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EMSCRIPTEN_KEEPALIVE
|
||||||
|
char *lcinterp_run_file(const char *path)
|
||||||
|
{
|
||||||
|
return lcinterp_run_file_with_ini(path, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE
|
EMSCRIPTEN_KEEPALIVE
|
||||||
void lcinterp_free_string(char *value)
|
void lcinterp_free_string(char *value)
|
||||||
{
|
{
|
||||||
|
|||||||
41
wasm-port/runtime/sdk/README.md
Normal file
41
wasm-port/runtime/sdk/README.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# LinuxCNC WASM SDK
|
||||||
|
|
||||||
|
This SDK exposes browser/Node JavaScript wrappers for standalone WASM modules
|
||||||
|
built from vendored LinuxCNC source.
|
||||||
|
|
||||||
|
## Boundary
|
||||||
|
|
||||||
|
The SDK is a host-boundary layer only. It may:
|
||||||
|
|
||||||
|
- load generated Emscripten modules;
|
||||||
|
- allocate and free C strings;
|
||||||
|
- write text files into the Emscripten filesystem;
|
||||||
|
- call exported C ABI functions;
|
||||||
|
- return LinuxCNC-produced text output to JavaScript callers.
|
||||||
|
|
||||||
|
The SDK must not implement G-code interpretation, canonical motion behavior,
|
||||||
|
tool semantics, parameter semantics, kinematics, or planner behavior. Those
|
||||||
|
behaviors must continue to come from vendored LinuxCNC source through the C ABI.
|
||||||
|
|
||||||
|
## Entrypoints
|
||||||
|
|
||||||
|
Use `src/index.js` for stable imports:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import {
|
||||||
|
createLinuxCncIniSdk,
|
||||||
|
createLinuxCncInterpSdk,
|
||||||
|
} from "./src/index.js";
|
||||||
|
```
|
||||||
|
|
||||||
|
`createLinuxCncIniSdk()` wraps the INI parser module built from vendored
|
||||||
|
LinuxCNC `inifile.cc`.
|
||||||
|
|
||||||
|
`createLinuxCncInterpSdk()` wraps the interpreter-core module built from
|
||||||
|
vendored LinuxCNC RS274NGC sources and exposes:
|
||||||
|
|
||||||
|
- `runProgram(programText)`
|
||||||
|
- `runProgramWithIni(programText, iniPath)`
|
||||||
|
- `runFile(path)`
|
||||||
|
- `runFileWithIni(path, iniPath)`
|
||||||
|
- `writeTextFile(path, text)`
|
||||||
2
wasm-port/runtime/sdk/src/index.js
Normal file
2
wasm-port/runtime/sdk/src/index.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { createLinuxCncIniSdk } from "./linuxcnc-ini.js";
|
||||||
|
export { createLinuxCncInterpSdk } from "./linuxcnc-interp.js";
|
||||||
73
wasm-port/runtime/sdk/src/linuxcnc-interp.js
Normal file
73
wasm-port/runtime/sdk/src/linuxcnc-interp.js
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import createLinuxCncInterpModule from "../../../build/wasm/core/linuxcnc_interp.js";
|
||||||
|
|
||||||
|
function allocCString(mod, value) {
|
||||||
|
const bytes = mod.lengthBytesUTF8(value) + 1;
|
||||||
|
const ptr = mod._malloc(bytes);
|
||||||
|
mod.stringToUTF8(value, ptr, bytes);
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureParentPath(mod, path) {
|
||||||
|
const parts = path.split("/").filter(Boolean);
|
||||||
|
let current = "";
|
||||||
|
for (const part of parts.slice(0, -1)) {
|
||||||
|
current += `/${part}`;
|
||||||
|
try {
|
||||||
|
mod.FS.mkdir(current);
|
||||||
|
} catch {
|
||||||
|
// Directory already exists.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyResultString(mod, resultPtr, functionName) {
|
||||||
|
if (!resultPtr) {
|
||||||
|
throw new Error(`${functionName} returned null`);
|
||||||
|
}
|
||||||
|
return mod.UTF8ToString(resultPtr);
|
||||||
|
}
|
||||||
|
|
||||||
|
function callStringResult(mod, functionName, ...values) {
|
||||||
|
const ptrs = values.map((value) => allocCString(mod, value));
|
||||||
|
let resultPtr = 0;
|
||||||
|
try {
|
||||||
|
resultPtr = mod[`_${functionName}`](...ptrs);
|
||||||
|
return copyResultString(mod, resultPtr, functionName);
|
||||||
|
} finally {
|
||||||
|
if (resultPtr) {
|
||||||
|
mod._lcinterp_free_string(resultPtr);
|
||||||
|
}
|
||||||
|
for (const ptr of ptrs) {
|
||||||
|
mod._free(ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createLinuxCncInterpSdk(moduleOptions = {}) {
|
||||||
|
const mod = await createLinuxCncInterpModule(moduleOptions);
|
||||||
|
|
||||||
|
return {
|
||||||
|
module: mod,
|
||||||
|
|
||||||
|
writeTextFile(path, text) {
|
||||||
|
ensureParentPath(mod, path);
|
||||||
|
mod.FS.writeFile(path, text, { encoding: "utf8" });
|
||||||
|
},
|
||||||
|
|
||||||
|
runProgram(programText) {
|
||||||
|
return callStringResult(mod, "lcinterp_run_program", programText);
|
||||||
|
},
|
||||||
|
|
||||||
|
runProgramWithIni(programText, iniPath) {
|
||||||
|
return callStringResult(mod, "lcinterp_run_program_with_ini", programText, iniPath);
|
||||||
|
},
|
||||||
|
|
||||||
|
runFile(path) {
|
||||||
|
return callStringResult(mod, "lcinterp_run_file", path);
|
||||||
|
},
|
||||||
|
|
||||||
|
runFileWithIni(path, iniPath) {
|
||||||
|
return callStringResult(mod, "lcinterp_run_file_with_ini", path, iniPath);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createLinuxCncIniSdk } from "../../sdk/src/linuxcnc-ini.js";
|
import { createLinuxCncIniSdk } from "../../sdk/src/index.js";
|
||||||
import {
|
import {
|
||||||
getOpfsRoot,
|
getOpfsRoot,
|
||||||
loadTextFile,
|
loadTextFile,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<pre id="status">running</pre>
|
<pre id="status">running</pre>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { createLinuxCncIniSdk } from "../../runtime/sdk/src/linuxcnc-ini.js";
|
import { createLinuxCncIniSdk } from "../../runtime/sdk/src/index.js";
|
||||||
import { loadTextFile, saveTextFile } from "../../runtime/opfs/file-service.js";
|
import { loadTextFile, saveTextFile } from "../../runtime/opfs/file-service.js";
|
||||||
import { machineIniPath } from "../../runtime/opfs/path-model.js";
|
import { machineIniPath } from "../../runtime/opfs/path-model.js";
|
||||||
import {
|
import {
|
||||||
|
|||||||
138
wasm-port/tests/browser/interp_smoke.html
Normal file
138
wasm-port/tests/browser/interp_smoke.html
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>LinuxCNC Interpreter Browser Smoke</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<pre id="status">running</pre>
|
||||||
|
<script type="module">
|
||||||
|
import { createLinuxCncInterpSdk } from "../../runtime/sdk/src/index.js";
|
||||||
|
|
||||||
|
const status = document.getElementById("status");
|
||||||
|
|
||||||
|
async function fetchText(path) {
|
||||||
|
const response = await fetch(path);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`${path}: HTTP ${response.status}`);
|
||||||
|
}
|
||||||
|
return response.text();
|
||||||
|
}
|
||||||
|
|
||||||
|
function verifyExpectedOutput(fixtureName, output, expectedText) {
|
||||||
|
const expectedLines = expectedText.split("\n").filter(Boolean);
|
||||||
|
for (const expectedLine of expectedLines) {
|
||||||
|
if (expectedLine.startsWith("absent=")) {
|
||||||
|
const forbidden = expectedLine.slice("absent=".length);
|
||||||
|
if (output.includes(forbidden)) {
|
||||||
|
throw new Error(`${fixtureName}: unexpected ${forbidden}`);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!output.includes(expectedLine)) {
|
||||||
|
throw new Error(`${fixtureName}: missing ${expectedLine}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const interp = await createLinuxCncInterpSdk({
|
||||||
|
locateFile(path) {
|
||||||
|
if (path === "linuxcnc_interp.wasm") {
|
||||||
|
return "../../build/wasm/core/linuxcnc_interp.wasm";
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
},
|
||||||
|
print() {},
|
||||||
|
printErr(message) {
|
||||||
|
console.error(message);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const fixtureName of [
|
||||||
|
"minimal_linear",
|
||||||
|
"arc_semantics",
|
||||||
|
"length_units",
|
||||||
|
"modal_incremental",
|
||||||
|
"plane_selection",
|
||||||
|
"coordinate_offsets",
|
||||||
|
"g53_machine_coordinates",
|
||||||
|
"feed_control_modes",
|
||||||
|
"canned_cycles",
|
||||||
|
"numbered_params",
|
||||||
|
"comment_logging",
|
||||||
|
"tool_semantics",
|
||||||
|
"tool_table_setup",
|
||||||
|
"probe_semantics",
|
||||||
|
"spindle_orient",
|
||||||
|
"cutter_comp_motion",
|
||||||
|
"threading_sync",
|
||||||
|
"nurbs_g5_semantics",
|
||||||
|
"nurbs_g6_semantics",
|
||||||
|
"state_tag_motion",
|
||||||
|
"canon_runtime_edges",
|
||||||
|
"tool_reload",
|
||||||
|
"program_end_modal_reset",
|
||||||
|
]) {
|
||||||
|
const programText = await fetchText(`../fixtures/gcode/${fixtureName}.ngc`);
|
||||||
|
const expectedText = await fetchText(`../fixtures/canon/${fixtureName}.events`);
|
||||||
|
verifyExpectedOutput(fixtureName, interp.runProgram(programText), expectedText.trimEnd());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const errorFixtureName of [
|
||||||
|
"g1_zero_feed",
|
||||||
|
"arc_radius_mismatch",
|
||||||
|
"arc_zero_radius",
|
||||||
|
"g53_incremental",
|
||||||
|
"cutter_comp_plane_change",
|
||||||
|
"namedparam_readonly",
|
||||||
|
"numbered_param_readonly",
|
||||||
|
"tool_length_offset_not_found",
|
||||||
|
"tool_not_found",
|
||||||
|
]) {
|
||||||
|
verifyExpectedOutput(
|
||||||
|
errorFixtureName,
|
||||||
|
interp.runProgram(await fetchText(`../fixtures/gcode_errors/${errorFixtureName}.ngc`)),
|
||||||
|
(await fetchText(`../fixtures/canon_errors/${errorFixtureName}.expected`)).trimEnd(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const positionParamsText = await fetchText("../fixtures/gcode/position_params.ngc");
|
||||||
|
const positionParamsPath = "/work/position_params.ngc";
|
||||||
|
interp.writeTextFile(positionParamsPath, positionParamsText);
|
||||||
|
verifyExpectedOutput(
|
||||||
|
"position_params_file",
|
||||||
|
interp.runFile(positionParamsPath),
|
||||||
|
(await fetchText("../fixtures/canon_file/position_params.events")).trimEnd(),
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const fileFixtureName of ["file_open_reset", "percent_file_finish", "oword_subroutine"]) {
|
||||||
|
const programPath = `/work/${fileFixtureName}.ngc`;
|
||||||
|
interp.writeTextFile(programPath, await fetchText(`../fixtures/gcode/${fileFixtureName}.ngc`));
|
||||||
|
verifyExpectedOutput(
|
||||||
|
`${fileFixtureName}_file`,
|
||||||
|
interp.runFile(programPath),
|
||||||
|
(await fetchText(`../fixtures/canon/${fileFixtureName}.events`)).trimEnd(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const namedParamIniPath = "/work/namedparams.ini";
|
||||||
|
interp.writeTextFile(namedParamIniPath, await fetchText("../fixtures/ini/namedparams.ini"));
|
||||||
|
const namedParamPath = "/work/namedparam_semantics.ngc";
|
||||||
|
interp.writeTextFile(
|
||||||
|
namedParamPath,
|
||||||
|
await fetchText("../fixtures/gcode/namedparam_semantics.ngc"),
|
||||||
|
);
|
||||||
|
verifyExpectedOutput(
|
||||||
|
"namedparam_semantics_file",
|
||||||
|
interp.runFileWithIni(namedParamPath, namedParamIniPath),
|
||||||
|
(await fetchText("../fixtures/canon/namedparam_semantics.events")).trimEnd(),
|
||||||
|
);
|
||||||
|
|
||||||
|
status.textContent = "browser_interp_smoke=ok";
|
||||||
|
} catch (error) {
|
||||||
|
status.textContent = `browser_interp_smoke=fail ${error.stack || error.message}`;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
78
wasm-port/tests/browser/verify_interp_browser.sh
Executable file
78
wasm-port/tests/browser/verify_interp_browser.sh
Executable file
@@ -0,0 +1,78 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||||
|
CHROMIUM="${CHROMIUM:-$(command -v chromium || command -v chromium-browser || command -v google-chrome || command -v google-chrome-stable || true)}"
|
||||||
|
|
||||||
|
if [[ -z "$CHROMIUM" ]]; then
|
||||||
|
echo "missing Chromium-compatible browser; set CHROMIUM=/path/to/browser" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${SKIP_INTERP_BUILD:-0}" != "1" ]]; then
|
||||||
|
"$ROOT_DIR/tools/build_wasm_core.sh"
|
||||||
|
fi
|
||||||
|
|
||||||
|
TMP_DIR="$(mktemp -d)"
|
||||||
|
PORT_FILE="$TMP_DIR/port"
|
||||||
|
SERVER_LOG="$TMP_DIR/server.log"
|
||||||
|
CHROME_PROFILE="$TMP_DIR/chrome-profile"
|
||||||
|
mkdir -p "$CHROME_PROFILE"
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
if [[ -n "${SERVER_PID:-}" ]]; then
|
||||||
|
kill "$SERVER_PID" 2>/dev/null || true
|
||||||
|
wait "$SERVER_PID" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
rm -rf "$TMP_DIR"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
python3 - <<'PY' "$ROOT_DIR" "$PORT_FILE" >"$SERVER_LOG" 2>&1 &
|
||||||
|
import functools
|
||||||
|
import http.server
|
||||||
|
import pathlib
|
||||||
|
import socketserver
|
||||||
|
import sys
|
||||||
|
|
||||||
|
root = pathlib.Path(sys.argv[1])
|
||||||
|
port_file = pathlib.Path(sys.argv[2])
|
||||||
|
|
||||||
|
handler = functools.partial(http.server.SimpleHTTPRequestHandler, directory=str(root))
|
||||||
|
with socketserver.TCPServer(("127.0.0.1", 0), handler) as httpd:
|
||||||
|
port_file.write_text(str(httpd.server_address[1]), encoding="ascii")
|
||||||
|
httpd.serve_forever()
|
||||||
|
PY
|
||||||
|
SERVER_PID=$!
|
||||||
|
|
||||||
|
for _ in $(seq 1 100); do
|
||||||
|
[[ -s "$PORT_FILE" ]] && break
|
||||||
|
sleep 0.05
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ! -s "$PORT_FILE" ]]; then
|
||||||
|
echo "browser interpreter smoke HTTP server did not start" >&2
|
||||||
|
cat "$SERVER_LOG" >&2 || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
PORT="$(cat "$PORT_FILE")"
|
||||||
|
URL="http://127.0.0.1:$PORT/tests/browser/interp_smoke.html"
|
||||||
|
OUT="$TMP_DIR/chromium.out"
|
||||||
|
|
||||||
|
"$CHROMIUM" \
|
||||||
|
--headless=new \
|
||||||
|
--disable-gpu \
|
||||||
|
--no-sandbox \
|
||||||
|
--user-data-dir="$CHROME_PROFILE" \
|
||||||
|
--virtual-time-budget=10000 \
|
||||||
|
--dump-dom \
|
||||||
|
"$URL" >"$OUT" 2>&1
|
||||||
|
|
||||||
|
if ! grep -Fq "browser_interp_smoke=ok" "$OUT"; then
|
||||||
|
echo "browser interpreter smoke failed" >&2
|
||||||
|
sed -n '1,220p' "$OUT" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "browser_interp_smoke=ok"
|
||||||
15
wasm-port/tests/fixtures/canon_file/position_params.events
vendored
Normal file
15
wasm-port/tests/fixtures/canon_file/position_params.events
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
file_open=0
|
||||||
|
file_read_count=3
|
||||||
|
file_execute_count=2
|
||||||
|
canon_event=STRAIGHT_TRAVERSE line=1 x=1 y=2 z=3 a=0 b=0 c=0 u=0 v=0 w=0
|
||||||
|
canon_event=SET_FEED_RATE rate=80
|
||||||
|
canon_event=COMMENT: interpreter: distance mode changed to incremental
|
||||||
|
canon_event=STRAIGHT_FEED line=2 x=1.5 y=1 z=5 a=0 b=0 c=0 u=0 v=0 w=0
|
||||||
|
setup.current_x=1.5
|
||||||
|
setup.current_y=1
|
||||||
|
setup.current_z=5
|
||||||
|
setup.parameter_5420=1.5
|
||||||
|
setup.parameter_5421=1
|
||||||
|
setup.parameter_5422=5
|
||||||
|
post_execute.distance_mode=1
|
||||||
|
post_execute.motion_mode=10
|
||||||
@@ -9,5 +9,6 @@ SKIP_INI_BUILD=1 "$ROOT_DIR/tests/wasm/node/verify_ini_wasm.sh"
|
|||||||
SKIP_INTERP_BUILD=1 "$ROOT_DIR/tests/wasm/node/verify_interp_wasm.sh"
|
SKIP_INTERP_BUILD=1 "$ROOT_DIR/tests/wasm/node/verify_interp_wasm.sh"
|
||||||
"$ROOT_DIR/tests/opfs/node/verify_file_service.sh"
|
"$ROOT_DIR/tests/opfs/node/verify_file_service.sh"
|
||||||
SKIP_INI_BUILD=1 "$ROOT_DIR/tests/browser/verify_ini_panel_browser.sh"
|
SKIP_INI_BUILD=1 "$ROOT_DIR/tests/browser/verify_ini_panel_browser.sh"
|
||||||
|
SKIP_INTERP_BUILD=1 "$ROOT_DIR/tests/browser/verify_interp_browser.sh"
|
||||||
|
|
||||||
echo "host_wasm_opfs_browser_smokes=ok"
|
echo "host_wasm_opfs_browser_smokes=ok"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { fileURLToPath } from "node:url";
|
|||||||
import { dirname, resolve } from "node:path";
|
import { dirname, resolve } from "node:path";
|
||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
|
|
||||||
import { createLinuxCncIniSdk } from "../../../runtime/sdk/src/linuxcnc-ini.js";
|
import { createLinuxCncIniSdk } from "../../../runtime/sdk/src/index.js";
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = dirname(__filename);
|
const __dirname = dirname(__filename);
|
||||||
|
|||||||
@@ -3,69 +3,7 @@ import { fileURLToPath } from "node:url";
|
|||||||
import { dirname, resolve } from "node:path";
|
import { dirname, resolve } from "node:path";
|
||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
|
|
||||||
import createLinuxCncInterpModule from "../../../build/wasm/core/linuxcnc_interp.js";
|
import { createLinuxCncInterpSdk } from "../../../runtime/sdk/src/index.js";
|
||||||
|
|
||||||
function allocCString(mod, value) {
|
|
||||||
const bytes = mod.lengthBytesUTF8(value) + 1;
|
|
||||||
const ptr = mod._malloc(bytes);
|
|
||||||
mod.stringToUTF8(value, ptr, bytes);
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
function runProgram(mod, programText) {
|
|
||||||
const programPtr = allocCString(mod, programText);
|
|
||||||
let resultPtr = 0;
|
|
||||||
try {
|
|
||||||
resultPtr = mod._lcinterp_run_program(programPtr);
|
|
||||||
assert.notEqual(resultPtr, 0);
|
|
||||||
return mod.UTF8ToString(resultPtr);
|
|
||||||
} finally {
|
|
||||||
if (resultPtr) {
|
|
||||||
mod._lcinterp_free_string(resultPtr);
|
|
||||||
}
|
|
||||||
mod._free(programPtr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function runProgramWithIni(mod, programText, iniPath) {
|
|
||||||
const programPtr = allocCString(mod, programText);
|
|
||||||
const iniPathPtr = allocCString(mod, iniPath);
|
|
||||||
let resultPtr = 0;
|
|
||||||
try {
|
|
||||||
resultPtr = mod._lcinterp_run_program_with_ini(programPtr, iniPathPtr);
|
|
||||||
assert.notEqual(resultPtr, 0);
|
|
||||||
return mod.UTF8ToString(resultPtr);
|
|
||||||
} finally {
|
|
||||||
if (resultPtr) {
|
|
||||||
mod._lcinterp_free_string(resultPtr);
|
|
||||||
}
|
|
||||||
mod._free(programPtr);
|
|
||||||
mod._free(iniPathPtr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
function verifyExpectedOutput(fixtureName, output, expectedText) {
|
||||||
const expectedLines = expectedText.split("\n").filter(Boolean);
|
const expectedLines = expectedText.split("\n").filter(Boolean);
|
||||||
@@ -94,7 +32,7 @@ const __dirname = dirname(__filename);
|
|||||||
const rootDir = resolve(__dirname, "../../..");
|
const rootDir = resolve(__dirname, "../../..");
|
||||||
const wasmPath = resolve(rootDir, "build/wasm/core/linuxcnc_interp.wasm");
|
const wasmPath = resolve(rootDir, "build/wasm/core/linuxcnc_interp.wasm");
|
||||||
|
|
||||||
const interp = await createLinuxCncInterpModule({
|
const interp = await createLinuxCncInterpSdk({
|
||||||
wasmBinary: readFileSync(wasmPath),
|
wasmBinary: readFileSync(wasmPath),
|
||||||
print() {},
|
print() {},
|
||||||
printErr(message) {
|
printErr(message) {
|
||||||
@@ -139,15 +77,13 @@ for (const fixtureName of fixtureNames) {
|
|||||||
"utf8",
|
"utf8",
|
||||||
).trimEnd();
|
).trimEnd();
|
||||||
|
|
||||||
verifyExpectedOutput(fixtureName, runProgram(interp, programText), expectedEvents);
|
verifyExpectedOutput(fixtureName, interp.runProgram(programText), expectedEvents);
|
||||||
}
|
}
|
||||||
|
|
||||||
ensureDir(interp, "/work");
|
|
||||||
const namedParamIniPath = "/work/namedparams.ini";
|
const namedParamIniPath = "/work/namedparams.ini";
|
||||||
interp.FS.writeFile(
|
interp.writeTextFile(
|
||||||
namedParamIniPath,
|
namedParamIniPath,
|
||||||
readFileSync(resolve(rootDir, "tests/fixtures/ini/namedparams.ini"), "utf8"),
|
readFileSync(resolve(rootDir, "tests/fixtures/ini/namedparams.ini"), "utf8"),
|
||||||
{ encoding: "utf8" },
|
|
||||||
);
|
);
|
||||||
const namedParamProgramText = readFileSync(
|
const namedParamProgramText = readFileSync(
|
||||||
resolve(rootDir, "tests/fixtures/gcode/namedparam_semantics.ngc"),
|
resolve(rootDir, "tests/fixtures/gcode/namedparam_semantics.ngc"),
|
||||||
@@ -159,7 +95,7 @@ const namedParamExpected = readFileSync(
|
|||||||
).trimEnd();
|
).trimEnd();
|
||||||
verifyExpectedOutput(
|
verifyExpectedOutput(
|
||||||
"namedparam_semantics",
|
"namedparam_semantics",
|
||||||
runProgramWithIni(interp, namedParamProgramText, namedParamIniPath),
|
interp.runProgramWithIni(namedParamProgramText, namedParamIniPath),
|
||||||
namedParamExpected,
|
namedParamExpected,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -185,7 +121,7 @@ for (const fixtureName of errorFixtureNames) {
|
|||||||
"utf8",
|
"utf8",
|
||||||
).trimEnd();
|
).trimEnd();
|
||||||
|
|
||||||
verifyExpectedOutput(fixtureName, runProgram(interp, programText), expectedOutput);
|
verifyExpectedOutput(fixtureName, interp.runProgram(programText), expectedOutput);
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileFixtureNames = [
|
const fileFixtureNames = [
|
||||||
@@ -228,7 +164,29 @@ for (const fixtureName of fileFixtureNames) {
|
|||||||
"utf8",
|
"utf8",
|
||||||
).trimEnd();
|
).trimEnd();
|
||||||
|
|
||||||
interp.FS.writeFile(programPath, programText, { encoding: "utf8" });
|
interp.writeTextFile(programPath, programText);
|
||||||
verifyExpectedOutput(fixtureName, runFile(interp, programPath), expectedEvents);
|
verifyExpectedOutput(fixtureName, interp.runFile(programPath), expectedEvents);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const namedParamFilePath = "/work/namedparam_semantics.ngc";
|
||||||
|
interp.writeTextFile(namedParamFilePath, namedParamProgramText);
|
||||||
|
verifyExpectedOutput(
|
||||||
|
"namedparam_semantics_file",
|
||||||
|
interp.runFileWithIni(namedParamFilePath, namedParamIniPath),
|
||||||
|
namedParamExpected,
|
||||||
|
);
|
||||||
|
|
||||||
|
const positionParamsFilePath = "/work/position_params.ngc";
|
||||||
|
interp.writeTextFile(
|
||||||
|
positionParamsFilePath,
|
||||||
|
readFileSync(resolve(rootDir, "tests/fixtures/gcode/position_params.ngc"), "utf8"),
|
||||||
|
);
|
||||||
|
verifyExpectedOutput(
|
||||||
|
"position_params_file",
|
||||||
|
interp.runFile(positionParamsFilePath),
|
||||||
|
readFileSync(
|
||||||
|
resolve(rootDir, "tests/fixtures/canon_file/position_params.events"),
|
||||||
|
"utf8",
|
||||||
|
).trimEnd(),
|
||||||
|
);
|
||||||
console.log("interp_wasm_node_smoke=ok");
|
console.log("interp_wasm_node_smoke=ok");
|
||||||
|
|||||||
@@ -95,5 +95,5 @@ link_wasm_module \
|
|||||||
-s STACK_SIZE=2MB \
|
-s STACK_SIZE=2MB \
|
||||||
-s NO_EXIT_RUNTIME=1 \
|
-s NO_EXIT_RUNTIME=1 \
|
||||||
-s FORCE_FILESYSTEM=1 \
|
-s FORCE_FILESYSTEM=1 \
|
||||||
-s EXPORTED_FUNCTIONS='["_malloc","_free","_lcinterp_run_program","_lcinterp_run_program_with_ini","_lcinterp_run_file","_lcinterp_free_string"]' \
|
-s EXPORTED_FUNCTIONS='["_malloc","_free","_lcinterp_run_program","_lcinterp_run_program_with_ini","_lcinterp_run_file","_lcinterp_run_file_with_ini","_lcinterp_free_string"]' \
|
||||||
-s EXPORTED_RUNTIME_METHODS='["FS","UTF8ToString","stringToUTF8","lengthBytesUTF8"]'
|
-s EXPORTED_RUNTIME_METHODS='["FS","UTF8ToString","stringToUTF8","lengthBytesUTF8"]'
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ set -euo pipefail
|
|||||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
|
||||||
TMP_FILE="$(mktemp)"
|
TMP_FILE="$(mktemp)"
|
||||||
trap 'rm -f "$TMP_FILE"' EXIT
|
VIOLATION_FILE="$(mktemp)"
|
||||||
|
trap 'rm -f "$TMP_FILE" "$VIOLATION_FILE"' EXIT
|
||||||
|
|
||||||
find \
|
find \
|
||||||
"$ROOT_DIR/runtime" \
|
"$ROOT_DIR/runtime" \
|
||||||
@@ -15,11 +16,44 @@ find \
|
|||||||
! -path "$ROOT_DIR/vendor/*" \
|
! -path "$ROOT_DIR/vendor/*" \
|
||||||
! -path "$ROOT_DIR/runtime/ui/ini-panel/linuxcnc_ini.js" \
|
! -path "$ROOT_DIR/runtime/ui/ini-panel/linuxcnc_ini.js" \
|
||||||
-print0 |
|
-print0 |
|
||||||
xargs -0 grep -nE '^[[:space:]]*int[[:space:]]+Interp::convert_g[[:space:]]*\(' > "$TMP_FILE" || true
|
xargs -0 grep -nE '^[[:space:]]*([A-Za-z_][A-Za-z0-9_:<>~*&[:space:]]+[[:space:]]+)?Interp::[A-Za-z_~][A-Za-z0-9_]*[[:space:]]*\(' > "$TMP_FILE" || true
|
||||||
|
|
||||||
if [[ -s "$TMP_FILE" ]]; then
|
ALLOWED_STUB_FILE="$ROOT_DIR/runtime/core/linuxcnc_wrap/linuxcnc_interp_edge_stubs.cpp"
|
||||||
echo "standalone Interp::convert_g definitions are not allowed:" >&2
|
|
||||||
cat "$TMP_FILE" >&2
|
is_allowed_interp_stub() {
|
||||||
|
local line="$1"
|
||||||
|
|
||||||
|
case "$line" in
|
||||||
|
"$ALLOWED_STUB_FILE":*Interp::is_pycallable\(* | \
|
||||||
|
"$ALLOWED_STUB_FILE":*Interp::is_user_defined_g_code\(* | \
|
||||||
|
"$ALLOWED_STUB_FILE":*Interp::is_any_m_code_remapped\(* | \
|
||||||
|
"$ALLOWED_STUB_FILE":*Interp::is_user_defined_m_code\(* | \
|
||||||
|
"$ALLOWED_STUB_FILE":*Interp::is_m_code_remappable\(* | \
|
||||||
|
"$ALLOWED_STUB_FILE":*Interp::is_g_code_remappable\(* | \
|
||||||
|
"$ALLOWED_STUB_FILE":*Interp::remap_in_progress\(* | \
|
||||||
|
"$ALLOWED_STUB_FILE":*Interp::remapping\(* | \
|
||||||
|
"$ALLOWED_STUB_FILE":*Interp::parse_remap\(* | \
|
||||||
|
"$ALLOWED_STUB_FILE":*Interp::convert_remapped_code\(* | \
|
||||||
|
"$ALLOWED_STUB_FILE":*Interp::add_parameters\(* | \
|
||||||
|
"$ALLOWED_STUB_FILE":*Interp::pycall\(* | \
|
||||||
|
"$ALLOWED_STUB_FILE":*Interp::py_execute\(* | \
|
||||||
|
"$ALLOWED_STUB_FILE":*Interp::py_reload\(*)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
while IFS= read -r line; do
|
||||||
|
if ! is_allowed_interp_stub "$line"; then
|
||||||
|
printf '%s\n' "$line" >> "$VIOLATION_FILE"
|
||||||
|
fi
|
||||||
|
done < "$TMP_FILE"
|
||||||
|
|
||||||
|
if [[ -s "$VIOLATION_FILE" ]]; then
|
||||||
|
echo "standalone Interp member definitions are not allowed outside documented runtime-edge stubs:" >&2
|
||||||
|
cat "$VIOLATION_FILE" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user