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,710 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Auto-generated by ThyScript CodeGenerator – do not edit manually
|
|
3
|
+
// ═══════════════════════════════════════════════════════════════
|
|
4
|
+
// ThyScript Standard Library – __thy_stdlib
|
|
5
|
+
// Auto-generated by ThyScript CodeGenerator
|
|
6
|
+
// ═══════════════════════════════════════════════════════════════
|
|
7
|
+
const __thy_stdlib = (() => {
|
|
8
|
+
'use strict';
|
|
9
|
+
const _fs = require('fs');
|
|
10
|
+
const _path = require('path');
|
|
11
|
+
const _os = require('os');
|
|
12
|
+
const _crypto = require('crypto');
|
|
13
|
+
|
|
14
|
+
// ── math ──────────────────────────────────────────────────────
|
|
15
|
+
const math = {
|
|
16
|
+
pi: Math.PI,
|
|
17
|
+
e: Math.E,
|
|
18
|
+
inf: Infinity,
|
|
19
|
+
nan: NaN,
|
|
20
|
+
epsilon: Number.EPSILON,
|
|
21
|
+
abs: (x) => Math.abs(x),
|
|
22
|
+
sqrt: (x) => Math.sqrt(x),
|
|
23
|
+
pow: (x, y) => Math.pow(x, y),
|
|
24
|
+
floor: (x) => Math.floor(x),
|
|
25
|
+
ceil: (x) => Math.ceil(x),
|
|
26
|
+
round: (x) => Math.round(x),
|
|
27
|
+
sin: (x) => Math.sin(x),
|
|
28
|
+
cos: (x) => Math.cos(x),
|
|
29
|
+
tan: (x) => Math.tan(x),
|
|
30
|
+
asin: (x) => Math.asin(x),
|
|
31
|
+
acos: (x) => Math.acos(x),
|
|
32
|
+
atan: (x) => Math.atan(x),
|
|
33
|
+
atan2: (y, x) => Math.atan2(y, x),
|
|
34
|
+
log: (x) => Math.log(x),
|
|
35
|
+
log2: (x) => Math.log2(x),
|
|
36
|
+
log10: (x) => Math.log10(x),
|
|
37
|
+
exp: (x) => Math.exp(x),
|
|
38
|
+
random: () => Math.random(),
|
|
39
|
+
min: (...args) => Math.min(...args),
|
|
40
|
+
max: (...args) => Math.max(...args),
|
|
41
|
+
clamp: (v, lo, hi) => Math.max(lo, Math.min(hi, v)),
|
|
42
|
+
lerp: (a, b, t) => a + (b - a) * t,
|
|
43
|
+
hypot: (...args) => Math.hypot(...args),
|
|
44
|
+
sign: (x) => Math.sign(x),
|
|
45
|
+
trunc: (x) => Math.trunc(x),
|
|
46
|
+
cbrt: (x) => Math.cbrt(x),
|
|
47
|
+
// Fast integer math via typed arrays
|
|
48
|
+
imul: (a, b) => Math.imul(a, b),
|
|
49
|
+
fround: (x) => Math.fround(x),
|
|
50
|
+
clz32: (x) => Math.clz32(x),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Fast-math helpers using Float64Array for type stability
|
|
54
|
+
const _buf64 = new Float64Array(1);
|
|
55
|
+
const _buf32 = new Float32Array(1);
|
|
56
|
+
const fastFloor = (x) => { _buf64[0] = x; return x | 0; };
|
|
57
|
+
const fastAbs = (x) => x < 0 ? -x : x;
|
|
58
|
+
const fastMod = (a, b) => a - b * ((a / b) | 0);
|
|
59
|
+
|
|
60
|
+
// ── vectors ───────────────────────────────────────────────────
|
|
61
|
+
const vec = {
|
|
62
|
+
add: (a, b) => a.map((v, i) => v + b[i]),
|
|
63
|
+
sub: (a, b) => a.map((v, i) => v - b[i]),
|
|
64
|
+
scale: (a, s) => a.map(v => v * s),
|
|
65
|
+
dot: (a, b) => a.reduce((acc, v, i) => acc + v * b[i], 0),
|
|
66
|
+
cross3: (a, b) => [
|
|
67
|
+
a[1]*b[2] - a[2]*b[1],
|
|
68
|
+
a[2]*b[0] - a[0]*b[2],
|
|
69
|
+
a[0]*b[1] - a[1]*b[0],
|
|
70
|
+
],
|
|
71
|
+
length: (a) => Math.hypot(...a),
|
|
72
|
+
normalize: (a) => { const len = Math.hypot(...a) || 1; return a.map(v => v / len); },
|
|
73
|
+
distance: (a, b) => Math.hypot(...a.map((v, i) => v - b[i])),
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// ── string helpers ────────────────────────────────────────────
|
|
77
|
+
const str = {
|
|
78
|
+
upper: (s) => String(s).toUpperCase(),
|
|
79
|
+
lower: (s) => String(s).toLowerCase(),
|
|
80
|
+
trim: (s) => String(s).trim(),
|
|
81
|
+
split: (s, sep) => String(s).split(sep),
|
|
82
|
+
replace: (s, pat, rep) => String(s).replace(pat, rep),
|
|
83
|
+
includes:(s, sub) => String(s).includes(sub),
|
|
84
|
+
startsWith:(s, pre) => String(s).startsWith(pre),
|
|
85
|
+
endsWith: (s, suf) => String(s).endsWith(suf),
|
|
86
|
+
repeat: (s, n) => String(s).repeat(n),
|
|
87
|
+
pad: (s, n, ch = ' ') => String(s).padStart(n, ch),
|
|
88
|
+
padEnd: (s, n, ch = ' ') => String(s).padEnd(n, ch),
|
|
89
|
+
indexOf: (s, sub) => String(s).indexOf(sub),
|
|
90
|
+
slice: (s, start, end) => String(s).slice(start, end),
|
|
91
|
+
charCode:(s, i = 0) => String(s).charCodeAt(i),
|
|
92
|
+
fromCode:(n) => String.fromCharCode(n),
|
|
93
|
+
reverse: (s) => String(s).split('').reverse().join(''),
|
|
94
|
+
count: (s, sub) => { let c = 0, i = 0; while ((i = s.indexOf(sub, i)) !== -1) { c++; i++; } return c; },
|
|
95
|
+
format: (tpl, ...args) => tpl.replace(/{(\d+)}/g, (_, i) => args[i] ?? ''),
|
|
96
|
+
escape: (s) => String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'),
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// ── array helpers ─────────────────────────────────────────────
|
|
100
|
+
const arr = {
|
|
101
|
+
map: (a, fn) => Array.from(a).map(fn),
|
|
102
|
+
filter: (a, fn) => Array.from(a).filter(fn),
|
|
103
|
+
reduce: (a, fn, init) => Array.from(a).reduce(fn, init),
|
|
104
|
+
find: (a, fn) => Array.from(a).find(fn),
|
|
105
|
+
findIdx: (a, fn) => Array.from(a).findIndex(fn),
|
|
106
|
+
sort: (a, fn) => [...a].sort(fn),
|
|
107
|
+
flat: (a, d = 1) => Array.from(a).flat(d),
|
|
108
|
+
flatMap: (a, fn) => Array.from(a).flatMap(fn),
|
|
109
|
+
unique: (a) => [...new Set(a)],
|
|
110
|
+
zip: (...arrs) => arrs[0].map((_, i) => arrs.map(a => a[i])),
|
|
111
|
+
chunk: (a, n) => { const res = []; for (let i = 0; i < a.length; i += n) res.push(a.slice(i, i+n)); return res; },
|
|
112
|
+
shuffle: (a) => { const r = [...a]; for (let i = r.length-1; i > 0; i--) { const j = Math.floor(Math.random()*(i+1)); [r[i],r[j]]=[r[j],r[i]]; } return r; },
|
|
113
|
+
range: (start, end, step = 1) => { const r = []; for (let i = start; i < end; i += step) r.push(i); return r; },
|
|
114
|
+
sum: (a) => a.reduce((acc, v) => acc + v, 0),
|
|
115
|
+
avg: (a) => a.reduce((acc, v) => acc + v, 0) / a.length,
|
|
116
|
+
min: (a) => Math.min(...a),
|
|
117
|
+
max: (a) => Math.max(...a),
|
|
118
|
+
includes:(a, v) => Array.from(a).includes(v),
|
|
119
|
+
count: (a, v) => Array.from(a).filter(x => x === v).length,
|
|
120
|
+
first: (a) => a[0],
|
|
121
|
+
last: (a) => a[a.length - 1],
|
|
122
|
+
take: (a, n) => a.slice(0, n),
|
|
123
|
+
drop: (a, n) => a.slice(n),
|
|
124
|
+
reverse: (a) => [...a].reverse(),
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// ── object helpers ────────────────────────────────────────────
|
|
128
|
+
const obj = {
|
|
129
|
+
keys: (o) => Object.keys(o),
|
|
130
|
+
values: (o) => Object.values(o),
|
|
131
|
+
entries: (o) => Object.entries(o),
|
|
132
|
+
merge: (...os) => Object.assign({}, ...os),
|
|
133
|
+
clone: (o) => JSON.parse(JSON.stringify(o)),
|
|
134
|
+
pick: (o, keys) => keys.reduce((acc, k) => { if (k in o) acc[k] = o[k]; return acc; }, {}),
|
|
135
|
+
omit: (o, keys) => { const r = {...o}; keys.forEach(k => delete r[k]); return r; },
|
|
136
|
+
has: (o, k) => Object.prototype.hasOwnProperty.call(o, k),
|
|
137
|
+
freeze: (o) => Object.freeze(o),
|
|
138
|
+
seal: (o) => Object.seal(o),
|
|
139
|
+
fromEntries: (e) => Object.fromEntries(e),
|
|
140
|
+
assign: (target, ...sources) => Object.assign(target, ...sources),
|
|
141
|
+
flatten: (o, prefix = '') => {
|
|
142
|
+
return Object.keys(o).reduce((acc, k) => {
|
|
143
|
+
const key = prefix ? prefix + '.' + k : k;
|
|
144
|
+
if (typeof o[k] === 'object' && o[k] !== null && !Array.isArray(o[k]))
|
|
145
|
+
Object.assign(acc, obj.flatten(o[k], key));
|
|
146
|
+
else acc[key] = o[k];
|
|
147
|
+
return acc;
|
|
148
|
+
}, {});
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// ── I/O helpers ───────────────────────────────────────────────
|
|
153
|
+
const io = {
|
|
154
|
+
print: (...args) => { process.stdout.write(args.map(a => __thy_stringify(a)).join(' ') + '\n'); },
|
|
155
|
+
printErr:(...args) => { process.stderr.write(args.map(a => __thy_stringify(a)).join(' ') + '\n'); },
|
|
156
|
+
write: (s) => { process.stdout.write(String(s)); },
|
|
157
|
+
read: (fp) => _fs.readFileSync(fp, 'utf8'),
|
|
158
|
+
readLines:(fp) => _fs.readFileSync(fp, 'utf8').split('\n'),
|
|
159
|
+
write_: (fp, data) => _fs.writeFileSync(fp, data, 'utf8'),
|
|
160
|
+
append: (fp, data) => _fs.appendFileSync(fp, data, 'utf8'),
|
|
161
|
+
exists: (fp) => _fs.existsSync(fp),
|
|
162
|
+
mkdir: (fp) => _fs.mkdirSync(fp, { recursive: true }),
|
|
163
|
+
readdir: (fp) => _fs.readdirSync(fp),
|
|
164
|
+
rm: (fp) => _fs.rmSync(fp, { recursive: true, force: true }),
|
|
165
|
+
stat: (fp) => _fs.statSync(fp),
|
|
166
|
+
isFile: (fp) => _fs.existsSync(fp) && _fs.statSync(fp).isFile(),
|
|
167
|
+
isDir: (fp) => _fs.existsSync(fp) && _fs.statSync(fp).isDirectory(),
|
|
168
|
+
cwd: () => process.cwd(),
|
|
169
|
+
home: () => _os.homedir(),
|
|
170
|
+
tempDir: () => _os.tmpdir(),
|
|
171
|
+
sep: _path.sep,
|
|
172
|
+
join: (...parts) => _path.join(...parts),
|
|
173
|
+
resolve: (...parts) => _path.resolve(...parts),
|
|
174
|
+
dirname: (p) => _path.dirname(p),
|
|
175
|
+
basename:(p, ext) => _path.basename(p, ext),
|
|
176
|
+
ext: (p) => _path.extname(p),
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// ── crypto helpers ────────────────────────────────────────────
|
|
180
|
+
const security = {
|
|
181
|
+
hash: (data, alg = 'sha256') => _crypto.createHash(alg).update(String(data)).digest('hex'),
|
|
182
|
+
hmac: (key, data, alg = 'sha256') => _crypto.createHmac(alg, key).update(String(data)).digest('hex'),
|
|
183
|
+
random: (n = 16) => _crypto.randomBytes(n).toString('hex'),
|
|
184
|
+
uuid: () => _crypto.randomUUID ? _crypto.randomUUID() : _crypto.randomBytes(16).toString('hex').replace(/(........)(....)(....)(....)(............)/, '$1-$2-$3-$4-$5'),
|
|
185
|
+
encrypt: (text, key) => {
|
|
186
|
+
const iv = _crypto.randomBytes(16);
|
|
187
|
+
const k = _crypto.createHash('sha256').update(key).digest();
|
|
188
|
+
const c = _crypto.createCipheriv('aes-256-cbc', k, iv);
|
|
189
|
+
return iv.toString('hex') + ':' + Buffer.concat([c.update(text), c.final()]).toString('hex');
|
|
190
|
+
},
|
|
191
|
+
decrypt: (enc, key) => {
|
|
192
|
+
const [ivHex, data] = enc.split(':');
|
|
193
|
+
const iv = Buffer.from(ivHex, 'hex');
|
|
194
|
+
const k = _crypto.createHash('sha256').update(key).digest();
|
|
195
|
+
const d = _crypto.createDecipheriv('aes-256-cbc', k, iv);
|
|
196
|
+
return Buffer.concat([d.update(Buffer.from(data, 'hex')), d.final()]).toString();
|
|
197
|
+
},
|
|
198
|
+
base64encode: (s) => Buffer.from(String(s)).toString('base64'),
|
|
199
|
+
base64decode: (s) => Buffer.from(String(s), 'base64').toString(),
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// ── type helpers ──────────────────────────────────────────────
|
|
203
|
+
const types = {
|
|
204
|
+
isNum: (v) => typeof v === 'number' && !isNaN(v),
|
|
205
|
+
isStr: (v) => typeof v === 'string',
|
|
206
|
+
isBool: (v) => typeof v === 'boolean',
|
|
207
|
+
isNull: (v) => v === null,
|
|
208
|
+
isUndef: (v) => v === undefined,
|
|
209
|
+
isArr: (v) => Array.isArray(v),
|
|
210
|
+
isObj: (v) => v !== null && typeof v === 'object' && !Array.isArray(v),
|
|
211
|
+
isFn: (v) => typeof v === 'function',
|
|
212
|
+
type: (v) => v === null ? 'null' : Array.isArray(v) ? 'array' : typeof v,
|
|
213
|
+
toNum: (v) => Number(v),
|
|
214
|
+
toStr: (v) => String(v),
|
|
215
|
+
toBool: (v) => Boolean(v),
|
|
216
|
+
toInt: (v) => parseInt(v, 10),
|
|
217
|
+
toFloat: (v) => parseFloat(v),
|
|
218
|
+
toArr: (v) => Array.isArray(v) ? v : [v],
|
|
219
|
+
toJson: (v) => JSON.stringify(v, null, 2),
|
|
220
|
+
fromJson:(v) => JSON.parse(v),
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// ── time helpers ──────────────────────────────────────────────
|
|
224
|
+
const time = {
|
|
225
|
+
now: () => Date.now(),
|
|
226
|
+
date: () => new Date(),
|
|
227
|
+
sleep: (ms) => new Promise(r => setTimeout(r, ms)),
|
|
228
|
+
perf: () => typeof performance !== 'undefined' ? performance.now() : Number(process.hrtime.bigint()) / 1e6,
|
|
229
|
+
format: (d = new Date()) => d.toISOString(),
|
|
230
|
+
unix: () => Math.floor(Date.now() / 1000),
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// ── async/process helpers ─────────────────────────────────────
|
|
234
|
+
const proc = {
|
|
235
|
+
env: (k, def = '') => process.env[k] || def,
|
|
236
|
+
args: () => process.argv.slice(2),
|
|
237
|
+
exit: (code = 0) => process.exit(code),
|
|
238
|
+
pid: () => process.pid,
|
|
239
|
+
memUsage:() => process.memoryUsage(),
|
|
240
|
+
cpuUsage:() => process.cpuUsage(),
|
|
241
|
+
platform:() => process.platform,
|
|
242
|
+
arch: () => process.arch,
|
|
243
|
+
version: () => process.version,
|
|
244
|
+
hrtime: () => process.hrtime.bigint(),
|
|
245
|
+
nextTick:(fn) => process.nextTick(fn),
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
// ── http helpers ──────────────────────────────────────────────
|
|
249
|
+
const http = {
|
|
250
|
+
fetch: async (url, opts = {}) => {
|
|
251
|
+
const https = url.startsWith('https') ? require('https') : require('http');
|
|
252
|
+
return new Promise((resolve, reject) => {
|
|
253
|
+
const req = https.request(url, { method: opts.method || 'GET', headers: opts.headers || {} }, (res) => {
|
|
254
|
+
let data = '';
|
|
255
|
+
res.on('data', chunk => data += chunk);
|
|
256
|
+
res.on('end', () => resolve({ status: res.statusCode, headers: res.headers, body: data,
|
|
257
|
+
json: () => JSON.parse(data), text: () => data }));
|
|
258
|
+
});
|
|
259
|
+
req.on('error', reject);
|
|
260
|
+
if (opts.body) req.write(typeof opts.body === 'string' ? opts.body : JSON.stringify(opts.body));
|
|
261
|
+
req.end();
|
|
262
|
+
});
|
|
263
|
+
},
|
|
264
|
+
get: async (url, headers = {}) => http.fetch(url, { method: 'GET', headers }),
|
|
265
|
+
post: async (url, body, headers = {}) => http.fetch(url, { method: 'POST', body, headers: { 'Content-Type': 'application/json', ...headers } }),
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
// ── persist helpers ───────────────────────────────────────────
|
|
269
|
+
const persist = {
|
|
270
|
+
save: (name, data) => {
|
|
271
|
+
const fp = _path.join(process.cwd(), name + '.thysave');
|
|
272
|
+
_fs.writeFileSync(fp, JSON.stringify(data, null, 2), 'utf8');
|
|
273
|
+
},
|
|
274
|
+
load: (name) => {
|
|
275
|
+
const fp = _path.join(process.cwd(), name + '.thysave');
|
|
276
|
+
if (!_fs.existsSync(fp)) return null;
|
|
277
|
+
return JSON.parse(_fs.readFileSync(fp, 'utf8'));
|
|
278
|
+
},
|
|
279
|
+
del: (name) => {
|
|
280
|
+
const fp = _path.join(process.cwd(), name + '.thysave');
|
|
281
|
+
if (_fs.existsSync(fp)) _fs.unlinkSync(fp);
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
// ── optimised calculate ───────────────────────────────────────
|
|
286
|
+
const __thy_calculate = (expr) => {
|
|
287
|
+
// expr is already a JS expression string – evaluate safely
|
|
288
|
+
// This is called at runtime for dynamic calculate() statements
|
|
289
|
+
try {
|
|
290
|
+
// Use Function constructor for safe, optimisable evaluation
|
|
291
|
+
return (new Function('Math', `return (${expr});`))(Math);
|
|
292
|
+
} catch(e) {
|
|
293
|
+
throw new TypeError('[ThyScript] calculate error: ' + e.message);
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
return Object.freeze({
|
|
298
|
+
math, vec, str, arr, obj, io, security, types, time, proc, http, persist,
|
|
299
|
+
fastFloor, fastAbs, fastMod,
|
|
300
|
+
__thy_calculate,
|
|
301
|
+
// convenience top-level aliases
|
|
302
|
+
print: io.print,
|
|
303
|
+
len: (v) => (Array.isArray(v) || typeof v === 'string') ? v.length : Object.keys(v).length,
|
|
304
|
+
range: arr.range,
|
|
305
|
+
type: types.type,
|
|
306
|
+
hash: security.hash,
|
|
307
|
+
sleep: time.sleep,
|
|
308
|
+
now: time.now,
|
|
309
|
+
env: proc.env,
|
|
310
|
+
fetch: http.fetch,
|
|
311
|
+
save: persist.save,
|
|
312
|
+
load: persist.load,
|
|
313
|
+
});
|
|
314
|
+
})();
|
|
315
|
+
|
|
316
|
+
// Convenience globals injected from stdlib
|
|
317
|
+
const __thy_print = __thy_stdlib.print;
|
|
318
|
+
const __thy_len = __thy_stdlib.len;
|
|
319
|
+
const __thy_range = __thy_stdlib.range;
|
|
320
|
+
const __thy_sleep = __thy_stdlib.time.sleep;
|
|
321
|
+
|
|
322
|
+
function __thy_stringify(v) {
|
|
323
|
+
if (v === null) return 'null';
|
|
324
|
+
if (v === undefined) return 'undefined';
|
|
325
|
+
if (typeof v === 'object') { try { return JSON.stringify(v); } catch(_) { return String(v); } }
|
|
326
|
+
return String(v);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Shared typed buffers for hot numeric paths
|
|
330
|
+
const __thy_f64 = new Float64Array(256);
|
|
331
|
+
const __thy_i32 = new Int32Array(256);
|
|
332
|
+
const __thy_sab = typeof SharedArrayBuffer !== 'undefined' ? new SharedArrayBuffer(4096) : null;
|
|
333
|
+
|
|
334
|
+
const __thy_express = (() => { try { return require('express'); } catch(_) { return null; } })();
|
|
335
|
+
const __thy_app = __thy_express ? __thy_express() : null;
|
|
336
|
+
|
|
337
|
+
// ── program body ──────────────────────────────────────────────
|
|
338
|
+
(async () => {
|
|
339
|
+
let port = 3001;
|
|
340
|
+
let secretKey = 'thyscript_super_secret_auth_key_2024';
|
|
341
|
+
let users = [];
|
|
342
|
+
let sessions = { };
|
|
343
|
+
// [public] class
|
|
344
|
+
class User {
|
|
345
|
+
constructor() {
|
|
346
|
+
this.id = '';
|
|
347
|
+
this.username = '';
|
|
348
|
+
this.email = '';
|
|
349
|
+
this.passwordHash = '';
|
|
350
|
+
this.role = 'user';
|
|
351
|
+
this.createdAt = '';
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
// [ThyScript] Server declaration
|
|
355
|
+
if (!__thy_express) throw new Error('[ThyScript] express is not installed. Run: npm install express');
|
|
356
|
+
__thy_app.use(require('express').json());
|
|
357
|
+
__thy_app.use(require('express').urlencoded({ extended: true }));
|
|
358
|
+
__thy_print('🔐 Starting ThyScript Auth Server on port 3001...');
|
|
359
|
+
const __thy_server = __thy_app.listen(3001, '0.0.0.0', () => {
|
|
360
|
+
__thy_print(`[ThyScript] Server running on http://${'0.0.0.0'}:${3001}`);
|
|
361
|
+
});
|
|
362
|
+
if (__thy_app) {
|
|
363
|
+
__thy_app.get('/', async (request, response) => {
|
|
364
|
+
const req = request; const res = response;
|
|
365
|
+
// HtmlClass: login_page
|
|
366
|
+
const __html_login_page = `<!DOCTYPE html>
|
|
367
|
+
<html lang="id">
|
|
368
|
+
<head>
|
|
369
|
+
<meta charset="UTF-8">
|
|
370
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
371
|
+
<title>ThyScript Secure Auth</title>
|
|
372
|
+
<style>
|
|
373
|
+
* { margin: 0; padding: 0; box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
|
|
374
|
+
body {
|
|
375
|
+
background: linear-gradient(135deg, #090919 0%, #151528 50%, #0d1b2a 100%);
|
|
376
|
+
min-height: 100vh;
|
|
377
|
+
display: flex;
|
|
378
|
+
align-items: center;
|
|
379
|
+
justify-content: center;
|
|
380
|
+
color: #fff;
|
|
381
|
+
padding: 20px;
|
|
382
|
+
}
|
|
383
|
+
.container {
|
|
384
|
+
width: 100%;
|
|
385
|
+
max-width: 420px;
|
|
386
|
+
}
|
|
387
|
+
.brand {
|
|
388
|
+
text-align: center;
|
|
389
|
+
margin-bottom: 24px;
|
|
390
|
+
}
|
|
391
|
+
.brand .logo {
|
|
392
|
+
font-size: 42px;
|
|
393
|
+
display: inline-block;
|
|
394
|
+
background: -webkit-linear-gradient(#6366f1, #a855f7);
|
|
395
|
+
-webkit-background-clip: text;
|
|
396
|
+
-webkit-text-fill-color: transparent;
|
|
397
|
+
font-weight: 900;
|
|
398
|
+
}
|
|
399
|
+
.brand p {
|
|
400
|
+
color: rgba(255, 255, 255, 0.5);
|
|
401
|
+
font-size: 14px;
|
|
402
|
+
margin-top: 4px;
|
|
403
|
+
}
|
|
404
|
+
.card {
|
|
405
|
+
background: rgba(255, 255, 255, 0.04);
|
|
406
|
+
backdrop-filter: blur(20px);
|
|
407
|
+
-webkit-backdrop-filter: blur(20px);
|
|
408
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
409
|
+
border-radius: 24px;
|
|
410
|
+
padding: 32px;
|
|
411
|
+
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5);
|
|
412
|
+
}
|
|
413
|
+
.tabs {
|
|
414
|
+
display: flex;
|
|
415
|
+
background: rgba(0, 0, 0, 0.3);
|
|
416
|
+
padding: 4px;
|
|
417
|
+
border-radius: 14px;
|
|
418
|
+
margin-bottom: 24px;
|
|
419
|
+
}
|
|
420
|
+
.tab {
|
|
421
|
+
flex: 1;
|
|
422
|
+
padding: 10px;
|
|
423
|
+
border: none;
|
|
424
|
+
background: transparent;
|
|
425
|
+
color: rgba(255, 255, 255, 0.6);
|
|
426
|
+
font-size: 14px;
|
|
427
|
+
font-weight: 600;
|
|
428
|
+
border-radius: 10px;
|
|
429
|
+
cursor: pointer;
|
|
430
|
+
transition: all 0.2s ease;
|
|
431
|
+
}
|
|
432
|
+
.tab.active {
|
|
433
|
+
background: linear-gradient(135deg, #6366f1, #a855f7);
|
|
434
|
+
color: #fff;
|
|
435
|
+
box-shadow: 0 4px 15px rgba(99, 102, 241, 0.35);
|
|
436
|
+
}
|
|
437
|
+
.form-group {
|
|
438
|
+
margin-bottom: 18px;
|
|
439
|
+
}
|
|
440
|
+
label {
|
|
441
|
+
display: block;
|
|
442
|
+
font-size: 13px;
|
|
443
|
+
font-weight: 500;
|
|
444
|
+
margin-bottom: 8px;
|
|
445
|
+
color: rgba(255, 255, 255, 0.8);
|
|
446
|
+
}
|
|
447
|
+
input {
|
|
448
|
+
width: 100%;
|
|
449
|
+
background: rgba(255, 255, 255, 0.06);
|
|
450
|
+
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
451
|
+
border-radius: 12px;
|
|
452
|
+
padding: 14px 16px;
|
|
453
|
+
font-size: 15px;
|
|
454
|
+
color: #fff;
|
|
455
|
+
outline: none;
|
|
456
|
+
transition: all 0.2s;
|
|
457
|
+
}
|
|
458
|
+
input:focus {
|
|
459
|
+
border-color: #818cf8;
|
|
460
|
+
background: rgba(255, 255, 255, 0.09);
|
|
461
|
+
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2);
|
|
462
|
+
}
|
|
463
|
+
.btn-submit {
|
|
464
|
+
width: 100%;
|
|
465
|
+
padding: 14px;
|
|
466
|
+
border: none;
|
|
467
|
+
border-radius: 12px;
|
|
468
|
+
background: linear-gradient(135deg, #6366f1, #a855f7);
|
|
469
|
+
color: white;
|
|
470
|
+
font-size: 16px;
|
|
471
|
+
font-weight: 600;
|
|
472
|
+
cursor: pointer;
|
|
473
|
+
margin-top: 10px;
|
|
474
|
+
transition: all 0.2s ease;
|
|
475
|
+
}
|
|
476
|
+
.btn-submit:hover { transform: translateY(-2px); filter: brightness(1.1); }
|
|
477
|
+
.btn-submit:active { transform: translateY(0); }
|
|
478
|
+
.alert {
|
|
479
|
+
padding: 12px 16px;
|
|
480
|
+
border-radius: 12px;
|
|
481
|
+
margin-top: 16px;
|
|
482
|
+
font-size: 14px;
|
|
483
|
+
display: none;
|
|
484
|
+
}
|
|
485
|
+
.alert-success { background: rgba(16, 185, 129, 0.2); border: 1px solid rgba(16, 185, 129, 0.4); color: #34d399; }
|
|
486
|
+
.alert-error { background: rgba(239, 68, 68, 0.2); border: 1px solid rgba(239, 68, 68, 0.4); color: #f87171; }
|
|
487
|
+
|
|
488
|
+
/* Dashboard view */
|
|
489
|
+
.dashboard {
|
|
490
|
+
display: none;
|
|
491
|
+
text-align: center;
|
|
492
|
+
}
|
|
493
|
+
.user-icon {
|
|
494
|
+
width: 80px;
|
|
495
|
+
height: 80px;
|
|
496
|
+
border-radius: 50%;
|
|
497
|
+
background: linear-gradient(135deg, #6366f1, #a855f7);
|
|
498
|
+
display: flex;
|
|
499
|
+
align-items: center;
|
|
500
|
+
justify-content: center;
|
|
501
|
+
font-size: 32px;
|
|
502
|
+
margin: 0 auto 16px;
|
|
503
|
+
color: white;
|
|
504
|
+
}
|
|
505
|
+
.token-box {
|
|
506
|
+
background: rgba(0, 0, 0, 0.4);
|
|
507
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
508
|
+
border-radius: 12px;
|
|
509
|
+
padding: 14px;
|
|
510
|
+
font-size: 12px;
|
|
511
|
+
font-family: monospace;
|
|
512
|
+
word-break: break-all;
|
|
513
|
+
color: #a78bfa;
|
|
514
|
+
margin: 16px 0;
|
|
515
|
+
text-align: left;
|
|
516
|
+
}
|
|
517
|
+
.btn-logout {
|
|
518
|
+
padding: 10px 24px;
|
|
519
|
+
border-radius: 12px;
|
|
520
|
+
border: 1px solid rgba(239, 68, 68, 0.4);
|
|
521
|
+
background: rgba(239, 68, 68, 0.15);
|
|
522
|
+
color: #fca5a5;
|
|
523
|
+
cursor: pointer;
|
|
524
|
+
font-weight: 600;
|
|
525
|
+
}
|
|
526
|
+
</style>
|
|
527
|
+
</head>
|
|
528
|
+
<body>
|
|
529
|
+
<div class="container">
|
|
530
|
+
<div class="brand">
|
|
531
|
+
<div class="logo">⚡ ThyScript Auth</div>
|
|
532
|
+
<p>High-Security Authentication System</p>
|
|
533
|
+
</div>
|
|
534
|
+
|
|
535
|
+
<div class="card" id="authBox">
|
|
536
|
+
<div class="tabs">
|
|
537
|
+
<button class="tab active" onclick="switchTab('login')">Masuk</button>
|
|
538
|
+
<button class="tab" onclick="switchTab('register')">Daftar</button>
|
|
539
|
+
</div>
|
|
540
|
+
|
|
541
|
+
<div id="loginForm">
|
|
542
|
+
<div class="form-group">
|
|
543
|
+
<label>Username</label>
|
|
544
|
+
<input type="text" id="logUser" placeholder="Masukkan username">
|
|
545
|
+
</div>
|
|
546
|
+
<div class="form-group">
|
|
547
|
+
<label>Password</label>
|
|
548
|
+
<input type="password" id="logPass" placeholder="Masukkan password">
|
|
549
|
+
</div>
|
|
550
|
+
<button class="btn-submit" onclick="doLogin()">🔑 Masuk Sekarang</button>
|
|
551
|
+
</div>
|
|
552
|
+
|
|
553
|
+
<div id="registerForm" style="display:none">
|
|
554
|
+
<div class="form-group">
|
|
555
|
+
<label>Username</label>
|
|
556
|
+
<input type="text" id="regUser" placeholder="Pilih username unik">
|
|
557
|
+
</div>
|
|
558
|
+
<div class="form-group">
|
|
559
|
+
<label>Email</label>
|
|
560
|
+
<input type="email" id="regEmail" placeholder="nama@domain.com">
|
|
561
|
+
</div>
|
|
562
|
+
<div class="form-group">
|
|
563
|
+
<label>Password</label>
|
|
564
|
+
<input type="password" id="regPass" placeholder="Minimal 6 karakter">
|
|
565
|
+
</div>
|
|
566
|
+
<button class="btn-submit" onclick="doRegister()">✨ Buat Akun Baru</button>
|
|
567
|
+
</div>
|
|
568
|
+
|
|
569
|
+
<div id="alertBox" class="alert"></div>
|
|
570
|
+
</div>
|
|
571
|
+
|
|
572
|
+
<div class="card dashboard" id="dashBox">
|
|
573
|
+
<div class="user-icon" id="userInitial">U</div>
|
|
574
|
+
<h2 id="userNameText">Selamat Datang!</h2>
|
|
575
|
+
<p id="userEmailText" style="color: rgba(255,255,255,0.6); margin-top: 4px;"></p>
|
|
576
|
+
<div class="token-box">
|
|
577
|
+
<div style="color: rgba(255,255,255,0.4); margin-bottom: 4px;">ACTIVE AUTH TOKEN (JWT):</div>
|
|
578
|
+
<span id="tokenText"></span>
|
|
579
|
+
</div>
|
|
580
|
+
<button class="btn-logout" onclick="doLogout()">🚪 Keluar</button>
|
|
581
|
+
</div>
|
|
582
|
+
</div>
|
|
583
|
+
|
|
584
|
+
<script>
|
|
585
|
+
function switchTab(t) {
|
|
586
|
+
document.querySelectorAll('.tab').forEach((el, idx) => {
|
|
587
|
+
el.classList.toggle('active', (t === 'login' && idx === 0) || (t === 'register' && idx === 1));
|
|
588
|
+
});
|
|
589
|
+
document.getElementById('loginForm').style.display = t === 'login' ? 'block' : 'none';
|
|
590
|
+
document.getElementById('registerForm').style.display = t === 'register' ? 'block' : 'none';
|
|
591
|
+
hideAlert();
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
function showAlert(msg, isError) {
|
|
595
|
+
const b = document.getElementById('alertBox');
|
|
596
|
+
b.className = 'alert ' + (isError ? 'alert-error' : 'alert-success');
|
|
597
|
+
b.innerText = msg;
|
|
598
|
+
b.style.display = 'block';
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function hideAlert() {
|
|
602
|
+
document.getElementById('alertBox').style.display = 'none';
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
async function doRegister() {
|
|
606
|
+
const username = document.getElementById('regUser').value.trim();
|
|
607
|
+
const email = document.getElementById('regEmail').value.trim();
|
|
608
|
+
const password = document.getElementById('regPass').value;
|
|
609
|
+
|
|
610
|
+
if (!username || !email || !password) return showAlert('Semua bidang harus diisi!', true);
|
|
611
|
+
|
|
612
|
+
const res = await fetch('/api/register', {
|
|
613
|
+
method: 'POST',
|
|
614
|
+
headers: { 'Content-Type': 'application/json' },
|
|
615
|
+
body: JSON.stringify({ username, email, password })
|
|
616
|
+
});
|
|
617
|
+
const data = await res.json();
|
|
618
|
+
if (data.success) {
|
|
619
|
+
showAlert('Pendaftaran sukses! Silakan masuk.', false);
|
|
620
|
+
switchTab('login');
|
|
621
|
+
} else {
|
|
622
|
+
showAlert(data.message || 'Registrasi gagal', true);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
async function doLogin() {
|
|
627
|
+
const username = document.getElementById('logUser').value.trim();
|
|
628
|
+
const password = document.getElementById('logPass').value;
|
|
629
|
+
|
|
630
|
+
if (!username || !password) return showAlert('Masukkan username dan password!', true);
|
|
631
|
+
|
|
632
|
+
const res = await fetch('/api/login', {
|
|
633
|
+
method: 'POST',
|
|
634
|
+
headers: { 'Content-Type': 'application/json' },
|
|
635
|
+
body: JSON.stringify({ username, password })
|
|
636
|
+
});
|
|
637
|
+
const data = await res.json();
|
|
638
|
+
if (data.success) {
|
|
639
|
+
document.getElementById('authBox').style.display = 'none';
|
|
640
|
+
document.getElementById('dashBox').style.display = 'block';
|
|
641
|
+
document.getElementById('userNameText').innerText = 'Halo, ' + data.user.username + '!';
|
|
642
|
+
document.getElementById('userEmailText').innerText = data.user.email;
|
|
643
|
+
document.getElementById('userInitial').innerText = data.user.username[0].toUpperCase();
|
|
644
|
+
document.getElementById('tokenText').innerText = data.token;
|
|
645
|
+
} else {
|
|
646
|
+
showAlert(data.message || 'Username atau password salah', true);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function doLogout() {
|
|
651
|
+
document.getElementById('dashBox').style.display = 'none';
|
|
652
|
+
document.getElementById('authBox').style.display = 'block';
|
|
653
|
+
document.getElementById('logPass').value = '';
|
|
654
|
+
hideAlert();
|
|
655
|
+
}
|
|
656
|
+
</script>
|
|
657
|
+
</body>
|
|
658
|
+
</html>`;
|
|
659
|
+
if (typeof response !== 'undefined' && response && typeof response.send === 'function') {
|
|
660
|
+
response.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
661
|
+
response.send(__html_login_page);
|
|
662
|
+
} else if (typeof __thy_app !== 'undefined' && __thy_app) {
|
|
663
|
+
__thy_app.get('/login_page', (req, res) => {
|
|
664
|
+
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
665
|
+
res.send(__html_login_page);
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
if (__thy_app) {
|
|
671
|
+
__thy_app.post('/api/register', async (request, response) => {
|
|
672
|
+
const req = request; const res = response;
|
|
673
|
+
let username = request.body.username;
|
|
674
|
+
let email = request.body.email;
|
|
675
|
+
let password = request.body.password;
|
|
676
|
+
if (((!username || !email) || !password)) {
|
|
677
|
+
response.json({ 'success': false, 'message': 'Field tidak boleh kosong' });
|
|
678
|
+
}
|
|
679
|
+
if (found) {
|
|
680
|
+
response.json({ 'success': false, 'message': 'Username sudah terdaftar' });
|
|
681
|
+
} else {
|
|
682
|
+
let hash = (security.hashPassword ? awaitsecurity.hashPassword(password) : security.hash(password));
|
|
683
|
+
let newUser = { 'id': String(Date.now()), 'username': username, 'email': email, 'passwordHash': hash, 'role': 'user', 'createdAt': new Date().toISOString() };
|
|
684
|
+
users += newUser;
|
|
685
|
+
__thy_print(('👤 New user registered: ' + username));
|
|
686
|
+
response.json({ 'success': true, 'message': 'User registered successfully' });
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
if (__thy_app) {
|
|
691
|
+
__thy_app.post('/api/login', async (request, response) => {
|
|
692
|
+
const req = request; const res = response;
|
|
693
|
+
let username = request.body.username;
|
|
694
|
+
let password = request.body.password;
|
|
695
|
+
if (!user) {
|
|
696
|
+
response.json({ 'success': false, 'message': 'User tidak ditemukan' });
|
|
697
|
+
} else {
|
|
698
|
+
let isValid = (security.verifyPassword ? awaitsecurity.verifyPassword(password, user.passwordHash) : (security.hash(password) === user.passwordHash));
|
|
699
|
+
if (!isValid) {
|
|
700
|
+
response.json({ 'success': false, 'message': 'Password salah' });
|
|
701
|
+
} else {
|
|
702
|
+
let token = (security.generateToken ? security.generateToken({ 'username': user.username, 'email': user.email }, secretKey) : security.hash((user.username + secretKey)));
|
|
703
|
+
__thy_print(('🔑 User logged in: ' + username));
|
|
704
|
+
response.json({ 'success': true, 'token': token, 'user': { 'username': user.username, 'email': user.email, 'role': user.role } });
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
__thy_print(('🚀 ThyScript Login Server ready on port ' + port));
|
|
710
|
+
})().catch(e => { console.error("[ThyScript Runtime Error]", e); process.exit(1); });
|