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,387 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * ThyStringLib - ThyScript Standard String Library
5
+ * Provides rich string manipulation: formatting, encoding, compression,
6
+ * fuzzy matching, CSV/JSON, regex, NLP basics, and more.
7
+ */
8
+
9
+ const zlib = require('zlib');
10
+ const { promisify } = require('util');
11
+
12
+ const gzipAsync = promisify(zlib.gzip);
13
+ const gunzipAsync = promisify(zlib.gunzip);
14
+
15
+ class ThyStringLib {
16
+ // ─── Basic Ops ────────────────────────────────────────────────────────────
17
+
18
+ trim(str) { return str.trim(); }
19
+ trimStart(str) { return str.trimStart(); }
20
+ trimEnd(str) { return str.trimEnd(); }
21
+ upper(str) { return str.toUpperCase(); }
22
+ lower(str) { return str.toLowerCase(); }
23
+ replace(str, from, to) { return str.split(from).join(to); }
24
+ split(str, sep, limit) { return limit !== undefined ? str.split(sep, limit) : str.split(sep); }
25
+ join(arr, sep = '') { return arr.join(sep); }
26
+ slice(str, start, end) { return str.slice(start, end); }
27
+ indexOf(str, sub, pos) { return str.indexOf(sub, pos); }
28
+ lastIndexOf(str, sub) { return str.lastIndexOf(sub); }
29
+ includes(str, sub) { return str.includes(sub); }
30
+ startsWith(str, prefix){ return str.startsWith(prefix); }
31
+ endsWith(str, suffix) { return str.endsWith(suffix); }
32
+ padStart(str, len, ch) { return str.padStart(len, ch); }
33
+ padEnd(str, len, ch) { return str.padEnd(len, ch); }
34
+ repeat(str, n) { return str.repeat(n); }
35
+
36
+ /** Reverse a string. */
37
+ reverse(str) { return [...str].reverse().join(''); }
38
+
39
+ // ─── Formatting ───────────────────────────────────────────────────────────
40
+
41
+ /**
42
+ * printf-style format: %s (string), %d (int), %f (float), %j (json).
43
+ * @param {string} template
44
+ * @param {...*} args
45
+ * @returns {string}
46
+ */
47
+ format(template, ...args) {
48
+ let i = 0;
49
+ return template.replace(/%([sdfj%])/g, (_, spec) => {
50
+ if (spec === '%') return '%';
51
+ const arg = args[i++];
52
+ if (spec === 's') return String(arg ?? '');
53
+ if (spec === 'd') return String(Math.round(Number(arg)));
54
+ if (spec === 'f') return Number(arg).toFixed(6);
55
+ if (spec === 'j') return JSON.stringify(arg);
56
+ return _;
57
+ });
58
+ }
59
+
60
+ /**
61
+ * Template interpolation using {{key}} placeholders.
62
+ * @param {string} template
63
+ * @param {object} obj
64
+ * @returns {string}
65
+ */
66
+ interpolate(template, obj) {
67
+ return template.replace(/\{\{(\s*[\w.]+\s*)\}\}/g, (_, key) => {
68
+ const parts = key.trim().split('.');
69
+ let val = obj;
70
+ for (const p of parts) val = val?.[p];
71
+ return val == null ? '' : String(val);
72
+ });
73
+ }
74
+
75
+ // ─── Case Conversions ─────────────────────────────────────────────────────
76
+
77
+ /** Convert camelCase to snake_case. */
78
+ camelToSnake(str) {
79
+ return str
80
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
81
+ .replace(/([a-z\d])([A-Z])/g, '$1_$2')
82
+ .toLowerCase();
83
+ }
84
+
85
+ /** Convert snake_case to camelCase. */
86
+ snakeToCamel(str) {
87
+ return str.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
88
+ }
89
+
90
+ /** Capitalize first letter. */
91
+ capitalize(str) {
92
+ if (!str) return str;
93
+ return str[0].toUpperCase() + str.slice(1).toLowerCase();
94
+ }
95
+
96
+ /** Title case every word. */
97
+ titleCase(str) {
98
+ return str.replace(/\w\S*/g, w => w[0].toUpperCase() + w.slice(1).toLowerCase());
99
+ }
100
+
101
+ // ─── Truncation ───────────────────────────────────────────────────────────
102
+
103
+ /**
104
+ * Truncate a string to len characters, appending suffix if needed.
105
+ * @param {string} str
106
+ * @param {number} len
107
+ * @param {string} suffix
108
+ * @returns {string}
109
+ */
110
+ truncate(str, len, suffix = '...') {
111
+ if (str.length <= len) return str;
112
+ return str.slice(0, len - suffix.length) + suffix;
113
+ }
114
+
115
+ // ─── Counts ───────────────────────────────────────────────────────────────
116
+
117
+ wordCount(str) { return str.trim() === '' ? 0 : str.trim().split(/\s+/).length; }
118
+ charCount(str) { return str.length; }
119
+ lineCount(str) { return str.split('\n').length; }
120
+
121
+ // ─── Encoding ─────────────────────────────────────────────────────────────
122
+
123
+ /** Encode string to base64. */
124
+ toBase64(str) { return Buffer.from(str, 'utf8').toString('base64'); }
125
+
126
+ /** Decode base64 string. */
127
+ fromBase64(b64) { return Buffer.from(b64, 'base64').toString('utf8'); }
128
+
129
+ // ─── HTML Entities ────────────────────────────────────────────────────────
130
+
131
+ /** Escape HTML special characters. */
132
+ escape(str) {
133
+ return str
134
+ .replace(/&/g, '&amp;')
135
+ .replace(/</g, '&lt;')
136
+ .replace(/>/g, '&gt;')
137
+ .replace(/"/g, '&quot;')
138
+ .replace(/'/g, '&#39;');
139
+ }
140
+
141
+ /** Unescape HTML entities. */
142
+ unescape(str) {
143
+ return str
144
+ .replace(/&amp;/g, '&')
145
+ .replace(/&lt;/g, '<')
146
+ .replace(/&gt;/g, '>')
147
+ .replace(/&quot;/g, '"')
148
+ .replace(/&#39;/g, "'");
149
+ }
150
+
151
+ // ─── Slugify ─────────────────────────────────────────────────────────────
152
+
153
+ /**
154
+ * Convert a string to a URL-safe slug.
155
+ * @param {string} str
156
+ * @returns {string}
157
+ */
158
+ slugify(str) {
159
+ return str
160
+ .normalize('NFD')
161
+ .replace(/[\u0300-\u036f]/g, '')
162
+ .toLowerCase()
163
+ .replace(/[^a-z0-9\s-]/g, '')
164
+ .trim()
165
+ .replace(/[\s-]+/g, '-');
166
+ }
167
+
168
+ // ─── Edit Distance ────────────────────────────────────────────────────────
169
+
170
+ /**
171
+ * Levenshtein edit distance between two strings.
172
+ * @param {string} a
173
+ * @param {string} b
174
+ * @returns {number}
175
+ */
176
+ levenshtein(a, b) {
177
+ const m = a.length, n = b.length;
178
+ const dp = Array.from({ length: m + 1 }, (_, i) =>
179
+ Array.from({ length: n + 1 }, (_, j) => (i === 0 ? j : j === 0 ? i : 0))
180
+ );
181
+ for (let i = 1; i <= m; i++) {
182
+ for (let j = 1; j <= n; j++) {
183
+ dp[i][j] = a[i - 1] === b[j - 1]
184
+ ? dp[i - 1][j - 1]
185
+ : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
186
+ }
187
+ }
188
+ return dp[m][n];
189
+ }
190
+
191
+ // ─── Fuzzy Match ─────────────────────────────────────────────────────────
192
+
193
+ /**
194
+ * Fuzzy match: returns a similarity score in [0, 1].
195
+ * Uses normalised Levenshtein distance.
196
+ * @param {string} needle
197
+ * @param {string} haystack
198
+ * @returns {number}
199
+ */
200
+ fuzzyMatch(needle, haystack) {
201
+ needle = needle.toLowerCase();
202
+ haystack = haystack.toLowerCase();
203
+ if (needle === haystack) return 1;
204
+ if (needle.length === 0) return 0;
205
+ const dist = this.levenshtein(needle, haystack);
206
+ return 1 - dist / Math.max(needle.length, haystack.length);
207
+ }
208
+
209
+ // ─── CSV ─────────────────────────────────────────────────────────────────
210
+
211
+ /**
212
+ * Parse a CSV string into a 2D array. Handles quoted fields.
213
+ * @param {string} str
214
+ * @param {string} delimiter
215
+ * @returns {string[][]}
216
+ */
217
+ parseCSV(str, delimiter = ',') {
218
+ const rows = [];
219
+ const lines = str.split('\n');
220
+ for (const line of lines) {
221
+ if (!line.trim()) continue;
222
+ const row = [];
223
+ let inQuote = false, cell = '';
224
+ for (let i = 0; i < line.length; i++) {
225
+ const ch = line[i];
226
+ if (ch === '"') {
227
+ if (inQuote && line[i + 1] === '"') { cell += '"'; i++; }
228
+ else inQuote = !inQuote;
229
+ } else if (ch === delimiter && !inQuote) {
230
+ row.push(cell); cell = '';
231
+ } else {
232
+ cell += ch;
233
+ }
234
+ }
235
+ row.push(cell);
236
+ rows.push(row);
237
+ }
238
+ return rows;
239
+ }
240
+
241
+ /**
242
+ * Convert 2D array to CSV string. Quotes fields containing delimiter/newline.
243
+ * @param {string[][]} data
244
+ * @param {string} delimiter
245
+ * @returns {string}
246
+ */
247
+ stringifyCSV(data, delimiter = ',') {
248
+ return data.map(row =>
249
+ row.map(cell => {
250
+ const s = String(cell ?? '');
251
+ return s.includes(delimiter) || s.includes('\n') || s.includes('"')
252
+ ? `"${s.replace(/"/g, '""')}"`
253
+ : s;
254
+ }).join(delimiter)
255
+ ).join('\n');
256
+ }
257
+
258
+ // ─── JSON ─────────────────────────────────────────────────────────────────
259
+
260
+ /**
261
+ * Parse JSON string with error context.
262
+ * @param {string} str
263
+ * @returns {*}
264
+ */
265
+ parseJSON(str) {
266
+ try {
267
+ return JSON.parse(str);
268
+ } catch (e) {
269
+ throw new SyntaxError(`ThyStringLib.parseJSON: ${e.message}`);
270
+ }
271
+ }
272
+
273
+ /**
274
+ * Stringify to JSON, optionally pretty-printed.
275
+ * @param {*} val
276
+ * @param {boolean} pretty
277
+ * @returns {string}
278
+ */
279
+ stringifyJSON(val, pretty = false) {
280
+ return pretty ? JSON.stringify(val, null, 2) : JSON.stringify(val);
281
+ }
282
+
283
+ // ─── Compression ─────────────────────────────────────────────────────────
284
+
285
+ /**
286
+ * Compress a string using gzip. Returns a Buffer.
287
+ * @param {string} str
288
+ * @returns {Promise<Buffer>}
289
+ */
290
+ async compress(str) {
291
+ return gzipAsync(Buffer.from(str, 'utf8'));
292
+ }
293
+
294
+ /**
295
+ * Decompress a gzip Buffer to string.
296
+ * @param {Buffer} buf
297
+ * @returns {Promise<string>}
298
+ */
299
+ async decompress(buf) {
300
+ const out = await gunzipAsync(buf);
301
+ return out.toString('utf8');
302
+ }
303
+
304
+ // ─── Regex ────────────────────────────────────────────────────────────────
305
+
306
+ /**
307
+ * Return all regex matches in str.
308
+ * @param {string} str
309
+ * @param {RegExp|string} pattern
310
+ * @param {string} flags
311
+ * @returns {RegExpMatchArray[]}
312
+ */
313
+ regexMatch(str, pattern, flags = 'g') {
314
+ const re = pattern instanceof RegExp ? pattern : new RegExp(pattern, flags);
315
+ return [...str.matchAll(re)];
316
+ }
317
+
318
+ /**
319
+ * Test if str matches pattern.
320
+ * @param {string} str
321
+ * @param {RegExp|string} pattern
322
+ * @returns {boolean}
323
+ */
324
+ regexTest(str, pattern) {
325
+ const re = pattern instanceof RegExp ? pattern : new RegExp(pattern);
326
+ return re.test(str);
327
+ }
328
+
329
+ /**
330
+ * Replace using regex.
331
+ * @param {string} str
332
+ * @param {RegExp|string} pattern
333
+ * @param {string|function} replacement
334
+ * @param {string} flags
335
+ * @returns {string}
336
+ */
337
+ regexReplace(str, pattern, replacement, flags = 'g') {
338
+ const re = pattern instanceof RegExp ? pattern : new RegExp(pattern, flags);
339
+ return str.replace(re, replacement);
340
+ }
341
+
342
+ // ─── Tokenize ────────────────────────────────────────────────────────────
343
+
344
+ /**
345
+ * Split string into word tokens (strips punctuation).
346
+ * @param {string} str
347
+ * @returns {string[]}
348
+ */
349
+ tokenize(str) {
350
+ return str.toLowerCase().match(/\b[a-z']+\b/g) ?? [];
351
+ }
352
+
353
+ // ─── Sentiment ───────────────────────────────────────────────────────────
354
+
355
+ static _positiveWords = new Set([
356
+ 'good','great','excellent','amazing','wonderful','fantastic','happy',
357
+ 'love','best','awesome','beautiful','nice','perfect','superb','joy',
358
+ 'brilliant','delightful','outstanding','positive','success','win',
359
+ 'helpful','innovative','effective','enjoy','like','fun','exciting',
360
+ ]);
361
+
362
+ static _negativeWords = new Set([
363
+ 'bad','terrible','awful','horrible','hate','worst','ugly','poor',
364
+ 'failure','sad','angry','frustrating','boring','waste','broken',
365
+ 'useless','annoying','negative','wrong','error','problem','fail',
366
+ 'difficult','confusing','disappointing','slow','crash','bug',
367
+ ]);
368
+
369
+ /**
370
+ * Simple bag-of-words sentiment analysis.
371
+ * @param {string} str
372
+ * @returns {{ score: number, label: 'positive'|'negative'|'neutral', pos: number, neg: number }}
373
+ */
374
+ sentiment(str) {
375
+ const tokens = this.tokenize(str);
376
+ let pos = 0, neg = 0;
377
+ for (const t of tokens) {
378
+ if (ThyStringLib._positiveWords.has(t)) pos++;
379
+ if (ThyStringLib._negativeWords.has(t)) neg++;
380
+ }
381
+ const score = pos - neg;
382
+ const label = score > 0 ? 'positive' : score < 0 ? 'negative' : 'neutral';
383
+ return { score, label, pos, neg };
384
+ }
385
+ }
386
+
387
+ module.exports = ThyStringLib;