wat4wasm 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.
Files changed (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -0
  3. package/examples/01-text/module-output.wat +228 -0
  4. package/examples/01-text/module.wat +9 -0
  5. package/examples/02-include/module-output.wat +22 -0
  6. package/examples/02-include/module.wat +3 -0
  7. package/examples/02-include/used-folder/included-file.wat +4 -0
  8. package/examples/03-ref.extern/module-output.wat +1537 -0
  9. package/examples/03-ref.extern/module.wat +33 -0
  10. package/examples/04-ref.func/module-output.wat +25 -0
  11. package/examples/04-ref.func/module.wat +8 -0
  12. package/examples/05-global.get/module-output.wat +991 -0
  13. package/examples/05-global.get/module.wat +26 -0
  14. package/examples/06-async/module-output.wat +661 -0
  15. package/examples/06-async/module.wat +15 -0
  16. package/examples/07-data/module-output.wasm +0 -0
  17. package/examples/07-data/module.wat +29 -0
  18. package/examples/07-data/used-folder/clear-text.txt +1 -0
  19. package/examples/07-data/used-folder/compile-this.wat +8 -0
  20. package/examples/08-reflectors/how-to/README.md +0 -0
  21. package/examples/08-reflectors/how-to/output-01-command.sh +0 -0
  22. package/examples/08-reflectors/how-to/output-02-command.sh +0 -0
  23. package/examples/08-reflectors/how-to/output-03-command.sh +0 -0
  24. package/examples/08-reflectors/how-to/output-04-command.sh +0 -0
  25. package/examples/08-reflectors/how-to/wat4wasm-outputs/01-module.wat +3 -0
  26. package/examples/08-reflectors/how-to/wat4wasm-outputs/02-module.wasm +3 -0
  27. package/examples/08-reflectors/how-to/wat4wasm-outputs/03-module.js +0 -0
  28. package/examples/08-reflectors/how-to/wat4wasm-outputs/04-module.html +0 -0
  29. package/examples/08-reflectors/module-output.wat +995 -0
  30. package/examples/08-reflectors/module.wat +108 -0
  31. package/examples/09-replaceAll/module-output.wat +347 -0
  32. package/examples/09-replaceAll/module.wat +68 -0
  33. package/examples/99-complex/module.wat +8 -0
  34. package/examples/99-complex/output.html +1 -0
  35. package/examples/99-complex/sub/worker.wat +2 -0
  36. package/examples/shell-usages.sh +60 -0
  37. package/lib/build +33 -0
  38. package/lib/clean.js +91 -0
  39. package/lib/cli.js +273 -0
  40. package/lib/helpers.js +567 -0
  41. package/lib/index.js +95 -0
  42. package/lib/processors/async.js +53 -0
  43. package/lib/processors/data.js +188 -0
  44. package/lib/processors/import.js +178 -0
  45. package/lib/processors/include.js +17 -0
  46. package/lib/processors/new.js +21 -0
  47. package/lib/processors/ref_extern.js +64 -0
  48. package/lib/processors/ref_func.js +44 -0
  49. package/lib/processors/replace_all.js +56 -0
  50. package/lib/processors/start.js +42 -0
  51. package/lib/processors/string.js +57 -0
  52. package/lib/processors/text.js +115 -0
  53. package/lib/processors/wat4wasm.js +285 -0
  54. package/lib/wat4beauty.js +320 -0
  55. package/package.json +30 -0
  56. package/ss-console.png +0 -0
  57. package/ss-terminal.png +0 -0
  58. package/test/boot.wat +5 -0
  59. package/test/test-output.html +1 -0
  60. package/test/test-output.js +27 -0
  61. package/test/test-output.wasm +0 -0
  62. package/test/test-sub.wat +4 -0
  63. package/test/test.wat +73 -0
  64. package/test/test_worker.js +1 -0
  65. package/wat4wasm +1998 -0
package/wat4wasm ADDED
@@ -0,0 +1,1998 @@
1
+ #!/usr/bin/env node
2
+ import fs from "fs";
3
+ import cp, { spawnSync } from "child_process";
4
+
5
+ function wat4beauty(watContent, alignIndentsWith = "\t", exportPadStart = 90) {
6
+
7
+ function alignIndents(watContent) {
8
+ let depth = 0;
9
+ const TAB = alignIndentsWith || "\t";
10
+ return watContent
11
+ .split("\n")
12
+ .map(line => {
13
+ const trimmed = line.trim();
14
+ if (!trimmed) return "";
15
+
16
+ // --- 1. Adım: Hesaplama için "Temiz" Satırı Oluştur ---
17
+ // Yorumları (;;) ve tırnak içindeki stringleri ("...") geçici olarak siliyoruz.
18
+ // Bu sayede yorumdaki veya stringdeki parantezler sayımı bozmaz.
19
+ let cleanLine = trimmed
20
+ .replace(/;;.*/g, "") // Yorumları sil
21
+ .replace(/"[^"]*"/g, '""'); // String içlerini boşalt
22
+
23
+ // --- 2. Adım: Parantez Bakiyesini Hesapla ---
24
+ const openCount = (cleanLine.match(/\(/g) || []).length;
25
+ const closeCount = (cleanLine.match(/\)/g) || []).length;
26
+
27
+ // Satırın BAŞINDAKİ kapanış parantezlerini say (Örn: "))" ile başlıyorsa)
28
+ // Bu, o satırın kendisini geri çekmek için lazım.
29
+ let leadingCloseCount = 0;
30
+ for (let char of cleanLine) {
31
+ if (char === ')') leadingCloseCount++;
32
+ else if (char === '(') break; // Açılış gelirse dur
33
+ else if (char === ' ') continue; // Boşlukları atla
34
+ else break; // Başka karakter gelirse dur
35
+ }
36
+
37
+ // --- 3. Adım: O anki satırı bas ---
38
+ // Satırın kendi seviyesi = Mevcut Derinlik - Başlangıçtaki Kapanışlar
39
+ let printDepth = Math.max(0, depth - leadingCloseCount);
40
+ const indentation = TAB.repeat(printDepth);
41
+
42
+ // --- 4. Adım: Gelecek satır için derinliği güncelle ---
43
+ // Net değişim = Açılan - Kapanan
44
+ // NOT: Senin istediğin "aynı satırda açılıp kapananlar" burada otomatik olarak
45
+ // birbirini götürür (1 - 1 = 0) ve derinliği değiştirmez.
46
+ depth += (openCount - closeCount);
47
+
48
+ return `${indentation}${trimmed}`;
49
+ })
50
+ .join("\n");
51
+ };
52
+
53
+ function alignGlobals(watContent) {
54
+ const lines = watContent.split("\n");
55
+ const globalUpdates = []; // Hangi satırların global olduğunu tutacağız
56
+ let maxNameLength = 0;
57
+
58
+ // --- 1. TUR: Keşif ---
59
+ // Tüm satırları gez, globalleri bul ve en uzun ismi ölç.
60
+ lines.forEach((line, index) => {
61
+ // Regex Açıklaması:
62
+ // ^(\s*\(global\s+) -> Grup 1: Girinti + "(global " kelimesi
63
+ // (\S+) -> Grup 2: Global ismi (boşluk olmayan her şey)
64
+ // \s+ -> İsimden sonraki (atılacak) boşluklar
65
+ // (.*)$ -> Grup 3: Satırın geri kalanı (tip tanımları vs.)
66
+ const match = line.match(/^(\s*\(global\s+)(\S+)\s+(.*)$/);
67
+
68
+ if (match) {
69
+ const name = match[2];
70
+ if (name.length > maxNameLength) {
71
+ maxNameLength = name.length;
72
+ }
73
+
74
+ // Bu satırı ve parçalarını daha sonra kullanmak üzere sakla
75
+ globalUpdates.push({
76
+ index: index,
77
+ prefix: match[1], // " (global " kısmı
78
+ name: name, // "$self.Object" kısmı
79
+ rest: match[3] // "(mut externref)..." kısmı
80
+ });
81
+ }
82
+ });
83
+
84
+ // Kural 2: En uzun isme 1 ekle (Minimum boşluk garantisi)
85
+ const alignLength = maxNameLength + 1;
86
+
87
+ // --- 2. TUR: Uygulama ---
88
+ // Sadece global satırlarını güncelle
89
+ globalUpdates.forEach(update => {
90
+ // Kural 3: İsim uzunluğunu bul ve aradaki boşluğu hesapla
91
+ const currentNameLength = update.name.length;
92
+
93
+ // Hedef uzunluktan ismin uzunluğunu çıkar
94
+ const spacesNeeded = alignLength - currentNameLength;
95
+
96
+ // Boşluk stringini oluştur
97
+ const padding = " ".repeat(Math.max(1, spacesNeeded));
98
+
99
+ // Parçaları birleştir: Prefix + İsim + Yeni Boşluklar + Geri Kalan
100
+ // Not: update.rest'i trim() yapıyoruz ki, eski düzensiz boşluklar gitsin.
101
+ const newLine = `${update.prefix}${update.name}${padding}${update.rest.trim()}`;
102
+
103
+ // Orijinal satırı güncelle
104
+ lines[update.index] = newLine;
105
+ });
106
+
107
+ return lines.join("\n");
108
+ };
109
+
110
+ function alignImports(watContent) {
111
+ const lines = watContent.split("\n");
112
+ const importUpdates = [];
113
+ let maxDefLength = 0;
114
+
115
+ lines.forEach((line, index) => {
116
+ // Regex Açıklaması:
117
+ // ^(\s*\(import\s+) -> Grup 1: Girinti + "(import "
118
+ // ("[^"]*"\s+"[^"]*") -> Grup 2: İki string bloğu (Modül ve İsim) - Burası ölçülecek
119
+ // (.*)$ -> Grup 3: Satırın geri kalanı (imza, tip vb.)
120
+
121
+ // Not: Bu regex tam senin dediğin gibi 1. tırnak ile 4. tırnak arasını (içindekilerle beraber) yakalar.
122
+ const match = line.match(/^(\s*\(import\s+)("[^"]*"\s+"[^"]*")(.*)$/);
123
+
124
+ if (match) {
125
+ const definitionPart = match[2]; // Örn: "env" "log"
126
+
127
+ if (definitionPart.length > maxDefLength) {
128
+ maxDefLength = definitionPart.length;
129
+ }
130
+
131
+ importUpdates.push({
132
+ index: index,
133
+ prefix: match[1], // " (import "
134
+ definition: definitionPart,
135
+ rest: match[3] // " (func $log...))"
136
+ });
137
+ }
138
+ });
139
+
140
+ // En uzun tanıma +1 ekleyerek hizalama sınırını belirle
141
+ const alignTarget = maxDefLength + 1;
142
+
143
+ // Hesaplanan boşluklarla satırları yeniden ör
144
+ importUpdates.forEach(update => {
145
+ const currentLen = update.definition.length;
146
+ const paddingNeeded = alignTarget - currentLen;
147
+ const padding = " ".repeat(Math.max(1, paddingNeeded));
148
+
149
+ // Parçaları birleştir
150
+ // update.rest.trim() yaparak imza kısmının başındaki düzensiz boşluğu atıyoruz.
151
+ lines[update.index] = `${update.prefix}${update.definition}${padding}${update.rest.trim()}`;
152
+ });
153
+
154
+ return lines.join("\n");
155
+ };
156
+
157
+ function alignImportItemsPerfectly(watContent) {
158
+ const lines = watContent.split("\n");
159
+ const updates = [];
160
+ let maxHeaderLength = 0; // "tip + isim" toplam uzunluğu için
161
+
162
+ lines.forEach((line, index) => {
163
+ // Regex Açıklaması:
164
+ // ^(\s*\(import\s+.*\() -> Grup 1: Prefix (parantez açılışına kadar)
165
+ // (func|global|table|memory) -> Grup 2: TİP (keyword)
166
+ // \s+ -> Aradaki boşluk
167
+ // (\$[^\s)]+) -> Grup 3: İSİM ($variable)
168
+ // (.*)$ -> Grup 4: Geri kalan (Rest)
169
+ const match = line.match(/^(\s*\(import\s+.*\()((?:func|global|table|memory))\s+(\$[^\s)]+)(.*)$/);
170
+
171
+ if (match) {
172
+ const type = match[2]; // örn: "func" veya "global"
173
+ const name = match[3]; // örn: "$add"
174
+
175
+ // Kritik Hesaplama: Tipin uzunluğu + 1 boşluk + İsmin uzunluğu
176
+ // Bu bize "func $add" bloğunun toplam kapladığı yeri verir.
177
+ const currentHeaderLength = type.length + 1 + name.length;
178
+
179
+ if (currentHeaderLength > maxHeaderLength) {
180
+ maxHeaderLength = currentHeaderLength;
181
+ }
182
+
183
+ updates.push({
184
+ index: index,
185
+ prefix: match[1], // " (import "mod" "item" ("
186
+ type: type, // "func"
187
+ name: name, // "$add"
188
+ headerLen: currentHeaderLength,
189
+ rest: match[4] // " (param i32)..."
190
+ });
191
+ }
192
+ });
193
+
194
+ // En uzun başlığa 1 karakter güvenli boşluk ekle
195
+ const alignTarget = maxHeaderLength + 1;
196
+
197
+ updates.forEach(update => {
198
+ // Ne kadar dolgu boşluğu lazım?
199
+ // Hedef - Şu anki toplam uzunluk
200
+ const paddingNeeded = alignTarget - update.headerLen;
201
+ const padding = " ".repeat(Math.max(1, paddingNeeded));
202
+
203
+ // Yeniden montaj:
204
+ // Prefix + Tip + " " + İsim + HESAPLANAN_DOLGU + Geri Kalan
205
+ lines[update.index] = `${update.prefix}${update.type} ${update.name}${padding}${update.rest.trim()}`;
206
+ });
207
+
208
+ return lines.join("\n");
209
+ };
210
+
211
+ function formatWatNearPerfectRestored(watContent) {
212
+
213
+ const namableMatches = Array.from(
214
+ watContent.matchAll(
215
+ /\((param|local|type)\s+\$(.[^\s]*)\s+(.[^\s]*)\)/g
216
+ )
217
+ );
218
+
219
+ const unNamedMatches = Array
220
+ .from(
221
+ watContent.matchAll(
222
+ /\((param|local|type|result)((\s(i32|f32|i64|f64|externref|funcref|v128))+)\)/g
223
+ )
224
+ )
225
+ .filter(m => !namableMatches.some(a => a.index === m.index))
226
+ .map(m => Object.defineProperties(m, { 3: { value: m[2] }, 2: { value: "" } }))
227
+ ;
228
+
229
+ const matches = namableMatches
230
+ .concat(unNamedMatches)
231
+ .sort((a, b) => b.index - a.index)
232
+ ;
233
+
234
+ const replace = new Array();
235
+ let maxLineLength = -Infinity;
236
+
237
+ matches
238
+ .filter(m => {
239
+ const wrapperFuncBegin = watContent.lastIndexOf("\n", watContent.lastIndexOf("(func", m.index));
240
+ const wrapperNextEOL = watContent.indexOf("\n", wrapperFuncBegin);
241
+ const wrapperFuncLine = watContent.substring(wrapperFuncBegin, wrapperNextEOL);
242
+
243
+ return wrapperFuncLine.split("(").length !== wrapperFuncLine.split(")").length;
244
+ })
245
+ .forEach(match => {
246
+ let [line, tag, name, kind] = match;
247
+
248
+ const length = String(line).length;
249
+ const begin = match.index;
250
+ const end = begin + length;
251
+
252
+ if (name.length > 0) {
253
+ name = `$${name} `
254
+ }
255
+
256
+ const lineLeft = `(${tag} ${name}`;
257
+ const lineRight = ` ${kind})`;
258
+
259
+ maxLineLength = Math.max(maxLineLength, length);
260
+ replace.push({ begin, end, lineLeft, lineRight });
261
+ });
262
+
263
+ replace
264
+ .forEach(({ begin, end, lineLeft, lineRight }) => {
265
+ const padding = maxLineLength - (lineLeft.length + lineRight.length) + 1;
266
+ const newLine = lineLeft.concat(" ".repeat(padding)).concat(lineRight);
267
+
268
+ watContent = ""
269
+ .concat(watContent.substring(0, begin))
270
+ .concat(newLine)
271
+ .concat(watContent.substring(end))
272
+ ;
273
+ });
274
+
275
+ return watContent;
276
+ };
277
+
278
+ function alignExportsRight(watContent) {
279
+
280
+ return watContent.split("\n").map(line => {
281
+ // 1. Export tanımını yakala: (export "...")
282
+ // Bu regex, tırnak içindeki kaçış karakterlerini (\") de güvenle atlar.
283
+ const exportRegex = /\(export\s+"(?:[^"\\]|\\.)*"\)/;
284
+ const match = line.match(exportRegex);
285
+
286
+ // Eğer satırda export yoksa olduğu gibi bırak
287
+ if (!match) return line;
288
+
289
+ const exportPart = match[0];
290
+ const targetColumn = exportPadStart || 90;
291
+
292
+ // 2. Export'u satırdan geçici olarak söküp al
293
+ // replace sadece ilk eşleşmeyi siler, bu tam istediğimiz şey.
294
+ let leftSide = line.replace(exportPart, "");
295
+
296
+ // Sildikten sonra sonda kalan gereksiz boşlukları temizle
297
+ // ÖNEMLİ: trim() değil trimEnd() kullanıyoruz ki baştaki girinti (indent) bozulmasın.
298
+ leftSide = leftSide.trimEnd();
299
+
300
+ // 3. Mesafe Hesabı (Hedef: 70. Karakter)
301
+ const currentLength = leftSide.length;
302
+
303
+ // Hedefe ulaşmak için kaç boşluk lazım?
304
+ let paddingCount = targetColumn - currentLength - exportPart.length;
305
+
306
+ // Eğer satır zaten 70'i geçtiyse veya çok yakınsa (çakışmaması için) en az 1 boşluk bırak
307
+ if (paddingCount < 1) {
308
+ paddingCount = 1;
309
+ }
310
+
311
+ // 4. Birleştir: Sol Taraf + Boşluklar + Export
312
+ return `${leftSide}${" ".repeat(paddingCount)}${exportPart}`;
313
+ }).join("\n");
314
+ };
315
+
316
+ watContent = alignIndents(watContent);
317
+ watContent = alignGlobals(watContent);
318
+ watContent = alignImports(watContent);
319
+ watContent = alignImportItemsPerfectly(watContent);
320
+ watContent = formatWatNearPerfectRestored(watContent);
321
+ watContent = alignExportsRight(watContent);
322
+
323
+ return watContent;
324
+ }const helpers = {
325
+ hasProtocol(str) {
326
+ return str?.includes("://");
327
+ },
328
+ parseProtoPath(path) {
329
+ const [protocol, fullpath, filename, basename, extension] = path.match(/([a-z0-9]+\:\/\/)((?:(?:.*)\/)*((.[^\/]*)\.(.[^\.]*)))/).slice(1);
330
+ const directory = fullpath.substring(0, fullpath.length - filename.length)
331
+ return { protocol, fullpath, directory, filename, basename, extension };
332
+ },
333
+ readFileAsText(fullpath) {
334
+ return fs.readFileSync(fullpath, "utf8");
335
+ },
336
+ readFileAsHex(fullpath) {
337
+ const data = fs.readFileSync(fullpath, "hex").replaceAll(/(..)/g, `\\$1`);
338
+ const size = data.length / 3;
339
+ return { data, size };
340
+ },
341
+ unlinkFile(path) {
342
+ return fs.unlinkSync(path);
343
+ },
344
+ copyFile(path, topath) {
345
+ return fs.cpSync(path, topath);
346
+ },
347
+ spawnSync(command, argv) {
348
+ return cp.spawnSync(command, argv, { stdio: "inherit" });
349
+ },
350
+ blockAt(raw, begin) {
351
+ raw = raw.toString();
352
+ if ((begin === -1) || !(raw = raw.substring(begin))) {
353
+ return "";
354
+ }
355
+ let maskUsed = raw.includes("\\)");
356
+ if (maskUsed) {
357
+ maskUsed = `__RAND${Math.random()}__`;
358
+ raw = raw.replaceAll("\\)", maskUsed);
359
+ }
360
+ let end = begin = 0, block = raw;
361
+ end = raw.indexOf(")", end);
362
+ block = raw.substring(begin, ++end);
363
+ while (block && (
364
+ block.split("(").length !==
365
+ block.split(")").length)
366
+ ) {
367
+ end = raw.indexOf(")", end);
368
+ block = raw.substring(begin, ++end);
369
+ }
370
+ if (maskUsed && block) {
371
+ block = block.replaceAll(maskUsed, "\\)");
372
+ }
373
+ return block;
374
+ },
375
+ rawContent(block) {
376
+ let raw = block.toString().trim();
377
+ let begin;
378
+ if (raw.startsWith("(")) {
379
+ raw = raw.substring(1);
380
+ while (raw && raw.at(0).match(/[a-z0-9\.\_\(\)]/)) {
381
+ raw = raw.substring(1);
382
+ }
383
+ raw = raw.trim();
384
+ if (raw.startsWith("$")) {
385
+ while (raw && raw.at(0).match(/[a-z0-9A-Z\:\.\<\>\/\_\+\-\`\[\]\$\=\#\!]/)) {
386
+ raw = raw.substring(1)
387
+ }
388
+ }
389
+ raw = raw.trim();
390
+ }
391
+ raw = raw.trim();
392
+ if (raw.endsWith(")")) {
393
+ raw = raw.substring(0, raw.length - 1)
394
+ while (raw && !raw.at(-1).trim()) {
395
+ raw = raw.substring(0, raw.length - 1)
396
+ }
397
+ }
398
+ raw = raw.trim();
399
+ return raw;
400
+ },
401
+ blockContent(block) {
402
+ let raw = this.rawContent(block);
403
+ while (raw.startsWith("(type")) { raw = raw.substring(raw.indexOf(")") + 1).trim(); }
404
+ while (raw.startsWith("(param")) { raw = raw.substring(raw.indexOf(")") + 1).trim(); }
405
+ while (raw.startsWith("(result")) { raw = raw.substring(raw.indexOf(")") + 1).trim(); }
406
+ while (raw.startsWith("(local")) { raw = raw.substring(raw.indexOf(")") + 1).trim(); }
407
+ raw = raw.trim();
408
+ return raw;
409
+ },
410
+ containsMemoryOperation(raw) {
411
+ return raw.toString().split(/(memory|i32|f32|i64|f64|v128)(\.)(init|load|store|atomic|fill|drop)/).length > 1;
412
+ },
413
+ prepend(raw, block) {
414
+ raw = raw.toString();
415
+ block = block.toString().trim().concat("\n\n");
416
+ if (raw.startsWith("(module")) {
417
+ return String(`(module\n${block}\n`).concat(
418
+ raw.substring("(module".length).trimStart()
419
+ )
420
+ }
421
+ if (raw.replaceAll(/\s+/g, '').match(/\(([a-z0-9\.\_]+)\)/)) {
422
+ return raw.substring(0, raw.length - 1).concat(block).concat(`)`);
423
+ }
424
+ let begin;
425
+ begin = raw.indexOf("(", 1);
426
+ let headmatch = true;
427
+ while (headmatch) {
428
+ if (headmatch = raw.substring(begin).startsWith("(param")) { begin = raw.indexOf("(", ++begin); continue; }
429
+ if (headmatch = raw.substring(begin).startsWith("(result")) { begin = raw.indexOf("(", ++begin); continue; }
430
+ if (headmatch = raw.substring(begin).startsWith("(local")) { begin = raw.indexOf("(", ++begin); continue; }
431
+ if (headmatch = raw.substring(begin).startsWith("(type")) { begin = raw.indexOf("(", ++begin); continue; }
432
+ }
433
+ if (begin !== -1) {
434
+ return raw.substring(0, begin).concat(block).concat(raw.substring(begin))
435
+ }
436
+ const blockparts = raw.split(/\s+/).filter(Boolean);
437
+ if (blockparts.length === 1) {
438
+ return this.append(raw, block);
439
+ }
440
+ const maybe$name = blockparts.at(1);
441
+ if (maybe$name.startsWith("$")) {
442
+ if (maybe$name.endsWith(")")) {
443
+ return this.append(raw, block);
444
+ }
445
+ }
446
+ const [firstLine, ...restLines] = raw.split(/\n/g);
447
+ return [firstLine, block, ...restLines].join("\n");
448
+ },
449
+ append(raw, block) {
450
+ raw = raw.toString();
451
+ return raw.substring(0, raw.lastIndexOf(")")).concat(`\n${block || ''}\n)`);
452
+ },
453
+ findQuotedTexts(rawBlock, maxcount = -1) {
454
+ let maskUsed = rawBlock.includes('\\"');
455
+ if (maskUsed) {
456
+ maskUsed = `__RAND${Math.random()}__`;
457
+ rawBlock = rawBlock.replaceAll('\\"', maskUsed);
458
+ }
459
+ let texts = [];
460
+ let begin = rawBlock.indexOf(`"`);
461
+ let end = rawBlock.indexOf(`"`, begin + 1);
462
+ while (maxcount-- && begin !== -1) {
463
+ texts.push(rawBlock.substring(begin + 1, end));
464
+ begin = rawBlock.indexOf('"', end + 1);
465
+ end = rawBlock.indexOf(`"`, begin + 1);
466
+ }
467
+ if (maskUsed) {
468
+ texts = texts.map(t => t.replaceAll(maskUsed, "\\)"));
469
+ }
470
+ return texts;
471
+ },
472
+ findQuotedText(rawBlock) {
473
+ return this.findQuotedTexts(rawBlock, 1).at(0);
474
+ },
475
+ encodeText: TextEncoder.prototype.encode.bind(new TextEncoder),
476
+ encodeString: str => Array.from(str || '').map(c => c.charCodeAt()),
477
+ fix$Name(keyword, self = false) {
478
+ if (keyword.startsWith("$") === false) {
479
+ return `$${keyword}`;
480
+ }
481
+ if (self && keyword.startsWith("$self") === false) {
482
+ return `$self.${keyword.substring(1)}`;
483
+ }
484
+ return keyword;
485
+ },
486
+ fixBlockKeyword(keyword, filter = {}) {
487
+ if (keyword.split(/\s/).length > 1) {
488
+ throw new Error(`Given keyword is wrong: ${keyword}`)
489
+ }
490
+ if (filter.$name) {
491
+ keyword = `${keyword} ${filter.$name}`;
492
+ }
493
+ if (filter.name) {
494
+ keyword = `${keyword} $${filter.name}`;
495
+ }
496
+ if (keyword.startsWith("(") === false) {
497
+ return `(${keyword}`;
498
+ }
499
+ return keyword;
500
+ },
501
+ getBlockKeyword(block) {
502
+ let keyword = block;
503
+ if (keyword.startsWith("(") === true) {
504
+ keyword = keyword.substring(1);
505
+ }
506
+ let i = 0;
507
+ while (keyword.at(i++).match(/[a-z0-9\_\.]/));;
508
+ return keyword.substring(0, i);
509
+ },
510
+ getBlockRootTag(block) {
511
+ return this.getBlockKeyword(block).split(".").at(0);
512
+ },
513
+ getBlockRootTagType(block) {
514
+ return this.getBlockRootTag(block).match(/(i32|f32|i64|f64)/)?.at(0) || "ext";
515
+ },
516
+ getTableOperator(block) {
517
+ let [match, $name = "", initial = "", maximum = "", kindof = "externref"] = block.toString().match(/\(table(?:\s*(.[^\s]*)?)\s+(\d+)(?:\s*(\d+)?)\s+(externref|funcref)\)/) ?? [];
518
+ initial = parseInt(initial);
519
+ maximum = parseInt(maximum);
520
+ return {
521
+ $name, initial, maximum, kindof,
522
+ grow: function (count = 1) {
523
+ return {
524
+ newTableBlock: `(table ${[$name, initial + count, maximum, kindof].filter(Boolean).join(" ").trim()})`,
525
+ getTableBlock: `(table.get ${[$name, `(i32.const ${initial})`].join(" ").trim()})`
526
+ };
527
+ }
528
+ };
529
+ },
530
+ clearExceptKnown(raw) {
531
+ return `${raw || ''}`.trim().split(/\n/)
532
+ .map(l => l.replaceAll(/\s+/g, " "))
533
+ .map(l => l.replace(/;;.*/g, ""))
534
+ .map(l => l.replaceAll(/\(;(.*);\)/g, ""))
535
+ .filter(l => l.replaceAll(/\s+/g, "").trim())
536
+ .join(" ").replaceAll(/\s+(\)|\()/g, `$1`)
537
+ ;
538
+ },
539
+ generateId(raw) {
540
+ let sum = 0;
541
+ this.clearExceptKnown(raw)
542
+ .split("").map((c, i) => sum += c.charCodeAt() * i);
543
+ return sum;
544
+ },
545
+ abstract(str, max = 15) {
546
+ str = `${str || ''}`.replaceAll(/\s+/g, ' ').replaceAll(/\s+\)/g, ")");
547
+ if (str.length < max) return str;
548
+ return `${str.substring(0, max / 3)} ... ${str.substring(str.length - max / 3)}`
549
+ },
550
+ createTableGetter(index, kindof = "extern") {
551
+ return `(ref.null (;${index};) ${kindof})`;
552
+ },
553
+ referenceId() {
554
+ return "0x" + crypto.randomUUID().replace(/\-/g, "");
555
+ },
556
+ hasBlock(raw, keyword, filter) {
557
+ if (!keyword) { return raw.indexOf("(", 1) !== -1; };
558
+ keyword = this.fixBlockKeyword(keyword, filter);
559
+ return raw.includes(keyword);
560
+ },
561
+ hasAnyBlock(raw) {
562
+ return this.hasBlock(raw);
563
+ },
564
+ MaskSet: class MaskSet extends Map {
565
+ constructor(raw) {
566
+ raw = (raw || '').toString().trim();
567
+ Reflect.defineProperty(super(), "input", { value: raw, writable: true })
568
+ }
569
+ remove(block) {
570
+ if (!block || !block?.uuid) { return this.input; };
571
+ if (this.has(block.uuid) !== false) {
572
+ this.delete(block.uuid);
573
+ }
574
+ return this.mask(block, "");
575
+ }
576
+ mask(block, maskWith = block.uuid) {
577
+ if (!block.uuid) {
578
+ throw new Error(`Raw block needs uuid: ${block}`);
579
+ };
580
+ if (this.has(block.uuid) === false) {
581
+ this.set(block.uuid, block);
582
+ }
583
+ const rawRange = this.input.substring(
584
+ block.begin, block.end
585
+ );
586
+ if (block.toString() !== rawRange) {
587
+ console.error({ block, rawRange })
588
+ throw new Error(`Raw block pointer range is not matched!`);
589
+ };
590
+ this.input = this.input
591
+ .substring(0, block.begin)
592
+ .concat(block.maskWith = maskWith)
593
+ .concat(this.input.substring(block.end))
594
+ ;
595
+ }
596
+ unmask(block) {
597
+ this.input = this.input.replaceAll(block.uuid, block.toString());
598
+ return this;
599
+ }
600
+ lastBlockOf(keyword, filter) {
601
+ return helpers.lastBlockOf(this.input, keyword, filter);
602
+ }
603
+ hasBlock(keyword, filter) {
604
+ return helpers.hasBlock(this.input, keyword, filter);
605
+ }
606
+ update(oldBlock, newBlock) {
607
+ this.set(oldBlock.uuid, newBlock);
608
+ }
609
+ get hasAnyBlock() {
610
+ return this.input.trim().indexOf("(", 1) !== -1;
611
+ }
612
+ parseFirstBlock() {
613
+ return helpers.parseFirstBlock(this.input);
614
+ }
615
+ refresh() {
616
+ this.input = this.restoreInto(this.input);
617
+ return this;
618
+ }
619
+ restore() {
620
+ return this.refresh().restoreInto();
621
+ }
622
+ toString() {
623
+ return this.input;
624
+ }
625
+ [Symbol.toPrimitive]() {
626
+ return this.toString();
627
+ }
628
+ get rawContent() {
629
+ return helpers.rawContent(this.restore())
630
+ }
631
+ get blockContent() {
632
+ return helpers.blockContent(this.restore())
633
+ }
634
+ restoreInto(raw = this.input) {
635
+ const masks = Array.from(this.keys());
636
+ const uuid = masks.find(uuid => raw.includes(uuid));
637
+ if (uuid) {
638
+ const block = this.get(uuid);
639
+ raw = raw.replaceAll(uuid, block.toString());
640
+ return this.restoreInto(raw);
641
+ }
642
+ return raw;
643
+ }
644
+ generateId() {
645
+ return helpers.generateId(this.restore())
646
+ }
647
+ },
648
+ nameSignatureofGlobal($name) {
649
+ return String($name || '')
650
+ .match(/\$(.[^<]*)(?:\<(.[^>]*)\>)?/)?.slice(1) || [];
651
+ },
652
+ assignBlockProperties(raw, block, begin) {
653
+ if (begin === -1) return null;
654
+ let $name = block.split(/[^a-z0-9A-Z\:\.\<\>\/\_\+\-\`\[\]\$\=\#\!\*]/g).filter(Boolean).at(1) || "";
655
+ let isGetter = false;
656
+ let isSetter = false;
657
+ let descriptorKey = "value";
658
+ if ($name.startsWith("$") === false) {
659
+ $name = "";
660
+ }
661
+ if ($name.includes("[")) {
662
+ descriptorKey = $name.substring(
663
+ $name.indexOf("[") + 1,
664
+ $name.indexOf("]")
665
+ );
666
+ $name = $name.substring(0, $name.indexOf("[")).concat("/").concat(descriptorKey);
667
+ }
668
+ isGetter = descriptorKey === "get";
669
+ isSetter = descriptorKey === "set";
670
+ $name = $name
671
+ .replaceAll(":", ".prototype.")
672
+ .replaceAll(".TypedArray", ".Uint8Array.__proto__")
673
+ ;
674
+ return block && Object.defineProperties({
675
+ block: `${block}`,
676
+ begin,
677
+ uuid: crypto.randomUUID(),
678
+ end: begin + block.length,
679
+ $name: $name || "",
680
+ toString: function () { return this.block; },
681
+ wrappedRaws: function () {
682
+ return {
683
+ before: this.input.substring(0, this.begin),
684
+ after: this.input.substring(this.end)
685
+ }
686
+ },
687
+ maskedRaw: function () { return this.replacedRaw(this.uuid); },
688
+ removedRaw: function () { return this.replacedRaw(""); },
689
+ replacedRaw: function (str) {
690
+ const { before, after } = this.wrappedRaws();
691
+ return before.concat(str.toString()).concat(after);
692
+ },
693
+ }, {
694
+ isGetter: { value: isGetter },
695
+ isSetter: { value: isSetter },
696
+ descriptorKey: { value: descriptorKey },
697
+ input: { value: raw },
698
+ hasBlock: { value: function (keyword, filter) { return helpers.hasBlock(this.toString(), keyword, filter); } },
699
+ blockName: {
700
+ get: function () {
701
+ let rawContent = this.toString().trim();
702
+ let begin;
703
+ let blockName = ``;
704
+ if (rawContent.startsWith("(")) {
705
+ rawContent = rawContent.substring(1);
706
+ while (rawContent && rawContent.at(0).match(/[a-z0-9\.\_]/)) {
707
+ blockName = blockName + rawContent.at(0)
708
+ rawContent = rawContent.substring(1);
709
+ }
710
+ }
711
+ return blockName;
712
+ }
713
+ },
714
+ generateId: { value: function () { return helpers.generateId(this.toString()) } },
715
+ name: { get: function () { return `${this.$name}`.substring(1); } },
716
+ rawContent: { get: function () { return helpers.rawContent(this.toString()) } },
717
+ blockContent: { get: function () { return helpers.blockContent(this.toString()) } },
718
+ hasAnyBlock: { get: function () { return helpers.hasAnyBlock(this.toString()) } },
719
+ indexOf: { value: function () { return this.block.indexOf(...arguments) } },
720
+ includes: { value: function () { return this.block.includes(...arguments) } },
721
+ lastIndexOf: { value: function () { return this.block.lastIndexOf(...arguments) } },
722
+ split: { value: function () { return this.block.split(...arguments) } },
723
+ at: { value: function () { return this.block.at(...arguments) } },
724
+ length: { get: function () { return this.block.length } },
725
+ charCodeAt: { value: function () { return this.block.charCodeAt(...arguments) } },
726
+ concat: { value: function () { return this.block.concat(...arguments) } },
727
+ startsWith: { value: function () { return this.block.startsWith(...arguments) } },
728
+ endsWith: { value: function () { return this.block.endsWith(...arguments) } },
729
+ substring: { value: function () { return this.block.substring(...arguments) } },
730
+ replace: { value: function () { return this.block.replace(...arguments) } },
731
+ replaceAll: { value: function () { return this.block.replaceAll(...arguments) } },
732
+ lastBlockOf: { value: function () { return helpers.lastBlockOf(this.block, ...arguments) } },
733
+ [Symbol.toPrimitive]: { value: function () { return this.block; } },
734
+ })
735
+ },
736
+ lastBlockOf(raw, keyword, filter) {
737
+ if (!raw) throw new Error(`no raw for block: ${keyword}`);
738
+ keyword = this.fixBlockKeyword(keyword, filter);
739
+ raw = raw.toString();
740
+ let begin = raw.lastIndexOf(keyword);
741
+ const block = this.parseBlockAt(raw, begin);
742
+ return block;
743
+ },
744
+ firstBlockOf(raw, keyword, filter) {
745
+ if (!raw) throw new Error(`no raw for block: ${keyword}`);
746
+ keyword = this.fixBlockKeyword(keyword, filter);
747
+ raw = raw.toString();
748
+ let begin = raw.indexOf(keyword);
749
+ const block = this.parseBlockAt(raw, begin);
750
+ return block;
751
+ },
752
+ parseBlockAt(raw, begin) {
753
+ return this.assignBlockProperties(raw, this.blockAt(raw, begin), begin);
754
+ },
755
+ parseBlock(raw) {
756
+ return this.parseFirstBlock(raw);
757
+ },
758
+ parseFirstBlock(raw) {
759
+ raw = raw.toString();
760
+ return this.parseBlockAt(raw, raw.indexOf("(", 1));
761
+ }
762
+ };
763
+
764
+ const ASYNC_BLOCK_NAME = "async";
765
+ function ASYNC (wat, WAT4WASM) {
766
+ const maskSet = new helpers.MaskSet(wat);
767
+ const inlineFunctions = new Array();
768
+ while (maskSet.hasBlock(ASYNC_BLOCK_NAME)) {
769
+ const block = maskSet.lastBlockOf(ASYNC_BLOCK_NAME);
770
+ const result = block.blockName.split(".").at(1) || "";
771
+ let chain, step, $exit, $name, $prop, $func,
772
+ steps = new helpers.MaskSet(block);
773
+ steps.mask(chain = steps.parseFirstBlock());
774
+ maskSet.mask(block);
775
+ while (steps.hasAnyBlock) {
776
+ steps.mask(step = steps.parseFirstBlock());
777
+ $prop = step.blockName;
778
+ $func = step.rawContent;
779
+ $exit = steps.hasAnyBlock && "ext" || result;
780
+ if ($func.startsWith("ref.func")) {
781
+ $name = `$${$func.split("ref.func").pop().trim()}`;
782
+ } else {
783
+ $name = `$${$prop}_${block.begin}_${step.begin}`;
784
+ inlineFunctions.push(`(func ${$name}\n${$func}\n)`);
785
+ }
786
+ chain = String(`
787
+ (call $self.Reflect.apply<ext.ext.ext>${$exit}
788
+ (ref.extern $self.Promise.prototype.${$prop}<ext>)
789
+ ${chain.toString()}
790
+ (call $self.Array.of<fun>ext (ref.func ${$name}))
791
+ )`);
792
+ }
793
+ maskSet.update(block, chain);
794
+ }
795
+ wat = maskSet.restore();
796
+ if (inlineFunctions.length) {
797
+ wat = helpers.append(wat, inlineFunctions.join("\n\n"));
798
+ }
799
+ return wat;
800
+ }
801
+
802
+ const DATA_BLOCK_NAME = "data";
803
+ const SIZE_BLOCK_NAME = "data.size";
804
+ const VIEW_BLOCK_NAME = "data.view";
805
+ const GENERATE_DATA_VIEWER = (size, $name) => {
806
+ return `
807
+ (block ${$name}>
808
+ (result externref)
809
+ (global.set $wat4wasm (call $self.Array<>ext))
810
+
811
+ (call $self.Reflect.set<ext.i32.i32>
812
+ (global.get $wat4wasm) (i32.const 0) (i32.const ${size})
813
+ )
814
+ (global.set $wat4wasm
815
+ (call $self.Reflect.construct<ext.ext>ext
816
+ (ref.extern $self.Uint8Array<ext>)
817
+ (global.get $wat4wasm)
818
+ )
819
+ )
820
+
821
+ (if (i32.const ${size})
822
+ (then
823
+ (i32.const 0)
824
+ (i64.load (i32.const 0))
825
+ (block $copy
826
+ (i32.store (i32.const 0) (i32.const ${size}))
827
+ (loop $i--
828
+ (if (i32.load (i32.const 0))
829
+ (then
830
+ (i32.store
831
+ (i32.const 0)
832
+ (i32.sub (i32.load (i32.const 0)) (i32.const 1))
833
+ )
834
+ (memory.init ${$name}
835
+ (i32.const 4)
836
+ (i32.load (i32.const 0))
837
+ (i32.const 1)
838
+ )
839
+ (call $self.Reflect.set<ext.i32.i32>
840
+ (global.get $wat4wasm)
841
+ (i32.load (i32.const 0))
842
+ (i32.load8_u (i32.const 4))
843
+ )
844
+ (br $i--)
845
+ )
846
+ )
847
+ )
848
+ )
849
+ (i64.store (; stack stack ;))
850
+ )
851
+ )
852
+ (global.get $wat4wasm)
853
+ (global.set $wat4wasm (null))
854
+ )
855
+ `;
856
+ };
857
+ function DATA (wat, WAT4WASM) {
858
+ const maskSet = new helpers.MaskSet(wat);
859
+ const externgetter = new Map();
860
+ const segmentSizes = new Map();
861
+ const sizeRequests = new Set();
862
+ const viewRequests = new Set();
863
+ while (maskSet.hasBlock(VIEW_BLOCK_NAME)) {
864
+ const block = maskSet.lastBlockOf(VIEW_BLOCK_NAME);
865
+ viewRequests.add(block);
866
+ maskSet.mask(block);
867
+ }
868
+ while (maskSet.hasBlock(SIZE_BLOCK_NAME)) {
869
+ const block = maskSet.lastBlockOf(SIZE_BLOCK_NAME);
870
+ sizeRequests.add(block);
871
+ maskSet.mask(block);
872
+ }
873
+ while (maskSet.hasBlock(DATA_BLOCK_NAME)) {
874
+ const block = maskSet.lastBlockOf(DATA_BLOCK_NAME);
875
+ const content = helpers.findQuotedText(block);
876
+ maskSet.mask(block);
877
+ if (helpers.hasProtocol(content) === false) {
878
+ continue;
879
+ }
880
+ let {
881
+ protocol, fullpath, directory,
882
+ filename, basename, extension
883
+ } = helpers.parseProtoPath(content);
884
+ if (protocol === "wasm://") {
885
+ const module_wat = `wat4wasm-${basename}.wat`;
886
+ const wasm_output = `wat4wasm-${basename}.wasm`;
887
+ const wat4wasm_out = `wat4wasm-${basename}.wasm.wat`;
888
+ const params = process.argv
889
+ .filter(a => a.startsWith("--"))
890
+ .filter(a => !a.startsWith("--input="))
891
+ .filter(a => !a.startsWith("--output="))
892
+ ;
893
+ helpers.copyFile(fullpath, module_wat)
894
+ const wat4wasm = process.argv[1];
895
+ const nodejs = process.argv[0];
896
+ const argv = Array.of(
897
+ wat4wasm,
898
+ `--input=${module_wat}`,
899
+ `--output=${wasm_output}`,
900
+ `--no-unlink`
901
+ ).concat(params);
902
+ helpers.spawnSync(nodejs, argv);
903
+ const { data, size } = helpers.readFileAsHex(wasm_output);
904
+ segmentSizes.set(block.$name, size);
905
+ maskSet.update(block, block.replace(content, data));
906
+ helpers.unlinkFile(module_wat);
907
+ helpers.unlinkFile(wasm_output);
908
+ helpers.unlinkFile(wat4wasm_out);
909
+ }
910
+ else if (protocol === "file://") {
911
+ const { data, size } = helpers.readFileAsHex(fullpath);
912
+ segmentSizes.set(block.$name, size);
913
+ maskSet.update(block, block.replace(content, data));
914
+ }
915
+ }
916
+ sizeRequests.forEach(block => {
917
+ const size = segmentSizes.get(block.$name);
918
+ const code = `(i32.const ${size})`;
919
+ maskSet.update(block, code);
920
+ });
921
+ viewRequests.forEach(block => {
922
+ block.id = helpers.referenceId();
923
+ maskSet.update(block, block.id);
924
+ });
925
+ wat = maskSet.restore();
926
+ let oninit = String();
927
+ viewRequests.forEach(block => {
928
+ if (externgetter.has(block.$name) === false) {
929
+ const size = segmentSizes.get(block.$name);
930
+ const code = GENERATE_DATA_VIEWER(size, block.$name);
931
+ const growRequest = WAT4WASM_GROW_EXTERN_TABLE(wat);
932
+ block.tableSetter = growRequest.generateSetter(code);
933
+ wat = growRequest.modifiedRaw;
934
+ oninit = `${oninit}\n\n${block.tableSetter}\n\n`;
935
+ externgetter.set(
936
+ block.$name,
937
+ growRequest.getter.concat(` ;; ${block.$name}\n`)
938
+ );
939
+ }
940
+ });
941
+ viewRequests.forEach(block => {
942
+ wat = wat.replaceAll(
943
+ block.id, externgetter.get(block.$name)
944
+ );
945
+ });
946
+ wat = APPEND_ON_EXTERN_READY(wat, oninit);
947
+ return wat;
948
+ }
949
+
950
+ const IMPORT_BLOCK_NAME = "import";
951
+ const RAWBLOCK_IMPORT_CODE = (pathName, rawBlock) => `(import "${pathName.split(".").at(-2) || 'self'}" "${pathName.split(".").at(-1)}" \t${rawBlock})`.replaceAll(" )", ")");
952
+ const FUNCTION_IMPORT_CODE = ($name) => {
953
+ const namesig = $name.match(/\$(.[^<]*)<(.[^>]*)?>(.[^\s]*)?/).slice(1);
954
+ const [pathName, inputs = "", outputs = ""] = namesig;
955
+ const tagName = `func`;
956
+ const longType = t => ({ ext: 'externref', fun: 'funcref' })[t] || t;
957
+ const paramBlock = `(param${`.${inputs}`.split('.').map(longType).join(' ')})`;
958
+ const resultBlock = `(result${`.${outputs}`.split('.').map(longType).join(' ')})`;
959
+ return RAWBLOCK_IMPORT_CODE(pathName, `(${tagName} ${$name} ${paramBlock} ${resultBlock})`);
960
+ }
961
+ const GLOBAL_IMPORT_CODE = ($name) => {
962
+ const namesig = $name.match(/\$(.[^<]*)(?:\<(.[^>]*)\>)?/)?.slice(1) || [];
963
+ const [pathName = "self", type = "ext"] = namesig;
964
+ const tagName = "global";
965
+ const typeName = type.replace(`ext`, `externref`);
966
+ return RAWBLOCK_IMPORT_CODE(pathName, `(${tagName} ${$name} ${typeName})`);
967
+ }
968
+ const GLOBAL_DEFINE_CODE = ($name) => {
969
+ const [pathName = "self", type = "ext"] = helpers.nameSignatureofGlobal($name);
970
+ const tagName = "global";
971
+ const mutValue = t => `(mut ${({ ext: 'externref', fun: 'funcref' })[t] || t})`;
972
+ const nilValue = t => `(${({ ext: 'ref.null extern', fun: 'ref.null func' })[t] || `${t}.const 0`})`;
973
+ return `(${tagName} ${$name} ${mutValue(type)} ${nilValue(type)})`;
974
+ }
975
+ const PATH_WALKER_CODE = ($name) => {
976
+ let descriptorKey;
977
+ [$name, descriptorKey = "value"] = $name.split("/")
978
+ const nameparts = $name.split("<").at(0).split("$").pop().split(".");
979
+ const stepType = new Array(nameparts.length - 1).fill("ext");
980
+ const type = $name.split(">").at(0).split("<").pop() || "ext";
981
+ let pathWalker = `(global.get $${nameparts[0]})`;
982
+ let currentKey;
983
+ stepType.push(type);
984
+ stepType.reverse().pop();
985
+ nameparts.reverse().pop();
986
+ let stepCount = nameparts.length;
987
+ if (descriptorKey !== "value") {
988
+ stepCount = stepCount - 1;
989
+ }
990
+ while (stepCount--) {
991
+ currentKey = nameparts.pop();
992
+ pathWalker = String(`
993
+ (call $self.Reflect.get<ext.ext>${stepType.pop()}
994
+ ${pathWalker}
995
+ (text "${currentKey}") ;; ${currentKey}
996
+ )`).trim();
997
+ }
998
+ if (descriptorKey !== "value") {
999
+ currentKey = nameparts.pop();
1000
+ pathWalker = String(`
1001
+ (call $self.Reflect.get<ext.ext>ext
1002
+ (call $self.Reflect.getOwnPropertyDescriptor<ext.ext>ext
1003
+ ${pathWalker}
1004
+ (text "${currentKey}") ;; ${currentKey}
1005
+ )
1006
+ (text "${descriptorKey}") ;; ${descriptorKey}
1007
+ )`).trim();
1008
+ }
1009
+ return String(`
1010
+ ${pathWalker}
1011
+ `);
1012
+ }
1013
+ function IMPORT (wat, WAT4WASM) {
1014
+ const maskSet = new helpers.MaskSet(wat);
1015
+ const selfSet = new Set();
1016
+ const imports = new Array();
1017
+ const globals = new Array();
1018
+ const oninits = new Array();
1019
+ while (maskSet.hasBlock(IMPORT_BLOCK_NAME)) {
1020
+ const wrapper = maskSet.lastBlockOf(IMPORT_BLOCK_NAME);
1021
+ const $name = helpers.parseFirstBlock(wrapper).$name;
1022
+ if ($name && $name.startsWith("$self")) {
1023
+ if (selfSet.has($name) === false) {
1024
+ selfSet.add($name);
1025
+ }
1026
+ }
1027
+ maskSet.mask(wrapper);
1028
+ }
1029
+ while (maskSet.hasBlock("global.get")) {
1030
+ const global = maskSet.lastBlockOf("global.get");
1031
+ const $name = global.$name;
1032
+ maskSet.mask(global);
1033
+ maskSet.update(global, `(global.get ${$name})`);
1034
+ if ($name && $name.startsWith("$self")) {
1035
+ if (selfSet.has($name) === false) {
1036
+ selfSet.add($name);
1037
+ const [pathName = "self", type = "ext"
1038
+ ] = helpers.nameSignatureofGlobal($name);
1039
+ const nameparts = pathName.split(".");
1040
+ /**
1041
+ * previous logic is based on direct import for
1042
+ * short paths but this is unnecessary.. just set
1043
+ * global value in the start function to gathering
1044
+ * more simple import segment.
1045
+ */
1046
+ //if (nameparts.length <= 3) {
1047
+ if (["$self", "$self.String.fromCharCode"].includes($name)) {
1048
+ imports.push(GLOBAL_IMPORT_CODE($name));
1049
+ }
1050
+ else if (globals.includes($name) === false) {
1051
+ globals.push(GLOBAL_DEFINE_CODE($name));
1052
+ const walker = PATH_WALKER_CODE($name, global.descriptorKey);
1053
+ const setter = `(global.set ${$name} ${walker})`;
1054
+ oninits.push({ setter, name: $name.substring(1), type: 'global' });
1055
+ }
1056
+ }
1057
+ }
1058
+ }
1059
+ while (maskSet.hasBlock("call")) {
1060
+ const caller = maskSet.lastBlockOf("call");
1061
+ const $name = caller.$name;
1062
+ if ($name && $name.startsWith("$self")) {
1063
+ if (selfSet.has($name) === false) {
1064
+ selfSet.add($name);
1065
+ imports.push(FUNCTION_IMPORT_CODE($name));
1066
+ }
1067
+ }
1068
+ maskSet.mask(caller);
1069
+ }
1070
+ wat = maskSet.restore();
1071
+ imports.forEach(code => {
1072
+ if (wat.includes(code) === false) {
1073
+ wat = helpers.prepend(wat, code);
1074
+ }
1075
+ });
1076
+ globals.forEach(code => {
1077
+ if (wat.includes(code) === false) {
1078
+ wat = helpers.append(wat, code);
1079
+ }
1080
+ });
1081
+ oninits.forEach(init => {
1082
+ const header = `block $${init.type}/${init.name}`;
1083
+ if (wat.includes(header) === false) {
1084
+ const code = String(`(${header}\n${init.setter}\n)`);
1085
+ wat = APPEND_ON_TEXT_READY(wat, code);
1086
+ }
1087
+ });
1088
+ return wat;
1089
+ }
1090
+
1091
+ const INCLUDE_BLOCK_NAME = "include";
1092
+ function INCLUDE (wat, WAT4WASM) {
1093
+ while (helpers.hasBlock(wat, INCLUDE_BLOCK_NAME)) {
1094
+ const block = helpers.lastBlockOf(wat, INCLUDE_BLOCK_NAME);
1095
+ const path = helpers.findQuotedText(block);
1096
+ wat = block.replacedRaw(
1097
+ helpers.readFileAsText(path)
1098
+ );
1099
+ }
1100
+ return wat;
1101
+ }
1102
+
1103
+ const NEW_BLOCK_NAME = "new";
1104
+ function NEW (wat) {
1105
+ while (helpers.hasBlock(wat, NEW_BLOCK_NAME)) {
1106
+ const block = helpers.lastBlockOf(wat, NEW_BLOCK_NAME);
1107
+ const $name = block.$name;
1108
+ const param = $name.split(">").at(0).split("<").at(1) || "";
1109
+ const $constructor = `(ref.extern $self.${$name.substring(1)}<ext>)`;
1110
+ const $arguments = block.replace(`(new ${$name}`, `(array $of<${param}>ext`);
1111
+ const $reflect = `(reflect $construct<ext.ext>ext\n${$constructor}\n${$arguments}\n)`;
1112
+ wat = block.replacedRaw($reflect);
1113
+ }
1114
+ return wat;
1115
+ }
1116
+
1117
+ const REF_EXTERN_BLOCK_NAME = "ref.extern";
1118
+ const FIXNAME_SELF_PATH = ($name, descriptorKey) => {
1119
+ if ($name.startsWith("$self") === false) {
1120
+ $name = `$self.${$name.substring(1)}`;
1121
+ }
1122
+ if ($name.endsWith(">") === false) {
1123
+ if (descriptorKey === "value") {
1124
+ $name = `${$name}<ext>`;
1125
+ }
1126
+ }
1127
+ return $name;
1128
+ };
1129
+ function REF_EXTERN (wat, WAT4WASM) {
1130
+ const externs = new Map();
1131
+ const getters = new Map();
1132
+ const maskSet = new helpers.MaskSet(wat);
1133
+ while (maskSet.hasBlock(REF_EXTERN_BLOCK_NAME)) {
1134
+ const block = maskSet.lastBlockOf(REF_EXTERN_BLOCK_NAME);
1135
+ const $name = FIXNAME_SELF_PATH(block.$name, block.descriptorKey);
1136
+ if (externs.has($name) === false) {
1137
+ externs.set($name, block.descriptorKey);
1138
+ }
1139
+ maskSet.mask(block);
1140
+ }
1141
+ wat = maskSet.restore();
1142
+ let oninit = String();
1143
+ externs.forEach((dKey, $name) => {
1144
+ const pathWalker = PATH_WALKER_CODE($name);
1145
+ const growExtern = WAT4WASM_GROW_EXTERN_TABLE(wat);
1146
+ const __getter__ = growExtern.getter.concat(` ;; ${$name}\n`);
1147
+ const __setter__ = growExtern.generateSetter(pathWalker);
1148
+ wat = growExtern.modifiedRaw;
1149
+ oninit = `${oninit}\n\n(block ${$name}\n${__setter__})\n`;
1150
+ getters.set($name, __getter__);
1151
+ });
1152
+ while (helpers.hasBlock(wat, REF_EXTERN_BLOCK_NAME)) {
1153
+ const block = helpers.lastBlockOf(wat, REF_EXTERN_BLOCK_NAME);
1154
+ const $name = FIXNAME_SELF_PATH(block.$name, block.descriptorKey);
1155
+ wat = block.replacedRaw(getters.get($name));
1156
+ }
1157
+ if (oninit.trim()) {
1158
+ wat = APPEND_ON_TEXT_READY(wat, oninit);
1159
+ }
1160
+ return wat;
1161
+ }
1162
+
1163
+ const REF_FUNC_BLOCK_NAME = "ref.func";
1164
+ function REF_FUNC (wat, WAT4WASM) {
1165
+ const maskSetElem = new helpers.MaskSet(wat);
1166
+ const elemSegments = new Array();
1167
+ const needReference = new Set();
1168
+ while (maskSetElem.hasBlock("elem")) {
1169
+ const block = maskSetElem.lastBlockOf("elem");
1170
+ const $name = block.$name;
1171
+ if (WAT4WASM.WAT4WASM_$NAME !== $name) {
1172
+ elemSegments.push(block.toString());
1173
+ }
1174
+ maskSetElem.mask(block);
1175
+ }
1176
+ const maskSetRef = new helpers.MaskSet(wat);
1177
+ while (maskSetRef.hasBlock(REF_FUNC_BLOCK_NAME)) {
1178
+ const block = maskSetRef.lastBlockOf(REF_FUNC_BLOCK_NAME);
1179
+ const $name = block.$name
1180
+ if (WAT4WASM.WAT4WASM_$NAME !== $name) {
1181
+ if (elemSegments.some(seg => seg.includes($name)) === false) {
1182
+ if (needReference.has($name) === false) {
1183
+ needReference.add($name);
1184
+ }
1185
+ }
1186
+ }
1187
+ maskSetRef.mask(block);
1188
+ }
1189
+ wat = maskSetRef.restore();
1190
+ needReference.forEach($name => {
1191
+ wat = WAT4WASM_REFERENCE_FUNC_ELEMENT(wat, $name);
1192
+ });
1193
+ return wat;
1194
+ }
1195
+
1196
+
1197
+ function REPLACE_ALL (wat) {
1198
+ return wat
1199
+ .replaceAll("(self)", "(global.get $self)")
1200
+ .replaceAll("(null)", "(ref.null extern)")
1201
+ .replaceAll("(func)", "(ref.null func)")
1202
+ .replaceAll("(this)", "(local.get 0)")
1203
+ .replaceAll("(array)", "(call $self.Array<>ext)")
1204
+ .replaceAll("(NaN)", "(ref.extern $self.NaN<ext>)")
1205
+ .replaceAll("(nan)", "(f32.const nan)")
1206
+ .replaceAll("(true)", "(i32.const 1)")
1207
+ .replaceAll("(false)", "(i32.const 0)")
1208
+ .replaceAll("(undefined)", "(ref.extern $self.undefined<ext>)")
1209
+ .replaceAll("(object)", "(call $self.Object<>ext)")
1210
+ .replaceAll("(string)", "(call $self.String<>ext)")
1211
+ .replaceAll("(console $", "(call $self.console.")
1212
+ .replaceAll("(reflect $", "(call $self.Reflect.")
1213
+ .replaceAll("(bigint $", "(call $self.BigInt.")
1214
+ .replaceAll("(number $", "(call $self.Number.")
1215
+ .replaceAll("(math $", "(call $self.Math.")
1216
+ .replaceAll("(string $", "(call $self.String.")
1217
+ .replaceAll("(object $", "(call $self.Object.")
1218
+ .replaceAll("(array $", "(call $self.Array.")
1219
+ .replaceAll("(grow $", "(table.grow $")
1220
+ .replaceAll("(url $", "(call $self.URL.")
1221
+ .replaceAll("(self $", "(ref.extern $self.")
1222
+ .replaceAll(" mut ext)", " (mut externref) (null))")
1223
+ .replaceAll(" mut fun)", " (mut funcref) (func))")
1224
+ .replaceAll(" mut vec)", " (mut v128) (v128.const i32x4 0 0 0 0))")
1225
+ .replaceAll(" fun)", " funcref)")
1226
+ .replaceAll(" ext)", " externref)")
1227
+ .replaceAll(" != null)", ") (ref.is_null) (i32.eqz)")
1228
+ .replaceAll(" == null)", ") (ref.is_null)")
1229
+ .replaceAll(" == 0)", ") (i32.eqz)")
1230
+ .replaceAll(" != 0)", ") (i32.const 0) (i32.ne)")
1231
+ .replaceAll(" == 1)", ") (i32.const 1) (i32.eq)")
1232
+ .replaceAll(" != 1)", ") (i32.const 1) (i32.ne)")
1233
+ .replaceAll(" == true)", ") (i32.const 1) (i32.eq)")
1234
+ .replaceAll(" == false)", ") (i32.eqz)")
1235
+ .replaceAll(" == nan)", ") (f32.const nan) (f32.eq)")
1236
+ .replaceAll(" != nan)", ") (f32.const nan) (f32.ne)")
1237
+ .replaceAll(" != NaN)", ") (NaN) (object $is<ext.ext>i32) (i32.eqz)")
1238
+ .replaceAll(" == NaN)", ") (NaN) (object $is<ext.ext>i32)")
1239
+ .replaceAll(" != undefined)", ") (undefined) (object $is<ext.ext>i32) (i32.eqz)")
1240
+ .replaceAll(" == undefined)", ") (undefined) (object $is<ext.ext>i32)")
1241
+ .replaceAll(/ mut\s+(i32|f32|i64|f64)\)/g, ` (mut $1) ($1.const 0))`)
1242
+ .replaceAll(/\<[A-Z](.[^>]*)\>/g, "externref")
1243
+ .replaceAll(/\(l(get|set|tee)(\s)/g, `(local.$1$2`)
1244
+ .replaceAll(/\(g(get|set)(\s)/g, `(global.$1$2`)
1245
+ .replaceAll(/\(t(get|set)(\s)/g, `(table.$1$2`)
1246
+ .replaceAll(/(i32|f32|i64|f64|fun|ext)\((\+|\-|)\s*([0-9\.]+)\)/g, `($1.const $2$3)`)
1247
+ .replaceAll(/\((.*)\.(set|tee)\s+([\+|\-])+\s+(\$.*)\s*\)/g, "($1.$2 $4 (i32.add ($1.get $4) (i32.const $31)))")
1248
+ .replaceAll(/\(apply(?:\.*)(i32|f32|i64|f64|fun|ext|)(\s*)/g, `(call $self.Reflect.apply<ext.ext.ext>$1 $2`)
1249
+ .replaceAll(/\(main(\s+)(\$.[^\s]*)(\s)/g, `(start$1$2)\n\n(func$1$2$3`)
1250
+ ;
1251
+ }
1252
+
1253
+ const START_BLOCK_NAME = "start";
1254
+ function START (wat, WAT4WASM) {
1255
+ let startCalls = [];
1256
+ let removedWat = wat;
1257
+ while (helpers.hasBlock(removedWat, START_BLOCK_NAME)) {
1258
+ let block = helpers.lastBlockOf(removedWat, START_BLOCK_NAME);
1259
+ removedWat = block.removedRaw();
1260
+ if (block.includes(WAT4WASM.WAT4WASM_$NAME) === false) {
1261
+ startCalls.push(block);
1262
+ }
1263
+ }
1264
+ if (startCalls.length > 0) {
1265
+ wat = removedWat;
1266
+ let $wat4func = helpers.lastBlockOf(wat, "func", { $name: WAT4WASM.WAT4WASM_$NAME });
1267
+ let funcblock = $wat4func.toString();
1268
+ const appends = startCalls.filter(start => {
1269
+ let $name = `${start.$name}`;
1270
+ let $call = `(call ${$name})`;
1271
+ if (helpers.hasBlock(funcblock, "call", { $name }) === false) {
1272
+ funcblock = helpers.append(funcblock, $call);
1273
+ return true;
1274
+ }
1275
+ });
1276
+ if (appends.length) {
1277
+ wat = $wat4func.replacedRaw(funcblock);
1278
+ }
1279
+ wat = helpers.append(wat, WAT4WASM.WAT4WASM_START);
1280
+ }
1281
+ return wat;
1282
+ }
1283
+
1284
+ const STRING_BLOCK_NAME = "string";
1285
+ function STRING (wat) {
1286
+ while (helpers.hasBlock(wat, STRING_BLOCK_NAME)) {
1287
+ let oldBlock = helpers.lastBlockOf(wat, STRING_BLOCK_NAME);
1288
+ const string = helpers.findQuotedText(oldBlock);
1289
+ const ccodes = helpers.encodeString(string);
1290
+ if (ccodes.length === 0) {
1291
+ wat = oldBlock.replacedRaw(`
1292
+ (reflect $apply<ext.ext.ext>ext
1293
+ (global.get $self.String.fromCharCode)
1294
+ (self)
1295
+ (self)
1296
+ )
1297
+ `);
1298
+ }
1299
+ else if (ccodes.length === 1) {
1300
+ wat = oldBlock.replacedRaw(`
1301
+ (reflect $apply<ext.ext.ext>ext
1302
+ (global.get $self.String.fromCharCode)
1303
+ (self)
1304
+ (array $of<i32>ext (i32.const ${string.charCodeAt(0)})) ;; "${string}"
1305
+ )
1306
+ `);
1307
+ }
1308
+ else {
1309
+ wat = oldBlock.replacedRaw(`
1310
+ (block (; "${helpers.abstract(string)}" ;)
1311
+ (result externref)
1312
+ (global.set $wat4wasm (call $self.Array<>ext))
1313
+
1314
+ ${ccodes
1315
+ .map((c, i) => `(global.get $wat4wasm) (i32.const ${i}) (i32.const ${c})`)
1316
+ .map((args) => `(call $self.Reflect.set<ext.i32.i32> ${args})`)
1317
+ .join("\n")}
1318
+
1319
+ (call $self.Reflect.apply<ext.ext.ext>ext
1320
+ (global.get $self.String.fromCharCode)
1321
+ (ref.null extern)
1322
+ (global.get $wat4wasm)
1323
+ )
1324
+ ;; stacked
1325
+
1326
+ (global.set $wat4wasm (null))
1327
+ ;; cleared
1328
+ )
1329
+ `);
1330
+ }
1331
+ }
1332
+ return wat;
1333
+ }
1334
+
1335
+ const TEXT_BLOCK_NAME = "text";
1336
+ const TEXT_ONINIT_BLOCK = (offset, length, setter) => String(
1337
+ `
1338
+ (block $decodeText/${offset}:${length}
1339
+ (local.set $viewAt (i32.const 0))
1340
+ (local.set $offset (i32.const ${offset}))
1341
+ (local.set $length (i32.const ${length}))
1342
+ (local.set $arguments (call $self.Array<>ext))
1343
+
1344
+ (call $self.Reflect.set<ext.i32.i32>
1345
+ (local.get $arguments) (i32.const 0) (local.get $length)
1346
+ )
1347
+ (local.set $arrayBufferView
1348
+ (call $self.Reflect.construct<ext.ext>ext
1349
+ (local.get $Uint8Array)
1350
+ (local.get $arguments)
1351
+ )
1352
+ )
1353
+ (loop $length--
1354
+ (if (local.get $length)
1355
+ (then
1356
+ (memory.init $wat4wasm
1357
+ (i32.const 0)
1358
+ (local.get $offset)
1359
+ (i32.const 1)
1360
+ )
1361
+ (call $self.Reflect.set<ext.i32.i32>
1362
+ (local.get $arrayBufferView)
1363
+ (local.get $viewAt)
1364
+ (i32.load8_u (i32.const 0))
1365
+ )
1366
+ (local.set $viewAt (i32.add (local.get $viewAt) (i32.const 1)))
1367
+ (local.set $offset (i32.add (local.get $offset) (i32.const 1)))
1368
+ (local.set $length (i32.sub (local.get $length) (i32.const 1)))
1369
+ (br $length--)
1370
+ )
1371
+ )
1372
+ )
1373
+ (local.set $arguments (call $self.Array<>ext))
1374
+ (call $self.Reflect.set<ext.i32.ext>
1375
+ (local.get $arguments)
1376
+ (i32.const 0)
1377
+ (local.get $arrayBufferView)
1378
+ )
1379
+ ${setter}
1380
+ )
1381
+ `).trim();
1382
+ function TEXT (wat, WAT4WASM) {
1383
+ const maskSet = new helpers.MaskSet(wat);
1384
+ const textBlocks = new Array();
1385
+ while (maskSet.hasBlock(TEXT_BLOCK_NAME)) {
1386
+ let block = maskSet.lastBlockOf(TEXT_BLOCK_NAME);
1387
+ maskSet.mask(block);
1388
+ textBlocks.push(block);
1389
+ }
1390
+ wat = maskSet.restore();
1391
+ textBlocks.forEach(block => {
1392
+ const text = helpers.findQuotedText(block);
1393
+ const view = helpers.encodeText(text);
1394
+ const dataRequest = WAT4WASM_ALLOC_DATA_BUFFER(wat, view.buffer);
1395
+ block.dataOffset = dataRequest.offset;
1396
+ block.viewLength = view.length;
1397
+ block.strPreview = helpers.abstract(text);
1398
+ wat = dataRequest.modifiedRaw;
1399
+ });
1400
+ textBlocks.forEach(block => {
1401
+ const growRequest = WAT4WASM_GROW_EXTERN_TABLE(wat);
1402
+ block.tableGetter = growRequest.getter.concat(`;; ${block.strPreview} \n`);
1403
+ block.tableSetter = growRequest.generateSetter(`
1404
+ (call $self.Reflect.apply<ext.ext.ext>ext
1405
+ (local.get $textDecoder.decode)
1406
+ (local.get $textDecoder)
1407
+ (local.get $arguments) ;; ${block.strPreview}
1408
+ )`).trim();
1409
+ wat = growRequest.modifiedRaw;
1410
+ });
1411
+ textBlocks.forEach(block => {
1412
+ wat = wat.replaceAll(block.toString(), block.tableGetter)
1413
+ });
1414
+ const oninit = textBlocks.map(block =>
1415
+ TEXT_ONINIT_BLOCK(
1416
+ block.dataOffset,
1417
+ block.viewLength, block.tableSetter
1418
+ )
1419
+ ).join("\n");
1420
+ wat = APPEND_ON_INIT(wat, oninit);
1421
+ return wat;
1422
+ }
1423
+
1424
+ let WAT4WASM_$NAME = `$wat4wasm`;
1425
+ let WAT4WASM_FUNC = String(`
1426
+ (func $wat4wasm
1427
+ (local $textDecoder externref)
1428
+ (local $textDecoder.decode externref)
1429
+ (local $Uint8Array externref)
1430
+ (local $arguments externref)
1431
+ (local $arrayBufferView externref)
1432
+ (local $viewAt i32)
1433
+ (local $offset i32)
1434
+ (local $length i32)
1435
+ (block $prepare
1436
+ (local.set $textDecoder
1437
+ (call $self.Reflect.construct<ext.ext>ext
1438
+ (call $self.Reflect.get<ext.ext>ext
1439
+ (self)
1440
+ (string "TextDecoder")
1441
+ )
1442
+ (self)
1443
+ )
1444
+ )
1445
+ (local.set $textDecoder.decode
1446
+ (call $self.Reflect.get<ext.ext>ext
1447
+ (local.get $textDecoder)
1448
+ (string "decode")
1449
+ )
1450
+ )
1451
+ (local.set $Uint8Array
1452
+ (call $self.Reflect.get<ext.ext>ext
1453
+ (self)
1454
+ (string "Uint8Array")
1455
+ )
1456
+ )
1457
+ )
1458
+ ;;secure zero heap for memory.init
1459
+ (i32.const 0)
1460
+ (i32.load (i32.const 0))
1461
+ ;; offset and value stacked now
1462
+ (block $oninit)
1463
+ (block $ontextready)
1464
+ (block $onexternready)
1465
+
1466
+ ;; restore zero heap value
1467
+ (i32.store (; stack stack ;))
1468
+ (nop)
1469
+ )
1470
+ `);
1471
+ let WAT4WASM_GLOBAL = String(
1472
+ `(global $wat4wasm (mut externref) (ref.null extern))`
1473
+ );
1474
+ let WAT4WASM_TABLE = (size = 1) => String(
1475
+ `(table $wat4wasm ${size} externref)`
1476
+ );
1477
+ let WAT4WASM_DATA = (buff = "0000") => String(
1478
+ `(data $wat4wasm "${Buffer.from(buff).toString("hex").replaceAll(/(..)/g, `\\$1`)}")`
1479
+ );
1480
+ let WAT4WASM_ELEM = String(
1481
+ `(elem $wat4wasm declare func)`
1482
+ );
1483
+ let WAT4WASM_MEMORY = String(
1484
+ `(memory $wat4wasm 1)`
1485
+ );
1486
+ let WAT4WASM_START = String(
1487
+ `(start $wat4wasm)`
1488
+ );
1489
+ const WAT4WASM_BLOCKS = {
1490
+ global: WAT4WASM_GLOBAL,
1491
+ table: WAT4WASM_TABLE(1),
1492
+ elem: WAT4WASM_ELEM,
1493
+ func: WAT4WASM_FUNC,
1494
+ data: WAT4WASM_DATA(),
1495
+ };
1496
+ function FUNC_WAT4WASM(wat) {
1497
+ return helpers.lastBlockOf(wat, "func", { $name: WAT4WASM_$NAME });
1498
+ }
1499
+ function ELEM_WAT4WASM(wat) {
1500
+ const block = helpers.lastBlockOf(wat, "elem", { $name: WAT4WASM_$NAME });
1501
+ return block && Object.assign(block, {
1502
+ isInitial: helpers.generateId(block) === helpers.generateId(WAT4WASM_ELEM)
1503
+ });
1504
+ }
1505
+ function MEMORY_WAT4WASM(wat) {
1506
+ return helpers.lastBlockOf(wat, "memory", { $name: WAT4WASM_$NAME });
1507
+ }
1508
+ function GLOBAL_WAT4WASM(wat) {
1509
+ return helpers.lastBlockOf(wat, "global", { $name: WAT4WASM_$NAME });
1510
+ }
1511
+ function START_WAT4WASM(wat) {
1512
+ return helpers.lastBlockOf(wat, "start", { $name: WAT4WASM_$NAME });
1513
+ }
1514
+ function FUNC_WAT4WASM_NOBLOCKS(wat) {
1515
+ const maskSet = new helpers.MaskSet(FUNC_WAT4WASM(wat));
1516
+ maskSet.remove(maskSet.lastBlockOf("block", { name: "onexternready" }));
1517
+ maskSet.remove(maskSet.lastBlockOf("block", { name: "ontextready" }));
1518
+ maskSet.remove(maskSet.lastBlockOf("block", { name: "oninit" }));
1519
+ maskSet.remove(maskSet.lastBlockOf("block", { name: "prepare" }));
1520
+ return maskSet.restore();
1521
+ }
1522
+ function FUNC_WAT4WASM_BLOCK_ONINIT(wat) {
1523
+ const wat4func = FUNC_WAT4WASM(wat);
1524
+ const blockoninit = helpers.lastBlockOf(wat4func, "block", { name: "oninit" });
1525
+ const $blockoninit = helpers.parseBlockAt(wat, wat4func.begin + blockoninit.begin);
1526
+ return $blockoninit;
1527
+ }
1528
+ function FUNC_WAT4WASM_BLOCK_ONTEXTREADY(wat) {
1529
+ const wat4func = FUNC_WAT4WASM(wat);
1530
+ const ontextready = helpers.lastBlockOf(wat4func, "block", { name: "ontextready" });
1531
+ const $ontextready = helpers.parseBlockAt(wat, wat4func.begin + ontextready.begin);
1532
+ return $ontextready;
1533
+ }
1534
+ function FUNC_WAT4WASM_BLOCK_ONEXTERNREADY(wat) {
1535
+ const wat4func = FUNC_WAT4WASM(wat);
1536
+ const onexternready = helpers.lastBlockOf(wat4func, "block", { name: "onexternready" });
1537
+ const $onexternready = helpers.parseBlockAt(wat, wat4func.begin + onexternready.begin);
1538
+ return $onexternready;
1539
+ }
1540
+ function APPEND_ON_INIT(wat, block) {
1541
+ const oninitblock = FUNC_WAT4WASM_BLOCK_ONINIT(wat);
1542
+ if (oninitblock.includes(block.toString())) {
1543
+ return wat;
1544
+ }
1545
+ wat = oninitblock.replacedRaw(
1546
+ helpers.append(oninitblock, block)
1547
+ );
1548
+ return wat;
1549
+ }
1550
+ function APPEND_ON_TEXT_READY(wat, block) {
1551
+ const ontextready = FUNC_WAT4WASM_BLOCK_ONTEXTREADY(wat);
1552
+ if (ontextready.hasBlock("block",
1553
+ { $name: helpers.firstBlockOf(block, "block")?.$name }
1554
+ ) === false && !ontextready.includes(block.trim())) {
1555
+ return ontextready.replacedRaw(
1556
+ helpers.append(ontextready, block)
1557
+ );
1558
+ }
1559
+ return wat;
1560
+ }
1561
+ function APPEND_ON_EXTERN_READY(wat, block) {
1562
+ const onexternready = FUNC_WAT4WASM_BLOCK_ONEXTERNREADY(wat);
1563
+ if (onexternready.includes(block.toString())) {
1564
+ return wat;
1565
+ }
1566
+ wat = onexternready.replacedRaw(
1567
+ helpers.append(onexternready, block)
1568
+ );
1569
+ return wat;
1570
+ }
1571
+ function TABLE_WAT4WASM(wat) {
1572
+ const wat4table = helpers.lastBlockOf(wat, "table", { $name: WAT4WASM_$NAME });
1573
+ const lastIndex = parseInt(wat4table.toString().match(/\s(\d+)/).pop());
1574
+ return wat4table && Object.assign(wat4table, {
1575
+ lastIndex, isInitial: lastIndex === 1
1576
+ })
1577
+ }
1578
+ function WAT4WASM_GROW_EXTERN_TABLE(wat) {
1579
+ const wat4table = TABLE_WAT4WASM(wat);
1580
+ const lastIndex = wat4table.lastIndex;
1581
+ return {
1582
+ index: lastIndex,
1583
+ getter: `(table.get ${WAT4WASM_$NAME} (i32.const ${lastIndex}))`,
1584
+ generateSetter: value => `(table.set ${WAT4WASM_$NAME} (i32.const ${lastIndex}) ${value.toString()})`,
1585
+ modifiedRaw: wat4table.replacedRaw(WAT4WASM_TABLE(lastIndex + 1))
1586
+ };
1587
+ }
1588
+ function WAT4WASM_REFERENCE_FUNC_ELEMENT(wat, $name) {
1589
+ const $wat4elem = ELEM_WAT4WASM(wat);
1590
+ const _wat4elem = $wat4elem.toString();
1591
+ if (_wat4elem.includes($name) === false) {
1592
+ wat = $wat4elem.replacedRaw(_wat4elem
1593
+ .substring(0, _wat4elem.lastIndexOf(")"))
1594
+ .concat(` ${$name}`)
1595
+ .concat(")")
1596
+ );
1597
+ }
1598
+ return wat;
1599
+ }
1600
+ function DATA_WAT4WASM(wat) {
1601
+ const block = helpers.lastBlockOf(wat, "data", { $name: WAT4WASM_$NAME });
1602
+ const strhex = helpers.findQuotedText(block);
1603
+ const buffer = Buffer.from(strhex.replaceAll(/[^a-f0-9A-F]/g, ""), "hex");
1604
+ return Object.assign(block, {
1605
+ buffer: buffer,
1606
+ offset: buffer.byteLength,
1607
+ isInitial: buffer.byteLength === 4
1608
+ });
1609
+ }
1610
+ function WAT4WASM_ALLOC_DATA_BUFFER(wat, buffer) {
1611
+ const new_data = Buffer.from(buffer);
1612
+ const all_data = DATA_WAT4WASM(wat);
1613
+ let offset = all_data.buffer.indexOf(new_data);
1614
+ let modifiedRaw = wat;
1615
+ if (offset < 1) {
1616
+ offset = all_data.offset;
1617
+ all_data.buffer.writeUint32LE(offset + new_data.byteLength);
1618
+ modifiedRaw = all_data.replacedRaw(WAT4WASM_DATA(Buffer.concat([all_data.buffer, new_data])));
1619
+ }
1620
+ return {
1621
+ offset,
1622
+ modifiedRaw,
1623
+ isRawModified: modifiedRaw.toString() !== wat.toString()
1624
+ };
1625
+ }
1626
+ function W4W (wat) {
1627
+ let i = 31, raw;
1628
+ if (helpers.hasBlock(wat, "memory") === false) {
1629
+ WAT4WASM_BLOCKS.memory = WAT4WASM_MEMORY;
1630
+ }
1631
+ for (const BLOCK_NAME in WAT4WASM_BLOCKS) {
1632
+ if (wat.includes(`(${BLOCK_NAME} ${WAT4WASM_$NAME}`)) {
1633
+ continue;
1634
+ }
1635
+ if (i === 31) console.log("")
1636
+ console.log(`🦋 appending element --> \x1b[${i++}m(${BLOCK_NAME} ${WAT4WASM_$NAME}) ...)\x1b[0m`);
1637
+ raw = WAT4WASM_BLOCKS[BLOCK_NAME].replaceAll("$wat4wasm", WAT4WASM_$NAME);
1638
+ wat = helpers.append(wat, raw);
1639
+ }
1640
+ if (i !== 31) console.log("")
1641
+ return wat;
1642
+ }function FUNC_WAT4WASM_START_CALLS(wat) {
1643
+ let $call;
1644
+ const calls = [];
1645
+ const maskSet = new helpers.MaskSet(
1646
+ FUNC_WAT4WASM_NOBLOCKS(wat)
1647
+ );
1648
+ while ($call = maskSet.lastBlockOf("call")) {
1649
+ if (calls.includes($call.$name) === false) {
1650
+ calls.push($call.$name);
1651
+ }
1652
+ maskSet.mask($call);
1653
+ }
1654
+ return calls;
1655
+ }
1656
+ function clean (wat) {
1657
+ let block;
1658
+ block = FUNC_WAT4WASM_BLOCK_ONEXTERNREADY(wat);
1659
+ if (!block.hasAnyBlock) { wat = block.removedRaw(); }
1660
+ block = FUNC_WAT4WASM_BLOCK_ONTEXTREADY(wat);
1661
+ if (!block.hasAnyBlock) { wat = block.removedRaw(); }
1662
+ block = FUNC_WAT4WASM_BLOCK_ONINIT(wat);
1663
+ if (!block.hasAnyBlock) {
1664
+ wat = block.removedRaw();
1665
+ let $starts = FUNC_WAT4WASM_START_CALLS(wat);
1666
+ if ($starts.length === 1) {
1667
+ wat = wat.replace(`(start $wat4wasm)`, `(start ${$starts.pop()})`);
1668
+ }
1669
+ let $func = FUNC_WAT4WASM(wat);
1670
+ if ($func) {
1671
+ wat = $func.removedRaw();
1672
+ }
1673
+ }
1674
+ block = ELEM_WAT4WASM(wat);
1675
+ if (block.isInitial) { wat = block.removedRaw(); }
1676
+ block = TABLE_WAT4WASM(wat);
1677
+ if (block.isInitial) { wat = block.removedRaw(); }
1678
+ block = DATA_WAT4WASM(wat);
1679
+ if (block.isInitial) { wat = block.removedRaw(); }
1680
+ if (false === helpers.hasBlock(wat, "global.get", { $name: WAT4WASM_$NAME }) &&
1681
+ false === helpers.hasBlock(wat, "global.set", { $name: WAT4WASM_$NAME })) {
1682
+ wat = GLOBAL_WAT4WASM(wat).removedRaw();
1683
+ }
1684
+ block = MEMORY_WAT4WASM(wat);
1685
+ if (block && (helpers.containsMemoryOperation(wat) === false)) {
1686
+ wat = block.removedRaw();
1687
+ }
1688
+ const maskSet = new helpers.MaskSet(wat);
1689
+ const imports = new Array();
1690
+ while (block = maskSet.lastBlockOf("import")) {
1691
+ block.$name = helpers.parseFirstBlock(block).$name;
1692
+ imports.push(block);
1693
+ maskSet.remove(block);
1694
+ }
1695
+ wat = maskSet.restore();
1696
+ wat = helpers.prepend(wat,
1697
+ imports
1698
+ .filter(b => new RegExp(`\\${b.$name}\(\\s|\\)\)`).test(wat))
1699
+ .sort((a, b) => helpers.generateId(a) - helpers.generateId(b))
1700
+ .map(b => b.toString())
1701
+ .join("\n")
1702
+ );
1703
+ return wat;
1704
+ }
1705
+
1706
+
1707
+ async function processCLI(compileCallback, PROCESS = process) {
1708
+ console.log("\x1b[31m")
1709
+ // Argument Parsing Logic
1710
+ const args = PROCESS.argv.slice(2);
1711
+ const config = {
1712
+ inputFile: null,
1713
+ outputFile: null,
1714
+ wat2wasmPath: null,
1715
+ unlinkWat4FileIfCompilationSuccess: true,
1716
+ windowtagName: "",
1717
+ defaultTagName: "🦋",
1718
+ keepChromeGlobal: false,
1719
+ keepHTMLDocument: false,
1720
+ keepWindowTagName: false,
1721
+ keepWindowObjects: false,
1722
+ keepFaviconRequest: false,
1723
+ generateWASMFromHEXString: true,
1724
+ generateWASMFromNumberArray: false,
1725
+ consoleLogInstance: false,
1726
+ faviconLinkHref: "data:null",
1727
+ printOnly: false,
1728
+ passthroughArgs: [],
1729
+ };
1730
+ for (let i = 0; i < args.length; i++) {
1731
+ const arg = args[i];
1732
+ if (arg.startsWith("--output=")) {
1733
+ config.outputFile = arg.split("=")[1];
1734
+ } else if (arg.startsWith("--input=")) {
1735
+ config.inputFile = arg.split("=")[1];
1736
+ } else if (arg.startsWith("--wat2wasm=")) {
1737
+ config.wat2wasmPath = arg.split("=")[1];
1738
+ } else if (arg.startsWith("--tag=")) {
1739
+ config.windowtagName = arg.split("=")[1];
1740
+ } else if (arg === "--print-only") {
1741
+ config.printOnly = true;
1742
+ } else if (arg === "--wasm-from-numbers-array") {
1743
+ config.generateWASMFromNumberArray = true;
1744
+ config.generateWASMFromHEXString = false;
1745
+ } else if (arg === "--wasm-from-hex-string") {
1746
+ config.generateWASMFromHEXString = true;
1747
+ config.generateWASMFromNumberArray = false;
1748
+ } else if (arg === "--untouched-window") {
1749
+ config.keepHTMLDocument = true;
1750
+ config.keepChromeGlobal = true;
1751
+ config.keepWindowObjects = true;
1752
+ config.keepFaviconRequest = true;
1753
+ config.windowtagName &&= "";
1754
+ } else if (arg === "--log-instance") {
1755
+ config.consoleLogInstance = true;
1756
+ } else if (arg === "--keep-window") {
1757
+ config.keepWindowObjects = true;
1758
+ } else if (arg === "--keep-chrome-global") {
1759
+ config.keepChromeGlobal = true;
1760
+ } else if (arg === "--keep-document") {
1761
+ config.keepHTMLDocument = true;
1762
+ } else if (arg === "--keep-favicon") {
1763
+ config.keepFaviconRequest = true;
1764
+ } else if (arg === "--no-unlink") {
1765
+ config.unlinkWat4FileIfCompilationSuccess = false;
1766
+ } else if (arg.startsWith("-")) {
1767
+ // Collect flags to pass to wat2wasm
1768
+ config.passthroughArgs.push(arg);
1769
+ } else {
1770
+ if (!config.inputFile) {
1771
+ config.inputFile = arg;
1772
+ } else {
1773
+ console.warn(`Warning: Ignoring extra argument '${arg}'`);
1774
+ }
1775
+ }
1776
+ }
1777
+ if (!config.inputFile) {
1778
+ console.error("Error: Input file required! Usage: wat4wasm <input.wat> [options]");
1779
+ PROCESS.exit(1);
1780
+ }
1781
+ const isJSTarget = config.outputFile?.endsWith(".js");
1782
+ const isHTMLTarget = config.outputFile?.endsWith(".html");
1783
+ if (isHTMLTarget || isJSTarget) {
1784
+ config.outputFile = config.outputFile.substring(
1785
+ 0, config.outputFile.lastIndexOf(".")
1786
+ ).concat(".wat");
1787
+ }
1788
+ // Default output file if not specified (and not printing only)
1789
+ if (!config.outputFile && !config.printOnly) {
1790
+ config.outputFile = config.inputFile.replace(/\.wat$/, "-output.wat");
1791
+ }
1792
+ try {
1793
+ console.log(`\x1b[0m\x1b[33m🚀 Wat4Wasm: Processing ${config.inputFile}...\x1b[0m`);
1794
+ if (!fs.existsSync(config.inputFile)) {
1795
+ throw new Error(`Input file not found: ${config.inputFile}`);
1796
+ }
1797
+ const rawCode = fs.readFileSync(config.inputFile, "utf8");
1798
+ // 2. Call the provided compile function
1799
+ const compiled = await compileCallback(rawCode);
1800
+ console.log("\x1b[0m")
1801
+ // 3. Handle Output
1802
+ if (config.printOnly) {
1803
+ console.log("\x1b[0m\x1b[33m--- Compiled Output ---");
1804
+ console.log(compiled);
1805
+ console.log("-----------------------\x1b[0m");
1806
+ return;
1807
+ }
1808
+ // Write compiled WAT (or temp WAT for WASM target)
1809
+ let watFile = config.outputFile;
1810
+ let wasmFile = config.outputFile.substring(
1811
+ 0, config.outputFile.lastIndexOf(".")
1812
+ ).concat(".wasm");
1813
+ const isWasmTarget = isJSTarget || isHTMLTarget || config.outputFile.endsWith(".wasm") || config.wat2wasmPath;
1814
+ if (isWasmTarget && config.outputFile.endsWith(".wasm")) {
1815
+ // If target is WASM, write WAT to a temp file
1816
+ watFile = config.outputFile + ".wat";
1817
+ }
1818
+ fs.writeFileSync(watFile, compiled);
1819
+ console.log(`\x1b[0m\x1b[36m✅ WAT Written to: ${watFile}\n`);
1820
+ // Run wat2wasm if requested
1821
+ if (config.wat2wasmPath || isHTMLTarget || isJSTarget) {
1822
+ console.log(`\x1b[0m\x1b[32m🔨 Running wat2wasm...`);
1823
+ // If user explicitly set .wasm output but output file was .wat?
1824
+ // The logic above sets wasmFile = config.outputFile.
1825
+ // If config.outputFile was "out.wat", then we overwrite it with "out.wasm"?
1826
+ // Standard wat2wasm behavior takes -o <file>.
1827
+ // Our config.outputFile came from --output.
1828
+ const cmdArgs = [watFile, ...config.passthroughArgs, "--output", wasmFile];
1829
+ console.log(`\x1b[0m\x1b[32m Command: ${config.wat2wasmPath} ${cmdArgs.join(" ")}\n`);
1830
+ const result = spawnSync(config.wat2wasmPath, cmdArgs, { stdio: "inherit" });
1831
+ if (result.status === 0) {
1832
+ console.log(`\x1b[0m\x1b[35m🎉 WASM Build Successful: ${wasmFile}`);
1833
+ if (config.unlinkWat4FileIfCompilationSuccess && watFile !== wasmFile) {
1834
+ try { fs.unlinkSync(watFile); } catch (e) { }
1835
+ }
1836
+ if (isJSTarget || isHTMLTarget) {
1837
+ const wasmhex = fs.readFileSync(wasmFile, "hex");
1838
+ let extension = "";
1839
+ let filecontent = "";
1840
+ if (isJSTarget) {
1841
+ let wasmGenerator = ``;
1842
+ if (config.generateWASMFromHEXString && !config.generateWASMFromNumberArray) {
1843
+ wasmGenerator = `Uint8Array.from(/${wasmhex}/.toString().match(/[a-f0-9]{2}/g).map(h => parseInt(h, 16)))`;
1844
+ }
1845
+ else if (!config.generateWASMFromHEXString && config.generateWASMFromNumberArray) {
1846
+ wasmGenerator = `Uint8Array.of(${wasmhex.toString().match(/[a-f0-9]{2}/g).map(h => parseInt(h, 16))})`;
1847
+ }
1848
+ extension = "js";
1849
+ filecontent = `
1850
+ const view = ${wasmGenerator};
1851
+ let wasm = view.buffer;
1852
+
1853
+ wasm.module = null;
1854
+ wasm.instances = new Array;
1855
+
1856
+ const compile = async () => WebAssembly.compile(wasm).then(m => {
1857
+ wasm.module = m;
1858
+ return wasm;
1859
+ });
1860
+
1861
+ const instantiate = async () => WebAssembly.instantiate(wasm.module, self).then(i => {
1862
+ wasm.instances.push(i);
1863
+ return i;
1864
+ });
1865
+
1866
+ wasm.spawn = async imports => {
1867
+ if (wasm.module instanceof WebAssembly.Module === false) {
1868
+ Object.assign(self, Object(imports));
1869
+ return compile().then(() => instantiate());
1870
+ }
1871
+ return instantiate();
1872
+ };
1873
+
1874
+ default wasm;
1875
+ `;
1876
+ }
1877
+ else {
1878
+ extension = "html";
1879
+ const thenCalls = [];
1880
+ if (config.consoleLogInstance) {
1881
+ thenCalls.push(`console.warn(wasm)`);
1882
+ }
1883
+ if (!config.keepHTMLDocument) {
1884
+ thenCalls.push(`Array.from(document.children).forEach(i => i.remove())`);
1885
+ }
1886
+ if (!config.keepChromeGlobal && !config.keepWindowObjects) {
1887
+ thenCalls.push(`self.chrome && (self.chrome = self)`)
1888
+ }
1889
+ const windowTag = config.windowtagName || config.defaultTagName;
1890
+ if ((!config.keepWindowTagName && !config.keepWindowObjects) || config.windowtagName) {
1891
+ thenCalls.push(`Reflect.defineProperty(__proto__, Symbol.toStringTag, {value: String.fromCharCode(${windowTag.split("").map(c => c.charCodeAt())}) })`)
1892
+ }
1893
+ if (!config.keepWindowObjects) {
1894
+ thenCalls.push(`Reflect.ownKeys(self).forEach(Reflect.deleteProperty.bind(Reflect, self))`);
1895
+ }
1896
+ const thenCall = thenCalls.length && String(`.then(wasm => [${thenCalls}])`) || String();
1897
+ let wasmGenerator = ``;
1898
+ if (config.generateWASMFromHEXString && !config.generateWASMFromNumberArray) {
1899
+ wasmGenerator = `Uint8Array.from(/${wasmhex}/.toString().match(/[a-f0-9]{2}/g).map(h => parseInt(h, 16)))`;
1900
+ }
1901
+ else if (!config.generateWASMFromHEXString && config.generateWASMFromNumberArray) {
1902
+ wasmGenerator = `Uint8Array.of(${wasmhex.toString().match(/[a-f0-9]{2}/g).map(h => parseInt(h, 16))})`;
1903
+ }
1904
+ const onload = [
1905
+ `WebAssembly.instantiate(${wasmGenerator}, self)`.concat(thenCall)
1906
+ ];
1907
+ if (!config.keepFaviconRequest && config.faviconLinkHref) {
1908
+ const faviconHTML = `<link rel=icon href=${config.faviconLinkHref}>`;
1909
+ onload.push(`Reflect.set(document.head, String.fromCharCode(${'innerHTML'.split("").map(c => c.charCodeAt())}), String.fromCharCode(${faviconHTML.split("").map(c => c.charCodeAt())}))`)
1910
+ }
1911
+ filecontent = `<body onload="[${onload}]"></body>`;
1912
+ }
1913
+ const filename = wasmFile
1914
+ .substring(0, wasmFile.lastIndexOf("."))
1915
+ .concat(".")
1916
+ .concat(extension);
1917
+ fs.writeFileSync(filename, filecontent);
1918
+ if (config.unlinkWat4FileIfCompilationSuccess) {
1919
+ try { fs.unlinkSync(wasmFile); } catch (e) { }
1920
+ }
1921
+ }
1922
+ } else {
1923
+ console.error(`💥 wat2wasm failed with exit code ${result.status}`);
1924
+ PROCESS.exit(result.status);
1925
+ }
1926
+ }
1927
+ } catch (err) {
1928
+ console.error(`\n💥 ERROR:`, err);
1929
+ PROCESS.exit(1);
1930
+ } finally {
1931
+ console.log("\x1b[0m")
1932
+ }
1933
+ }
1934
+
1935
+
1936
+ const processors = [
1937
+ W4W,
1938
+ TEXT,
1939
+ ASYNC,
1940
+ DATA,
1941
+ IMPORT,
1942
+ INCLUDE,
1943
+ NEW,
1944
+ REF_EXTERN,
1945
+ REF_FUNC,
1946
+ START,
1947
+ STRING,
1948
+ REPLACE_ALL,
1949
+ ];
1950
+ processCLI(async wat4 => {
1951
+ let wat2 = wat4, f, i = -1, llen, m = -1, c = 0, ci = 0;
1952
+ while (f = processors[++i]) {
1953
+ wat4 = f(wat2, W4W).toString();
1954
+ const input = wat2;
1955
+ const output = wat4;
1956
+ const inLines = input.split("\n").map(l => l.trim()).filter(Boolean);
1957
+ const outLines = output.split("\n").map(l => l.trim()).filter(Boolean);
1958
+ let startCommon = 0;
1959
+ while (
1960
+ startCommon < inLines.length &&
1961
+ startCommon < outLines.length &&
1962
+ inLines[startCommon] === outLines[startCommon]
1963
+ ) { startCommon++; }
1964
+ let endCommon = 0;
1965
+ while (
1966
+ endCommon < inLines.length - startCommon &&
1967
+ endCommon < outLines.length - startCommon &&
1968
+ inLines[inLines.length - 1 - endCommon] === outLines[outLines.length - 1 - endCommon]
1969
+ ) { endCommon++; }
1970
+ const commonLines = startCommon + endCommon;
1971
+ const removedLines = inLines.length - commonLines;
1972
+ const addedLines = outLines.length - commonLines;
1973
+ const netChange = outLines.length - inLines.length;
1974
+ const stat = [
1975
+ `\x1b[32m+${addedLines}\x1b[0m`.padStart(15, " "),
1976
+ `\x1b[34m-${removedLines}\x1b[0m`.padStart(15, " "),
1977
+ `\x1b[${netChange && 36 || 33}m\u0394\x1b[0m`.padStart(12, " "),
1978
+ `\x1b[${netChange && 36 || 33}m${netChange}\x1b[0m`.padStart(12, " "),
1979
+ ` byte(\u03B4) :`,
1980
+ `\x1b[33m${wat2.length}\x1b[0m`.padStart(14, " "),
1981
+ `-->`,
1982
+ `\x1b[33m${wat4.length}\x1b[0m`,
1983
+ ];
1984
+ if (wat2 !== wat4) {
1985
+ c++;
1986
+ wat2 = wat4;
1987
+ console.log(`👀 ƒ( ${f.name.padEnd(12, " ")} )`.padStart(12, " "), ...stat);
1988
+ }
1989
+ if (!processors[i + 1]) {
1990
+ if (c) { i = -1; c = 0; }
1991
+ else { console.log("☘️ untouched raw \x1b[32m-->\x1b[0m finalizing..") }
1992
+ }
1993
+ }
1994
+ wat2 = clean(wat2);
1995
+ wat2 = wat4beauty(wat2, " ");
1996
+ return wat2;
1997
+ });
1998
+