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.
Files changed (49) hide show
  1. package/bin/thy-node.js +332 -0
  2. package/bin/thy.js +828 -0
  3. package/bin/thys.js +25 -0
  4. package/examples/calculator.thy +274 -0
  5. package/examples/calculator.thy.js +628 -0
  6. package/examples/hello.thy +34 -0
  7. package/examples/login.thy +382 -0
  8. package/examples/login.thy.js +710 -0
  9. package/package.json +54 -0
  10. package/scripts/_class.js +18 -0
  11. package/scripts/_class2.js +11 -0
  12. package/scripts/_class3.js +21 -0
  13. package/scripts/_dstruct.js +20 -0
  14. package/scripts/_dstruct2.js +24 -0
  15. package/scripts/_scaffold.js +16 -0
  16. package/scripts/_scaffold2.js +16 -0
  17. package/scripts/_scaffold3.js +26 -0
  18. package/scripts/_tpl_compile.js +13 -0
  19. package/scripts/_tpl_test.js +9 -0
  20. package/scripts/_typeof.js +26 -0
  21. package/scripts/_typeof2.js +25 -0
  22. package/scripts/patch_genmethod.js +44 -0
  23. package/scripts/smoke_test.js +167 -0
  24. package/scripts/smoke_test2.js +151 -0
  25. package/scripts/test_control_panel.js +58 -0
  26. package/src/compiler/CodeGenerator.js +1449 -0
  27. package/src/compiler/JITCompiler.js +325 -0
  28. package/src/database/DatabaseEngine.js +931 -0
  29. package/src/errors/ErrorDetector.js +305 -0
  30. package/src/errors/ErrorMessages.js +497 -0
  31. package/src/index.js +169 -0
  32. package/src/lexer/Lexer.js +342 -0
  33. package/src/lexer/TokenTypes.js +1234 -0
  34. package/src/memory/MemoryManager.js +283 -0
  35. package/src/parser/AST.js +606 -0
  36. package/src/parser/Parser.js +1942 -0
  37. package/src/runtime/ThyRuntime.js +265 -0
  38. package/src/runtime/stdlib/ArrayLib.js +207 -0
  39. package/src/runtime/stdlib/FileLib.js +168 -0
  40. package/src/runtime/stdlib/MathLib.js +411 -0
  41. package/src/runtime/stdlib/StringLib.js +387 -0
  42. package/src/server/ControlPanel.js +1188 -0
  43. package/src/server/HTMLCSSEngine.js +866 -0
  44. package/src/server/SecurityEngine.js +714 -0
  45. package/src/server/ServerEngine.js +738 -0
  46. package/tests/test1_math.thy +82 -0
  47. package/tests/test2_oop.thy +133 -0
  48. package/tests/test3_server.thy +143 -0
  49. 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;