|
| 1 | +# WebMYSTRAN (WebAssembly) |
| 2 | + |
| 3 | +Builds [MYSTRAN](https://github.com/MYSTRANsolver/MYSTRAN) into WebAssembly and provides: |
| 4 | + |
| 5 | +- reproducible fetch/build scripts |
| 6 | +- quick smoke and full benchmark-deck smoke validation |
| 7 | +- an ESM JavaScript library (`src/web_mystran.js`, `src/mystran_input.js`, `src/output_parser.js`) |
| 8 | + |
| 9 | +## Requirements |
| 10 | + |
| 11 | +- Docker (or compatible daemon) |
| 12 | +- Node.js 18+ |
| 13 | +- PowerShell (Windows helper scripts) |
| 14 | + |
| 15 | +## Quick Start (Windows) |
| 16 | + |
| 17 | +1. `powershell -File scripts/fetch_deps.ps1` |
| 18 | +2. `powershell -File scripts/build.ps1` |
| 19 | +3. `node tools/smoke_test.js` |
| 20 | + |
| 21 | +For full benchmark-deck smoke checks: |
| 22 | + |
| 23 | +- `powershell -File scripts/smoke_test.ps1` |
| 24 | + |
| 25 | +## npm Scripts |
| 26 | + |
| 27 | +- `npm run fetch-deps` |
| 28 | +- `npm run build` |
| 29 | +- `npm run smoke` (library-level smoke) |
| 30 | +- `npm run smoke:full` (full deck smoke) |
| 31 | + |
| 32 | +If PowerShell blocks `npm.ps1` by execution policy, run `npm.cmd` instead (for example: `npm.cmd run smoke`). |
| 33 | + |
| 34 | +## Build Outputs |
| 35 | + |
| 36 | +- `dist/mystran.js` (ES module, `MODULARIZE=1`, `EXPORT_ES6=1`) |
| 37 | +- `dist/mystran.wasm` |
| 38 | +- `sources/MYSTRAN_native_ref/Binaries/mystran` (native Linux reference binary for smoke compare) |
| 39 | + |
| 40 | +## Module Exports |
| 41 | + |
| 42 | +- `./index.js` -> `WebMYSTRAN`, `MystranInput`, `parseMystranOutput` |
| 43 | +- `./src/web_mystran.js` -> `WebMYSTRAN` |
| 44 | +- `./src/mystran_input.js` -> `MystranInput` |
| 45 | +- `./src/output_parser.js` -> `parseMystranOutput` |
| 46 | + |
| 47 | +## JavaScript API (`index.js` exports) |
| 48 | + |
| 49 | +### `WebMYSTRAN` |
| 50 | + |
| 51 | +- `static async load(options?) -> Promise<WebMYSTRAN>` |
| 52 | +- `static input(lines?) -> MystranInput` |
| 53 | +- `static Input` (alias for `MystranInput`) |
| 54 | +- `input(lines?) -> MystranInput` |
| 55 | +- `run(deckText, options?) -> result` |
| 56 | +- `readOutput(result, extension, encoding?)` |
| 57 | +- `writeFile(path, data)`, `readFile(path, encoding?)`, `FS`, `reset()`, `destroy()` |
| 58 | + |
| 59 | +`WebMYSTRAN.load(options?)` accepts: |
| 60 | + |
| 61 | +- `moduleUrl`: URL/path override for `dist/mystran.js` |
| 62 | +- `wasmUrl`: URL/path override for `dist/mystran.wasm` |
| 63 | +- `moduleFactory`: custom emscripten module factory function |
| 64 | +- `exportName`: export name when loading from `moduleUrl` (default `WebmystranModule`) |
| 65 | +- `wasmBinary`: `Uint8Array`/`ArrayBuffer`/`Buffer` override for wasm bytes |
| 66 | +- `workDir`: virtual FS working directory (default `/work`) |
| 67 | +- `onStdout`/`onStderr`: per-line callbacks |
| 68 | +- `moduleOptions`/`locateFile`: forwarded to emscripten module options |
| 69 | + |
| 70 | +`run(deckText, options?)`: |
| 71 | + |
| 72 | +- writes `deckText` to `deckFileName` (default `WEBMYSTRAN.DAT`) |
| 73 | +- executes wasm in-process via ESM module factory |
| 74 | +- returns: |
| 75 | + - `raw`: `{ stdout, stderr, exitCode, signal, timedOut }` |
| 76 | + - `output`: parsed status object from `parseMystranOutput(raw)` |
| 77 | + - `outputs`: discovered result files by extension (`F06`, `OP2`, ...) |
| 78 | + - `files`: artifact metadata `{ extension, name, path, size }` |
| 79 | + - `workDir`, `deckFileName`, `deckPath` |
| 80 | + |
| 81 | +`run(..., options?)` options: |
| 82 | + |
| 83 | +- `deckFileName`, `workDir`, `stdinText`, `args` |
| 84 | +- `files`: array of `{ path, data }` staged before execution |
| 85 | +- `parseOptions`: forwarded to parser |
| 86 | +- `outputExtensions`: custom extension scan list |
| 87 | + |
| 88 | +### `MystranInput` |
| 89 | + |
| 90 | +Builder for deck text. |
| 91 | + |
| 92 | +- `add(line | lines[])`, `addLines(lines)` |
| 93 | +- `blank(count?)`, `comment(text?)` |
| 94 | +- `sol(value)`, `cend()`, `subcase(id)` |
| 95 | +- `set(key, value)`, `setTitle(value)`, `setSubtitle(value)`, `setLabel(value)` |
| 96 | +- `beginBulk()`, `endData()` |
| 97 | +- `param(name, value)`, `include(path)` |
| 98 | +- `toArray()`, `toString()` |
| 99 | + |
| 100 | +### `parseMystranOutput(raw)` |
| 101 | + |
| 102 | +Parses stdout/stderr into a structured status object: |
| 103 | + |
| 104 | +- `hasNormalTermination` |
| 105 | +- `hasRuntimeAbort` |
| 106 | +- `hasSolverError` |
| 107 | +- `warningCount` |
| 108 | +- `errorCodes`, `firstErrorCode` |
| 109 | + |
| 110 | +## Example |
| 111 | + |
| 112 | +```js |
| 113 | +import fs from "node:fs"; |
| 114 | +import { WebMYSTRAN } from "./index.js"; |
| 115 | + |
| 116 | +const deck = fs.readFileSync( |
| 117 | + "sources/MYSTRAN_Benchmark/Benchmark_Decks/Bush_Bar/bar_01.DAT", |
| 118 | + "utf8" |
| 119 | +); |
| 120 | + |
| 121 | +const solver = await WebMYSTRAN.load(); |
| 122 | +const result = solver.run(deck, { deckFileName: "WEBMYSTRAN.DAT" }); |
| 123 | + |
| 124 | +if (!result.output.hasNormalTermination) { |
| 125 | + throw new Error("MYSTRAN did not terminate normally."); |
| 126 | +} |
| 127 | + |
| 128 | +const f06Text = solver.readOutput(result, "F06", "utf8"); |
| 129 | +console.log(f06Text.slice(0, 400)); |
| 130 | +solver.destroy(); |
| 131 | +``` |
| 132 | + |
| 133 | +## Smoke Testing |
| 134 | + |
| 135 | +### Library smoke |
| 136 | + |
| 137 | +- `node tools/smoke_test.js` |
| 138 | +- Uses benchmark deck `Bush_Bar/bar_01.DAT` |
| 139 | +- Uses the same per-deck PASS/FAIL line format and final summary format as full smoke |
| 140 | +- Runs both wasm and native reference, then checks wasm/native parity for the deck |
| 141 | +- Validates: |
| 142 | + - process exit code |
| 143 | + - `MYSTRAN terminated normally` |
| 144 | + - no parsed `*ERROR` codes |
| 145 | + - required output artifacts (`F06`, `OP2`) |
| 146 | + |
| 147 | +### Full deck smoke |
| 148 | + |
| 149 | +- `scripts/smoke_test.ps1` (or `node tools/smoke_test.js full`) runs wasm and native reference over selected benchmark decks |
| 150 | +- Checks: |
| 151 | + - per-deck wasm and native run outcomes (timeouts/runtime aborts/process errors) |
| 152 | + - required output generation (`F06` plus non-empty result artifacts) |
| 153 | + - wasm/native parity checks (outcome class, termination marker, error code, OP2 presence) |
| 154 | +- Reports one terminal line per deck and a final summary line |
| 155 | +- Writes no smoke artifacts to host disk (no `smoke_run` folder or host-side deck outputs) |
| 156 | + |
| 157 | +## Technical References |
| 158 | + |
| 159 | +- Source/dependency provenance: [SOURCES.md](./SOURCES.md) |
| 160 | +- Patch inventory: [PATCHING.md](./PATCHING.md) |
| 161 | +- Third-party notices: [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md) |
| 162 | + |
| 163 | +## Environment Variables |
| 164 | + |
| 165 | +`scripts/build.ps1` forwards: |
| 166 | + |
| 167 | +- `WEBMYSTRAN_DEBUG_STACKS` |
| 168 | +- `WEBMYSTRAN_TRACE_MALLOC` |
| 169 | +- `WEBMYSTRAN_PROFILE_CHECKPOINTS` |
| 170 | + |
| 171 | +`scripts/smoke_test.ps1` accepts smoke arguments directly. |
| 172 | + |
| 173 | +## License |
| 174 | + |
| 175 | +- Project/package: MIT ([LICENSE](./LICENSE)) |
| 176 | +- Upstream MYSTRAN source is MIT (see `sources/MYSTRAN/LICENSE.txt`) |
| 177 | +- Additional notices: [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md) |
0 commit comments