@live-codes/pascal-wasm 0.1.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/LICENSE +501 -0
- package/README.md +278 -0
- package/THIRD-PARTY-NOTICES.md +48 -0
- package/assets/pas2js.wasm +0 -0
- package/assets/rtl/Rtl.BrowserLoadHelper.pas +179 -0
- package/assets/rtl/browserconsole.pas +190 -0
- package/assets/rtl/classes.pas +11371 -0
- package/assets/rtl/js.pas +2211 -0
- package/assets/rtl/manifest.json +19 -0
- package/assets/rtl/math.pas +903 -0
- package/assets/rtl/p2jsres.pp +334 -0
- package/assets/rtl/rtl.js +1563 -0
- package/assets/rtl/rtlconsts.pas +97 -0
- package/assets/rtl/simplelinkedlist.pas +152 -0
- package/assets/rtl/system.pas +1166 -0
- package/assets/rtl/sysutils.pas +8959 -0
- package/assets/rtl/types.pas +2296 -0
- package/assets/rtl/typinfo.pas +1680 -0
- package/assets/rtl/web.pas +3586 -0
- package/assets/rtl/weborworker.pas +2101 -0
- package/dist/pascal-wasm.iife.min.js +6 -0
- package/dist/pascal-wasm.iife.min.js.map +7 -0
- package/package.json +54 -0
- package/src/assets.js +119 -0
- package/src/compiler.js +142 -0
- package/src/config.js +24 -0
- package/src/iife.js +27 -0
- package/src/index.js +130 -0
- package/src/vendor/browser_wasi_shim/debug.js +1 -0
- package/src/vendor/browser_wasi_shim/fd.js +1 -0
- package/src/vendor/browser_wasi_shim/fs_mem.js +1 -0
- package/src/vendor/browser_wasi_shim/fs_opfs.js +1 -0
- package/src/vendor/browser_wasi_shim/index.js +1 -0
- package/src/vendor/browser_wasi_shim/strace.js +1 -0
- package/src/vendor/browser_wasi_shim/wasi.js +1 -0
- package/src/vendor/browser_wasi_shim/wasi_defs.js +1 -0
- package/types/index.d.ts +89 -0
package/README.md
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
# @live-codes/pascal-wasm
|
|
2
|
+
|
|
3
|
+
The [pas2js](https://www.freepascal.org/) compiler — the Free Pascal team's
|
|
4
|
+
Pascal → JavaScript transpiler — compiled to WebAssembly, for use in browsers and
|
|
5
|
+
Web Workers.
|
|
6
|
+
|
|
7
|
+
Compilation runs entirely on the client. There is no server, no account, and the
|
|
8
|
+
source never leaves the page.
|
|
9
|
+
|
|
10
|
+
```js
|
|
11
|
+
import { compile } from '@live-codes/pascal-wasm';
|
|
12
|
+
|
|
13
|
+
const { js, diagnostics } = await compile(`
|
|
14
|
+
program hello;
|
|
15
|
+
begin
|
|
16
|
+
Writeln('Hello from Pascal');
|
|
17
|
+
end.
|
|
18
|
+
`);
|
|
19
|
+
|
|
20
|
+
if (js) {
|
|
21
|
+
// `js` defines the program and the pas2js runtime; the host starts it.
|
|
22
|
+
document.body.append(Object.assign(document.createElement('script'), { textContent: js }));
|
|
23
|
+
rtl.run();
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
npm install @live-codes/pascal-wasm
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Or import it straight from a CDN — the package resolves its own assets relative
|
|
34
|
+
to itself, so no configuration is needed:
|
|
35
|
+
|
|
36
|
+
```js
|
|
37
|
+
import { compile } from 'https://cdn.jsdelivr.net/npm/@live-codes/pascal-wasm@0.1.0/src/index.js';
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
There is also a minified **classic-script build** for non-module workers and
|
|
41
|
+
plain `<script>` tags, at `dist/pascal-wasm.iife.min.js` — see
|
|
42
|
+
[Classic scripts and non-module workers](#classic-scripts-and-non-module-workers).
|
|
43
|
+
Both builds share the same `assets/`.
|
|
44
|
+
|
|
45
|
+
## API
|
|
46
|
+
|
|
47
|
+
### `compile(source, options?)`
|
|
48
|
+
|
|
49
|
+
One-shot convenience call. Reuses a shared compiler instance.
|
|
50
|
+
|
|
51
|
+
Returns `Promise<CompileResult>`:
|
|
52
|
+
|
|
53
|
+
| Field | Type | Notes |
|
|
54
|
+
| --- | --- | --- |
|
|
55
|
+
| `js` | `string \| null` | The generated JavaScript, or `null` if compilation failed |
|
|
56
|
+
| `sourceMap` | `string \| null` | The map, if `sourceMap: true` was requested |
|
|
57
|
+
| `diagnostics` | `string` | Raw compiler output, including progress lines |
|
|
58
|
+
| `exitCode` | `number` | `0` on success |
|
|
59
|
+
|
|
60
|
+
Compilation *errors are not exceptions*: they come back as `js: null` with the
|
|
61
|
+
details in `diagnostics`. Only failures to load the compiler itself reject.
|
|
62
|
+
|
|
63
|
+
### `createCompiler(options?)`
|
|
64
|
+
|
|
65
|
+
Returns `Promise<Compiler>` for reuse, which avoids re-loading the ~9 MB binary:
|
|
66
|
+
|
|
67
|
+
```js
|
|
68
|
+
const compiler = await createCompiler();
|
|
69
|
+
await compiler.version(); // "3.3.1"
|
|
70
|
+
await compiler.compile(source);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### `parseDiagnostics(diagnostics)`
|
|
74
|
+
|
|
75
|
+
Turns the compiler's text output into structured messages, with the progress
|
|
76
|
+
chatter filtered out. Useful for editor markers.
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
parseDiagnostics(result.diagnostics);
|
|
80
|
+
// [{ file: 'tmp/main.pp', line: 5, column: 11, severity: 'error',
|
|
81
|
+
// message: 'identifier not found "missingThing"' }]
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`severity` is one of `error`, `warning`, `hint`, `note` or `fatal`.
|
|
85
|
+
|
|
86
|
+
### `configure(options)`
|
|
87
|
+
|
|
88
|
+
Sets package-wide defaults: `baseUrl` and `flags`. Per-call options win over
|
|
89
|
+
these, and `flags` are prepended to each call's own `flags`.
|
|
90
|
+
|
|
91
|
+
```js
|
|
92
|
+
configure({ baseUrl: 'https://example.com/pascal/assets/', flags: ['-O2'] });
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
This is mainly for the classic-script build, where there is no import to attach
|
|
96
|
+
options to.
|
|
97
|
+
|
|
98
|
+
### `defaultBaseUrl()`
|
|
99
|
+
|
|
100
|
+
Returns the asset location that will be used when none is given explicitly —
|
|
101
|
+
useful for checking what was resolved.
|
|
102
|
+
|
|
103
|
+
## Options
|
|
104
|
+
|
|
105
|
+
| Option | Type | Description |
|
|
106
|
+
| --- | --- | --- |
|
|
107
|
+
| `flags` | `string[]` | Extra pas2js command-line options, appended as-is |
|
|
108
|
+
| `units` | `Record<string, string \| Uint8Array>` or `Map` | Extra units by filename, placed on the compiler's search path |
|
|
109
|
+
| `sourceMap` | `boolean` | Emit `js.map`, mapping generated JavaScript back to the Pascal |
|
|
110
|
+
| `baseUrl` | `string \| URL` | Load `pas2js.wasm` and `rtl/` from somewhere else. Required by the IIFE build |
|
|
111
|
+
| `wasmUrl` / `rtlUrl` | `string \| URL` | Override just one of the two |
|
|
112
|
+
|
|
113
|
+
### Compiler options
|
|
114
|
+
|
|
115
|
+
`flags` is a pass-through to the compiler, so anything pas2js accepts works. The
|
|
116
|
+
ones you are most likely to want:
|
|
117
|
+
|
|
118
|
+
| Flag | Effect |
|
|
119
|
+
| --- | --- |
|
|
120
|
+
| `-O2` | Optimization level |
|
|
121
|
+
| `-dNAME` | Define a symbol for `{$IFDEF NAME}` |
|
|
122
|
+
| `-Mobjfpc`, `-Mdelphi`, `-Mtp`, `-Miso` | Pascal dialect (modeswitch) |
|
|
123
|
+
| `-C-`, `-Cr`, `-Co` | Range/overflow/object checking |
|
|
124
|
+
| `-Jm` | Source maps, beyond what `sourceMap: true` sets up |
|
|
125
|
+
|
|
126
|
+
The compiler can list what it supports. Pass a query switch and read the answer
|
|
127
|
+
back from `diagnostics` — this is how the demo UI reports them:
|
|
128
|
+
|
|
129
|
+
```js
|
|
130
|
+
const { diagnostics } = await compiler.compile('', { flags: ['-iM'] }); // modeswitches
|
|
131
|
+
// also: -it targets, -ic JS processors, -io optimizations
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Extra units
|
|
135
|
+
|
|
136
|
+
Additional units are written into the compiler's unit search path:
|
|
137
|
+
|
|
138
|
+
```js
|
|
139
|
+
const result = await compile(
|
|
140
|
+
`program main; uses doubler; begin Writeln(Twice(21)); end.`,
|
|
141
|
+
{
|
|
142
|
+
units: {
|
|
143
|
+
'doubler.pas': `unit doubler;
|
|
144
|
+
interface
|
|
145
|
+
function Twice(x: Integer): Integer;
|
|
146
|
+
implementation
|
|
147
|
+
function Twice(x: Integer): Integer;
|
|
148
|
+
begin Twice := x * 2; end;
|
|
149
|
+
end.`,
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Web Workers
|
|
156
|
+
|
|
157
|
+
The package has no DOM dependencies, so it runs unchanged in a module worker —
|
|
158
|
+
which is the sensible place for it, since compiling blocks for a moment.
|
|
159
|
+
|
|
160
|
+
```js
|
|
161
|
+
// compiler-worker.js
|
|
162
|
+
import { createCompiler } from 'https://cdn.jsdelivr.net/npm/@live-codes/pascal-wasm@0.1.0/src/index.js';
|
|
163
|
+
|
|
164
|
+
const compiler = await createCompiler();
|
|
165
|
+
self.postMessage({ type: 'ready' });
|
|
166
|
+
|
|
167
|
+
self.onmessage = async ({ data }) => {
|
|
168
|
+
self.postMessage({ type: 'result', ...(await compiler.compile(data.source)) });
|
|
169
|
+
};
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
```js
|
|
173
|
+
const worker = new Worker('compiler-worker.js', { type: 'module' });
|
|
174
|
+
worker.onmessage = ({ data }) => {
|
|
175
|
+
if (data.type !== 'result' || !data.js) return;
|
|
176
|
+
const script = document.createElement('script');
|
|
177
|
+
script.textContent = data.js;
|
|
178
|
+
document.body.append(script);
|
|
179
|
+
rtl.run();
|
|
180
|
+
};
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Note the worker must be a *module* worker (`{ type: 'module' }`) because the
|
|
184
|
+
package is ESM.
|
|
185
|
+
|
|
186
|
+
## Classic scripts and non-module workers
|
|
187
|
+
|
|
188
|
+
Not every consumer can use ES modules — a classic worker cannot, because
|
|
189
|
+
`importScripts()` only runs classic scripts. For those, a minified IIFE bundle is
|
|
190
|
+
shipped in `dist/`, exposing the same API on a global named `pascalWasm`:
|
|
191
|
+
|
|
192
|
+
```js
|
|
193
|
+
// compiler-worker.js — a *classic* worker, no { type: 'module' }
|
|
194
|
+
importScripts('https://cdn.jsdelivr.net/npm/@live-codes/pascal-wasm@0.1.0/dist/pascal-wasm.iife.min.js');
|
|
195
|
+
|
|
196
|
+
// This build has no module URL, so it cannot find the assets by itself.
|
|
197
|
+
pascalWasm.configure({ baseUrl: 'https://cdn.jsdelivr.net/npm/@live-codes/pascal-wasm@0.1.0/assets/' });
|
|
198
|
+
|
|
199
|
+
pascalWasm.compile('begin Writeln(42) end.').then(({ js, diagnostics }) => {
|
|
200
|
+
self.postMessage({ js, diagnostics });
|
|
201
|
+
});
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
```js
|
|
205
|
+
new Worker('compiler-worker.js'); // ...and no module type here either
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
```html
|
|
209
|
+
<!-- the same bundle works as a plain script tag -->
|
|
210
|
+
<script src="https://cdn.jsdelivr.net/npm/@live-codes/pascal-wasm@0.1.0/dist/pascal-wasm.iife.min.js"></script>
|
|
211
|
+
<script>
|
|
212
|
+
pascalWasm.configure({ baseUrl: 'https://cdn.jsdelivr.net/npm/@live-codes/pascal-wasm@0.1.0/assets/' });
|
|
213
|
+
pascalWasm.compile('begin Writeln(42) end.').then(console.log);
|
|
214
|
+
</script>
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
The bundle is ~27 kB and contains only the glue — the compiler itself stays in
|
|
218
|
+
`assets/`, which is shared with the ES module build and cached between them.
|
|
219
|
+
|
|
220
|
+
`baseUrl` can be relative, in which case it resolves against the worker's or page's
|
|
221
|
+
own location:
|
|
222
|
+
|
|
223
|
+
```js
|
|
224
|
+
pascalWasm.configure({ baseUrl: '../packages/pascal-wasm/assets/' });
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
It may also be passed per call instead — `createCompiler({ baseUrl })` — which
|
|
228
|
+
takes precedence over `configure()`. Calling `compile()` without a usable
|
|
229
|
+
`baseUrl` in this build fails with an explanatory error rather than guessing,
|
|
230
|
+
and `pascalWasm.defaultBaseUrl()` reports what a resolution would produce.
|
|
231
|
+
|
|
232
|
+
## Running the compiled output
|
|
233
|
+
|
|
234
|
+
pas2js emits a script that registers the program with its runtime but does not
|
|
235
|
+
start it. The host document starts it with `rtl.run()`, and the program then
|
|
236
|
+
behaves like any other page script: it can read and write the DOM (`uses web`),
|
|
237
|
+
call host functions, and set `ExitCode`.
|
|
238
|
+
|
|
239
|
+
Uncaught Pascal exceptions can be captured through the runtime's hooks:
|
|
240
|
+
|
|
241
|
+
```js
|
|
242
|
+
rtl.showUncaughtExceptions = true;
|
|
243
|
+
rtl.onUncaughtException = (error) => {
|
|
244
|
+
console.error(error.$classname, error.fMessage);
|
|
245
|
+
return true; // handled — suppress the default alert()
|
|
246
|
+
};
|
|
247
|
+
rtl.run();
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Node
|
|
251
|
+
|
|
252
|
+
The same API works in Node, reading the assets from disk. Node needs a flag for
|
|
253
|
+
the WebAssembly exception-handling instructions this compiler uses:
|
|
254
|
+
|
|
255
|
+
```sh
|
|
256
|
+
node --experimental-wasm-exnref app.mjs
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Assets
|
|
260
|
+
|
|
261
|
+
`assets/pas2js.wasm` is about 9 MB and `assets/rtl/` about 1.2 MB. The binary is
|
|
262
|
+
downloaded and compiled once per compiler instance, then reused across calls.
|
|
263
|
+
`compile()` memoizes instances by asset location.
|
|
264
|
+
|
|
265
|
+
To host the assets yourself, either point `baseUrl` at your own copy, or import
|
|
266
|
+
them from the package — `@live-codes/pascal-wasm/assets/...` is exported for that.
|
|
267
|
+
|
|
268
|
+
## Licensing
|
|
269
|
+
|
|
270
|
+
The package is licensed under **LGPL-2.1-or-later**, matching pas2js, which is
|
|
271
|
+
itself LGPL-2.1 — this package redistributes its compiler binary and runtime
|
|
272
|
+
library. The WASI shim it bundles is MIT OR Apache-2.0.
|
|
273
|
+
|
|
274
|
+
See [`THIRD-PARTY-NOTICES.md](./THIRD-PARTY-NOTICES.md) for versions, the
|
|
275
|
+
provenance and SHA-256 of the compiler binary, and pointers to the corresponding
|
|
276
|
+
source. Note in particular that the bundled binary comes from the official Free
|
|
277
|
+
Pascal demo deployment rather than a tagged release, and that building it from
|
|
278
|
+
source is preferable if you need a version-pinned artifact.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Third-party notices
|
|
2
|
+
|
|
3
|
+
This package redistributes third-party software. Their licenses are reproduced or
|
|
4
|
+
referenced here; the package's own code is licensed under LGPL-2.1-or-later (see
|
|
5
|
+
`LICENSE`).
|
|
6
|
+
|
|
7
|
+
## pas2js compiler — `assets/pas2js.wasm`
|
|
8
|
+
|
|
9
|
+
- **Project**: pas2js, the Pascal to JavaScript transpiler, part of Free Pascal
|
|
10
|
+
(<https://www.freepascal.org/>, source at
|
|
11
|
+
<https://gitlab.com/freepascal.org/fpc/pas2js>).
|
|
12
|
+
- **License**: GNU Lesser General Public License v2.1 (LGPL-2.1).
|
|
13
|
+
- **Binary**: the pas2js command-line compiler built for the `wasm32-wasi` target.
|
|
14
|
+
- **Version**: `Pas2JS Compiler version 3.3.1 [2026/02/22] for Wasip1 wasm32`
|
|
15
|
+
(as reported by the binary).
|
|
16
|
+
- **Source of the binary**: the deployment of the official Free Pascal browser
|
|
17
|
+
compiler demo, <https://live.freepascal.org/wasm/pas2js.wasm>.
|
|
18
|
+
- **SHA-256**:
|
|
19
|
+
`ade2bc6334f971ce835ce8e2a1861c808de230e285767115021df38391841377`
|
|
20
|
+
|
|
21
|
+
Note that this binary is **not** from a tagged pas2js release: the official
|
|
22
|
+
release archives (currently 3.2.0) are per-platform native builds and contain no
|
|
23
|
+
WebAssembly at all. It was taken from the official demo deployment above, which
|
|
24
|
+
tracks pas2js trunk and is therefore newer than the latest release. Prefer
|
|
25
|
+
building the compiler yourself from the pas2js sources (FPC plus `demo/webcompiler`
|
|
26
|
+
in that tree, targeting wasm32-wasi) if you need a reproducible, version-pinned
|
|
27
|
+
binary.
|
|
28
|
+
|
|
29
|
+
### Corresponding source
|
|
30
|
+
|
|
31
|
+
LGPL-2.1 requires that the corresponding source be available. It is the pas2js
|
|
32
|
+
source tree linked above. To rebuild this binary, build the Free Pascal compiler
|
|
33
|
+
with a `wasm32-wasi` target and compile pas2js with it.
|
|
34
|
+
|
|
35
|
+
## Pascal RTL — `assets/rtl/`
|
|
36
|
+
|
|
37
|
+
- **Project**: the pas2js runtime library, from the same source tree.
|
|
38
|
+
- **License**: LGPL-2.1, with the Free Pascal static-linking exception that
|
|
39
|
+
applies to the runtime library.
|
|
40
|
+
- **Source of these files**: <https://live.freepascal.org/files/> (the unit
|
|
41
|
+
manifest and sources the official browser compiler demo ships).
|
|
42
|
+
|
|
43
|
+
## `@bjorn3/browser_wasi_shim` — `src/vendor/browser_wasi_shim/`
|
|
44
|
+
|
|
45
|
+
- **Project**: <https://github.com/bjorn3/browser_wasi_shim>
|
|
46
|
+
- **License**: MIT OR Apache-2.0 (dual licensed; either may be chosen).
|
|
47
|
+
- **Version**: 0.4.2, vendored here so the package works from a CDN with no
|
|
48
|
+
additional resolver or dependency graph.
|
|
Binary file
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
{
|
|
2
|
+
This file is part of the Pas2JS run time library.
|
|
3
|
+
Copyright (c) 2023 by Michael Van Canneyt
|
|
4
|
+
|
|
5
|
+
Loader helper for TStringList, usable in the browser.
|
|
6
|
+
|
|
7
|
+
See the file COPYING.FPC, included in this distribution,
|
|
8
|
+
for details about the copyright.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
13
|
+
|
|
14
|
+
**********************************************************************}
|
|
15
|
+
{$IFNDEF FPC_DOTTEDUNITS}
|
|
16
|
+
unit Rtl.BrowserLoadHelper;
|
|
17
|
+
{$ENDIF}
|
|
18
|
+
|
|
19
|
+
{$mode objfpc}
|
|
20
|
+
|
|
21
|
+
interface
|
|
22
|
+
|
|
23
|
+
uses
|
|
24
|
+
{$IFDEF FPC_DOTTEDUNITS}
|
|
25
|
+
System.Classes, System.SysUtils, JSApi.JS, BrowserApi.Web;
|
|
26
|
+
{$ELSE}
|
|
27
|
+
Classes, SysUtils, JS, Web;
|
|
28
|
+
{$ENDIF}
|
|
29
|
+
|
|
30
|
+
Type
|
|
31
|
+
{ TBrowserLoadHelper }
|
|
32
|
+
|
|
33
|
+
TBrowserLoadHelper = Class (TLoadHelper)
|
|
34
|
+
Public
|
|
35
|
+
Class Procedure LoadText(aURL : String; aSync : Boolean; aOnLoaded : TTextLoadedCallBack; aOnError : TErrorCallBack); override;
|
|
36
|
+
Class Procedure LoadBytes(aURL : String; aSync : Boolean; aOnLoaded : TBytesLoadedCallBack; aOnError : TErrorCallBack); override;
|
|
37
|
+
end;
|
|
38
|
+
|
|
39
|
+
implementation
|
|
40
|
+
|
|
41
|
+
{ TBrowserLoadHelper }
|
|
42
|
+
|
|
43
|
+
class procedure TBrowserLoadHelper.LoadText(aURL: String; aSync: Boolean; aOnLoaded: TTextLoadedCallBack; aOnError: TErrorCallBack);
|
|
44
|
+
|
|
45
|
+
function doFetchOK(response : JSValue) : JSValue;
|
|
46
|
+
|
|
47
|
+
var
|
|
48
|
+
Res : TJSResponse absolute response;
|
|
49
|
+
|
|
50
|
+
begin
|
|
51
|
+
Result:=False;
|
|
52
|
+
If (Res.status<>200) then
|
|
53
|
+
begin
|
|
54
|
+
If Assigned(aOnError) then
|
|
55
|
+
aOnError('Error '+IntToStr(Res.Status)+ ': '+Res.StatusText)
|
|
56
|
+
end
|
|
57
|
+
else
|
|
58
|
+
Res.Text._then(
|
|
59
|
+
function (value : JSValue) : JSValue
|
|
60
|
+
begin
|
|
61
|
+
aOnLoaded(String(value));
|
|
62
|
+
end
|
|
63
|
+
);
|
|
64
|
+
end;
|
|
65
|
+
|
|
66
|
+
function doFetchFail(response : JSValue) : JSValue;
|
|
67
|
+
|
|
68
|
+
begin
|
|
69
|
+
Result:=False;
|
|
70
|
+
aOnError('Error 999: unknown error: '+TJSJSON.Stringify(response));
|
|
71
|
+
end;
|
|
72
|
+
|
|
73
|
+
begin
|
|
74
|
+
if ASync then
|
|
75
|
+
Window.Fetch(aURl)._then(@DoFetchOK).catch(@DoFetchFail)
|
|
76
|
+
else
|
|
77
|
+
With TJSXMLHttpRequest.new do
|
|
78
|
+
begin
|
|
79
|
+
open('GET', aURL, False);
|
|
80
|
+
AddEventListener('load',Procedure (oEvent: JSValue)
|
|
81
|
+
begin
|
|
82
|
+
aOnLoaded(responseText);
|
|
83
|
+
end
|
|
84
|
+
);
|
|
85
|
+
AddEventListener('error',Procedure (oEvent: JSValue)
|
|
86
|
+
begin
|
|
87
|
+
if Assigned(aOnError) then
|
|
88
|
+
aOnError(TJSError(oEvent).Message);
|
|
89
|
+
end
|
|
90
|
+
);
|
|
91
|
+
send();
|
|
92
|
+
end;
|
|
93
|
+
end;
|
|
94
|
+
|
|
95
|
+
class procedure TBrowserLoadHelper.LoadBytes(aURL: String; aSync: Boolean; aOnLoaded: TBytesLoadedCallBack; aOnError: TErrorCallBack);
|
|
96
|
+
|
|
97
|
+
function doFetchFail(response : JSValue) : JSValue;
|
|
98
|
+
|
|
99
|
+
begin
|
|
100
|
+
Result:=False;
|
|
101
|
+
if assigned(aOnError) then
|
|
102
|
+
if isObject(Response) and (TJSObject(Response) is TJSError) then
|
|
103
|
+
aOnError('Error 999: '+TJSError(Response).Message)
|
|
104
|
+
else
|
|
105
|
+
aOnError('Error 999: unknown error');
|
|
106
|
+
end;
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
function doFetchOK(response : JSValue) : JSValue;
|
|
110
|
+
|
|
111
|
+
var
|
|
112
|
+
Res : TJSResponse absolute response;
|
|
113
|
+
|
|
114
|
+
begin
|
|
115
|
+
Result:=False;
|
|
116
|
+
If (Res.status<>200) then
|
|
117
|
+
begin
|
|
118
|
+
If Assigned(aOnError) then
|
|
119
|
+
aOnError('Error '+IntToStr(Res.Status)+ ': '+Res.StatusText)
|
|
120
|
+
end
|
|
121
|
+
else
|
|
122
|
+
Res.Blob._then(
|
|
123
|
+
function (value : JSValue) : JSValue
|
|
124
|
+
begin
|
|
125
|
+
TJSBlob(Value).ArrayBuffer._then(function(arr : JSValue) : JSValue
|
|
126
|
+
begin
|
|
127
|
+
aOnLoaded(TJSArrayBuffer(arr))
|
|
128
|
+
end
|
|
129
|
+
).Catch(@DoFetchFail);
|
|
130
|
+
end
|
|
131
|
+
);
|
|
132
|
+
end;
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
function StringToArrayBuffer(str : string) : TJSArrayBuffer;
|
|
136
|
+
|
|
137
|
+
Var
|
|
138
|
+
i,l : Integer;
|
|
139
|
+
|
|
140
|
+
begin
|
|
141
|
+
L:=Length(str);
|
|
142
|
+
Result:=TJSArrayBuffer.New(l*2); // 2 bytes for each char
|
|
143
|
+
With TJSUint16Array.New(Result) do
|
|
144
|
+
for i:=1 to L do
|
|
145
|
+
Values[i-1]:=Ord(Str[i]);
|
|
146
|
+
end;
|
|
147
|
+
|
|
148
|
+
begin
|
|
149
|
+
if ASync then
|
|
150
|
+
Window.Fetch(aURl)._then(@DoFetchOK).catch(@DoFetchFail)
|
|
151
|
+
else
|
|
152
|
+
With TJSXMLHttpRequest.new do
|
|
153
|
+
begin
|
|
154
|
+
open('GET', aURL, False);
|
|
155
|
+
AddEventListener('load',Procedure (oEvent: JSValue)
|
|
156
|
+
begin
|
|
157
|
+
if (Status<>200) then
|
|
158
|
+
begin
|
|
159
|
+
if assigned(aOnError) then
|
|
160
|
+
aOnError('Error '+IntToStr(Status)+ ': '+StatusText)
|
|
161
|
+
end
|
|
162
|
+
else
|
|
163
|
+
aOnLoaded(StringToArrayBuffer(responseText));
|
|
164
|
+
end
|
|
165
|
+
);
|
|
166
|
+
AddEventListener('error',Procedure (oEvent: JSValue)
|
|
167
|
+
begin
|
|
168
|
+
if Assigned(aOnError) then
|
|
169
|
+
aOnError(TJSError(oEvent).Message);
|
|
170
|
+
end
|
|
171
|
+
);
|
|
172
|
+
send();
|
|
173
|
+
end;
|
|
174
|
+
end;
|
|
175
|
+
|
|
176
|
+
initialization
|
|
177
|
+
SetLoadHelperClass(TBrowserLoadHelper);
|
|
178
|
+
end.
|
|
179
|
+
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
{ Unit that emulates console output in the browser.
|
|
2
|
+
|
|
3
|
+
Copyright (C) 2020- Michael Van Canneyt michael@freepascal.org
|
|
4
|
+
|
|
5
|
+
This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public
|
|
6
|
+
License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version
|
|
7
|
+
with the following modification:
|
|
8
|
+
|
|
9
|
+
As a special exception, the copyright holders of this library give you permission to link this library with independent modules
|
|
10
|
+
to produce an executable, regardless of the license terms of these independent modules,and to copy and distribute the resulting
|
|
11
|
+
executable under terms of your choice, provided that you also meet, for each linked independent module, the terms and conditions
|
|
12
|
+
of the license of that module. An independent module is a module which is not derived from or based on this library. If you
|
|
13
|
+
modify this library, you may extend this exception to your version of the library, but you are not obligated to do so. If you do
|
|
14
|
+
not wish to do so, delete this exception statement from your version.
|
|
15
|
+
|
|
16
|
+
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
17
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details.
|
|
18
|
+
|
|
19
|
+
You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free
|
|
20
|
+
Software Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.
|
|
21
|
+
}
|
|
22
|
+
{$IFNDEF FPC_DOTTEDUNITS}
|
|
23
|
+
unit browserconsole;
|
|
24
|
+
{$ENDIF}
|
|
25
|
+
|
|
26
|
+
{$mode objfpc}
|
|
27
|
+
|
|
28
|
+
interface
|
|
29
|
+
|
|
30
|
+
uses
|
|
31
|
+
{$IFDEF FPC_DOTTEDUNITS}
|
|
32
|
+
JSApi.JS, BrowserApi.Web, BrowserApi.LoadHelper, System.SysUtils;
|
|
33
|
+
{$ELSE}
|
|
34
|
+
js, web, Rtl.BrowserLoadHelper,sysutils;
|
|
35
|
+
{$ENDIF}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
Const
|
|
39
|
+
BrowserLineBreak = #10;
|
|
40
|
+
DefaultMaxConsoleLines = 25;
|
|
41
|
+
DefaultConsoleStyle = '.pasconsole { '+BrowserLineBreak+
|
|
42
|
+
'font-family: courier;'+BrowserLineBreak+
|
|
43
|
+
'font-size: 14px;'+BrowserLineBreak+
|
|
44
|
+
'background: #FFFFFF;'+BrowserLineBreak+
|
|
45
|
+
'color: #000000;'+BrowserLineBreak+
|
|
46
|
+
'display: block;'+BrowserLineBreak+
|
|
47
|
+
'}';
|
|
48
|
+
DefaultCRTConsoleStyle = '.pasconsole { '+BrowserLineBreak+
|
|
49
|
+
'font-family: courier;'+BrowserLineBreak+
|
|
50
|
+
'font-size: 14px;'+BrowserLineBreak+
|
|
51
|
+
'background: #000;'+BrowserLineBreak+
|
|
52
|
+
'color: #14fdce;'+BrowserLineBreak+
|
|
53
|
+
'display: block;'+BrowserLineBreak+
|
|
54
|
+
'}';
|
|
55
|
+
|
|
56
|
+
Var
|
|
57
|
+
// Element ID for console output. If this is not set, body is used.
|
|
58
|
+
// If you cange this, call HookConsole.
|
|
59
|
+
ConsoleElementID : String;
|
|
60
|
+
// Style to use for console lines. If you change this, call initconsole
|
|
61
|
+
ConsoleStyle : String;
|
|
62
|
+
// Style to use for console lines. You can change this at any time.
|
|
63
|
+
MaxConsoleLines : Integer;
|
|
64
|
+
// Copy console lines on newline to browser console log
|
|
65
|
+
ConsoleLinesToBrowserLog : Boolean;
|
|
66
|
+
|
|
67
|
+
// Clear console content
|
|
68
|
+
Procedure ResetConsole;
|
|
69
|
+
// Re-initialize console (style)
|
|
70
|
+
Procedure InitConsole;
|
|
71
|
+
// Re-hook console
|
|
72
|
+
Procedure HookConsole;
|
|
73
|
+
|
|
74
|
+
implementation
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
Var
|
|
78
|
+
LastLine,
|
|
79
|
+
StyleElement,
|
|
80
|
+
LinesParent,
|
|
81
|
+
ConsoleElement : TJSElement;
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
Procedure AppendLine;
|
|
85
|
+
|
|
86
|
+
Var
|
|
87
|
+
CurrentCount : Integer;
|
|
88
|
+
S : TJSNode;
|
|
89
|
+
|
|
90
|
+
begin
|
|
91
|
+
CurrentCount:=0;
|
|
92
|
+
S:=LinesParent.firstChild;
|
|
93
|
+
While Assigned(S) do
|
|
94
|
+
begin
|
|
95
|
+
Inc(CurrentCount);
|
|
96
|
+
S:=S.nextSibling;
|
|
97
|
+
end;
|
|
98
|
+
While CurrentCount>MaxConsoleLines do
|
|
99
|
+
begin
|
|
100
|
+
Dec(CurrentCount);
|
|
101
|
+
LinesParent.removeChild(LinesParent.firstChild);
|
|
102
|
+
end;
|
|
103
|
+
LastLine:=Document.createElement('div');
|
|
104
|
+
LastLine.className:='pasconsole';
|
|
105
|
+
LinesParent.AppendChild(LastLine);
|
|
106
|
+
end;
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
Function EscapeString(S : String) : String;
|
|
110
|
+
|
|
111
|
+
Var
|
|
112
|
+
CL : string;
|
|
113
|
+
|
|
114
|
+
begin
|
|
115
|
+
cl:=StringReplace(S,'<','<',[rfReplaceAll]);
|
|
116
|
+
cl:=StringReplace(cl,'>','>',[rfReplaceAll]);
|
|
117
|
+
cl:=StringReplace(cl,' ',' ',[rfReplaceAll]);
|
|
118
|
+
cl:=StringReplace(cl,#13#10,'<br>',[rfReplaceAll]);
|
|
119
|
+
cl:=StringReplace(cl,#10,'<br>',[rfReplaceAll]);
|
|
120
|
+
cl:=StringReplace(cl,#13,'<br>',[rfReplaceAll]);
|
|
121
|
+
Result:=CL;
|
|
122
|
+
end;
|
|
123
|
+
|
|
124
|
+
Procedure WriteConsole(S : JSValue; NewLine : Boolean);
|
|
125
|
+
|
|
126
|
+
Var
|
|
127
|
+
CL: String;
|
|
128
|
+
|
|
129
|
+
begin
|
|
130
|
+
CL:=LastLine.InnerHtml;
|
|
131
|
+
CL:=CL+EscapeString(String(S));
|
|
132
|
+
LastLine.InnerHtml:=CL;
|
|
133
|
+
if NewLine then
|
|
134
|
+
begin
|
|
135
|
+
if ConsoleLinesToBrowserLog then
|
|
136
|
+
console.log(LastLine.InnerText);
|
|
137
|
+
AppendLine;
|
|
138
|
+
end;
|
|
139
|
+
end;
|
|
140
|
+
|
|
141
|
+
Procedure ResetConsole;
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
begin
|
|
145
|
+
if LinesParent=Nil then exit;
|
|
146
|
+
While LinesParent.firstElementChild<>Nil do
|
|
147
|
+
LinesParent.removeChild(LinesParent.firstElementChild);
|
|
148
|
+
AppendLine;
|
|
149
|
+
end;
|
|
150
|
+
|
|
151
|
+
Procedure InitConsole;
|
|
152
|
+
|
|
153
|
+
begin
|
|
154
|
+
if ConsoleElement=Nil then
|
|
155
|
+
exit;
|
|
156
|
+
if (TJSString(ConsoleElement.nodeName).toLowerCase<>'body') then
|
|
157
|
+
begin
|
|
158
|
+
While ConsoleElement.firstElementChild<>Nil do
|
|
159
|
+
ConsoleElement.removeChild(ConsoleElement.firstElementChild);
|
|
160
|
+
end;
|
|
161
|
+
StyleElement:=Document.createElement('style');
|
|
162
|
+
StyleElement.innerText:=ConsoleStyle;
|
|
163
|
+
ConsoleElement.appendChild(StyleElement);
|
|
164
|
+
LinesParent:=Document.createElement('div');
|
|
165
|
+
ConsoleElement.appendChild(LinesParent);
|
|
166
|
+
end;
|
|
167
|
+
|
|
168
|
+
Procedure HookConsole;
|
|
169
|
+
|
|
170
|
+
begin
|
|
171
|
+
ConsoleElement:=Nil;
|
|
172
|
+
if (ConsoleElementID<>'') then
|
|
173
|
+
ConsoleElement:=document.getElementById(ConsoleElementID);
|
|
174
|
+
if (ConsoleElement=Nil) then
|
|
175
|
+
ConsoleElement:=document.body;
|
|
176
|
+
if ConsoleElement=Nil then
|
|
177
|
+
exit;
|
|
178
|
+
InitConsole;
|
|
179
|
+
ResetConsole;
|
|
180
|
+
SetWriteCallBack(@WriteConsole);
|
|
181
|
+
end;
|
|
182
|
+
|
|
183
|
+
initialization
|
|
184
|
+
ConsoleLinesToBrowserLog:=True;
|
|
185
|
+
ConsoleElementID:='pasjsconsole';
|
|
186
|
+
ConsoleStyle:=DefaultConsoleStyle;
|
|
187
|
+
MaxConsoleLines:=DefaultMaxConsoleLines;
|
|
188
|
+
HookConsole;
|
|
189
|
+
end.
|
|
190
|
+
|