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,283 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MemoryManager.js — ThyScript Memory Management & Heap Tracking
|
|
5
|
+
*
|
|
6
|
+
* Tracks object allocations, monitors heap health, triggers GC, and
|
|
7
|
+
* can produce V8 heap snapshots for offline analysis.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const v8 = require('v8');
|
|
11
|
+
const { EventEmitter } = require('events');
|
|
12
|
+
|
|
13
|
+
// Default limits
|
|
14
|
+
const DEFAULT_LIMIT_MB = 512; // warn above 80% of this
|
|
15
|
+
const DEFAULT_CHECK_MS = 5000; // periodic check interval
|
|
16
|
+
const HIGH_MEMORY_PCT = 0.80; // 80% threshold
|
|
17
|
+
|
|
18
|
+
class MemoryManager extends EventEmitter {
|
|
19
|
+
/**
|
|
20
|
+
* @param {object} [opts]
|
|
21
|
+
* @param {number} [opts.limitMB=512] - heap limit in megabytes
|
|
22
|
+
* @param {number} [opts.checkMs=5000] - interval for periodic checks
|
|
23
|
+
* @param {boolean} [opts.autoCheck=true] - start periodic checks automatically
|
|
24
|
+
*/
|
|
25
|
+
constructor(opts = {}) {
|
|
26
|
+
super();
|
|
27
|
+
|
|
28
|
+
this._limitBytes = (opts.limitMB ?? DEFAULT_LIMIT_MB) * 1024 * 1024;
|
|
29
|
+
this._checkMs = opts.checkMs ?? DEFAULT_CHECK_MS;
|
|
30
|
+
this._autoCheck = opts.autoCheck ?? true;
|
|
31
|
+
|
|
32
|
+
/** @type {Map<string, WeakRef|{ref:any}>} */
|
|
33
|
+
this._tracked = new Map();
|
|
34
|
+
|
|
35
|
+
/** allocation counter */
|
|
36
|
+
this._allocated = 0;
|
|
37
|
+
/** release counter */
|
|
38
|
+
this._released = 0;
|
|
39
|
+
|
|
40
|
+
/** periodic check timer */
|
|
41
|
+
this._timer = null;
|
|
42
|
+
|
|
43
|
+
/** high-memory callbacks */
|
|
44
|
+
this._highMemCbs = [];
|
|
45
|
+
|
|
46
|
+
if (this._autoCheck) this._startPeriodicCheck();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ─── Periodic check ────────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
_startPeriodicCheck() {
|
|
52
|
+
if (this._timer) return;
|
|
53
|
+
this._timer = setInterval(() => {
|
|
54
|
+
const warning = this.checkLeak();
|
|
55
|
+
if (warning) {
|
|
56
|
+
this.emit('highMemory', warning);
|
|
57
|
+
this._highMemCbs.forEach(cb => {
|
|
58
|
+
try { cb(warning); } catch {}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}, this._checkMs);
|
|
62
|
+
|
|
63
|
+
// Do not keep the process alive just for memory monitoring
|
|
64
|
+
if (this._timer.unref) this._timer.unref();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
_stopPeriodicCheck() {
|
|
68
|
+
if (this._timer) {
|
|
69
|
+
clearInterval(this._timer);
|
|
70
|
+
this._timer = null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
startMonitoring(intervalMs) {
|
|
75
|
+
if (intervalMs) this._checkMs = intervalMs;
|
|
76
|
+
this._startPeriodicCheck();
|
|
77
|
+
return this;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
stopMonitoring() {
|
|
81
|
+
this._stopPeriodicCheck();
|
|
82
|
+
return this;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ─── Public API ────────────────────────────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Track an object allocation.
|
|
89
|
+
* Uses WeakRef when available so tracking does not prevent GC.
|
|
90
|
+
*
|
|
91
|
+
* @param {string} name - identifier for this allocation
|
|
92
|
+
* @param {object} ref - the object to track
|
|
93
|
+
*/
|
|
94
|
+
track(name, ref) {
|
|
95
|
+
if (ref !== null && typeof ref === 'object' && typeof WeakRef !== 'undefined') {
|
|
96
|
+
this._tracked.set(name, { type: 'weak', ref: new WeakRef(ref) });
|
|
97
|
+
} else {
|
|
98
|
+
this._tracked.set(name, { type: 'strong', ref });
|
|
99
|
+
}
|
|
100
|
+
this._allocated++;
|
|
101
|
+
this.emit('track', name);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Release a tracked reference.
|
|
106
|
+
* @param {string} name
|
|
107
|
+
*/
|
|
108
|
+
release(name) {
|
|
109
|
+
if (this._tracked.has(name)) {
|
|
110
|
+
this._tracked.delete(name);
|
|
111
|
+
this._released++;
|
|
112
|
+
this.emit('release', name);
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Check how many tracked objects are still alive (WeakRef targets not GC'd).
|
|
120
|
+
* @returns {number}
|
|
121
|
+
*/
|
|
122
|
+
liveCount() {
|
|
123
|
+
let alive = 0;
|
|
124
|
+
for (const [, entry] of this._tracked) {
|
|
125
|
+
if (entry.type === 'strong') { alive++; continue; }
|
|
126
|
+
const deref = entry.ref.deref();
|
|
127
|
+
if (deref !== undefined) alive++;
|
|
128
|
+
}
|
|
129
|
+
return alive;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get combined memory statistics.
|
|
134
|
+
* @returns {{heapUsed:number, heapTotal:number, external:number, rss:number,
|
|
135
|
+
* allocated:number, released:number, live:number,
|
|
136
|
+
* limitBytes:number, usagePct:number, v8HeapStats:object}}
|
|
137
|
+
*/
|
|
138
|
+
getStats() {
|
|
139
|
+
const mu = process.memoryUsage();
|
|
140
|
+
const v8Stats = v8.getHeapStatistics();
|
|
141
|
+
const usagePct = mu.heapUsed / this._limitBytes;
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
heapUsed: mu.heapUsed,
|
|
145
|
+
heapTotal: mu.heapTotal,
|
|
146
|
+
external: mu.external,
|
|
147
|
+
rss: mu.rss,
|
|
148
|
+
arrayBuffers: mu.arrayBuffers ?? 0,
|
|
149
|
+
allocated: this._allocated,
|
|
150
|
+
released: this._released,
|
|
151
|
+
live: this.liveCount(),
|
|
152
|
+
limitBytes: this._limitBytes,
|
|
153
|
+
usagePct: Math.min(1, usagePct),
|
|
154
|
+
v8HeapStats,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Check for a potential memory leak (heap > threshold).
|
|
160
|
+
* @returns {string|null} warning message or null if healthy
|
|
161
|
+
*/
|
|
162
|
+
checkLeak() {
|
|
163
|
+
const mu = process.memoryUsage();
|
|
164
|
+
const pct = mu.heapUsed / this._limitBytes;
|
|
165
|
+
const usedMB = (mu.heapUsed / 1024 / 1024).toFixed(1);
|
|
166
|
+
const limitMB = (this._limitBytes / 1024 / 1024).toFixed(0);
|
|
167
|
+
|
|
168
|
+
if (pct >= HIGH_MEMORY_PCT) {
|
|
169
|
+
return `Heap usage is ${(pct * 100).toFixed(1)}% (${usedMB} MB / ${limitMB} MB limit). Consider calling gc() or releasing references.`;
|
|
170
|
+
}
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Set the memory limit in bytes.
|
|
176
|
+
* @param {number} bytes
|
|
177
|
+
*/
|
|
178
|
+
setLimit(bytes) {
|
|
179
|
+
if (typeof bytes !== 'number' || bytes <= 0) {
|
|
180
|
+
throw new RangeError('Memory limit must be a positive number of bytes.');
|
|
181
|
+
}
|
|
182
|
+
this._limitBytes = bytes;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Register a callback for when memory usage is high.
|
|
187
|
+
* @param {function(string):void} callback
|
|
188
|
+
*/
|
|
189
|
+
onHighMemory(callback) {
|
|
190
|
+
if (typeof callback !== 'function') throw new TypeError('callback must be a function');
|
|
191
|
+
this._highMemCbs.push(callback);
|
|
192
|
+
// Also wire up the EventEmitter listener for symmetry
|
|
193
|
+
this.on('highMemory', callback);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Trigger garbage collection.
|
|
198
|
+
* Only available when Node.js is started with --expose-gc.
|
|
199
|
+
* @returns {boolean} true if GC was triggered
|
|
200
|
+
*/
|
|
201
|
+
gc() {
|
|
202
|
+
if (typeof global.gc === 'function') {
|
|
203
|
+
global.gc();
|
|
204
|
+
this.emit('gc');
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Write a V8 heap snapshot to a file.
|
|
212
|
+
* @param {string} [filename] - output path (defaults to heap-TIMESTAMP.heapsnapshot)
|
|
213
|
+
* @returns {string} the path where the snapshot was written
|
|
214
|
+
*/
|
|
215
|
+
snapshot(filename) {
|
|
216
|
+
const outPath = filename || `heap-${Date.now()}.heapsnapshot`;
|
|
217
|
+
const snapshotStream = v8.writeHeapSnapshot(outPath);
|
|
218
|
+
this.emit('snapshot', snapshotStream || outPath);
|
|
219
|
+
return snapshotStream || outPath;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Stop the periodic memory checker and clean up.
|
|
224
|
+
*/
|
|
225
|
+
destroy() {
|
|
226
|
+
this._stopPeriodicCheck();
|
|
227
|
+
this._tracked.clear();
|
|
228
|
+
this._highMemCbs = [];
|
|
229
|
+
this.removeAllListeners();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ─── Diagnostic helpers ────────────────────────────────────────────────────
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Human-readable memory report.
|
|
236
|
+
* @returns {string}
|
|
237
|
+
*/
|
|
238
|
+
report() {
|
|
239
|
+
const s = this.getStats();
|
|
240
|
+
const mb = (n) => (n / 1024 / 1024).toFixed(2) + ' MB';
|
|
241
|
+
return [
|
|
242
|
+
'┌─ MemoryManager Report ───────────────────────────────┐',
|
|
243
|
+
`│ Heap Used : ${mb(s.heapUsed).padEnd(12)} │`,
|
|
244
|
+
`│ Heap Total : ${mb(s.heapTotal).padEnd(12)} │`,
|
|
245
|
+
`│ External : ${mb(s.external).padEnd(12)} │`,
|
|
246
|
+
`│ RSS : ${mb(s.rss).padEnd(12)} │`,
|
|
247
|
+
`│ Limit : ${mb(s.limitBytes).padEnd(12)} │`,
|
|
248
|
+
`│ Usage : ${(s.usagePct * 100).toFixed(1).padEnd(6)} % │`,
|
|
249
|
+
`│ Tracked Allocs : ${String(s.allocated).padEnd(12)} │`,
|
|
250
|
+
`│ Tracked Rel. : ${String(s.released).padEnd(12)} │`,
|
|
251
|
+
`│ Live Objects : ${String(s.live).padEnd(12)} │`,
|
|
252
|
+
'└──────────────────────────────────────────────────────┘',
|
|
253
|
+
].join('\n');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* List all currently tracked allocation names.
|
|
258
|
+
* @returns {string[]}
|
|
259
|
+
*/
|
|
260
|
+
list() {
|
|
261
|
+
return Array.from(this._tracked.keys());
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ─── V8 heap statistics helpers ───────────────────────────────────────────
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Get V8 heap space statistics (each space: new_space, old_space, etc.)
|
|
268
|
+
* @returns {object[]}
|
|
269
|
+
*/
|
|
270
|
+
heapSpaces() {
|
|
271
|
+
return v8.getHeapSpaceStatistics();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Get V8 heap code statistics.
|
|
276
|
+
* @returns {object}
|
|
277
|
+
*/
|
|
278
|
+
codeStats() {
|
|
279
|
+
return v8.getHeapCodeStatistics();
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
module.exports = MemoryManager;
|