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,305 @@
1
+ 'use strict';
2
+ /**
3
+ * ThyScript Error Detector
4
+ * High-speed static code analysis and error detection engine.
5
+ * Scans tokens and AST for syntax and semantic bugs,
6
+ * providing pinpoint code location pointers and actionable fix suggestions.
7
+ */
8
+
9
+ const ErrorMessages = require('./ErrorMessages');
10
+
11
+ class ErrorDetector {
12
+ constructor(options = {}) {
13
+ this.options = Object.assign({
14
+ maxErrors: 20,
15
+ warnUnused: true,
16
+ strictTypes: false,
17
+ }, options);
18
+ this.errors = [];
19
+ this.warnings = [];
20
+ }
21
+
22
+ /**
23
+ * Complete scan of tokens, AST, and raw source
24
+ * @param {Array} tokens - Tokens from Lexer
25
+ * @param {Object} ast - AST from Parser
26
+ * @param {string} source - Raw source code
27
+ * @param {string} filename - Source filename
28
+ * @returns {Object} { errors: [], warnings: [], isValid: boolean }
29
+ */
30
+ scan(tokens = [], ast = null, source = '', filename = '<anonymous>') {
31
+ this.errors = [];
32
+ this.warnings = [];
33
+
34
+ // Pass 1: Token-level syntax scan
35
+ if (tokens && tokens.length > 0) {
36
+ this.detectSyntaxErrors(tokens, source, filename);
37
+ }
38
+
39
+ // Pass 2: AST-level semantic scan
40
+ if (ast) {
41
+ this.detectSemanticErrors(ast, source, filename);
42
+ }
43
+
44
+ return {
45
+ errors: this.errors,
46
+ warnings: this.warnings,
47
+ isValid: this.errors.length === 0,
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Fast token stream validation
53
+ */
54
+ detectSyntaxErrors(tokens, source, filename) {
55
+ const bracketStack = [];
56
+ const parenStack = [];
57
+ const braceStack = [];
58
+
59
+ for (let i = 0; i < tokens.length; i++) {
60
+ const tok = tokens[i];
61
+
62
+ // Bracket balancing
63
+ if (tok.type === 'LPAREN') parenStack.push(tok);
64
+ else if (tok.type === 'RPAREN') {
65
+ if (parenStack.length === 0) {
66
+ this._addError('THY-004', [')'], tok.line, tok.col, filename, source, 'Remove unmatched closing parenthesis');
67
+ } else {
68
+ parenStack.pop();
69
+ }
70
+ } else if (tok.type === 'LBRACKET') bracketStack.push(tok);
71
+ else if (tok.type === 'RBRACKET') {
72
+ if (bracketStack.length === 0) {
73
+ this._addError('THY-004', [']'], tok.line, tok.col, filename, source, 'Remove unmatched closing square bracket');
74
+ } else {
75
+ bracketStack.pop();
76
+ }
77
+ } else if (tok.type === 'LBRACE') braceStack.push(tok);
78
+ else if (tok.type === 'RBRACE') {
79
+ if (braceStack.length === 0) {
80
+ this._addError('THY-004', ['}'], tok.line, tok.col, filename, source, 'Remove unmatched closing curly brace');
81
+ } else {
82
+ braceStack.pop();
83
+ }
84
+ }
85
+
86
+ // Check for consecutive operators e.g. ++ without operand or ** /
87
+ if (tok.type === 'PLUS' && tokens[i + 1] && tokens[i + 1].type === 'STAR') {
88
+ this._addError('THY-001', ['*'], tok.line, tok.col, filename, source, 'Unexpected * following + operator');
89
+ }
90
+
91
+ // Stop scanning if max errors reached
92
+ if (this.errors.length >= this.options.maxErrors) break;
93
+ }
94
+
95
+ // Report unclosed brackets
96
+ while (parenStack.length > 0) {
97
+ const tok = parenStack.pop();
98
+ this._addError('THY-004', ['('], tok.line, tok.col, filename, source, 'Add matching closing parenthesis )');
99
+ }
100
+ while (bracketStack.length > 0) {
101
+ const tok = bracketStack.pop();
102
+ this._addError('THY-004', ['['], tok.line, tok.col, filename, source, 'Add matching closing bracket ]');
103
+ }
104
+ while (braceStack.length > 0) {
105
+ const tok = braceStack.pop();
106
+ this._addError('THY-004', ['{'], tok.line, tok.col, filename, source, 'Add matching closing brace }');
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Semantic AST validation
112
+ */
113
+ detectSemanticErrors(ast, source, filename) {
114
+ const declaredVars = new Set();
115
+ const declaredFuncs = new Set();
116
+ const usedVars = new Set();
117
+
118
+ // Standard builtins known to ThyScript
119
+ const builtins = new Set([
120
+ 'print', 'calculate', 'input', 'save', 'free', 'change', 'to', 'add',
121
+ 'Math', 'Date', 'JSON', 'String', 'Number', 'Boolean', 'Array', 'Object',
122
+ 'parseInt', 'parseFloat', 'isNaN', 'isFinite', 'console', 'process', 'require',
123
+ 'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval', 'crypto', 'Buffer',
124
+ 'request', 'response', 'req', 'res', 'window', 'document', 'history', 'calc',
125
+ ]);
126
+
127
+ const traverse = (node) => {
128
+ if (!node || typeof node !== 'object') return;
129
+
130
+ // Variable declaration: const table name = ...
131
+ if (node.type === 'VarDecl' || node.type === 'VariableDeclaration') {
132
+ const name = node.name || (node.id && node.id.name);
133
+ if (name) {
134
+ if (declaredVars.has(name)) {
135
+ this._addWarning('THY-202', [name], node.line || 1, 1, filename, source, `Variable '${name}' is already declared.`);
136
+ }
137
+ declaredVars.add(name);
138
+ }
139
+ }
140
+
141
+ // Function declaration
142
+ if (node.type === 'FunctionDecl' || node.type === 'FunctionDeclaration') {
143
+ if (node.name) declaredFuncs.add(node.name);
144
+ if (Array.isArray(node.params)) {
145
+ for (const p of node.params) {
146
+ const pname = typeof p === 'string' ? p : p.name;
147
+ if (pname) declaredVars.add(pname);
148
+ }
149
+ }
150
+ }
151
+
152
+ // Class declaration
153
+ if (node.type === 'ClassDecl' || node.type === 'ClassDeclaration') {
154
+ if (node.name) declaredVars.add(node.name);
155
+ const stmts = Array.isArray(node.body) ? node.body : (node.body && node.body.body ? node.body.body : []);
156
+ for (const s of stmts) {
157
+ if (s.name) declaredVars.add(s.name);
158
+ }
159
+ }
160
+
161
+ // Variable reference
162
+ if (node.type === 'Identifier') {
163
+ usedVars.add(node.name);
164
+ if (!declaredVars.has(node.name) &&
165
+ !declaredFuncs.has(node.name) &&
166
+ !builtins.has(node.name) &&
167
+ !node.isDeclaration &&
168
+ !node.isProperty) {
169
+ // Check for similar declared name
170
+ const suggestion = this._findClosestMatch(node.name, [...declaredVars, ...builtins]);
171
+ if (suggestion) {
172
+ this._addWarning('THY-201', [node.name], node.line || 1, node.col || 1, filename, source,
173
+ `Did you mean '${suggestion}'?`);
174
+ }
175
+ }
176
+ }
177
+
178
+ // Check division by zero: calculate(x / 0)
179
+ if (node.type === 'BinaryExpr' && node.operator === '/') {
180
+ if (node.right && node.right.type === 'Literal' && node.right.value === 0) {
181
+ this._addError('THY-801', ['0'], node.line || 1, node.col || 1, filename, source,
182
+ 'Division by zero is undefined. Check denominator.');
183
+ }
184
+ }
185
+
186
+ // Traverse children
187
+ for (const key of Object.keys(node)) {
188
+ if (key === 'parent') continue;
189
+ const val = node[key];
190
+ if (Array.isArray(val)) {
191
+ val.forEach(traverse);
192
+ } else if (val && typeof val === 'object') {
193
+ traverse(val);
194
+ }
195
+ }
196
+ };
197
+
198
+ traverse(ast);
199
+
200
+ // Unused variables check
201
+ if (this.options.warnUnused) {
202
+ for (const v of declaredVars) {
203
+ if (!usedVars.has(v) && !builtins.has(v)) {
204
+ this.warnings.push({
205
+ code: 'THY-204',
206
+ message: `Variable '${v}' is declared but never read.`,
207
+ suggestion: `Consider removing '${v}' or prefixing with '_' if intentionally unused.`,
208
+ filename,
209
+ severity: 'warning',
210
+ });
211
+ }
212
+ }
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Helper to format an error object
218
+ */
219
+ _addError(code, args, line, col, filename, source, suggestion) {
220
+ const template = ErrorMessages.get(code, ...args);
221
+ this.errors.push({
222
+ code,
223
+ type: 'SyntaxError',
224
+ message: template ? template.message : (args[0] || 'Unknown error'),
225
+ suggestion: suggestion || (template ? template.suggestion : ''),
226
+ line,
227
+ column: col,
228
+ filename,
229
+ severity: 'error',
230
+ });
231
+ }
232
+
233
+ /**
234
+ * Helper to format a warning object
235
+ */
236
+ _addWarning(code, args, line, col, filename, source, suggestion) {
237
+ const template = ErrorMessages.get(code, ...args);
238
+ this.warnings.push({
239
+ code,
240
+ type: 'Warning',
241
+ message: template ? template.message : (args[0] || 'Warning'),
242
+ suggestion: suggestion || (template ? template.suggestion : ''),
243
+ line,
244
+ column: col,
245
+ filename,
246
+ severity: 'warning',
247
+ });
248
+ }
249
+
250
+ /**
251
+ * Simple Levenshtein distance for typo suggestions
252
+ */
253
+ _findClosestMatch(word, candidates) {
254
+ let minDistance = 3;
255
+ let closest = null;
256
+ for (const c of candidates) {
257
+ const d = this._levenshtein(word.toLowerCase(), c.toLowerCase());
258
+ if (d < minDistance) {
259
+ minDistance = d;
260
+ closest = c;
261
+ }
262
+ }
263
+ return closest;
264
+ }
265
+
266
+ _levenshtein(a, b) {
267
+ const m = a.length, n = b.length;
268
+ const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
269
+ for (let i = 0; i <= m; i++) dp[i][0] = i;
270
+ for (let j = 0; j <= n; j++) dp[0][j] = j;
271
+ for (let i = 1; i <= m; i++) {
272
+ for (let j = 1; j <= n; j++) {
273
+ dp[i][j] = a[i - 1] === b[j - 1]
274
+ ? dp[i - 1][j - 1]
275
+ : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
276
+ }
277
+ }
278
+ return dp[m][n];
279
+ }
280
+
281
+ /**
282
+ * Formats error for console display with code pointer
283
+ */
284
+ formatError(err, source = '') {
285
+ const lines = source ? source.split('\n') : [];
286
+ let output = `[${err.code || 'THY-ERR'}] ${err.type || 'Error'}: ${err.message}\n`;
287
+ output += ` at ${err.filename || '<anonymous>'}:${err.line || 1}:${err.column || 1}\n`;
288
+ if (err.line && lines[err.line - 1]) {
289
+ const codeLine = lines[err.line - 1];
290
+ const pointer = ' '.repeat(Math.max(0, (err.column || 1) - 1)) + '^';
291
+ output += `\n ${String(err.line).padStart(4)} | ${codeLine}\n`;
292
+ output += ` | ${pointer}\n`;
293
+ }
294
+ if (err.suggestion) {
295
+ output += ` 💡 Fix: ${err.suggestion}\n`;
296
+ }
297
+ return output;
298
+ }
299
+
300
+ formatWarning(warn) {
301
+ return `[${warn.code || 'THY-WARN'}] Warning: ${warn.message} (${warn.filename || ''}:${warn.line || 1})\n 💡 ${warn.suggestion || ''}`;
302
+ }
303
+ }
304
+
305
+ module.exports = ErrorDetector;