@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.
Files changed (37) hide show
  1. package/LICENSE +501 -0
  2. package/README.md +278 -0
  3. package/THIRD-PARTY-NOTICES.md +48 -0
  4. package/assets/pas2js.wasm +0 -0
  5. package/assets/rtl/Rtl.BrowserLoadHelper.pas +179 -0
  6. package/assets/rtl/browserconsole.pas +190 -0
  7. package/assets/rtl/classes.pas +11371 -0
  8. package/assets/rtl/js.pas +2211 -0
  9. package/assets/rtl/manifest.json +19 -0
  10. package/assets/rtl/math.pas +903 -0
  11. package/assets/rtl/p2jsres.pp +334 -0
  12. package/assets/rtl/rtl.js +1563 -0
  13. package/assets/rtl/rtlconsts.pas +97 -0
  14. package/assets/rtl/simplelinkedlist.pas +152 -0
  15. package/assets/rtl/system.pas +1166 -0
  16. package/assets/rtl/sysutils.pas +8959 -0
  17. package/assets/rtl/types.pas +2296 -0
  18. package/assets/rtl/typinfo.pas +1680 -0
  19. package/assets/rtl/web.pas +3586 -0
  20. package/assets/rtl/weborworker.pas +2101 -0
  21. package/dist/pascal-wasm.iife.min.js +6 -0
  22. package/dist/pascal-wasm.iife.min.js.map +7 -0
  23. package/package.json +54 -0
  24. package/src/assets.js +119 -0
  25. package/src/compiler.js +142 -0
  26. package/src/config.js +24 -0
  27. package/src/iife.js +27 -0
  28. package/src/index.js +130 -0
  29. package/src/vendor/browser_wasi_shim/debug.js +1 -0
  30. package/src/vendor/browser_wasi_shim/fd.js +1 -0
  31. package/src/vendor/browser_wasi_shim/fs_mem.js +1 -0
  32. package/src/vendor/browser_wasi_shim/fs_opfs.js +1 -0
  33. package/src/vendor/browser_wasi_shim/index.js +1 -0
  34. package/src/vendor/browser_wasi_shim/strace.js +1 -0
  35. package/src/vendor/browser_wasi_shim/wasi.js +1 -0
  36. package/src/vendor/browser_wasi_shim/wasi_defs.js +1 -0
  37. 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,'<','&lt;',[rfReplaceAll]);
116
+ cl:=StringReplace(cl,'>','&gt;',[rfReplaceAll]);
117
+ cl:=StringReplace(cl,' ','&nbsp;',[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
+