cppbolt 0.1.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,8 +2,6 @@
2
2
 
3
3
  cppbolt compiles a subset of C++17 directly to WebAssembly without LLVM, Emscripten, or a native toolchain. This package contains the compiler built for `wasm32-unknown-unknown`; it exposes a C ABI and has no imports, so you can instantiate it with an empty import object in Node.js, browsers, or Web Workers.
4
4
 
5
- The package contains the raw WebAssembly module with no JavaScript wrapper or CLI. You call the exports directly.
6
-
7
5
  ## Installation
8
6
 
9
7
  ```sh
@@ -12,74 +10,86 @@ npm install cppbolt
12
10
 
13
11
  ## Usage
14
12
 
13
+ ### Node.js: compile only
14
+
15
+ `createCompiler()` returns a compiler. `compile()` turns C++ source into a
16
+ complete WebAssembly module as a `Uint8Array`.
17
+
15
18
  ```js
16
- import { readFile } from "node:fs/promises";
17
- import { createRequire } from "node:module";
18
-
19
- const require = createRequire(import.meta.url);
20
- const wasmPath = require.resolve("cppbolt/wasm/cppbolt.wasm");
21
- const { instance } = await WebAssembly.instantiate(await readFile(wasmPath), {});
22
- const compiler = instance.exports;
23
-
24
- function compile(source) {
25
- const input = new TextEncoder().encode(source);
26
- const inputPtr = compiler.alloc_buffer(input.length);
27
- new Uint8Array(compiler.memory.buffer, inputPtr, input.length).set(input);
28
-
29
- const resultPtr = compiler.compile_wasm(inputPtr, input.length);
30
- compiler.dealloc_buffer(inputPtr, input.length);
31
-
32
- if (resultPtr === 0) {
33
- const length = compiler.get_error_len();
34
- const bytes = new Uint8Array(compiler.memory.buffer, compiler.get_error_ptr(), length);
35
- throw new Error(new TextDecoder().decode(bytes));
36
- }
37
-
38
- const length = compiler.get_result_len();
39
- const output = new Uint8Array(compiler.memory.buffer, resultPtr, length).slice();
40
- compiler.free_result(resultPtr, length);
41
- return output;
42
- }
43
-
44
- const wasm = compile(`
45
- #include <iostream>
46
-
47
- int main() {
48
- std::cout << "Hello from WebAssembly C++!" << std::endl;
49
- return 0;
50
- }
19
+ import { createCompiler } from "cppbolt";
20
+
21
+ const compiler = await createCompiler();
22
+
23
+ const wasm = compiler.compile(`
24
+ #include <iostream>
25
+ int main() {
26
+ std::cout << "cppbolt says hello!" << std::endl;
27
+ return 0;
28
+ }
51
29
  `);
52
30
  ```
53
31
 
54
- `wasm` is a complete WebAssembly module.
32
+ ### Node.js: compile and run
55
33
 
56
- ### Browser
34
+ `compileAndRun()` compiles the source and immediately executes it, returning
35
+ its stdout and exit status.
57
36
 
58
- Serve `cppbolt.wasm` from your site, then load it:
37
+ ```js
38
+ import { createCompiler, compileAndRun } from "cppbolt";
39
+
40
+ const compiler = await createCompiler();
41
+
42
+ const { stdout, exitCode } = await compileAndRun(
43
+ compiler,
44
+ `
45
+ #include <iostream>
46
+ int main() {
47
+ std::cout << "cppbolt says hello!" << std::endl;
48
+ return 0;
49
+ }
50
+ `,
51
+ );
52
+
53
+ console.log(stdout); // "cppbolt says hello!\n"
54
+ console.log(exitCode); // 0
55
+ ```
56
+
57
+ ### Browser (CDN `<script>` tag)
59
58
 
60
59
  ```html
61
- <script type="module">
62
- const response = await fetch("/wasm/cppbolt.wasm");
63
- const { instance } = await WebAssembly.instantiateStreaming(response, {});
64
- const compiler = instance.exports;
60
+ <script src="https://cdn.jsdelivr.net/npm/cppbolt@1.0.1/dist/cppbolt.browser.js"></script>
61
+ <script>
62
+ (async () => {
63
+ const compiler = await CppBolt.createCompiler();
64
+ const { stdout, exitCode } = await CppBolt.compileAndRun(compiler, `
65
+ #include <iostream>
66
+ int main() {
67
+ std::cout << "cppbolt says hello!" << std::endl;
68
+ return 0;
69
+ }
70
+ `,
71
+ );
72
+ console.log(stdout, exitCode);
73
+ })();
65
74
  </script>
66
75
  ```
67
76
 
68
- `instantiateStreaming` needs the server to send `Content-Type: application/wasm`; otherwise use `WebAssembly.instantiate(await response.arrayBuffer(), {})`. The compiler ABI interaction inside the `compile` function can be reused in browsers. Replace the Node.js-specific WASM loading code with the browser loading example above.
77
+ `createCompiler()` resolves `wasm/cppbolt.wasm` relative to the script URL. When
78
+ using a bundler, import from `"cppbolt"` instead.
69
79
 
70
80
  ## Compiler API
71
81
 
72
82
  Pointers are 32-bit offsets into the module's exported `memory`.
73
83
 
74
- | Export | Signature | Description |
75
- |---|---|---|
76
- | `alloc_buffer` | `(size) -> ptr` | Allocates an uninitialized input buffer. |
77
- | `dealloc_buffer` | `(ptr, size) -> void` | Releases an input buffer with the size passed to `alloc_buffer`. |
78
- | `compile_wasm` | `(ptr, len) -> ptr` | Compiles UTF-8 source. Returns a pointer to the emitted module, or `0` on error. |
79
- | `get_result_len` | `() -> len` | Returns the byte length of the result from the most recent `compile_wasm` call, or `0` if that call failed. |
80
- | `free_result` | `(ptr, len) -> void` | Releases a result buffer returned by `compile_wasm`. |
81
- | `get_error_ptr` | `() -> ptr` | Returns the address of the static error buffer. |
82
- | `get_error_len` | `() -> len` | Returns the byte length of the diagnostic from the most recent failed `compile_wasm` call, or `0` if it succeeded. |
84
+ | Export | Signature | Description |
85
+ | ---------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------ |
86
+ | `alloc_buffer` | `(size) -> ptr` | Allocates an uninitialized input buffer. |
87
+ | `dealloc_buffer` | `(ptr, size) -> void` | Releases an input buffer with the size passed to `alloc_buffer`. |
88
+ | `compile_wasm` | `(ptr, len) -> ptr` | Compiles UTF-8 source. Returns a pointer to the emitted module, or `0` on error. |
89
+ | `get_result_len` | `() -> len` | Returns the byte length of the result from the most recent `compile_wasm` call, or `0` if that call failed. |
90
+ | `free_result` | `(ptr, len) -> void` | Releases a result buffer returned by `compile_wasm`. |
91
+ | `get_error_ptr` | `() -> ptr` | Returns the address of the static error buffer. |
92
+ | `get_error_len` | `() -> len` | Returns the byte length of the diagnostic from the most recent failed `compile_wasm` call, or `0` if it succeeded. |
83
93
 
84
94
  Ownership and lifetime rules:
85
95
 
@@ -104,17 +114,29 @@ See the [supported C++ subset](https://ajtimepyro.is-a.dev/cppbolt/book/user/sup
104
114
 
105
115
  Emitted modules are WebAssembly binaries that import standard I/O functions from an `env` namespace. Every emitted module imports all five functions:
106
116
 
107
- | Import | Signature | Purpose |
108
- |---|---|---|
109
- | `env.print_char` | `(i32) -> void` | Write one character byte to stdout. |
110
- | `env.print_int` | `(i64, i32, i32, i32, i32) -> i32` | Format and write an integer. Returns the character count. |
111
- | `env.print_double` | `(f64, i32, i32, i32, i32) -> i32` | Format and write a double. Returns the character count. |
112
- | `env.read_char` | `() -> i32` | Read one character byte from stdin, or `-1` at end of input. |
113
- | `env.exit` | `(i32) -> void` | Terminate with the given status code. |
117
+ | Import | Signature | Purpose |
118
+ | ------------------ | ---------------------------------- | ------------------------------------------------------------ |
119
+ | `env.print_char` | `(i32) -> void` | Write one character byte to stdout. |
120
+ | `env.print_int` | `(i64, i32, i32, i32, i32) -> i32` | Format and write an integer. Returns the character count. |
121
+ | `env.print_double` | `(f64, i32, i32, i32, i32) -> i32` | Format and write a double. Returns the character count. |
122
+ | `env.read_char` | `() -> i32` | Read one character byte from stdin, or `-1` at end of input. |
123
+ | `env.exit` | `(i32) -> void` | Terminate with the given status code. |
114
124
 
115
125
  Emitted modules export `memory`, `__stack_pointer`, and `__heap_base`, plus `main` if the program defines it. The compiler gives them a 256 MiB maximum linear memory, raised only when static data requires more.
116
126
 
117
- The package does not include a host runtime. The repository's [`web/host.js`](https://github.com/AJTimePyro/cppbolt/blob/main/web/host.js) provides a reference implementation for Node.js and browsers. It handles the integer and floating-point formatting flags.
127
+ ### Host runtime API
128
+
129
+ The package ships a host for those imports, so you do not implement `env.*`
130
+ yourself.
131
+
132
+ - `createWasmHost(options?) -> WasmHost` builds the import environment. Options
133
+ are `stdin` (a string, or `() => number` for streaming input), `onStdout(chunk)`,
134
+ and `onExit(code)`. The result exposes `imports` for `WebAssembly.instantiate`
135
+ and `getStdout()`.
136
+ - `new CppBoltRunner(host).run(wasmBytes) -> Promise<RunResult>` instantiates a
137
+ module, calls `main` (or `_start`), and returns `{ stdout, exitCode }`. A
138
+ WebAssembly trap is reported as exit code 134 instead of throwing.
139
+ - `compileAndRun(compiler, source, options?)` compiles and runs in one call.
118
140
 
119
141
  ## Notes
120
142
 
@@ -0,0 +1,16 @@
1
+ export declare class CompileError extends Error {
2
+ readonly diagnostic: string;
3
+ constructor(diagnostic: string);
4
+ }
5
+ export declare class CppBoltCompiler {
6
+ private readonly _exports;
7
+ constructor(instance: WebAssembly.Instance);
8
+ compile(source: string): Uint8Array;
9
+ }
10
+ /**
11
+ * Load cppbolt.wasm and return a CppBoltCompiler.
12
+ *
13
+ * In Node.js, resolves wasm/cppbolt.wasm from the package directory.
14
+ * In a browser <script> tag, fetches it relative to the script's own URL.
15
+ */
16
+ export declare function createCompiler(): Promise<CppBoltCompiler>;
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CppBoltCompiler = exports.CompileError = void 0;
4
+ exports.createCompiler = createCompiler;
5
+ function tryRequire(id) {
6
+ if (typeof require !== "function")
7
+ return null;
8
+ try {
9
+ return require(id);
10
+ }
11
+ catch {
12
+ return null;
13
+ }
14
+ }
15
+ const fs = tryRequire("fs");
16
+ const path = tryRequire("path");
17
+ const scriptSrc = typeof document !== "undefined" &&
18
+ document.currentScript instanceof HTMLScriptElement
19
+ ? document.currentScript.src
20
+ : null;
21
+ function decodeString(memory, ptr, len) {
22
+ return new TextDecoder().decode(new Uint8Array(memory.buffer, ptr, len));
23
+ }
24
+ class CompileError extends Error {
25
+ diagnostic;
26
+ constructor(diagnostic) {
27
+ super(diagnostic);
28
+ this.name = "CompileError";
29
+ this.diagnostic = diagnostic;
30
+ }
31
+ }
32
+ exports.CompileError = CompileError;
33
+ class CppBoltCompiler {
34
+ _exports;
35
+ constructor(instance) {
36
+ this._exports = instance.exports;
37
+ }
38
+ compile(source) {
39
+ const ex = this._exports;
40
+ const mem = ex.memory;
41
+ const srcBytes = new TextEncoder().encode(source);
42
+ const srcPtr = ex.alloc_buffer(srcBytes.length);
43
+ new Uint8Array(mem.buffer, srcPtr, srcBytes.length).set(srcBytes);
44
+ const resultPtr = ex.compile_wasm(srcPtr, srcBytes.length);
45
+ ex.dealloc_buffer(srcPtr, srcBytes.length);
46
+ if (resultPtr === 0) {
47
+ throw new CompileError(decodeString(mem, ex.get_error_ptr(), ex.get_error_len()));
48
+ }
49
+ const resultLen = ex.get_result_len();
50
+ const output = new Uint8Array(mem.buffer, resultPtr, resultLen).slice();
51
+ ex.free_result(resultPtr, resultLen);
52
+ return output;
53
+ }
54
+ }
55
+ exports.CppBoltCompiler = CppBoltCompiler;
56
+ /**
57
+ * Load cppbolt.wasm and return a CppBoltCompiler.
58
+ *
59
+ * In Node.js, resolves wasm/cppbolt.wasm from the package directory.
60
+ * In a browser <script> tag, fetches it relative to the script's own URL.
61
+ */
62
+ async function createCompiler() {
63
+ if (fs && path && typeof __dirname === "string") {
64
+ const localPath = path.join(__dirname, "..", "wasm", "cppbolt.wasm");
65
+ if (fs.existsSync(localPath)) {
66
+ return new CppBoltCompiler(await loadFromBytes(fs.readFileSync(localPath)));
67
+ }
68
+ }
69
+ const wasmUrl = scriptSrc
70
+ ? new URL("../wasm/cppbolt.wasm", scriptSrc).href
71
+ : null;
72
+ if (wasmUrl) {
73
+ return new CppBoltCompiler(await loadFromResponse(await fetch(wasmUrl)));
74
+ }
75
+ throw new Error("cppbolt: could not locate wasm binary. " +
76
+ "Load this script from a URL (CDN / local server) or use the Node.js package.");
77
+ }
78
+ async function loadFromResponse(response) {
79
+ if (!response.ok) {
80
+ throw new Error(`cppbolt: failed to fetch wasm (HTTP ${response.status})`);
81
+ }
82
+ const contentType = response.headers.get("content-type") ?? "";
83
+ if (contentType.includes("application/wasm")) {
84
+ const { instance } = await WebAssembly.instantiateStreaming(response, {});
85
+ return instance;
86
+ }
87
+ return loadFromBytes(await response.arrayBuffer());
88
+ }
89
+ async function loadFromBytes(bytes) {
90
+ const { instance } = await WebAssembly.instantiate(bytes, {});
91
+ return instance;
92
+ }