thyscript 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/thy-node.js +332 -0
- package/bin/thy.js +828 -0
- package/bin/thys.js +25 -0
- package/examples/calculator.thy +274 -0
- package/examples/calculator.thy.js +628 -0
- package/examples/hello.thy +34 -0
- package/examples/login.thy +382 -0
- package/examples/login.thy.js +710 -0
- package/package.json +54 -0
- package/scripts/_class.js +18 -0
- package/scripts/_class2.js +11 -0
- package/scripts/_class3.js +21 -0
- package/scripts/_dstruct.js +20 -0
- package/scripts/_dstruct2.js +24 -0
- package/scripts/_scaffold.js +16 -0
- package/scripts/_scaffold2.js +16 -0
- package/scripts/_scaffold3.js +26 -0
- package/scripts/_tpl_compile.js +13 -0
- package/scripts/_tpl_test.js +9 -0
- package/scripts/_typeof.js +26 -0
- package/scripts/_typeof2.js +25 -0
- package/scripts/patch_genmethod.js +44 -0
- package/scripts/smoke_test.js +167 -0
- package/scripts/smoke_test2.js +151 -0
- package/scripts/test_control_panel.js +58 -0
- package/src/compiler/CodeGenerator.js +1449 -0
- package/src/compiler/JITCompiler.js +325 -0
- package/src/database/DatabaseEngine.js +931 -0
- package/src/errors/ErrorDetector.js +305 -0
- package/src/errors/ErrorMessages.js +497 -0
- package/src/index.js +169 -0
- package/src/lexer/Lexer.js +342 -0
- package/src/lexer/TokenTypes.js +1234 -0
- package/src/memory/MemoryManager.js +283 -0
- package/src/parser/AST.js +606 -0
- package/src/parser/Parser.js +1942 -0
- package/src/runtime/ThyRuntime.js +265 -0
- package/src/runtime/stdlib/ArrayLib.js +207 -0
- package/src/runtime/stdlib/FileLib.js +168 -0
- package/src/runtime/stdlib/MathLib.js +411 -0
- package/src/runtime/stdlib/StringLib.js +387 -0
- package/src/server/ControlPanel.js +1188 -0
- package/src/server/HTMLCSSEngine.js +866 -0
- package/src/server/SecurityEngine.js +714 -0
- package/src/server/ServerEngine.js +738 -0
- package/tests/test1_math.thy +82 -0
- package/tests/test2_oop.thy +133 -0
- package/tests/test3_server.thy +143 -0
- package/tests/test4_functions.thy +159 -0
|
@@ -0,0 +1,1449 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* ThyScript CodeGenerator
|
|
4
|
+
* Translates a ThyScript AST into optimized, runnable Node.js JavaScript.
|
|
5
|
+
*
|
|
6
|
+
* Every compiled file receives:
|
|
7
|
+
* - 'use strict' directive
|
|
8
|
+
* - Full __thy_stdlib object (all built-ins)
|
|
9
|
+
* - All necessary require/import stubs
|
|
10
|
+
* - Optimised code for every ThyScript AST node type
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
class CodeGenerator {
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
this.options = Object.assign({
|
|
16
|
+
optimise: true,
|
|
17
|
+
sourceMap: false,
|
|
18
|
+
minify: false,
|
|
19
|
+
targetES: 2022,
|
|
20
|
+
}, options);
|
|
21
|
+
|
|
22
|
+
/** Indentation depth */
|
|
23
|
+
this._indent = 0;
|
|
24
|
+
/** Unique counter for generated identifiers */
|
|
25
|
+
this._uid = 0;
|
|
26
|
+
/** Whether express app has been declared in this file */
|
|
27
|
+
this._expressAppDeclared = false;
|
|
28
|
+
/** Whether readline interface has been declared */
|
|
29
|
+
this._readlineDeclared = false;
|
|
30
|
+
/** Whether tf (TensorFlow.js) has been declared */
|
|
31
|
+
this._tfDeclared = false;
|
|
32
|
+
/** Whether Matter.js has been declared */
|
|
33
|
+
this._matterDeclared = false;
|
|
34
|
+
/** Collected top-level require statements */
|
|
35
|
+
this._requires = new Set();
|
|
36
|
+
/** Lines of the header section (stdlib + requires) */
|
|
37
|
+
this._headerLines = [];
|
|
38
|
+
/** Lines of the body section (compiled statements) */
|
|
39
|
+
this._bodyLines = [];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ─── helpers ────────────────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
_uid_() { return `__thy_${this._uid++}`; }
|
|
45
|
+
|
|
46
|
+
_tab() { return ' '.repeat(this._indent); }
|
|
47
|
+
|
|
48
|
+
_emit(line) {
|
|
49
|
+
this._bodyLines.push(this._tab() + line);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
_emitRaw(line) {
|
|
53
|
+
this._bodyLines.push(line);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
_push() { this._indent++; }
|
|
57
|
+
_pop() { if (this._indent > 0) this._indent--; }
|
|
58
|
+
|
|
59
|
+
_needRequire(pkg) { this._requires.add(pkg); }
|
|
60
|
+
|
|
61
|
+
_escStr(s) {
|
|
62
|
+
return String(s)
|
|
63
|
+
.replace(/\\/g, '\\\\')
|
|
64
|
+
.replace(/'/g, "\\'")
|
|
65
|
+
.replace(/\n/g, '\\n')
|
|
66
|
+
.replace(/\r/g, '\\r')
|
|
67
|
+
.replace(/\t/g, '\\t');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ─── stdlib header ───────────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
_buildStdlib() {
|
|
73
|
+
return `
|
|
74
|
+
// ═══════════════════════════════════════════════════════════════
|
|
75
|
+
// ThyScript Standard Library – __thy_stdlib
|
|
76
|
+
// Auto-generated by ThyScript CodeGenerator
|
|
77
|
+
// ═══════════════════════════════════════════════════════════════
|
|
78
|
+
const __thy_stdlib = (() => {
|
|
79
|
+
'use strict';
|
|
80
|
+
const _fs = require('fs');
|
|
81
|
+
const _path = require('path');
|
|
82
|
+
const _os = require('os');
|
|
83
|
+
const _crypto = require('crypto');
|
|
84
|
+
|
|
85
|
+
// ── math ──────────────────────────────────────────────────────
|
|
86
|
+
const math = {
|
|
87
|
+
pi: Math.PI,
|
|
88
|
+
e: Math.E,
|
|
89
|
+
inf: Infinity,
|
|
90
|
+
nan: NaN,
|
|
91
|
+
epsilon: Number.EPSILON,
|
|
92
|
+
abs: (x) => Math.abs(x),
|
|
93
|
+
sqrt: (x) => Math.sqrt(x),
|
|
94
|
+
pow: (x, y) => Math.pow(x, y),
|
|
95
|
+
floor: (x) => Math.floor(x),
|
|
96
|
+
ceil: (x) => Math.ceil(x),
|
|
97
|
+
round: (x) => Math.round(x),
|
|
98
|
+
sin: (x) => Math.sin(x),
|
|
99
|
+
cos: (x) => Math.cos(x),
|
|
100
|
+
tan: (x) => Math.tan(x),
|
|
101
|
+
asin: (x) => Math.asin(x),
|
|
102
|
+
acos: (x) => Math.acos(x),
|
|
103
|
+
atan: (x) => Math.atan(x),
|
|
104
|
+
atan2: (y, x) => Math.atan2(y, x),
|
|
105
|
+
log: (x) => Math.log(x),
|
|
106
|
+
log2: (x) => Math.log2(x),
|
|
107
|
+
log10: (x) => Math.log10(x),
|
|
108
|
+
exp: (x) => Math.exp(x),
|
|
109
|
+
random: () => Math.random(),
|
|
110
|
+
min: (...args) => Math.min(...args),
|
|
111
|
+
max: (...args) => Math.max(...args),
|
|
112
|
+
clamp: (v, lo, hi) => Math.max(lo, Math.min(hi, v)),
|
|
113
|
+
lerp: (a, b, t) => a + (b - a) * t,
|
|
114
|
+
hypot: (...args) => Math.hypot(...args),
|
|
115
|
+
sign: (x) => Math.sign(x),
|
|
116
|
+
trunc: (x) => Math.trunc(x),
|
|
117
|
+
cbrt: (x) => Math.cbrt(x),
|
|
118
|
+
// Fast integer math via typed arrays
|
|
119
|
+
imul: (a, b) => Math.imul(a, b),
|
|
120
|
+
fround: (x) => Math.fround(x),
|
|
121
|
+
clz32: (x) => Math.clz32(x),
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// Fast-math helpers using Float64Array for type stability
|
|
125
|
+
const _buf64 = new Float64Array(1);
|
|
126
|
+
const _buf32 = new Float32Array(1);
|
|
127
|
+
const fastFloor = (x) => { _buf64[0] = x; return x | 0; };
|
|
128
|
+
const fastAbs = (x) => x < 0 ? -x : x;
|
|
129
|
+
const fastMod = (a, b) => a - b * ((a / b) | 0);
|
|
130
|
+
|
|
131
|
+
// ── vectors ───────────────────────────────────────────────────
|
|
132
|
+
const vec = {
|
|
133
|
+
add: (a, b) => a.map((v, i) => v + b[i]),
|
|
134
|
+
sub: (a, b) => a.map((v, i) => v - b[i]),
|
|
135
|
+
scale: (a, s) => a.map(v => v * s),
|
|
136
|
+
dot: (a, b) => a.reduce((acc, v, i) => acc + v * b[i], 0),
|
|
137
|
+
cross3: (a, b) => [
|
|
138
|
+
a[1]*b[2] - a[2]*b[1],
|
|
139
|
+
a[2]*b[0] - a[0]*b[2],
|
|
140
|
+
a[0]*b[1] - a[1]*b[0],
|
|
141
|
+
],
|
|
142
|
+
length: (a) => Math.hypot(...a),
|
|
143
|
+
normalize: (a) => { const len = Math.hypot(...a) || 1; return a.map(v => v / len); },
|
|
144
|
+
distance: (a, b) => Math.hypot(...a.map((v, i) => v - b[i])),
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// ── string helpers ────────────────────────────────────────────
|
|
148
|
+
const str = {
|
|
149
|
+
upper: (s) => String(s).toUpperCase(),
|
|
150
|
+
lower: (s) => String(s).toLowerCase(),
|
|
151
|
+
trim: (s) => String(s).trim(),
|
|
152
|
+
split: (s, sep) => String(s).split(sep),
|
|
153
|
+
replace: (s, pat, rep) => String(s).replace(pat, rep),
|
|
154
|
+
includes:(s, sub) => String(s).includes(sub),
|
|
155
|
+
startsWith:(s, pre) => String(s).startsWith(pre),
|
|
156
|
+
endsWith: (s, suf) => String(s).endsWith(suf),
|
|
157
|
+
repeat: (s, n) => String(s).repeat(n),
|
|
158
|
+
pad: (s, n, ch = ' ') => String(s).padStart(n, ch),
|
|
159
|
+
padEnd: (s, n, ch = ' ') => String(s).padEnd(n, ch),
|
|
160
|
+
indexOf: (s, sub) => String(s).indexOf(sub),
|
|
161
|
+
slice: (s, start, end) => String(s).slice(start, end),
|
|
162
|
+
charCode:(s, i = 0) => String(s).charCodeAt(i),
|
|
163
|
+
fromCode:(n) => String.fromCharCode(n),
|
|
164
|
+
reverse: (s) => String(s).split('').reverse().join(''),
|
|
165
|
+
count: (s, sub) => { let c = 0, i = 0; while ((i = s.indexOf(sub, i)) !== -1) { c++; i++; } return c; },
|
|
166
|
+
format: (tpl, ...args) => tpl.replace(/{(\\d+)}/g, (_, i) => args[i] ?? ''),
|
|
167
|
+
escape: (s) => String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'),
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// ── array helpers ─────────────────────────────────────────────
|
|
171
|
+
const arr = {
|
|
172
|
+
map: (a, fn) => Array.from(a).map(fn),
|
|
173
|
+
filter: (a, fn) => Array.from(a).filter(fn),
|
|
174
|
+
reduce: (a, fn, init) => Array.from(a).reduce(fn, init),
|
|
175
|
+
find: (a, fn) => Array.from(a).find(fn),
|
|
176
|
+
findIdx: (a, fn) => Array.from(a).findIndex(fn),
|
|
177
|
+
sort: (a, fn) => [...a].sort(fn),
|
|
178
|
+
flat: (a, d = 1) => Array.from(a).flat(d),
|
|
179
|
+
flatMap: (a, fn) => Array.from(a).flatMap(fn),
|
|
180
|
+
unique: (a) => [...new Set(a)],
|
|
181
|
+
zip: (...arrs) => arrs[0].map((_, i) => arrs.map(a => a[i])),
|
|
182
|
+
chunk: (a, n) => { const res = []; for (let i = 0; i < a.length; i += n) res.push(a.slice(i, i+n)); return res; },
|
|
183
|
+
shuffle: (a) => { const r = [...a]; for (let i = r.length-1; i > 0; i--) { const j = Math.floor(Math.random()*(i+1)); [r[i],r[j]]=[r[j],r[i]]; } return r; },
|
|
184
|
+
range: (start, end, step = 1) => { const r = []; for (let i = start; i < end; i += step) r.push(i); return r; },
|
|
185
|
+
sum: (a) => a.reduce((acc, v) => acc + v, 0),
|
|
186
|
+
avg: (a) => a.reduce((acc, v) => acc + v, 0) / a.length,
|
|
187
|
+
min: (a) => Math.min(...a),
|
|
188
|
+
max: (a) => Math.max(...a),
|
|
189
|
+
includes:(a, v) => Array.from(a).includes(v),
|
|
190
|
+
count: (a, v) => Array.from(a).filter(x => x === v).length,
|
|
191
|
+
first: (a) => a[0],
|
|
192
|
+
last: (a) => a[a.length - 1],
|
|
193
|
+
take: (a, n) => a.slice(0, n),
|
|
194
|
+
drop: (a, n) => a.slice(n),
|
|
195
|
+
reverse: (a) => [...a].reverse(),
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// ── object helpers ────────────────────────────────────────────
|
|
199
|
+
const obj = {
|
|
200
|
+
keys: (o) => Object.keys(o),
|
|
201
|
+
values: (o) => Object.values(o),
|
|
202
|
+
entries: (o) => Object.entries(o),
|
|
203
|
+
merge: (...os) => Object.assign({}, ...os),
|
|
204
|
+
clone: (o) => JSON.parse(JSON.stringify(o)),
|
|
205
|
+
pick: (o, keys) => keys.reduce((acc, k) => { if (k in o) acc[k] = o[k]; return acc; }, {}),
|
|
206
|
+
omit: (o, keys) => { const r = {...o}; keys.forEach(k => delete r[k]); return r; },
|
|
207
|
+
has: (o, k) => Object.prototype.hasOwnProperty.call(o, k),
|
|
208
|
+
freeze: (o) => Object.freeze(o),
|
|
209
|
+
seal: (o) => Object.seal(o),
|
|
210
|
+
fromEntries: (e) => Object.fromEntries(e),
|
|
211
|
+
assign: (target, ...sources) => Object.assign(target, ...sources),
|
|
212
|
+
flatten: (o, prefix = '') => {
|
|
213
|
+
return Object.keys(o).reduce((acc, k) => {
|
|
214
|
+
const key = prefix ? prefix + '.' + k : k;
|
|
215
|
+
if (typeof o[k] === 'object' && o[k] !== null && !Array.isArray(o[k]))
|
|
216
|
+
Object.assign(acc, obj.flatten(o[k], key));
|
|
217
|
+
else acc[key] = o[k];
|
|
218
|
+
return acc;
|
|
219
|
+
}, {});
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// ── I/O helpers ───────────────────────────────────────────────
|
|
224
|
+
const io = {
|
|
225
|
+
print: (...args) => { process.stdout.write(args.map(a => __thy_stringify(a)).join(' ') + '\\n'); },
|
|
226
|
+
printErr:(...args) => { process.stderr.write(args.map(a => __thy_stringify(a)).join(' ') + '\\n'); },
|
|
227
|
+
write: (s) => { process.stdout.write(String(s)); },
|
|
228
|
+
read: (fp) => _fs.readFileSync(fp, 'utf8'),
|
|
229
|
+
readLines:(fp) => _fs.readFileSync(fp, 'utf8').split('\\n'),
|
|
230
|
+
write_: (fp, data) => _fs.writeFileSync(fp, data, 'utf8'),
|
|
231
|
+
append: (fp, data) => _fs.appendFileSync(fp, data, 'utf8'),
|
|
232
|
+
exists: (fp) => _fs.existsSync(fp),
|
|
233
|
+
mkdir: (fp) => _fs.mkdirSync(fp, { recursive: true }),
|
|
234
|
+
readdir: (fp) => _fs.readdirSync(fp),
|
|
235
|
+
rm: (fp) => _fs.rmSync(fp, { recursive: true, force: true }),
|
|
236
|
+
stat: (fp) => _fs.statSync(fp),
|
|
237
|
+
isFile: (fp) => _fs.existsSync(fp) && _fs.statSync(fp).isFile(),
|
|
238
|
+
isDir: (fp) => _fs.existsSync(fp) && _fs.statSync(fp).isDirectory(),
|
|
239
|
+
cwd: () => process.cwd(),
|
|
240
|
+
home: () => _os.homedir(),
|
|
241
|
+
tempDir: () => _os.tmpdir(),
|
|
242
|
+
sep: _path.sep,
|
|
243
|
+
join: (...parts) => _path.join(...parts),
|
|
244
|
+
resolve: (...parts) => _path.resolve(...parts),
|
|
245
|
+
dirname: (p) => _path.dirname(p),
|
|
246
|
+
basename:(p, ext) => _path.basename(p, ext),
|
|
247
|
+
ext: (p) => _path.extname(p),
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
// ── crypto helpers ────────────────────────────────────────────
|
|
251
|
+
const security = {
|
|
252
|
+
hash: (data, alg = 'sha256') => _crypto.createHash(alg).update(String(data)).digest('hex'),
|
|
253
|
+
hmac: (key, data, alg = 'sha256') => _crypto.createHmac(alg, key).update(String(data)).digest('hex'),
|
|
254
|
+
random: (n = 16) => _crypto.randomBytes(n).toString('hex'),
|
|
255
|
+
uuid: () => _crypto.randomUUID ? _crypto.randomUUID() : _crypto.randomBytes(16).toString('hex').replace(/(........)(....)(....)(....)(............)/, '$1-$2-$3-$4-$5'),
|
|
256
|
+
encrypt: (text, key) => {
|
|
257
|
+
const iv = _crypto.randomBytes(16);
|
|
258
|
+
const k = _crypto.createHash('sha256').update(key).digest();
|
|
259
|
+
const c = _crypto.createCipheriv('aes-256-cbc', k, iv);
|
|
260
|
+
return iv.toString('hex') + ':' + Buffer.concat([c.update(text), c.final()]).toString('hex');
|
|
261
|
+
},
|
|
262
|
+
decrypt: (enc, key) => {
|
|
263
|
+
const [ivHex, data] = enc.split(':');
|
|
264
|
+
const iv = Buffer.from(ivHex, 'hex');
|
|
265
|
+
const k = _crypto.createHash('sha256').update(key).digest();
|
|
266
|
+
const d = _crypto.createDecipheriv('aes-256-cbc', k, iv);
|
|
267
|
+
return Buffer.concat([d.update(Buffer.from(data, 'hex')), d.final()]).toString();
|
|
268
|
+
},
|
|
269
|
+
base64encode: (s) => Buffer.from(String(s)).toString('base64'),
|
|
270
|
+
base64decode: (s) => Buffer.from(String(s), 'base64').toString(),
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
// ── type helpers ──────────────────────────────────────────────
|
|
274
|
+
const types = {
|
|
275
|
+
isNum: (v) => typeof v === 'number' && !isNaN(v),
|
|
276
|
+
isStr: (v) => typeof v === 'string',
|
|
277
|
+
isBool: (v) => typeof v === 'boolean',
|
|
278
|
+
isNull: (v) => v === null,
|
|
279
|
+
isUndef: (v) => v === undefined,
|
|
280
|
+
isArr: (v) => Array.isArray(v),
|
|
281
|
+
isObj: (v) => v !== null && typeof v === 'object' && !Array.isArray(v),
|
|
282
|
+
isFn: (v) => typeof v === 'function',
|
|
283
|
+
type: (v) => v === null ? 'null' : Array.isArray(v) ? 'array' : typeof v,
|
|
284
|
+
toNum: (v) => Number(v),
|
|
285
|
+
toStr: (v) => String(v),
|
|
286
|
+
toBool: (v) => Boolean(v),
|
|
287
|
+
toInt: (v) => parseInt(v, 10),
|
|
288
|
+
toFloat: (v) => parseFloat(v),
|
|
289
|
+
toArr: (v) => Array.isArray(v) ? v : [v],
|
|
290
|
+
toJson: (v) => JSON.stringify(v, null, 2),
|
|
291
|
+
fromJson:(v) => JSON.parse(v),
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
// ── time helpers ──────────────────────────────────────────────
|
|
295
|
+
const time = {
|
|
296
|
+
now: () => Date.now(),
|
|
297
|
+
date: () => new Date(),
|
|
298
|
+
sleep: (ms) => new Promise(r => setTimeout(r, ms)),
|
|
299
|
+
perf: () => typeof performance !== 'undefined' ? performance.now() : Number(process.hrtime.bigint()) / 1e6,
|
|
300
|
+
format: (d = new Date()) => d.toISOString(),
|
|
301
|
+
unix: () => Math.floor(Date.now() / 1000),
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
// ── async/process helpers ─────────────────────────────────────
|
|
305
|
+
const proc = {
|
|
306
|
+
env: (k, def = '') => process.env[k] || def,
|
|
307
|
+
args: () => process.argv.slice(2),
|
|
308
|
+
exit: (code = 0) => process.exit(code),
|
|
309
|
+
pid: () => process.pid,
|
|
310
|
+
memUsage:() => process.memoryUsage(),
|
|
311
|
+
cpuUsage:() => process.cpuUsage(),
|
|
312
|
+
platform:() => process.platform,
|
|
313
|
+
arch: () => process.arch,
|
|
314
|
+
version: () => process.version,
|
|
315
|
+
hrtime: () => process.hrtime.bigint(),
|
|
316
|
+
nextTick:(fn) => process.nextTick(fn),
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
// ── http helpers ──────────────────────────────────────────────
|
|
320
|
+
const http = {
|
|
321
|
+
fetch: async (url, opts = {}) => {
|
|
322
|
+
const https = url.startsWith('https') ? require('https') : require('http');
|
|
323
|
+
return new Promise((resolve, reject) => {
|
|
324
|
+
const req = https.request(url, { method: opts.method || 'GET', headers: opts.headers || {} }, (res) => {
|
|
325
|
+
let data = '';
|
|
326
|
+
res.on('data', chunk => data += chunk);
|
|
327
|
+
res.on('end', () => resolve({ status: res.statusCode, headers: res.headers, body: data,
|
|
328
|
+
json: () => JSON.parse(data), text: () => data }));
|
|
329
|
+
});
|
|
330
|
+
req.on('error', reject);
|
|
331
|
+
if (opts.body) req.write(typeof opts.body === 'string' ? opts.body : JSON.stringify(opts.body));
|
|
332
|
+
req.end();
|
|
333
|
+
});
|
|
334
|
+
},
|
|
335
|
+
get: async (url, headers = {}) => http.fetch(url, { method: 'GET', headers }),
|
|
336
|
+
post: async (url, body, headers = {}) => http.fetch(url, { method: 'POST', body, headers: { 'Content-Type': 'application/json', ...headers } }),
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
// ── persist helpers ───────────────────────────────────────────
|
|
340
|
+
const persist = {
|
|
341
|
+
save: (name, data) => {
|
|
342
|
+
const fp = _path.join(process.cwd(), name + '.thysave');
|
|
343
|
+
_fs.writeFileSync(fp, JSON.stringify(data, null, 2), 'utf8');
|
|
344
|
+
},
|
|
345
|
+
load: (name) => {
|
|
346
|
+
const fp = _path.join(process.cwd(), name + '.thysave');
|
|
347
|
+
if (!_fs.existsSync(fp)) return null;
|
|
348
|
+
return JSON.parse(_fs.readFileSync(fp, 'utf8'));
|
|
349
|
+
},
|
|
350
|
+
del: (name) => {
|
|
351
|
+
const fp = _path.join(process.cwd(), name + '.thysave');
|
|
352
|
+
if (_fs.existsSync(fp)) _fs.unlinkSync(fp);
|
|
353
|
+
},
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
// ── optimised calculate ───────────────────────────────────────
|
|
357
|
+
const __thy_calculate = (expr) => {
|
|
358
|
+
// expr is already a JS expression string – evaluate safely
|
|
359
|
+
// This is called at runtime for dynamic calculate() statements
|
|
360
|
+
try {
|
|
361
|
+
// Use Function constructor for safe, optimisable evaluation
|
|
362
|
+
return (new Function('Math', \`return (\${expr});\`))(Math);
|
|
363
|
+
} catch(e) {
|
|
364
|
+
throw new TypeError('[ThyScript] calculate error: ' + e.message);
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
return Object.freeze({
|
|
369
|
+
math, vec, str, arr, obj, io, security, types, time, proc, http, persist,
|
|
370
|
+
fastFloor, fastAbs, fastMod,
|
|
371
|
+
__thy_calculate,
|
|
372
|
+
// convenience top-level aliases
|
|
373
|
+
print: io.print,
|
|
374
|
+
len: (v) => (Array.isArray(v) || typeof v === 'string') ? v.length : Object.keys(v).length,
|
|
375
|
+
range: arr.range,
|
|
376
|
+
type: types.type,
|
|
377
|
+
hash: security.hash,
|
|
378
|
+
sleep: time.sleep,
|
|
379
|
+
now: time.now,
|
|
380
|
+
env: proc.env,
|
|
381
|
+
fetch: http.fetch,
|
|
382
|
+
save: persist.save,
|
|
383
|
+
load: persist.load,
|
|
384
|
+
});
|
|
385
|
+
})();
|
|
386
|
+
|
|
387
|
+
// Convenience globals injected from stdlib
|
|
388
|
+
const __thy_print = __thy_stdlib.print;
|
|
389
|
+
const __thy_len = __thy_stdlib.len;
|
|
390
|
+
const __thy_range = __thy_stdlib.range;
|
|
391
|
+
const __thy_sleep = __thy_stdlib.time.sleep;
|
|
392
|
+
|
|
393
|
+
function __thy_stringify(v) {
|
|
394
|
+
if (v === null) return 'null';
|
|
395
|
+
if (v === undefined) return 'undefined';
|
|
396
|
+
if (typeof v === 'object') { try { return JSON.stringify(v); } catch(_) { return String(v); } }
|
|
397
|
+
return String(v);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Shared typed buffers for hot numeric paths
|
|
401
|
+
const __thy_f64 = new Float64Array(256);
|
|
402
|
+
const __thy_i32 = new Int32Array(256);
|
|
403
|
+
const __thy_sab = typeof SharedArrayBuffer !== 'undefined' ? new SharedArrayBuffer(4096) : null;
|
|
404
|
+
`.trimStart();
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// ─── entry point ─────────────────────────────────────────────────────────────
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Generate optimised JS source from a ThyScript AST.
|
|
411
|
+
* @param {Object} ast – The parsed AST (Program node)
|
|
412
|
+
* @returns {string} – Runnable Node.js JavaScript source
|
|
413
|
+
*/
|
|
414
|
+
generate(ast) {
|
|
415
|
+
// Unwrap { ast, errors } if returned directly from parser
|
|
416
|
+
if (ast && ast.ast && typeof ast.ast === 'object') {
|
|
417
|
+
ast = ast.ast;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Reset state
|
|
421
|
+
this._indent = 0;
|
|
422
|
+
this._uid = 0;
|
|
423
|
+
this._expressAppDeclared = false;
|
|
424
|
+
this._readlineDeclared = false;
|
|
425
|
+
this._tfDeclared = false;
|
|
426
|
+
this._matterDeclared = false;
|
|
427
|
+
this._requires = new Set();
|
|
428
|
+
this._headerLines = [];
|
|
429
|
+
this._bodyLines = [];
|
|
430
|
+
|
|
431
|
+
// Walk the AST
|
|
432
|
+
this._genNode(ast);
|
|
433
|
+
|
|
434
|
+
// Build final source
|
|
435
|
+
const parts = [
|
|
436
|
+
"'use strict';",
|
|
437
|
+
'// Auto-generated by ThyScript CodeGenerator – do not edit manually',
|
|
438
|
+
this._buildStdlib(),
|
|
439
|
+
];
|
|
440
|
+
|
|
441
|
+
// Extra requires collected during generation
|
|
442
|
+
for (const pkg of this._requires) {
|
|
443
|
+
parts.push(`const ${this._requireVarName(pkg)} = (() => { try { return require('${pkg}'); } catch(_) { return null; } })();`);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (this._expressAppDeclared) {
|
|
447
|
+
parts.push(`const __thy_express = (() => { try { return require('express'); } catch(_) { return null; } })();`);
|
|
448
|
+
parts.push(`const __thy_app = __thy_express ? __thy_express() : null;`);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
parts.push('');
|
|
452
|
+
parts.push(this._bodyLines.join('\n'));
|
|
453
|
+
|
|
454
|
+
return parts.join('\n');
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
_requireVarName(pkg) {
|
|
458
|
+
return '__thy_req_' + pkg.replace(/[^a-zA-Z0-9]/g, '_');
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// ─── node dispatcher ─────────────────────────────────────────────────────────
|
|
462
|
+
|
|
463
|
+
_genNode(node) {
|
|
464
|
+
if (!node) return 'undefined';
|
|
465
|
+
switch (node.type) {
|
|
466
|
+
case 'Program': return this._genProgram(node);
|
|
467
|
+
case 'VarDecl': return this._genVarDecl(node);
|
|
468
|
+
case 'DestructureDecl': return this._genDestructureDecl(node);
|
|
469
|
+
case 'PrintStmt': return this._genPrintStmt(node);
|
|
470
|
+
case 'ClassDecl': return this._genClassDecl(node);
|
|
471
|
+
case 'HtmlClassDecl': return this._genHtmlClassDecl(node);
|
|
472
|
+
case 'IfStmt': return this._genIfStmt(node);
|
|
473
|
+
case 'WhileStmt': return this._genWhileStmt(node);
|
|
474
|
+
case 'ForStmt': return this._genForStmt(node);
|
|
475
|
+
case 'AddStmt': return this._genAddStmt(node);
|
|
476
|
+
case 'ChangeStmt': return this._genChangeStmt(node);
|
|
477
|
+
case 'CalculateStmt': return this._genCalculateStmt(node);
|
|
478
|
+
case 'InputStmt': return this._genInputStmt(node);
|
|
479
|
+
case 'SaveStmt': return this._genSaveStmt(node);
|
|
480
|
+
case 'FreeStmt': return this._genFreeStmt(node);
|
|
481
|
+
case 'FunctionDecl': return this._genFunctionDecl(node);
|
|
482
|
+
case 'ReturnStmt': return this._genReturnStmt(node);
|
|
483
|
+
case 'TryCatch': return this._genTryCatch(node);
|
|
484
|
+
case 'ImportDecl': return this._genImportDecl(node);
|
|
485
|
+
case 'ExportDecl': return this._genExportDecl(node);
|
|
486
|
+
case 'ServerDecl': return this._genServerDecl(node);
|
|
487
|
+
case 'RouteDecl': return this._genRouteDecl(node);
|
|
488
|
+
case 'ComponentDecl': return this._genComponentDecl(node);
|
|
489
|
+
case 'NeuralDecl': return this._genNeuralDecl(node);
|
|
490
|
+
case 'PhysicsDecl': return this._genPhysicsDecl(node);
|
|
491
|
+
case 'ParallelDecl': return this._genParallelDecl(node);
|
|
492
|
+
case 'StreamDecl': return this._genStreamDecl(node);
|
|
493
|
+
case 'BreakStmt': return this._emit('break;');
|
|
494
|
+
case 'ContinueStmt': return this._emit('continue;');
|
|
495
|
+
case 'ThrowStmt': return this._emit(`throw ${this._genExpr(node.argument || node.value)};`);
|
|
496
|
+
case 'ExprStmt': return this._emit(`${this._genExpr(node.expression)};`);
|
|
497
|
+
case 'ExpressionStmt': return this._emit(`${this._genExpr(node.expression)};`);
|
|
498
|
+
case 'Block': return node.body.forEach(n => this._genNode(n));
|
|
499
|
+
case 'SwitchStmt': return this._genSwitchStmt(node);
|
|
500
|
+
case 'MatchStmt': return this._genMatchStmt(node);
|
|
501
|
+
case 'AwaitExpr': return this._genExpr(node); // handled in expr
|
|
502
|
+
default:
|
|
503
|
+
// Fallback: try to generate as expression statement
|
|
504
|
+
try {
|
|
505
|
+
const expr = this._genExpr(node);
|
|
506
|
+
if (expr && expr !== 'undefined') this._emit(`${expr};`);
|
|
507
|
+
} catch (_) {
|
|
508
|
+
this._emit(`/* [ThyScript] unhandled node type: ${node.type} */`);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// ─── program ─────────────────────────────────────────────────────────────────
|
|
514
|
+
|
|
515
|
+
_genProgram(node) {
|
|
516
|
+
this._emit('// ── program body ──────────────────────────────────────────────');
|
|
517
|
+
this._emit('(async () => {');
|
|
518
|
+
this._push();
|
|
519
|
+
for (const stmt of (node.body || [])) {
|
|
520
|
+
this._genNode(stmt);
|
|
521
|
+
}
|
|
522
|
+
this._pop();
|
|
523
|
+
this._emit('})().catch(e => { console.error("[ThyScript Runtime Error]", e); process.exit(1); });');
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// ─── variable declaration ────────────────────────────────────────────────────
|
|
527
|
+
|
|
528
|
+
_genVarDecl(node) {
|
|
529
|
+
// `const table x = 0` → `let x = 0;`
|
|
530
|
+
// Always use `let` so ThyScript variables are mutable by default
|
|
531
|
+
const name = node.name;
|
|
532
|
+
const init = node.init != null ? this._genExpr(node.init) : 'undefined';
|
|
533
|
+
this._emit(`let ${name} = ${init};`);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// ─── destructuring declaration ──────────────────────────────────────────────
|
|
537
|
+
|
|
538
|
+
_genDestructureDecl(node) {
|
|
539
|
+
// `const table [a, b, ...rest] = init` → `let [a, b, ...rest] = init;`
|
|
540
|
+
// `const table {x, y: z} = init` → `let { x, y: z } = init;`
|
|
541
|
+
const init = this._genExpr(node.init);
|
|
542
|
+
if (node.kind === 'array') {
|
|
543
|
+
const elems = (node.targets || []).map(t => (t.rest ? `...${t.name}` : t.name));
|
|
544
|
+
this._emit(`let [${elems.join(', ')}] = ${init};`);
|
|
545
|
+
} else {
|
|
546
|
+
const props = (node.targets || []).map(({ key, alias, default: def }) => {
|
|
547
|
+
const target = key === alias ? key : `${key}: ${alias}`;
|
|
548
|
+
return def != null ? `${target} = ${this._genExpr(def)}` : target;
|
|
549
|
+
});
|
|
550
|
+
this._emit(`let { ${props.join(', ')} } = ${init};`);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// ─── print ───────────────────────────────────────────────────────────────────
|
|
555
|
+
|
|
556
|
+
_genPrintStmt(node) {
|
|
557
|
+
const args = (node.args || [node.value]).map(a => this._genExpr(a)).join(', ');
|
|
558
|
+
this._emit(`__thy_print(${args});`);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// ─── class ───────────────────────────────────────────────────────────────────
|
|
562
|
+
|
|
563
|
+
_genBlockOrList(body) {
|
|
564
|
+
if (!body) return;
|
|
565
|
+
if (Array.isArray(body)) {
|
|
566
|
+
for (const s of body) this._genNode(s);
|
|
567
|
+
} else if (body.type === 'Block') {
|
|
568
|
+
for (const s of (body.body || [])) this._genNode(s);
|
|
569
|
+
} else {
|
|
570
|
+
this._genNode(body);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// ─── class ───────────────────────────────────────────────────────────────────
|
|
575
|
+
|
|
576
|
+
_genClassDecl(node) {
|
|
577
|
+
const name = node.name;
|
|
578
|
+
const parent = node.superClass ? ` extends ${node.superClass}` : '';
|
|
579
|
+
const access = node.access || 'public'; // public / private
|
|
580
|
+
this._emit(`// [${access}] class`);
|
|
581
|
+
this._emit(`class ${name}${parent} {`);
|
|
582
|
+
this._push();
|
|
583
|
+
|
|
584
|
+
const stmts = Array.isArray(node.body) ? node.body : (node.body && node.body.body ? node.body.body : []);
|
|
585
|
+
const props = [];
|
|
586
|
+
const methods = [...(node.methods || [])];
|
|
587
|
+
const constructorStmts = [...(node.constructorBody || [])];
|
|
588
|
+
|
|
589
|
+
for (const stmt of stmts) {
|
|
590
|
+
if (stmt.type === 'VarDecl') {
|
|
591
|
+
props.push(stmt);
|
|
592
|
+
} else if (stmt.type === 'FunctionDecl') {
|
|
593
|
+
methods.push(stmt);
|
|
594
|
+
} else {
|
|
595
|
+
constructorStmts.push(stmt);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
this._emit(`constructor(${(node.constructorParams || []).join(', ')}) {`);
|
|
600
|
+
this._push();
|
|
601
|
+
if (node.superClass) this._emit('super();');
|
|
602
|
+
for (const prop of props) {
|
|
603
|
+
const init = prop.init != null ? this._genExpr(prop.init) : 'undefined';
|
|
604
|
+
this._emit(`this.${prop.name} = ${init};`);
|
|
605
|
+
}
|
|
606
|
+
for (const stmt of constructorStmts) this._genNode(stmt);
|
|
607
|
+
this._pop();
|
|
608
|
+
this._emit('}');
|
|
609
|
+
|
|
610
|
+
const propNames = props.map(p => p.name);
|
|
611
|
+
const methodNames = methods.map(m => m.name);
|
|
612
|
+
for (const method of methods) {
|
|
613
|
+
this._genMethod(method, propNames, methodNames);
|
|
614
|
+
}
|
|
615
|
+
for (const prop of (node.staticProps || [])) {
|
|
616
|
+
this._emit(`static ${prop.name} = ${this._genExpr(prop.value)};`);
|
|
617
|
+
}
|
|
618
|
+
this._pop();
|
|
619
|
+
this._emit('}');
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
_genMethod(method, classProps = [], classMethods = []) {
|
|
623
|
+
const isStatic = method.isStatic ? 'static ' : '';
|
|
624
|
+
const isAsync = method.isAsync ? 'async ' : '';
|
|
625
|
+
const name = method.name;
|
|
626
|
+
const params = (method.params || []).map(p => typeof p === 'string' ? p : p.name).join(', ');
|
|
627
|
+
this._emit(`${isStatic}${isAsync}${name}(${params}) {`);
|
|
628
|
+
this._push();
|
|
629
|
+
if (!isStatic && classMethods.length > 0) {
|
|
630
|
+
for (const m of classMethods) {
|
|
631
|
+
if (m !== name) {
|
|
632
|
+
this._emit(`const ${m} = (...args) => this.${m}(...args);`);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
if (!isStatic && classProps.length > 0) {
|
|
637
|
+
// Destructure class props as mutable lets (allows += inside methods)
|
|
638
|
+
this._emit('let { ' + classProps.join(', ') + ' } = this;');
|
|
639
|
+
this._genBlockOrList(method.body);
|
|
640
|
+
// Write any mutations back to the instance
|
|
641
|
+
this._emit('Object.assign(this, { ' + classProps.join(', ') + ' });');
|
|
642
|
+
} else {
|
|
643
|
+
this._genBlockOrList(method.body);
|
|
644
|
+
}
|
|
645
|
+
this._pop();
|
|
646
|
+
this._emit('}');
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// ─── HTML class (express route serving HTML) ──────────────────────────────────
|
|
650
|
+
|
|
651
|
+
_genHtmlClassDecl(node) {
|
|
652
|
+
this._expressAppDeclared = true;
|
|
653
|
+
const route = node.route || `/${node.name}`;
|
|
654
|
+
const name = (node.name || 'html_page').replace(/[^a-zA-Z0-9_]/g, '_');
|
|
655
|
+
const safeHtml = (node.html || '').replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\${/g, '\\${');
|
|
656
|
+
this._emit(`// HtmlClass: ${node.name}`);
|
|
657
|
+
this._emit(`const __html_${name} = \`${safeHtml}\`;`);
|
|
658
|
+
this._emit(`if (typeof response !== 'undefined' && response && typeof response.send === 'function') {`);
|
|
659
|
+
this._push();
|
|
660
|
+
this._emit(`response.setHeader('Content-Type', 'text/html; charset=utf-8');`);
|
|
661
|
+
this._emit(`response.send(__html_${name});`);
|
|
662
|
+
this._pop();
|
|
663
|
+
this._emit(`} else if (typeof __thy_app !== 'undefined' && __thy_app) {`);
|
|
664
|
+
this._push();
|
|
665
|
+
this._emit(`__thy_app.get('${route}', (req, res) => {`);
|
|
666
|
+
this._push();
|
|
667
|
+
this._emit(`res.setHeader('Content-Type', 'text/html; charset=utf-8');`);
|
|
668
|
+
this._emit(`res.send(__html_${name});`);
|
|
669
|
+
this._pop();
|
|
670
|
+
this._emit(`});`);
|
|
671
|
+
this._pop();
|
|
672
|
+
this._emit(`}`);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// ─── if / elif / else ────────────────────────────────────────────────────────
|
|
676
|
+
|
|
677
|
+
_genIfStmt(node) {
|
|
678
|
+
const cond = this._genExpr(node.condition || node.test);
|
|
679
|
+
this._emit(`if (${cond}) {`);
|
|
680
|
+
this._push();
|
|
681
|
+
this._genBlockOrList(node.consequent);
|
|
682
|
+
this._pop();
|
|
683
|
+
const alternates = node.alternates || node.elseifs || [];
|
|
684
|
+
if (alternates.length > 0) {
|
|
685
|
+
for (const alt of alternates) {
|
|
686
|
+
const altCond = this._genExpr(alt.condition || alt.test);
|
|
687
|
+
this._emit(`} else if (${altCond}) {`);
|
|
688
|
+
this._push();
|
|
689
|
+
this._genBlockOrList(alt.body || alt.consequent);
|
|
690
|
+
this._pop();
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
if (node.alternate) {
|
|
694
|
+
this._emit('} else {');
|
|
695
|
+
this._push();
|
|
696
|
+
this._genBlockOrList(node.alternate);
|
|
697
|
+
this._pop();
|
|
698
|
+
}
|
|
699
|
+
this._emit('}');
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// ─── while ───────────────────────────────────────────────────────────────────
|
|
703
|
+
|
|
704
|
+
_genWhileStmt(node) {
|
|
705
|
+
const cond = this._genExpr(node.condition || node.test);
|
|
706
|
+
this._emit(`while (${cond}) {`);
|
|
707
|
+
this._push();
|
|
708
|
+
this._genBlockOrList(node.body);
|
|
709
|
+
this._pop();
|
|
710
|
+
this._emit('}');
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// ─── for ─────────────────────────────────────────────────────────────────────
|
|
714
|
+
|
|
715
|
+
_genForStmt(node) {
|
|
716
|
+
if (node.style === 'range' || node.to != null) {
|
|
717
|
+
const varName = node.var || node.variable;
|
|
718
|
+
const from_ = this._genExpr(node.from || { type: 'Literal', value: 0 });
|
|
719
|
+
const to_ = this._genExpr(node.to);
|
|
720
|
+
const step_ = node.step ? this._genExpr(node.step) : '1';
|
|
721
|
+
this._emit(`for (let ${varName} = ${from_}; ${varName} < ${to_}; ${varName} += ${step_}) {`);
|
|
722
|
+
} else {
|
|
723
|
+
const varName = node.var || node.variable || (node.left && node.left.name ? node.left.name : 'item');
|
|
724
|
+
const iterExpr = this._genExpr(node.iterable || node.right);
|
|
725
|
+
this._emit(`for (const ${varName} of ${iterExpr}) {`);
|
|
726
|
+
}
|
|
727
|
+
this._push();
|
|
728
|
+
this._genBlockOrList(node.body);
|
|
729
|
+
this._pop();
|
|
730
|
+
this._emit('}');
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// ─── add stmt `add 10 in x` ─────────────────────────────────────────────────
|
|
734
|
+
|
|
735
|
+
_genAddStmt(node) {
|
|
736
|
+
const target = typeof node.target === 'string' ? node.target : this._genExpr(node.target);
|
|
737
|
+
const value = this._genExpr(node.value);
|
|
738
|
+
this._emit(`if (Array.isArray(${target})) { ${target}.push(${value}); } else { ${target} += ${value}; }`);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// ─── change stmt `change(x to(0))` ─────────────────────────────────────────
|
|
742
|
+
|
|
743
|
+
_genChangeStmt(node) {
|
|
744
|
+
const target = typeof node.target === 'string' ? node.target : this._genExpr(node.target);
|
|
745
|
+
const value = this._genExpr(node.value);
|
|
746
|
+
this._emit(`${target} = ${value};`);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// ─── calculate `calculate(x + y)` ───────────────────────────────────────────
|
|
750
|
+
|
|
751
|
+
_genCalculateStmt(node) {
|
|
752
|
+
if (node.result) {
|
|
753
|
+
// calculate(expr) stored in a variable
|
|
754
|
+
const expr = this._genExpr(node.expression);
|
|
755
|
+
const target = node.result;
|
|
756
|
+
this._emit(`${target} = (${expr});`);
|
|
757
|
+
} else {
|
|
758
|
+
const expr = this._genExpr(node.expression);
|
|
759
|
+
this._emit(`(${expr});`);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// ─── input `input(prompt)` ──────────────────────────────────────────────────
|
|
764
|
+
|
|
765
|
+
_genInputStmt(node) {
|
|
766
|
+
if (!this._readlineDeclared) {
|
|
767
|
+
this._readlineDeclared = true;
|
|
768
|
+
this._emit(`const __thy_rl = require('readline').createInterface({ input: process.stdin, output: process.stdout });`);
|
|
769
|
+
this._emit(`const __thy_input = (prompt) => new Promise(resolve => __thy_rl.question(prompt, resolve));`);
|
|
770
|
+
}
|
|
771
|
+
const prompt = node.prompt ? this._genExpr(node.prompt) : "''";
|
|
772
|
+
if (node.result) {
|
|
773
|
+
this._emit(`const ${node.result} = await __thy_input(${prompt});`);
|
|
774
|
+
} else {
|
|
775
|
+
this._emit(`await __thy_input(${prompt});`);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// ─── save `save(var)` ───────────────────────────────────────────────────────
|
|
780
|
+
|
|
781
|
+
_genSaveStmt(node) {
|
|
782
|
+
const varName = typeof node.value === 'string' ? node.value : this._genExpr(node.value);
|
|
783
|
+
const filename = node.filename ? this._genExpr(node.filename) : `'${varName}'`;
|
|
784
|
+
this._emit(`__thy_stdlib.persist.save(${filename}, ${varName});`);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// ─── free `free()` ──────────────────────────────────────────────────────────
|
|
788
|
+
|
|
789
|
+
_genFreeStmt(node) {
|
|
790
|
+
for (const v of (node.vars || (node.value ? [node.value] : []))) {
|
|
791
|
+
const name = typeof v === 'string' ? v : this._genExpr(v);
|
|
792
|
+
this._emit(`${name} = null; // GC hint`);
|
|
793
|
+
}
|
|
794
|
+
if (node.gc) {
|
|
795
|
+
this._emit(`if (global.gc) global.gc(); // expose-gc`);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// ─── function declaration ────────────────────────────────────────────────────
|
|
800
|
+
|
|
801
|
+
_genFunctionDecl(node) {
|
|
802
|
+
const name = node.name;
|
|
803
|
+
const params = (node.params || []).map(p => {
|
|
804
|
+
if (typeof p === 'string') return p;
|
|
805
|
+
if (p.default != null) return `${p.name} = ${this._genExpr(p.default)}`;
|
|
806
|
+
if (p.rest) return `...${p.name}`;
|
|
807
|
+
return p.name;
|
|
808
|
+
}).join(', ');
|
|
809
|
+
const isAsync = node.isAsync !== false ? 'async ' : '';
|
|
810
|
+
const isGen = node.isGenerator ? '*' : '';
|
|
811
|
+
this._emit(`${isAsync}function${isGen} ${name}(${params}) {`);
|
|
812
|
+
this._push();
|
|
813
|
+
// V8 hint: mark as optimisable by keeping hot paths monomorphic
|
|
814
|
+
this._emit(`// [ThyScript JIT] function "${name}" – V8 optimisation target`);
|
|
815
|
+
this._genBlockOrList(node.body);
|
|
816
|
+
this._pop();
|
|
817
|
+
this._emit('}');
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// ─── return ──────────────────────────────────────────────────────────────────
|
|
821
|
+
|
|
822
|
+
_genReturnStmt(node) {
|
|
823
|
+
const val = node.argument != null ? node.argument : node.value;
|
|
824
|
+
if (val != null) {
|
|
825
|
+
this._emit(`return ${this._genExpr(val)};`);
|
|
826
|
+
} else {
|
|
827
|
+
this._emit('return;');
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// ─── try / catch / finally ───────────────────────────────────────────────────
|
|
832
|
+
|
|
833
|
+
_genTryCatch(node) {
|
|
834
|
+
this._emit('try {');
|
|
835
|
+
this._push();
|
|
836
|
+
this._genBlockOrList(node.tryBody || node.block);
|
|
837
|
+
this._pop();
|
|
838
|
+
if (node.catchBody || node.handler) {
|
|
839
|
+
const errName = node.catchVar || (node.handler && node.handler.param ? node.handler.param.name : '__thy_err');
|
|
840
|
+
this._emit(`} catch (${errName}) {`);
|
|
841
|
+
this._push();
|
|
842
|
+
this._genBlockOrList(node.catchBody || (node.handler && node.handler.body));
|
|
843
|
+
this._pop();
|
|
844
|
+
}
|
|
845
|
+
if (node.finallyBody || node.finalizer) {
|
|
846
|
+
this._emit('} finally {');
|
|
847
|
+
this._push();
|
|
848
|
+
this._genBlockOrList(node.finallyBody || node.finalizer);
|
|
849
|
+
this._pop();
|
|
850
|
+
}
|
|
851
|
+
this._emit('}');
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// ─── import ──────────────────────────────────────────────────────────────────
|
|
855
|
+
|
|
856
|
+
_genImportDecl(node) {
|
|
857
|
+
const src = node.source;
|
|
858
|
+
if (node.default) {
|
|
859
|
+
this._emit(`const ${node.default} = require(${JSON.stringify(src)});`);
|
|
860
|
+
} else if (node.named && node.named.length) {
|
|
861
|
+
const bindings = node.named.map(n => typeof n === 'string' ? n : `${n.imported}: ${n.local}`).join(', ');
|
|
862
|
+
this._emit(`const { ${bindings} } = require(${JSON.stringify(src)});`);
|
|
863
|
+
} else if (node.namespace) {
|
|
864
|
+
this._emit(`const ${node.namespace} = require(${JSON.stringify(src)});`);
|
|
865
|
+
} else {
|
|
866
|
+
this._emit(`require(${JSON.stringify(src)});`);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// ─── export ──────────────────────────────────────────────────────────────────
|
|
871
|
+
|
|
872
|
+
_genExportDecl(node) {
|
|
873
|
+
if (node.default) {
|
|
874
|
+
this._emit(`module.exports = ${this._genExpr(node.value)};`);
|
|
875
|
+
} else if (node.named) {
|
|
876
|
+
const bindings = (node.named || []).map(n => typeof n === 'string' ? n : `${n}: ${n}`).join(', ');
|
|
877
|
+
this._emit(`Object.assign(module.exports, { ${bindings} });`);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// ─── server declaration ──────────────────────────────────────────────────────
|
|
882
|
+
|
|
883
|
+
_genServerDecl(node) {
|
|
884
|
+
this._expressAppDeclared = true;
|
|
885
|
+
const port = node.port ? this._genExpr(node.port) : '3000';
|
|
886
|
+
const host = node.host ? this._genExpr(node.host) : "'0.0.0.0'";
|
|
887
|
+
const name = node.name || '__thy_server';
|
|
888
|
+
this._emit(`// [ThyScript] Server declaration`);
|
|
889
|
+
this._emit(`if (!__thy_express) throw new Error('[ThyScript] express is not installed. Run: npm install express');`);
|
|
890
|
+
this._emit(`__thy_app.use(require('express').json());`);
|
|
891
|
+
this._emit(`__thy_app.use(require('express').urlencoded({ extended: true }));`);
|
|
892
|
+
// Body
|
|
893
|
+
this._genBlockOrList(node.body);
|
|
894
|
+
// listen
|
|
895
|
+
this._emit(`const ${name} = __thy_app.listen(${port}, ${host}, () => {`);
|
|
896
|
+
this._push();
|
|
897
|
+
this._emit(`__thy_print(\`[ThyScript] Server running on http://\${${host}}:\${${port}}\`);`);
|
|
898
|
+
this._pop();
|
|
899
|
+
this._emit(`});`);
|
|
900
|
+
// Socket.IO support
|
|
901
|
+
if (node.socket) {
|
|
902
|
+
this._emit(`const __thy_io = (() => { try { const { Server } = require('socket.io'); return new Server(${name}); } catch(_) { return null; } })();`);
|
|
903
|
+
this._emit(`if (__thy_io) { __thy_print('[ThyScript] Socket.IO attached'); }`);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
// ─── route declaration ───────────────────────────────────────────────────────
|
|
908
|
+
|
|
909
|
+
_genRouteDecl(node) {
|
|
910
|
+
this._expressAppDeclared = true;
|
|
911
|
+
const method = (node.method || 'get').toLowerCase();
|
|
912
|
+
const path_ = this._genExpr(node.path);
|
|
913
|
+
const reqVar = node.reqVar || 'request';
|
|
914
|
+
const resVar = node.resVar || 'response';
|
|
915
|
+
const isAsync = node.isAsync !== false;
|
|
916
|
+
this._emit(`if (__thy_app) {`);
|
|
917
|
+
this._push();
|
|
918
|
+
this._emit(`__thy_app.${method}(${path_}, ${isAsync ? 'async ' : ''}(${reqVar}, ${resVar}) => {`);
|
|
919
|
+
this._push();
|
|
920
|
+
this._emit(`const req = ${reqVar}; const res = ${resVar};`);
|
|
921
|
+
this._genBlockOrList(node.body);
|
|
922
|
+
this._pop();
|
|
923
|
+
this._emit(`});`);
|
|
924
|
+
this._pop();
|
|
925
|
+
this._emit(`}`);
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// ─── component (React-like) ──────────────────────────────────────────────────
|
|
929
|
+
|
|
930
|
+
_genComponentDecl(node) {
|
|
931
|
+
const name = node.name;
|
|
932
|
+
const props = (node.props || []).join(', ');
|
|
933
|
+
this._emit(`// [ThyScript] Component: ${name}`);
|
|
934
|
+
this._emit(`function ${name}({ ${props} }) {`);
|
|
935
|
+
this._push();
|
|
936
|
+
this._emit(`// state`);
|
|
937
|
+
for (const s of (node.state || [])) {
|
|
938
|
+
this._emit(`let __state_${s.name} = ${this._genExpr(s.init)};`);
|
|
939
|
+
}
|
|
940
|
+
this._emit(`// render`);
|
|
941
|
+
this._emit(`const render = () => {`);
|
|
942
|
+
this._push();
|
|
943
|
+
this._genBlockOrList(node.body);
|
|
944
|
+
this._pop();
|
|
945
|
+
this._emit(`};`);
|
|
946
|
+
this._emit(`render();`);
|
|
947
|
+
this._emit(`return { render, getState: () => ({ ${(node.state || []).map(s => s.name + ': __state_' + s.name).join(', ')} }) };`);
|
|
948
|
+
this._pop();
|
|
949
|
+
this._emit(`}`);
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
// ─── neural declaration (TensorFlow.js) ─────────────────────────────────────
|
|
953
|
+
|
|
954
|
+
_genNeuralDecl(node) {
|
|
955
|
+
if (!this._tfDeclared) {
|
|
956
|
+
this._tfDeclared = true;
|
|
957
|
+
this._emit(`const __thy_tf = (() => { try { return require('@tensorflow/tfjs-node'); } catch(_) { try { return require('@tensorflow/tfjs'); } catch(__) { return null; } } })();`);
|
|
958
|
+
}
|
|
959
|
+
const name = node.name || '__thy_model';
|
|
960
|
+
const layers = node.layers || [];
|
|
961
|
+
const optName = (node.optimizer || 'adam').toLowerCase();
|
|
962
|
+
const loss = node.loss || 'meanSquaredError';
|
|
963
|
+
const lr = node.learningRate || 0.001;
|
|
964
|
+
|
|
965
|
+
this._emit(`// [ThyScript] Neural network: ${name}`);
|
|
966
|
+
this._emit(`let ${name} = null;`);
|
|
967
|
+
this._emit(`if (__thy_tf) {`);
|
|
968
|
+
this._push();
|
|
969
|
+
this._emit(`${name} = __thy_tf.sequential();`);
|
|
970
|
+
for (const layer of layers) {
|
|
971
|
+
const ltype = layer.kind || 'dense';
|
|
972
|
+
const units = layer.units || 1;
|
|
973
|
+
const activ = layer.activation || 'relu';
|
|
974
|
+
const inputSh = layer.inputShape ? `, inputShape: [${layer.inputShape}]` : '';
|
|
975
|
+
this._emit(`${name}.add(__thy_tf.layers.${ltype}({ units: ${units}, activation: '${activ}'${inputSh} }));`);
|
|
976
|
+
}
|
|
977
|
+
const optStr = optName === 'adam' ?
|
|
978
|
+
`__thy_tf.train.adam(${lr})` :
|
|
979
|
+
optName === 'sgd' ? `__thy_tf.train.sgd(${lr})` :
|
|
980
|
+
`__thy_tf.train.${optName}(${lr})`;
|
|
981
|
+
this._emit(`${name}.compile({ optimizer: ${optStr}, loss: '${loss}', metrics: ['accuracy'] });`);
|
|
982
|
+
this._emit(`__thy_print('[ThyScript Neural] Model "${name}" compiled');`);
|
|
983
|
+
this._pop();
|
|
984
|
+
this._emit(`} else { __thy_print('[ThyScript Neural] TensorFlow.js not installed – model "${name}" skipped'); }`);
|
|
985
|
+
|
|
986
|
+
// train / predict helpers
|
|
987
|
+
this._emit(`const __train_${name} = async (xs, ys, epochs = 10, batchSize = 32) => {`);
|
|
988
|
+
this._push();
|
|
989
|
+
this._emit(`if (!${name}) return null;`);
|
|
990
|
+
this._emit(`const xsTensor = __thy_tf.tensor(xs);`);
|
|
991
|
+
this._emit(`const ysTensor = __thy_tf.tensor(ys);`);
|
|
992
|
+
this._emit(`const hist = await ${name}.fit(xsTensor, ysTensor, { epochs, batchSize });`);
|
|
993
|
+
this._emit(`xsTensor.dispose(); ysTensor.dispose();`);
|
|
994
|
+
this._emit(`return hist;`);
|
|
995
|
+
this._pop();
|
|
996
|
+
this._emit(`};`);
|
|
997
|
+
|
|
998
|
+
this._emit(`const __predict_${name} = (xs) => {`);
|
|
999
|
+
this._push();
|
|
1000
|
+
this._emit(`if (!${name}) return null;`);
|
|
1001
|
+
this._emit(`return ${name}.predict(__thy_tf.tensor(xs)).arraySync();`);
|
|
1002
|
+
this._pop();
|
|
1003
|
+
this._emit(`};`);
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
// ─── physics declaration (matter.js) ────────────────────────────────────────
|
|
1007
|
+
|
|
1008
|
+
_genPhysicsDecl(node) {
|
|
1009
|
+
if (!this._matterDeclared) {
|
|
1010
|
+
this._matterDeclared = true;
|
|
1011
|
+
this._emit(`const __thy_matter = (() => { try { return require('matter-js'); } catch(_) { return null; } })();`);
|
|
1012
|
+
}
|
|
1013
|
+
const name = node.name || '__thy_physics';
|
|
1014
|
+
const gravity = node.gravity != null ? node.gravity : 1;
|
|
1015
|
+
const bodies = node.bodies || [];
|
|
1016
|
+
|
|
1017
|
+
this._emit(`// [ThyScript] Physics world: ${name}`);
|
|
1018
|
+
this._emit(`let ${name} = null;`);
|
|
1019
|
+
this._emit(`if (__thy_matter) {`);
|
|
1020
|
+
this._push();
|
|
1021
|
+
this._emit(`const { Engine, World, Bodies, Runner } = __thy_matter;`);
|
|
1022
|
+
this._emit(`const __engine_${name} = Engine.create({ gravity: { y: ${gravity} } });`);
|
|
1023
|
+
this._emit(`${name} = __engine_${name}.world;`);
|
|
1024
|
+
for (const body of bodies) {
|
|
1025
|
+
const bname = body.name || this._uid_();
|
|
1026
|
+
const btype = body.kind || 'rectangle';
|
|
1027
|
+
const bx = body.x || 0;
|
|
1028
|
+
const by = body.y || 0;
|
|
1029
|
+
const bw = body.width || 100;
|
|
1030
|
+
const bh = body.height || 100;
|
|
1031
|
+
const bopts = body.isStatic ? ', { isStatic: true }' : '';
|
|
1032
|
+
this._emit(`const __body_${bname} = Bodies.${btype}(${bx}, ${by}, ${bw}, ${bh}${bopts});`);
|
|
1033
|
+
this._emit(`World.add(${name}, __body_${bname});`);
|
|
1034
|
+
}
|
|
1035
|
+
this._emit(`Runner.run(__engine_${name});`);
|
|
1036
|
+
this._emit(`__thy_print('[ThyScript Physics] World "${name}" running');`);
|
|
1037
|
+
this._pop();
|
|
1038
|
+
this._emit(`} else { __thy_print('[ThyScript Physics] matter-js not installed'); }`);
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
// ─── parallel declaration (worker_threads) ───────────────────────────────────
|
|
1042
|
+
|
|
1043
|
+
_genParallelDecl(node) {
|
|
1044
|
+
this._emit(`// [ThyScript] Parallel execution – worker_threads`);
|
|
1045
|
+
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');
|
|
1046
|
+
const tasks = node.tasks || [];
|
|
1047
|
+
const workers = node.workers || 4;
|
|
1048
|
+
const name = node.name || '__thy_parallel';
|
|
1049
|
+
|
|
1050
|
+
this._emit(`const { Worker: __W, isMainThread: __IMT } = require('worker_threads');`);
|
|
1051
|
+
this._emit(`const __thy_parallel_run = async (fn, data, nWorkers = ${workers}) => {`);
|
|
1052
|
+
this._push();
|
|
1053
|
+
this._emit(`const chunks = [];`);
|
|
1054
|
+
this._emit(`const chunkSize = Math.ceil(data.length / nWorkers);`);
|
|
1055
|
+
this._emit(`for (let i = 0; i < data.length; i += chunkSize) chunks.push(data.slice(i, i + chunkSize));`);
|
|
1056
|
+
this._emit(`const code = \`'use strict'; const { parentPort, workerData } = require('worker_threads'); parentPort.postMessage((\${fn.toString()})(workerData));\`;`);
|
|
1057
|
+
this._emit(`const results = await Promise.all(chunks.map(chunk => new Promise((res, rej) => {`);
|
|
1058
|
+
this._push();
|
|
1059
|
+
this._emit(`const w = new __W(code, { eval: true, workerData: chunk });`);
|
|
1060
|
+
this._emit(`w.on('message', res); w.on('error', rej);`);
|
|
1061
|
+
this._pop();
|
|
1062
|
+
this._emit(`})));`);
|
|
1063
|
+
this._emit(`return results.flat();`);
|
|
1064
|
+
this._pop();
|
|
1065
|
+
this._emit(`};`);
|
|
1066
|
+
|
|
1067
|
+
for (const task of tasks) {
|
|
1068
|
+
const tname = task.name || this._uid_();
|
|
1069
|
+
const tdata = this._genExpr(task.data || { type: 'Literal', value: [] });
|
|
1070
|
+
this._emit(`const __parallel_result_${tname} = await __thy_parallel_run(${task.fn || 'x => x'}, ${tdata}, ${workers});`);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// ─── stream declaration ──────────────────────────────────────────────────────
|
|
1075
|
+
|
|
1076
|
+
_genStreamDecl(node) {
|
|
1077
|
+
this._emit(`// [ThyScript] Stream`);
|
|
1078
|
+
const name = node.name || '__thy_stream';
|
|
1079
|
+
const src = node.source ? this._genExpr(node.source) : "process.stdin";
|
|
1080
|
+
const dest = node.dest ? this._genExpr(node.dest) : "process.stdout";
|
|
1081
|
+
const transforms = node.transforms || [];
|
|
1082
|
+
|
|
1083
|
+
this._emit(`const { Transform: __TransformStream, pipeline: __pipeline } = require('stream');`);
|
|
1084
|
+
this._emit(`const { promisify: __promisify } = require('util');`);
|
|
1085
|
+
this._emit(`const __pipelineAsync = __promisify(__pipeline);`);
|
|
1086
|
+
|
|
1087
|
+
for (const t of transforms) {
|
|
1088
|
+
const tname = t.name || this._uid_();
|
|
1089
|
+
const logic = t.fn || '(chunk, enc, cb) => cb(null, chunk)';
|
|
1090
|
+
this._emit(`const __transform_${tname} = new __TransformStream({ transform: ${logic} });`);
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
const pipeChain = transforms.length > 0
|
|
1094
|
+
? [src, ...transforms.map(t => `__transform_${t.name || '__t'}`), dest].join(', ')
|
|
1095
|
+
: `${src}, ${dest}`;
|
|
1096
|
+
this._emit(`await __pipelineAsync(${pipeChain}).catch(e => console.error('[ThyScript Stream]', e));`);
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// ─── switch ──────────────────────────────────────────────────────────────────
|
|
1100
|
+
|
|
1101
|
+
_genSwitchStmt(node) {
|
|
1102
|
+
const disc = this._genExpr(node.discriminant);
|
|
1103
|
+
this._emit(`switch (${disc}) {`);
|
|
1104
|
+
this._push();
|
|
1105
|
+
for (const c of (node.cases || [])) {
|
|
1106
|
+
if (c.test) {
|
|
1107
|
+
this._emit(`case ${this._genExpr(c.test)}:`);
|
|
1108
|
+
} else {
|
|
1109
|
+
this._emit('default:');
|
|
1110
|
+
}
|
|
1111
|
+
this._push();
|
|
1112
|
+
this._genBlockOrList(c.body);
|
|
1113
|
+
if (!c.fallthrough) this._emit('break;');
|
|
1114
|
+
this._pop();
|
|
1115
|
+
}
|
|
1116
|
+
this._pop();
|
|
1117
|
+
this._emit('}');
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// ─── match statement (pattern matching) ──────────────────────────────────────
|
|
1121
|
+
|
|
1122
|
+
_genMatchStmt(node) {
|
|
1123
|
+
const disc = this._genExpr(node.discriminant);
|
|
1124
|
+
const tmp = this._uid_();
|
|
1125
|
+
this._emit(`const ${tmp} = ${disc};`);
|
|
1126
|
+
let first = true;
|
|
1127
|
+
for (const arm of (node.arms || [])) {
|
|
1128
|
+
const kw = first ? 'if' : '} else if';
|
|
1129
|
+
first = false;
|
|
1130
|
+
if (arm.pattern === '_' || arm.isOtherwise) {
|
|
1131
|
+
this._emit('} else {');
|
|
1132
|
+
} else {
|
|
1133
|
+
const pat = this._genExpr(arm.pattern);
|
|
1134
|
+
this._emit(`${kw} (${tmp} === ${pat}) {`);
|
|
1135
|
+
}
|
|
1136
|
+
this._push();
|
|
1137
|
+
this._genBlockOrList(arm.body);
|
|
1138
|
+
this._pop();
|
|
1139
|
+
}
|
|
1140
|
+
this._emit('}');
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
// ─── expression generator ────────────────────────────────────────────────────
|
|
1144
|
+
|
|
1145
|
+
_genExpr(node) {
|
|
1146
|
+
if (node == null) return 'undefined';
|
|
1147
|
+
|
|
1148
|
+
// Raw string shorthand
|
|
1149
|
+
if (typeof node === 'string') return node;
|
|
1150
|
+
if (typeof node === 'number') return String(node);
|
|
1151
|
+
if (typeof node === 'boolean') return String(node);
|
|
1152
|
+
|
|
1153
|
+
switch (node.type) {
|
|
1154
|
+
case 'Literal': return this._genLiteral(node);
|
|
1155
|
+
case 'Identifier': return this._genIdentifier(node);
|
|
1156
|
+
case 'BinaryExpr': return this._genBinaryExpr(node);
|
|
1157
|
+
case 'UnaryExpr': return this._genUnaryExpr(node);
|
|
1158
|
+
case 'LogicalExpr': return this._genLogicalExpr(node);
|
|
1159
|
+
case 'AssignExpr': return this._genAssignExpr(node);
|
|
1160
|
+
case 'CallExpr': return this._genCallExpr(node);
|
|
1161
|
+
case 'MemberExpr': return this._genMemberExpr(node);
|
|
1162
|
+
case 'IndexExpr': return this._genIndexExpr(node);
|
|
1163
|
+
case 'ArrayExpr': return this._genArrayExpr(node);
|
|
1164
|
+
case 'ObjectExpr': return this._genObjectExpr(node);
|
|
1165
|
+
case 'ArrowFunc':
|
|
1166
|
+
case 'ArrowFunctionExpr':return this._genArrowFunc(node);
|
|
1167
|
+
case 'FunctionExpr': return this._genFunctionExpr(node);
|
|
1168
|
+
case 'ConditionalExpr': return this._genConditionalExpr(node);
|
|
1169
|
+
case 'TemplateLiteral': return this._genTemplateLiteral(node);
|
|
1170
|
+
case 'SpreadExpr': return `...${this._genExpr(node.argument)}`;
|
|
1171
|
+
case 'AwaitExpr': return `await ${this._genExpr(node.argument)}`;
|
|
1172
|
+
case 'YieldExpr': return `yield ${this._genExpr(node.argument)}`;
|
|
1173
|
+
case 'NewExpr': return this._genNewExpr(node);
|
|
1174
|
+
case 'InputExpr': return `await __thy_input(${node.prompt ? this._genExpr(node.prompt) : "''"})`;
|
|
1175
|
+
case 'TypeofExpr': return `typeof ${this._genExpr(node.argument)}`;
|
|
1176
|
+
case 'InstanceofExpr': return `(${this._genExpr(node.left)} instanceof ${this._genExpr(node.right)})`;
|
|
1177
|
+
case 'NullCoalesce': return `(${this._genExpr(node.left)} ?? ${this._genExpr(node.right)})`;
|
|
1178
|
+
case 'OptionalChain': return `${this._genExpr(node.object)}?.${this._genExpr(node.property)}`;
|
|
1179
|
+
case 'DeleteExpr': return `delete ${this._genExpr(node.argument)}`;
|
|
1180
|
+
case 'VoidExpr': return `void ${this._genExpr(node.argument)}`;
|
|
1181
|
+
case 'SequenceExpr': return `(${(node.expressions || []).map(e => this._genExpr(e)).join(', ')})`;
|
|
1182
|
+
case 'TaggedTemplate': return `${this._genExpr(node.tag)}\`${this._genExpr(node.quasi)}\``;
|
|
1183
|
+
case 'CalculateExpr': return `(${this._genExpr(node.expression)})`;
|
|
1184
|
+
case 'MathExpr': return this._genMathExpr(node);
|
|
1185
|
+
case 'VectorExpr': return this._genVectorExpr(node);
|
|
1186
|
+
case 'MatrixExpr': return this._genMatrixExpr(node);
|
|
1187
|
+
|
|
1188
|
+
default:
|
|
1189
|
+
// Try to coerce unknown expression nodes
|
|
1190
|
+
if (node.value !== undefined && Object.keys(node).length <= 3) return this._genLiteral(node);
|
|
1191
|
+
return `/* unknown expr: ${node.type} */ undefined`;
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
_genLiteral(node) {
|
|
1196
|
+
const v = node.value;
|
|
1197
|
+
if (v === null) return 'null';
|
|
1198
|
+
if (v === undefined) return 'undefined';
|
|
1199
|
+
if (typeof v === 'string') return `'${this._escStr(v)}'`;
|
|
1200
|
+
if (typeof v === 'boolean') return String(v);
|
|
1201
|
+
if (typeof v === 'number') return String(v);
|
|
1202
|
+
// Array / object literals embedded
|
|
1203
|
+
try { return JSON.stringify(v); } catch(_) { return String(v); }
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
_genIdentifier(node) {
|
|
1207
|
+
// Map ThyScript built-in identifiers to stdlib aliases
|
|
1208
|
+
const aliases = {
|
|
1209
|
+
'true': 'true',
|
|
1210
|
+
'false': 'false',
|
|
1211
|
+
'null': 'null',
|
|
1212
|
+
'undefined': 'undefined',
|
|
1213
|
+
'NaN': 'NaN',
|
|
1214
|
+
'Infinity': 'Infinity',
|
|
1215
|
+
'this': 'this',
|
|
1216
|
+
'self': 'this',
|
|
1217
|
+
'super': 'super',
|
|
1218
|
+
'print': '__thy_print',
|
|
1219
|
+
'len': '__thy_len',
|
|
1220
|
+
'range': '__thy_range',
|
|
1221
|
+
'sleep': '__thy_stdlib.time.sleep',
|
|
1222
|
+
'now': '__thy_stdlib.time.now',
|
|
1223
|
+
'env': '__thy_stdlib.proc.env',
|
|
1224
|
+
'fetch': '__thy_stdlib.http.fetch',
|
|
1225
|
+
'save': '__thy_stdlib.persist.save',
|
|
1226
|
+
'load': '__thy_stdlib.persist.load',
|
|
1227
|
+
};
|
|
1228
|
+
return aliases[node.name] || node.name;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
_genBinaryExpr(node) {
|
|
1232
|
+
const l = this._genExpr(node.left);
|
|
1233
|
+
const r = this._genExpr(node.right);
|
|
1234
|
+
const opMap = {
|
|
1235
|
+
'+': '+', '-': '-', '*': '*', '/': '/',
|
|
1236
|
+
'%': '%', '**': '**', '&': '&', '|': '|',
|
|
1237
|
+
'^': '^', '<<': '<<', '>>': '>>',
|
|
1238
|
+
'==': '===', '!=': '!==', '<': '<', '>': '>',
|
|
1239
|
+
'<=': '<=', '>=': '>=',
|
|
1240
|
+
'and': '&&', 'or': '||',
|
|
1241
|
+
'is': '===', 'isnt': '!==',
|
|
1242
|
+
};
|
|
1243
|
+
const rawOp = node.operator || node.op;
|
|
1244
|
+
const op = opMap[rawOp] || rawOp;
|
|
1245
|
+
|
|
1246
|
+
if (this.options.optimise && (op === '+' || op === '-' || op === '*' || op === '/')) {
|
|
1247
|
+
return `(${l} ${op} ${r})`;
|
|
1248
|
+
}
|
|
1249
|
+
return `(${l} ${op} ${r})`;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
_genUnaryExpr(node) {
|
|
1253
|
+
const opMap = { 'not': '!', '!': '!', '-': '-', '+': '+', '~': '~', '++': '++', '--': '--' };
|
|
1254
|
+
const rawOp = node.operator || node.op;
|
|
1255
|
+
const op = opMap[rawOp] || rawOp;
|
|
1256
|
+
const arg = this._genExpr(node.argument || node.operand);
|
|
1257
|
+
// Word-like unary operators (typeof/await/yield/delete/void) need a space.
|
|
1258
|
+
const wordOps = new Set(['typeof', 'await', 'yield', 'delete', 'void', 'new', 'instanceof', 'not', 'in']);
|
|
1259
|
+
const sep = wordOps.has(op) ? ' ' : '';
|
|
1260
|
+
return node.prefix !== false ? `${op}${sep}${arg}` : `${arg}${op}`;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
_genLogicalExpr(node) {
|
|
1264
|
+
const l = this._genExpr(node.left);
|
|
1265
|
+
const r = this._genExpr(node.right);
|
|
1266
|
+
const opMap = { 'and': '&&', 'or': '||', '&&': '&&', '||': '||' };
|
|
1267
|
+
const rawOp = node.operator || node.op;
|
|
1268
|
+
const op = opMap[rawOp] || rawOp;
|
|
1269
|
+
return `(${l} ${op} ${r})`;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
_genAssignExpr(node) {
|
|
1273
|
+
const l = this._genExpr(node.left);
|
|
1274
|
+
const r = this._genExpr(node.right);
|
|
1275
|
+
const op = node.operator || node.op || '=';
|
|
1276
|
+
return `${l} ${op} ${r}`;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
_genCallExpr(node) {
|
|
1280
|
+
const callee = this._genExpr(node.callee);
|
|
1281
|
+
const args = (node.args || node.arguments || []).map(a => this._genExpr(a)).join(', ');
|
|
1282
|
+
const opt = node.optional ? '?.' : '';
|
|
1283
|
+
|
|
1284
|
+
// Intercept ThyScript built-in calls and map them
|
|
1285
|
+
const builtins = {
|
|
1286
|
+
'print': `__thy_print(${args})`,
|
|
1287
|
+
'len': `__thy_len(${args})`,
|
|
1288
|
+
'range': `__thy_range(${args})`,
|
|
1289
|
+
'sleep': `await __thy_stdlib.time.sleep(${args})`,
|
|
1290
|
+
'fetch': `await __thy_stdlib.http.fetch(${args})`,
|
|
1291
|
+
'hash': `__thy_stdlib.security.hash(${args})`,
|
|
1292
|
+
'save': `__thy_stdlib.persist.save(${args})`,
|
|
1293
|
+
'load': `__thy_stdlib.persist.load(${args})`,
|
|
1294
|
+
'type': `__thy_stdlib.types.type(${args})`,
|
|
1295
|
+
'int': `parseInt(${args})`,
|
|
1296
|
+
'float': `parseFloat(${args})`,
|
|
1297
|
+
'str': `String(${args})`,
|
|
1298
|
+
'bool': `Boolean(${args})`,
|
|
1299
|
+
'abs': `Math.abs(${args})`,
|
|
1300
|
+
'sqrt': `Math.sqrt(${args})`,
|
|
1301
|
+
'floor': `Math.floor(${args})`,
|
|
1302
|
+
'ceil': `Math.ceil(${args})`,
|
|
1303
|
+
'round': `Math.round(${args})`,
|
|
1304
|
+
'min': `Math.min(${args})`,
|
|
1305
|
+
'max': `Math.max(${args})`,
|
|
1306
|
+
'pow': `Math.pow(${args})`,
|
|
1307
|
+
'random': `Math.random()`,
|
|
1308
|
+
'input': `await __thy_input(${args})`,
|
|
1309
|
+
'calculate': `__thy_stdlib.__thy_calculate(${args})`,
|
|
1310
|
+
};
|
|
1311
|
+
|
|
1312
|
+
const calleeStr = typeof node.callee === 'object' && node.callee.type === 'Identifier'
|
|
1313
|
+
? node.callee.name : null;
|
|
1314
|
+
|
|
1315
|
+
if (calleeStr && builtins[calleeStr]) return builtins[calleeStr];
|
|
1316
|
+
|
|
1317
|
+
const awaitPrefix = node.isAwaited ? 'await ' : '';
|
|
1318
|
+
return `${awaitPrefix}${callee}${opt}(${args})`;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
_genMemberExpr(node) {
|
|
1322
|
+
const obj_ = this._genExpr(node.object);
|
|
1323
|
+
const prop = node.computed
|
|
1324
|
+
? `[${this._genExpr(node.property)}]`
|
|
1325
|
+
: `.${typeof node.property === 'string' ? node.property : this._genExpr(node.property)}`;
|
|
1326
|
+
const opt = node.optional ? '?' : '';
|
|
1327
|
+
return `${obj_}${opt}${prop}`;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
_genIndexExpr(node) {
|
|
1331
|
+
const obj_ = this._genExpr(node.object);
|
|
1332
|
+
const idx = this._genExpr(node.index);
|
|
1333
|
+
return `${obj_}[${idx}]`;
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
_genArrayExpr(node) {
|
|
1337
|
+
const elems = (node.elements || []).map(e => e ? this._genExpr(e) : '').join(', ');
|
|
1338
|
+
return `[${elems}]`;
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
_genObjectExpr(node) {
|
|
1342
|
+
const props = (node.properties || []).map(p => {
|
|
1343
|
+
if (p.spread) return `...${this._genExpr(p.argument)}`;
|
|
1344
|
+
const key = p.computed ? `[${this._genExpr(p.key)}]` :
|
|
1345
|
+
(typeof p.key === 'string' ? p.key : this._genExpr(p.key));
|
|
1346
|
+
if (p.shorthand) return key;
|
|
1347
|
+
const val = this._genExpr(p.value);
|
|
1348
|
+
if (p.method) {
|
|
1349
|
+
const isAsync = p.async ? 'async ' : '';
|
|
1350
|
+
const isGen = p.generator ? '*' : '';
|
|
1351
|
+
const params = (p.params || []).join(', ');
|
|
1352
|
+
return `${isAsync}${isGen}${key}(${params}) { ${val} }`;
|
|
1353
|
+
}
|
|
1354
|
+
return `${key}: ${val}`;
|
|
1355
|
+
}).join(', ');
|
|
1356
|
+
return `{ ${props} }`;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
_genArrowFunc(node) {
|
|
1360
|
+
const params = (node.params || []).map(p =>
|
|
1361
|
+
typeof p === 'string' ? p : p.name || this._genExpr(p)).join(', ');
|
|
1362
|
+
const isAsync = node.isAsync ? 'async ' : '';
|
|
1363
|
+
if (node.expression || (node.body && node.body.type !== 'Block' && !Array.isArray(node.body))) {
|
|
1364
|
+
return `${isAsync}(${params}) => ${this._genExpr(node.body)}`;
|
|
1365
|
+
}
|
|
1366
|
+
const lines = [];
|
|
1367
|
+
const savedBody = this._bodyLines;
|
|
1368
|
+
this._bodyLines = lines;
|
|
1369
|
+
const savedIndent = this._indent;
|
|
1370
|
+
this._indent = 0;
|
|
1371
|
+
this._genBlockOrList(node.body);
|
|
1372
|
+
this._bodyLines = savedBody;
|
|
1373
|
+
this._indent = savedIndent;
|
|
1374
|
+
return `${isAsync}(${params}) => { ${lines.join(' ')} }`;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
_genFunctionExpr(node) {
|
|
1378
|
+
const name = node.name ? ` ${node.name}` : '';
|
|
1379
|
+
const params = (node.params || []).map(p =>
|
|
1380
|
+
typeof p === 'string' ? p : p.name || this._genExpr(p)).join(', ');
|
|
1381
|
+
const isAsync = node.isAsync ? 'async ' : '';
|
|
1382
|
+
const lines = [];
|
|
1383
|
+
const savedBody = this._bodyLines;
|
|
1384
|
+
this._bodyLines = lines;
|
|
1385
|
+
const savedIndent = this._indent;
|
|
1386
|
+
this._indent = 0;
|
|
1387
|
+
this._genBlockOrList(node.body);
|
|
1388
|
+
this._bodyLines = savedBody;
|
|
1389
|
+
this._indent = savedIndent;
|
|
1390
|
+
return `${isAsync}function${name}(${params}) { ${lines.join(' ')} }`;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
_genConditionalExpr(node) {
|
|
1394
|
+
const test = this._genExpr(node.test);
|
|
1395
|
+
const cons = this._genExpr(node.consequent);
|
|
1396
|
+
const alt = this._genExpr(node.alternate);
|
|
1397
|
+
return `(${test} ? ${cons} : ${alt})`;
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
_genTemplateLiteral(node) {
|
|
1401
|
+
// node.parts is an array of { type: 'str'|'expr', value }
|
|
1402
|
+
const parts = (node.parts || node.quasis || []).map(p => {
|
|
1403
|
+
if (p.type === 'str' || p.type === 'TemplateElement') {
|
|
1404
|
+
const raw = (p.value && p.value.raw) ? p.value.raw : String(p.value || '');
|
|
1405
|
+
return raw;
|
|
1406
|
+
}
|
|
1407
|
+
// expr part: the embedded expression is stored in p.value (sub-AST)
|
|
1408
|
+
// or p.expression (ESTree-style). Fall back to the part itself.
|
|
1409
|
+
const exprNode = (p.expression != null) ? p.expression : (p.value != null ? p.value : p);
|
|
1410
|
+
return `\${${this._genExpr(exprNode)}}`;
|
|
1411
|
+
});
|
|
1412
|
+
return '`' + parts.join('') + '`';
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
_genNewExpr(node) {
|
|
1416
|
+
let ctorNode = node.callee;
|
|
1417
|
+
let argsList = node.args || node.arguments || [];
|
|
1418
|
+
if (ctorNode && ctorNode.type === 'CallExpr') {
|
|
1419
|
+
argsList = ctorNode.args || ctorNode.arguments || [];
|
|
1420
|
+
ctorNode = ctorNode.callee;
|
|
1421
|
+
}
|
|
1422
|
+
const ctor = this._genExpr(ctorNode);
|
|
1423
|
+
const args = argsList.map(a => this._genExpr(a)).join(', ');
|
|
1424
|
+
return `new ${ctor}(${args})`;
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
// ─── math expression (fast-math) ─────────────────────────────────────────────
|
|
1428
|
+
|
|
1429
|
+
_genMathExpr(node) {
|
|
1430
|
+
// Use typed-array backed computation for numeric heavy paths
|
|
1431
|
+
// V8 will emit efficient machine code for monomorphic Float64 ops
|
|
1432
|
+
const expr = this._genExpr(node.expression);
|
|
1433
|
+
return `(__thy_f64[0] = ${expr}, __thy_f64[0])`;
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
_genVectorExpr(node) {
|
|
1437
|
+
const components = (node.components || []).map(c => this._genExpr(c)).join(', ');
|
|
1438
|
+
return `new Float64Array([${components}])`;
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
_genMatrixExpr(node) {
|
|
1442
|
+
const rows = (node.rows || []).map(r =>
|
|
1443
|
+
`[${(r || []).map(c => this._genExpr(c)).join(', ')}]`
|
|
1444
|
+
).join(', ');
|
|
1445
|
+
return `[${rows}]`;
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
module.exports = CodeGenerator;
|