@luonghaminhvy/songma 0.5.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/LICENSE +21 -0
- package/bin/songma.js +61 -0
- package/index.js +79 -0
- package/loader.mjs +30 -0
- package/package.json +36 -0
- package/register.js +14 -0
- package/test_runner.js +33 -0
- package/transpiler.js +242 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Luong Ha Minh Vy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/bin/songma.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { transpileFile } = require('../index');
|
|
5
|
+
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
|
|
8
|
+
if (args.length === 0 || args.includes('-h') || args.includes('--help')) {
|
|
9
|
+
console.log(`
|
|
10
|
+
SongMa (Sông Mã) Node.js CLI Runner
|
|
11
|
+
|
|
12
|
+
Cách sử dụng:
|
|
13
|
+
songma <tệp.smjs|tệp.smts> Chạy trực tiếp tệp mã nguồn SongMa trên Node.js
|
|
14
|
+
songma build <tệp> [-o đầu_ra] Biên dịch tệp sang JavaScript hoặc TypeScript
|
|
15
|
+
|
|
16
|
+
Ví dụ:
|
|
17
|
+
songma nguon/chinh.smjs
|
|
18
|
+
songma build nguon/kieu_du_lieu.smts -o dist/bundle.ts
|
|
19
|
+
`);
|
|
20
|
+
process.exit(0);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const command = args[0];
|
|
24
|
+
|
|
25
|
+
if (command === 'build') {
|
|
26
|
+
const targetFile = args[1];
|
|
27
|
+
if (!targetFile) {
|
|
28
|
+
console.error('Lỗi: Cần đường dẫn tệp để biên dịch.');
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let outIndex = args.indexOf('-o');
|
|
33
|
+
if (outIndex === -1) outIndex = args.indexOf('--output');
|
|
34
|
+
const outPath = outIndex !== -1 ? args[outIndex + 1] : null;
|
|
35
|
+
const isTs = args.includes('--ts');
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const output = transpileFile(targetFile, { isTs });
|
|
39
|
+
if (outPath) {
|
|
40
|
+
const fs = require('fs');
|
|
41
|
+
fs.writeFileSync(outPath, output, 'utf-8');
|
|
42
|
+
console.log(`Đã biên dịch thành công sang: ${outPath}`);
|
|
43
|
+
} else {
|
|
44
|
+
console.log(output);
|
|
45
|
+
}
|
|
46
|
+
} catch (err) {
|
|
47
|
+
console.error(err.message);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
// Chạy trực tiếp tệp với require('songma/register')
|
|
52
|
+
const targetFile = path.resolve(process.cwd(), command);
|
|
53
|
+
require('../register');
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
require(targetFile);
|
|
57
|
+
} catch (err) {
|
|
58
|
+
console.error(err);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { spawnSync } = require('child_process');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const { transpilePureJS } = require('./transpiler');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Kiểm tra xem binary `sm` hoặc `songma` có sẵn trên hệ thống hay không
|
|
9
|
+
*/
|
|
10
|
+
function getBinaryPath() {
|
|
11
|
+
if (process.env.SONGMA_BIN && fs.existsSync(process.env.SONGMA_BIN)) {
|
|
12
|
+
return process.env.SONGMA_BIN;
|
|
13
|
+
}
|
|
14
|
+
const localBinName = process.platform === 'win32' ? 'sm.exe' : 'sm';
|
|
15
|
+
const workspaceDebug = path.resolve(__dirname, '../../target/debug', localBinName);
|
|
16
|
+
const workspaceRelease = path.resolve(__dirname, '../../target/release', localBinName);
|
|
17
|
+
|
|
18
|
+
if (fs.existsSync(workspaceRelease)) {
|
|
19
|
+
return workspaceRelease;
|
|
20
|
+
}
|
|
21
|
+
if (fs.existsSync(workspaceDebug)) {
|
|
22
|
+
return workspaceDebug;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return localBinName;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Biên dịch một tệp mã nguồn SongMa sang JavaScript hoặc TypeScript
|
|
30
|
+
* Tự động chuyển tiếp sang JavaScript Transpiler tích hợp nếu máy chưa cài binary Rust.
|
|
31
|
+
* @param {string} filePath - Đường dẫn tới tệp (.smjs hoặc .smts)
|
|
32
|
+
* @param {object} options - Tùy chọn biên dịch ({ isTs?: boolean })
|
|
33
|
+
* @returns {string} Mã nguồn JavaScript hoặc TypeScript đã biên dịch
|
|
34
|
+
*/
|
|
35
|
+
function transpileFile(filePath, options = {}) {
|
|
36
|
+
const isTs = options.isTs !== undefined ? options.isTs : false;
|
|
37
|
+
|
|
38
|
+
// Đọc nội dung tệp
|
|
39
|
+
const source = fs.readFileSync(filePath, 'utf-8');
|
|
40
|
+
|
|
41
|
+
// Thử gọi binary Rust `sm` trước để đạt tốc độ tối đa nếu có sẵn
|
|
42
|
+
try {
|
|
43
|
+
const bin = getBinaryPath();
|
|
44
|
+
const args = ['build', filePath];
|
|
45
|
+
if (isTs) {
|
|
46
|
+
args.push('--ts');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const result = spawnSync(bin, args, {
|
|
50
|
+
encoding: 'utf-8',
|
|
51
|
+
windowsHide: true,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (!result.error && result.status === 0 && result.stdout) {
|
|
55
|
+
return result.stdout;
|
|
56
|
+
}
|
|
57
|
+
} catch (_) {
|
|
58
|
+
// Bỏ qua lỗi spawn để chuyển sang JavaScript Engine tích hợp sẵn
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Fallback: Chạy trình biên dịch JavaScript thuần tích hợp sẵn
|
|
62
|
+
return transpilePureJS(source, isTs);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Biên dịch một chuỗi mã nguồn SongMa
|
|
67
|
+
* @param {string} code - Chuỗi mã nguồn tiếng Việt
|
|
68
|
+
* @param {object} options - Tùy chọn biên dịch ({ filename?: string, isTs?: boolean })
|
|
69
|
+
* @returns {string}
|
|
70
|
+
*/
|
|
71
|
+
function transpile(code, options = {}) {
|
|
72
|
+
const isTs = options.isTs !== undefined ? options.isTs : false;
|
|
73
|
+
return transpilePureJS(code, isTs);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = {
|
|
77
|
+
transpile,
|
|
78
|
+
transpileFile,
|
|
79
|
+
};
|
package/loader.mjs
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { fileURLToPath } from 'url';
|
|
2
|
+
import { createRequire } from 'module';
|
|
3
|
+
|
|
4
|
+
const require = createRequire(import.meta.url);
|
|
5
|
+
const { transpileFile } = require('./index.js');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Node.js ESM resolve hook
|
|
9
|
+
*/
|
|
10
|
+
export async function resolve(specifier, context, nextResolve) {
|
|
11
|
+
return nextResolve(specifier, context);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Node.js ESM load hook
|
|
16
|
+
*/
|
|
17
|
+
export async function load(url, context, nextLoad) {
|
|
18
|
+
if (url.endsWith('.sm') || url.endsWith('.smx')) {
|
|
19
|
+
const filePath = fileURLToPath(url);
|
|
20
|
+
const source = transpileFile(filePath, { isTs: false });
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
format: 'commonjs',
|
|
24
|
+
shortCircuit: true,
|
|
25
|
+
source,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return nextLoad(url, context);
|
|
30
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@luonghaminhvy/songma",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Node.js Runtime Loader and CLI for SongMa (Sông Mã) programming language",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"songma": "bin/songma.js",
|
|
8
|
+
"sm": "bin/songma.js"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./index.js",
|
|
12
|
+
"./register": "./register.js",
|
|
13
|
+
"./loader": "./loader.mjs"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"songma",
|
|
17
|
+
"vietlang",
|
|
18
|
+
"vietscript",
|
|
19
|
+
"vietnamese",
|
|
20
|
+
"transpiler",
|
|
21
|
+
"loader"
|
|
22
|
+
],
|
|
23
|
+
"author": "Luong Ha Minh Vy",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/LuongHaMinhVy/SongMa.git"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/LuongHaMinhVy/SongMa",
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=20.0.0"
|
|
35
|
+
}
|
|
36
|
+
}
|
package/register.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const { transpileFile } = require('./index');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Đăng ký extension hook cho CommonJS require()
|
|
5
|
+
*/
|
|
6
|
+
function compileSongMa(module, filename) {
|
|
7
|
+
const compiledJs = transpileFile(filename, { isTs: false });
|
|
8
|
+
module._compile(compiledJs, filename);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (require.extensions) {
|
|
12
|
+
require.extensions['.sm'] = compileSongMa;
|
|
13
|
+
require.extensions['.smx'] = compileSongMa;
|
|
14
|
+
}
|
package/test_runner.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const assert = require('assert');
|
|
3
|
+
const { transpile, transpileFile } = require('./index');
|
|
4
|
+
|
|
5
|
+
console.log('--- 1. Kiểm tra API transpile() ---');
|
|
6
|
+
const sampleCode = `
|
|
7
|
+
biến a = 10;
|
|
8
|
+
biến b = 20;
|
|
9
|
+
hàm tinh_tong(x, y) {
|
|
10
|
+
trả x + y;
|
|
11
|
+
}
|
|
12
|
+
in("Tổng: " + tinh_tong(a, b));
|
|
13
|
+
`;
|
|
14
|
+
const outputJs = transpile(sampleCode);
|
|
15
|
+
assert(outputJs.includes('function tinh_tong'), 'Cần chứa định nghĩa hàm');
|
|
16
|
+
console.log('✓ transpile() hoạt động chính xác.');
|
|
17
|
+
|
|
18
|
+
console.log('--- 2. Kiểm tra require() hook (register.js) ---');
|
|
19
|
+
require('./register');
|
|
20
|
+
const xinChaoPath = path.resolve(__dirname, '../../examples/xin_chao.sm');
|
|
21
|
+
require(xinChaoPath);
|
|
22
|
+
console.log('✓ require(examples/xin_chao.sm) thành công.');
|
|
23
|
+
|
|
24
|
+
console.log('--- 3. Kiểm tra transpileFile() với .smts ---');
|
|
25
|
+
const tsPath = path.resolve(__dirname, '../../examples/typescript_demo.sm');
|
|
26
|
+
const tsOutput = transpileFile(tsPath, { isTs: true });
|
|
27
|
+
assert(tsOutput.includes('interface SanPham'), 'Cần chứa interface SanPham');
|
|
28
|
+
assert(tsOutput.includes('function tinh_thue_vat'), 'Cần chứa hàm TypeScript');
|
|
29
|
+
console.log('✓ transpileFile(.sm) thành công.');
|
|
30
|
+
|
|
31
|
+
console.log('\n========================================');
|
|
32
|
+
console.log('TẤT CẢ TEST CỦA GIAI ĐOẠN 1 ĐỀU THÀNH CÔNG!');
|
|
33
|
+
console.log('========================================');
|
package/transpiler.js
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SongMa Pure JavaScript Transpiler Engine
|
|
3
|
+
* Tự động chuyển đổi mã nguồn SongMa (.smjs / .smts) sang JavaScript/TypeScript mà không cần cài đặt binary Rust.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
function getRuntimeHeader(isTs = false) {
|
|
7
|
+
return isTs
|
|
8
|
+
? `// --- SongMa TypeScript Runtime Library ---
|
|
9
|
+
const fs_songma = typeof require !== 'undefined' ? require('fs') : null;
|
|
10
|
+
const in_songma = (...args: any[]) => console.log(...args);
|
|
11
|
+
const in_dòng = in_songma;
|
|
12
|
+
const in_ngang = (...args: any[]) => (typeof process !== 'undefined' ? process.stdout.write(args.join(' ')) : console.log(...args));
|
|
13
|
+
const độ_dài = (item: any) => (item ? (typeof item === 'string' ? [...item].length : item.length) : 0);
|
|
14
|
+
const ép_số = (val: any) => Number(val);
|
|
15
|
+
const ép_chuỗi = (val: any) => String(val);
|
|
16
|
+
const thời_gian = () => Date.now();
|
|
17
|
+
const căn_bậc_hai = (n: number) => Math.sqrt(n);
|
|
18
|
+
const làm_tròn = (n: number) => Math.round(n);
|
|
19
|
+
const sàn = (n: number) => Math.floor(n);
|
|
20
|
+
const trần = (n: number) => Math.ceil(n);
|
|
21
|
+
const trị_tuyệt_đối = (n: number) => Math.abs(n);
|
|
22
|
+
const mũ = (a: number, b: number) => Math.pow(a, b);
|
|
23
|
+
const lớn_nhất = (...args: number[]) => Math.max(...args);
|
|
24
|
+
const nhỏ_nhất = (...args: number[]) => Math.min(...args);
|
|
25
|
+
const ngẫu_nhiên = (min?: number, max?: number) => (min !== undefined && max !== undefined ? Math.floor(Math.random() * (max - min + 1)) + min : Math.random());
|
|
26
|
+
const thêm = (arr: any[], item: any) => { arr.push(item); return arr; };
|
|
27
|
+
const đọc_tệp = (p: string) => (fs_songma ? fs_songma.readFileSync(p, 'utf-8') : '');
|
|
28
|
+
const ghi_tệp = (p: string, data: any) => (fs_songma ? (fs_songma.writeFileSync(p, String(data), 'utf-8'), true) : false);
|
|
29
|
+
const tồn_tại_tệp = (p: string) => (fs_songma ? fs_songma.existsSync(p) : false);
|
|
30
|
+
const lấy_môi_trường = (k: string) => (typeof process !== 'undefined' && process.env[k] ? process.env[k] : null);
|
|
31
|
+
const giải_mã_json = (s: string) => JSON.parse(s);
|
|
32
|
+
const tạo_json = (v: any) => JSON.stringify(v, null, 2);
|
|
33
|
+
const tách = (s: string, d = ' ') => String(s).split(d);
|
|
34
|
+
const tách_chuỗi = tách;
|
|
35
|
+
const nối = (arr: any[], d = '') => (Array.isArray(arr) ? arr.join(d) : String(arr));
|
|
36
|
+
const nối_mảng = nối;
|
|
37
|
+
const cắt = (s: string, start: number, len?: number) => (len !== undefined ? String(s).substring(start, start + len) : String(s).substring(start));
|
|
38
|
+
const cắt_chuỗi = cắt;
|
|
39
|
+
const viết_hoa = (s: string) => String(s).toUpperCase();
|
|
40
|
+
const viết_thường = (s: string) => String(s).toLowerCase();
|
|
41
|
+
const cắt_khoảng_trắng = (s: string) => String(s).trim();
|
|
42
|
+
const chứa = (s: any, q: any) => (typeof s === 'string' || Array.isArray(s) ? s.includes(q) : false);
|
|
43
|
+
const bắt_đầu_bằng = (s: string, prefix: string) => String(s).startsWith(prefix);
|
|
44
|
+
const kết_thúc_bằng = (s: string, suffix: string) => String(s).endsWith(suffix);
|
|
45
|
+
const thay_thế = (s: string, find: any, replace: string) => String(s).replace(find, replace);
|
|
46
|
+
const thay_thế_tất_cả = (s: string, find: any, replace: string) => String(s).replaceAll(find, replace);
|
|
47
|
+
const khớp_mẫu = (s: string, pattern: string | RegExp) => new RegExp(pattern).test(String(s));
|
|
48
|
+
const mã_hóa_base64 = (s: string) => (typeof Buffer !== 'undefined' ? Buffer.from(s).toString('base64') : btoa(s));
|
|
49
|
+
const giải_mã_base64 = (s: string) => (typeof Buffer !== 'undefined' ? Buffer.from(s, 'base64').toString('utf-8') : atob(s));
|
|
50
|
+
const tìm = (target: any, q: any) => (typeof target === 'string' || Array.isArray(target) ? target.indexOf(q) : -1);
|
|
51
|
+
const dừng_chờ = (ms: number) => { const start = Date.now(); while (Date.now() - start < ms) {} };
|
|
52
|
+
const lấy_dữ_liệu = (url: string, opts?: any) => fetch(url, opts);
|
|
53
|
+
const hẹn_giờ = (fn: (...args: any[]) => void, ms: number) => setTimeout(fn, ms);
|
|
54
|
+
const hủy_hẹn_giờ = (id: any) => clearTimeout(id);
|
|
55
|
+
const lặp_định_kỳ = (fn: (...args: any[]) => void, ms: number) => setInterval(fn, ms);
|
|
56
|
+
const hủy_lặp = (id: any) => clearInterval(id);
|
|
57
|
+
// --- End Runtime ---
|
|
58
|
+
|
|
59
|
+
`
|
|
60
|
+
: `// --- SongMa JavaScript Runtime Library ---
|
|
61
|
+
const fs_songma = typeof require !== 'undefined' ? require('fs') : null;
|
|
62
|
+
const in_songma = (...args) => console.log(...args);
|
|
63
|
+
const in_dòng = in_songma;
|
|
64
|
+
const in_ngang = (...args) => (typeof process !== 'undefined' ? process.stdout.write(args.join(' ')) : console.log(...args));
|
|
65
|
+
const độ_dài = (item) => (item ? (typeof item === 'string' ? [...item].length : item.length) : 0);
|
|
66
|
+
const ép_số = (val) => Number(val);
|
|
67
|
+
const ép_chuỗi = (val) => String(val);
|
|
68
|
+
const thời_gian = () => Date.now();
|
|
69
|
+
const căn_bậc_hai = (n) => Math.sqrt(n);
|
|
70
|
+
const làm_tròn = (n) => Math.round(n);
|
|
71
|
+
const sàn = (n) => Math.floor(n);
|
|
72
|
+
const trần = (n) => Math.ceil(n);
|
|
73
|
+
const trị_tuyệt_đối = (n) => Math.abs(n);
|
|
74
|
+
const mũ = (a, b) => Math.pow(a, b);
|
|
75
|
+
const lớn_nhất = (...args) => Math.max(...args);
|
|
76
|
+
const nhỏ_nhất = (...args) => Math.min(...args);
|
|
77
|
+
const ngẫu_nhiên = (min, max) => (min !== undefined && max !== undefined ? Math.floor(Math.random() * (max - min + 1)) + min : Math.random());
|
|
78
|
+
const thêm = (arr, item) => { arr.push(item); return arr; };
|
|
79
|
+
const đọc_tệp = (p) => (fs_songma ? fs_songma.readFileSync(p, 'utf-8') : '');
|
|
80
|
+
const ghi_tệp = (p, data) => (fs_songma ? (fs_songma.writeFileSync(p, String(data), 'utf-8'), true) : false);
|
|
81
|
+
const tồn_tại_tệp = (p) => (fs_songma ? fs_songma.existsSync(p) : false);
|
|
82
|
+
const lấy_môi_trường = (k) => (typeof process !== 'undefined' && process.env[k] ? process.env[k] : null);
|
|
83
|
+
const giải_mã_json = (s) => JSON.parse(s);
|
|
84
|
+
const tạo_json = (v) => JSON.stringify(v, null, 2);
|
|
85
|
+
const tách = (s, d = ' ') => String(s).split(d);
|
|
86
|
+
const tách_chuỗi = tách;
|
|
87
|
+
const nối = (arr, d = '') => (Array.isArray(arr) ? arr.join(d) : String(arr));
|
|
88
|
+
const nối_mảng = nối;
|
|
89
|
+
const cắt = (s, start, len) => (len !== undefined ? String(s).substring(start, start + len) : String(s).substring(start));
|
|
90
|
+
const cắt_chuỗi = cắt;
|
|
91
|
+
const viết_hoa = (s) => String(s).toUpperCase();
|
|
92
|
+
const viết_thường = (s) => String(s).toLowerCase();
|
|
93
|
+
const cắt_khoảng_trắng = (s) => String(s).trim();
|
|
94
|
+
const chứa = (s, q) => (typeof s === 'string' || Array.isArray(s) ? s.includes(q) : false);
|
|
95
|
+
const bắt_đầu_bằng = (s, prefix) => String(s).startsWith(prefix);
|
|
96
|
+
const kết_thúc_bằng = (s, suffix) => String(s).endsWith(suffix);
|
|
97
|
+
const thay_thế = (s, find, replace) => String(s).replace(find, replace);
|
|
98
|
+
const thay_thế_tất_cả = (s, find, replace) => String(s).replaceAll(find, replace);
|
|
99
|
+
const khớp_mẫu = (s, pattern) => new RegExp(pattern).test(String(s));
|
|
100
|
+
const mã_hóa_base64 = (s) => (typeof Buffer !== 'undefined' ? Buffer.from(s).toString('base64') : btoa(s));
|
|
101
|
+
const giải_mã_base64 = (s) => (typeof Buffer !== 'undefined' ? Buffer.from(s, 'base64').toString('utf-8') : atob(s));
|
|
102
|
+
const tìm = (target, q) => (typeof target === 'string' || Array.isArray(target) ? target.indexOf(q) : -1);
|
|
103
|
+
const dừng_chờ = (ms) => { const start = Date.now(); while (Date.now() - start < ms) {} };
|
|
104
|
+
const lấy_dữ_liệu = (url, opts) => fetch(url, opts);
|
|
105
|
+
const hẹn_giờ = (fn, ms) => setTimeout(fn, ms);
|
|
106
|
+
const hủy_hẹn_giờ = (id) => clearTimeout(id);
|
|
107
|
+
const lặp_định_kỳ = (fn, ms) => setInterval(fn, ms);
|
|
108
|
+
const hủy_lặp = (id) => clearInterval(id);
|
|
109
|
+
if (typeof Array !== 'undefined') {
|
|
110
|
+
const ap = Array.prototype;
|
|
111
|
+
ap.ánh_xạ = ap.map; ap.lọc = ap.filter; ap.thu_gọn = ap.reduce; ap.duyệt = ap.forEach; ap.cho_mỗi = ap.forEach;
|
|
112
|
+
ap.tìm = ap.find; ap.tìm_vị_trí = ap.findIndex; ap.có_ít_nhất = ap.some; ap.tất_cả = ap.every;
|
|
113
|
+
ap.chứa = ap.includes; ap.bao_gồm = ap.includes; ap.thêm = ap.push; ap.đẩy = ap.push;
|
|
114
|
+
ap.rút = ap.pop; ap.lấy_ra = ap.pop; ap.thêm_đầu = ap.unshift; ap.lấy_đầu = ap.shift;
|
|
115
|
+
ap.trích = ap.slice; ap.cắt = ap.slice; ap.chắp_vá = ap.splice; ap.sắp_xếp = ap.sort;
|
|
116
|
+
ap.đảo_ngược = ap.reverse; ap.nối = ap.join; ap.gộp = ap.concat; ap.làm_phẳng = ap.flat;
|
|
117
|
+
ap.ánh_xạ_phẳng = ap.flatMap; ap.lấp_đầy = ap.fill; ap.vị_trí = ap.indexOf; ap.vị_trí_cuối = ap.lastIndexOf;
|
|
118
|
+
}
|
|
119
|
+
if (typeof String !== 'undefined') {
|
|
120
|
+
const sp = String.prototype;
|
|
121
|
+
sp.viết_hoa = sp.toUpperCase; sp.viết_thường = sp.toLowerCase; sp.cắt_khoảng_trắng = sp.trim;
|
|
122
|
+
sp.cắt_đầu = sp.trimStart; sp.cắt_đuôi = sp.trimEnd; sp.bắt_đầu_bằng = sp.startsWith; sp.kết_thúc_bằng = sp.endsWith;
|
|
123
|
+
sp.chứa = sp.includes; sp.thay_thế = sp.replace; sp.thay_thế_tất_cả = sp.replaceAll; sp.tách = sp.split;
|
|
124
|
+
sp.cắt = sp.substring; sp.khớp_mẫu = function(pattern) { return new RegExp(pattern).test(this); };
|
|
125
|
+
}
|
|
126
|
+
// --- End Runtime ---
|
|
127
|
+
|
|
128
|
+
`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function transpilePureJS(source, isTs = false) {
|
|
132
|
+
const runtimeHeader = getRuntimeHeader(isTs);
|
|
133
|
+
let code = source;
|
|
134
|
+
|
|
135
|
+
// 1. Chuyển đổi Interface / Kiểu & Enum
|
|
136
|
+
if (isTs) {
|
|
137
|
+
code = code.replace(/\b(?:kiểu|mẫu|giao_diện)\s+([a-zA-Z_À-ỹ][a-zA-Z0-9_À-ỹ]*)\s*\{/g, 'interface $1 {');
|
|
138
|
+
code = code.replace(/\bchỉ_đọc\s+/g, 'readonly ');
|
|
139
|
+
code = code.replace(/:\s*chuỗi\b/g, ': string');
|
|
140
|
+
code = code.replace(/:\s*số\b/g, ': number');
|
|
141
|
+
code = code.replace(/:\s*(?:logic|đúng_sai)\b/g, ': boolean');
|
|
142
|
+
code = code.replace(/:\s*mọi\b/g, ': any');
|
|
143
|
+
code = code.replace(/:\s*rỗng\b/g, ': void');
|
|
144
|
+
code = code.replace(/:\s*đối_tượng\b/g, ': Record<string, any>');
|
|
145
|
+
code = code.replace(/:\s*mảng<([^>]+)>/g, ': $1[]');
|
|
146
|
+
code = code.replace(/:\s*mảng\b/g, ': any[]');
|
|
147
|
+
} else {
|
|
148
|
+
// Xóa interface ở chế độ JavaScript thuần
|
|
149
|
+
code = code.replace(/\b(?:kiểu|mẫu|giao_diện)\s+[a-zA-Z_À-ỹ][a-zA-Z0-9_À-ỹ]*\s*\{[^}]*\}/g, '');
|
|
150
|
+
code = code.replace(/\bchỉ_đọc\s+/g, '');
|
|
151
|
+
// Xóa kiểu trả về của hàm và kiểu tham số/biến
|
|
152
|
+
code = code.replace(/\)\s*:\s*(?:mảng<[^>]+>|[a-zA-Z_À-ỹ][a-zA-Z0-9_À-ỹ]*(?:\s*\[\s*\])*)/g, ')');
|
|
153
|
+
code = code.replace(/([a-zA-Z_À-ỹ0-9]+)\s*:\s*(?:mảng<[^>]+>|[a-zA-Z_À-ỹ][a-zA-Z0-9_À-ỹ]*(?:\s*\[\s*\])*)(?=[,\)=;\r\n])/g, '$1');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Liệt kê (Enum)
|
|
157
|
+
if (isTs) {
|
|
158
|
+
code = code.replace(/(^|[\r\n\s;{}()])liệt_kê\s+([a-zA-Z_À-ỹ][a-zA-Z0-9_À-ỹ]*)\s*\{/g, (_, p1, p2) => p1 + 'enum ' + p2 + ' {');
|
|
159
|
+
} else {
|
|
160
|
+
code = code.replace(/(^|[\r\n\s;{}()])liệt_kê\s+([a-zA-Z_À-ỹ][a-zA-Z0-9_À-ỹ]*)\s*\{([^}]*)\}/g, (_, p1, p2, p3) => {
|
|
161
|
+
const entries = p3.split(',').map(item => item.trim()).filter(Boolean);
|
|
162
|
+
const objEntries = entries.map((e, idx) => {
|
|
163
|
+
if (e.includes('=')) {
|
|
164
|
+
const [k, v] = e.split('=').map(s => s.trim());
|
|
165
|
+
return ` ${k}: ${v}`;
|
|
166
|
+
}
|
|
167
|
+
return ` ${e}: ${idx}`;
|
|
168
|
+
}).join(',\n');
|
|
169
|
+
return `${p1}const ${p2} = Object.freeze({\n${objEntries}\n});`;
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Xuất khẩu (Export)
|
|
174
|
+
code = code.replace(/(^|[\r\n\s;{}()])xuất_mặc_định\s+/g, (_, p1) => p1 + 'export default ');
|
|
175
|
+
code = code.replace(/(^|[\r\n\s;{}()])xuất\s+/g, (_, p1) => p1 + 'export ');
|
|
176
|
+
|
|
177
|
+
// 2. Chuyển đổi từ khóa cốt lõi
|
|
178
|
+
code = code.replace(/(^|[\r\n\s;{}()])lớp\s+([^\s;{}()]+)\s+kế_thừa\s+([^\s;{}()]+)\s+hiện_thực\s+([^\s;{}()]+)\s*\{/g, (_, p1, p2, p3, p4) => p1 + 'class ' + p2 + ' extends ' + p3 + (isTs ? ' implements ' + p4 : '') + ' {');
|
|
179
|
+
code = code.replace(/(^|[\r\n\s;{}()])lớp\s+([^\s;{}()]+)\s+kế_thừa\s+([^\s;{}()]+)\s*\{/g, (_, p1, p2, p3) => p1 + 'class ' + p2 + ' extends ' + p3 + ' {');
|
|
180
|
+
code = code.replace(/(^|[\r\n\s;{}()])lớp\s+([^\s;{}()]+)\s+hiện_thực\s+([^\s;{}()]+)\s*\{/g, (_, p1, p2, p3) => p1 + 'class ' + p2 + (isTs ? ' implements ' + p3 : '') + ' {');
|
|
181
|
+
code = code.replace(/(^|[\r\n\s;{}()])lớp\s+([^\s;{}()]+)\s*\{/g, (_, p1, p2) => p1 + 'class ' + p2 + ' {');
|
|
182
|
+
|
|
183
|
+
code = code.replace(/\btĩnh\s+(?:hàm\s+)?/g, 'static ');
|
|
184
|
+
code = code.replace(/\blấy\s+([a-zA-Z_À-ỹ][a-zA-Z0-9_À-ỹ]*)\s*\(/g, 'get $1(');
|
|
185
|
+
code = code.replace(/\bgán\s+([a-zA-Z_À-ỹ][a-zA-Z0-9_À-ỹ]*)\s*\(/g, 'set $1(');
|
|
186
|
+
|
|
187
|
+
code = code.replace(/khởi_tạo\s*\(/g, 'constructor(');
|
|
188
|
+
code = code.replace(/gốc\s*\(/g, 'super(');
|
|
189
|
+
code = code.replace(/(^|[\r\n\s;{}()])mới\s+/g, (_, p1) => p1 + 'new ');
|
|
190
|
+
code = code.replace(/này\./g, 'this.');
|
|
191
|
+
code = code.replace(/(^|[\r\n\s;{}()])này(?=[\s;,):])/g, (_, p1) => p1 + 'this');
|
|
192
|
+
code = code.replace(/(^|[\r\n\s;{}()])(?:biến|đặt)\s+/g, (_, p1) => p1 + 'let ');
|
|
193
|
+
code = code.replace(/(^|[\r\n\s;{}()])hằng\s+/g, (_, p1) => p1 + 'const ');
|
|
194
|
+
code = code.replace(/(^|[\r\n\s;{}()])(?:bất_đồng_bộ|bất_đồng)\s+hàm\b/g, (_, p1) => p1 + 'async function');
|
|
195
|
+
code = code.replace(/(^|[\r\n\s;{}()])hàm\b/g, (_, p1) => p1 + 'function');
|
|
196
|
+
code = code.replace(/(^|[\r\n\s;{}()])(?:trả_về|trả)\s+/g, (_, p1) => p1 + 'return ');
|
|
197
|
+
|
|
198
|
+
// Switch - Case
|
|
199
|
+
code = code.replace(/(^|[\r\n\s;{}()])chọn\s*\(/g, (_, p1) => p1 + 'switch (');
|
|
200
|
+
code = code.replace(/(^|[\r\n\s;{}()])trường_hợp\s+/g, (_, p1) => p1 + 'case ');
|
|
201
|
+
code = code.replace(/(^|[\r\n\s;{}()])mặc_định\s*:/g, (_, p1) => p1 + 'default:');
|
|
202
|
+
|
|
203
|
+
// Control Flow & Loops
|
|
204
|
+
code = code.replace(/(^|[\r\n\s;{}()])nếu\s*\(/g, (_, p1) => p1 + 'if (');
|
|
205
|
+
code = code.replace(/(^|[\r\n\s;{}()])khác_nếu\s*\(/g, (_, p1) => p1 + 'else if (');
|
|
206
|
+
code = code.replace(/(^|[\r\n\s;{}()])(?:ngược_lại|khác)\s*\{/g, (_, p1) => p1 + 'else {');
|
|
207
|
+
|
|
208
|
+
code = code.replace(/(^|[\r\n\s;{}()])làm\s*\{/g, (_, p1) => p1 + 'do {');
|
|
209
|
+
code = code.replace(/(^|[\r\n\s;{}()])(?:trong_khi|khi)\s*\(/g, (_, p1) => p1 + 'while (');
|
|
210
|
+
code = code.replace(/(^|[\r\n\s;{}()])(?:cho_mỗi|mỗi)\s*\(\s*([^\s]+)\s+trong\s+([^)]+)\)/g, (_, p1, p2, p3) => p1 + 'for (const ' + p2 + ' of ' + p3 + ')');
|
|
211
|
+
code = code.replace(/(^|[\r\n\s;{}()])lặp\s*\(/g, (_, p1) => p1 + 'for (');
|
|
212
|
+
code = code.replace(/(^|[\r\n\s;{}()])(?:tiếp_tục|tiếp)\s*;/g, (_, p1) => p1 + 'continue;');
|
|
213
|
+
code = code.replace(/(^|[\r\n\s;{}()])dừng\s*;/g, (_, p1) => p1 + 'break;');
|
|
214
|
+
|
|
215
|
+
// Error Handling
|
|
216
|
+
code = code.replace(/(^|[\r\n\s;{}()])ném\s+/g, (_, p1) => p1 + 'throw ');
|
|
217
|
+
code = code.replace(/(^|[\r\n\s;{}()])thử\s*\{/g, (_, p1) => p1 + 'try {');
|
|
218
|
+
code = code.replace(/(^|[\r\n\s;{}()])(?:bắt_lỗi|bắt)\s*\(\s*([^\s)]+)\s*\)\s*\{/g, (_, p1, p2) => p1 + 'catch (' + p2 + ') {');
|
|
219
|
+
code = code.replace(/(^|[\r\n\s;{}()])cuối_cùng\s*\{/g, (_, p1) => p1 + 'finally {');
|
|
220
|
+
|
|
221
|
+
// Yield & Await
|
|
222
|
+
code = code.replace(/(^|[\r\n\s;{}()])nhượng\s+/g, (_, p1) => p1 + 'yield ');
|
|
223
|
+
code = code.replace(/(^|[\r\n\s;{}()])(?:chờ|đợi)\s+/g, (_, p1) => p1 + 'await ');
|
|
224
|
+
|
|
225
|
+
// Module imports
|
|
226
|
+
code = code.replace(/(^|[\r\n\s;{}()])dùng\s+"([^"]+)"\s+là\s+([^\s;]+)\s*;/g, (_, p1, p2, p3) => p1 + 'const ' + p3 + ' = require("' + p2 + '");');
|
|
227
|
+
code = code.replace(/(^|[\r\n\s;{}()])dùng\s+"([^"]+)"\s*;/g, (_, p1, p2) => p1 + 'require("' + p2 + '");');
|
|
228
|
+
|
|
229
|
+
// 3. Giá trị hằng ngữ nghĩa
|
|
230
|
+
code = code.replace(/(^|[\r\n\s;{}()])đúng(?=[\s;,):])/g, (_, p1) => p1 + 'true');
|
|
231
|
+
code = code.replace(/(^|[\r\n\s;{}()])sai(?=[\s;,):])/g, (_, p1) => p1 + 'false');
|
|
232
|
+
code = code.replace(/(^|[\r\n\s;{}()])rỗng(?=[\s;,):])/g, (_, p1) => p1 + 'null');
|
|
233
|
+
|
|
234
|
+
// 4. Hàm in
|
|
235
|
+
code = code.replace(/\b(?:in_dòng|in)\s*\(/g, 'in_songma(');
|
|
236
|
+
|
|
237
|
+
return runtimeHeader + code;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
module.exports = {
|
|
241
|
+
transpilePureJS,
|
|
242
|
+
};
|