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,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;
|