@live-codes/pascal-wasm
v0.1.0
Published
The Free Pascal pas2js compiler compiled to WebAssembly, for browsers and Web Workers. Compiles Pascal entirely client-side.
Maintainers
Readme
@live-codes/pascal-wasm
The pas2js compiler — the Free Pascal team's Pascal → JavaScript transpiler — compiled to WebAssembly, for use in browsers and Web Workers.
Compilation runs entirely on the client. There is no server, no account, and the source never leaves the page.
import { compile } from '@live-codes/pascal-wasm';
const { js, diagnostics } = await compile(`
program hello;
begin
Writeln('Hello from Pascal');
end.
`);
if (js) {
// `js` defines the program and the pas2js runtime; the host starts it.
document.body.append(Object.assign(document.createElement('script'), { textContent: js }));
rtl.run();
}Install
npm install @live-codes/pascal-wasmOr import it straight from a CDN — the package resolves its own assets relative to itself, so no configuration is needed:
import { compile } from 'https://cdn.jsdelivr.net/npm/@live-codes/[email protected]/src/index.js';There is also a minified classic-script build for non-module workers and
plain <script> tags, at dist/pascal-wasm.iife.min.js — see
Classic scripts and non-module workers.
Both builds share the same assets/.
API
compile(source, options?)
One-shot convenience call. Reuses a shared compiler instance.
Returns Promise<CompileResult>:
| Field | Type | Notes |
| --- | --- | --- |
| js | string \| null | The generated JavaScript, or null if compilation failed |
| sourceMap | string \| null | The map, if sourceMap: true was requested |
| diagnostics | string | Raw compiler output, including progress lines |
| exitCode | number | 0 on success |
Compilation errors are not exceptions: they come back as js: null with the
details in diagnostics. Only failures to load the compiler itself reject.
createCompiler(options?)
Returns Promise<Compiler> for reuse, which avoids re-loading the ~9 MB binary:
const compiler = await createCompiler();
await compiler.version(); // "3.3.1"
await compiler.compile(source);parseDiagnostics(diagnostics)
Turns the compiler's text output into structured messages, with the progress chatter filtered out. Useful for editor markers.
parseDiagnostics(result.diagnostics);
// [{ file: 'tmp/main.pp', line: 5, column: 11, severity: 'error',
// message: 'identifier not found "missingThing"' }]severity is one of error, warning, hint, note or fatal.
configure(options)
Sets package-wide defaults: baseUrl and flags. Per-call options win over
these, and flags are prepended to each call's own flags.
configure({ baseUrl: 'https://example.com/pascal/assets/', flags: ['-O2'] });This is mainly for the classic-script build, where there is no import to attach options to.
defaultBaseUrl()
Returns the asset location that will be used when none is given explicitly — useful for checking what was resolved.
Options
| Option | Type | Description |
| --- | --- | --- |
| flags | string[] | Extra pas2js command-line options, appended as-is |
| units | Record<string, string \| Uint8Array> or Map | Extra units by filename, placed on the compiler's search path |
| sourceMap | boolean | Emit js.map, mapping generated JavaScript back to the Pascal |
| baseUrl | string \| URL | Load pas2js.wasm and rtl/ from somewhere else. Required by the IIFE build |
| wasmUrl / rtlUrl | string \| URL | Override just one of the two |
Compiler options
flags is a pass-through to the compiler, so anything pas2js accepts works. The
ones you are most likely to want:
| Flag | Effect |
| --- | --- |
| -O2 | Optimization level |
| -dNAME | Define a symbol for {$IFDEF NAME} |
| -Mobjfpc, -Mdelphi, -Mtp, -Miso | Pascal dialect (modeswitch) |
| -C-, -Cr, -Co | Range/overflow/object checking |
| -Jm | Source maps, beyond what sourceMap: true sets up |
The compiler can list what it supports. Pass a query switch and read the answer
back from diagnostics — this is how the demo UI reports them:
const { diagnostics } = await compiler.compile('', { flags: ['-iM'] }); // modeswitches
// also: -it targets, -ic JS processors, -io optimizationsExtra units
Additional units are written into the compiler's unit search path:
const result = await compile(
`program main; uses doubler; begin Writeln(Twice(21)); end.`,
{
units: {
'doubler.pas': `unit doubler;
interface
function Twice(x: Integer): Integer;
implementation
function Twice(x: Integer): Integer;
begin Twice := x * 2; end;
end.`,
},
},
);Web Workers
The package has no DOM dependencies, so it runs unchanged in a module worker — which is the sensible place for it, since compiling blocks for a moment.
// compiler-worker.js
import { createCompiler } from 'https://cdn.jsdelivr.net/npm/@live-codes/[email protected]/src/index.js';
const compiler = await createCompiler();
self.postMessage({ type: 'ready' });
self.onmessage = async ({ data }) => {
self.postMessage({ type: 'result', ...(await compiler.compile(data.source)) });
};const worker = new Worker('compiler-worker.js', { type: 'module' });
worker.onmessage = ({ data }) => {
if (data.type !== 'result' || !data.js) return;
const script = document.createElement('script');
script.textContent = data.js;
document.body.append(script);
rtl.run();
};Note the worker must be a module worker ({ type: 'module' }) because the
package is ESM.
Classic scripts and non-module workers
Not every consumer can use ES modules — a classic worker cannot, because
importScripts() only runs classic scripts. For those, a minified IIFE bundle is
shipped in dist/, exposing the same API on a global named pascalWasm:
// compiler-worker.js — a *classic* worker, no { type: 'module' }
importScripts('https://cdn.jsdelivr.net/npm/@live-codes/[email protected]/dist/pascal-wasm.iife.min.js');
// This build has no module URL, so it cannot find the assets by itself.
pascalWasm.configure({ baseUrl: 'https://cdn.jsdelivr.net/npm/@live-codes/[email protected]/assets/' });
pascalWasm.compile('begin Writeln(42) end.').then(({ js, diagnostics }) => {
self.postMessage({ js, diagnostics });
});new Worker('compiler-worker.js'); // ...and no module type here either<!-- the same bundle works as a plain script tag -->
<script src="https://cdn.jsdelivr.net/npm/@live-codes/[email protected]/dist/pascal-wasm.iife.min.js"></script>
<script>
pascalWasm.configure({ baseUrl: 'https://cdn.jsdelivr.net/npm/@live-codes/[email protected]/assets/' });
pascalWasm.compile('begin Writeln(42) end.').then(console.log);
</script>The bundle is ~27 kB and contains only the glue — the compiler itself stays in
assets/, which is shared with the ES module build and cached between them.
baseUrl can be relative, in which case it resolves against the worker's or page's
own location:
pascalWasm.configure({ baseUrl: '../packages/pascal-wasm/assets/' });It may also be passed per call instead — createCompiler({ baseUrl }) — which
takes precedence over configure(). Calling compile() without a usable
baseUrl in this build fails with an explanatory error rather than guessing,
and pascalWasm.defaultBaseUrl() reports what a resolution would produce.
Running the compiled output
pas2js emits a script that registers the program with its runtime but does not
start it. The host document starts it with rtl.run(), and the program then
behaves like any other page script: it can read and write the DOM (uses web),
call host functions, and set ExitCode.
Uncaught Pascal exceptions can be captured through the runtime's hooks:
rtl.showUncaughtExceptions = true;
rtl.onUncaughtException = (error) => {
console.error(error.$classname, error.fMessage);
return true; // handled — suppress the default alert()
};
rtl.run();Node
The same API works in Node, reading the assets from disk. Node needs a flag for the WebAssembly exception-handling instructions this compiler uses:
node --experimental-wasm-exnref app.mjsAssets
assets/pas2js.wasm is about 9 MB and assets/rtl/ about 1.2 MB. The binary is
downloaded and compiled once per compiler instance, then reused across calls.
compile() memoizes instances by asset location.
To host the assets yourself, either point baseUrl at your own copy, or import
them from the package — @live-codes/pascal-wasm/assets/... is exported for that.
Licensing
The package is licensed under LGPL-2.1-or-later, matching pas2js, which is itself LGPL-2.1 — this package redistributes its compiler binary and runtime library. The WASI shim it bundles is MIT OR Apache-2.0.
See `THIRD-PARTY-NOTICES.md for versions, the provenance and SHA-256 of the compiler binary, and pointers to the corresponding source. Note in particular that the bundled binary comes from the official Free Pascal demo deployment rather than a tagged release, and that building it from source is preferable if you need a version-pinned artifact.
