152 lines
5.6 KiB
JavaScript
152 lines
5.6 KiB
JavaScript
const fs = require('fs/promises');
|
|
const path = require('path');
|
|
const { inspect } = require('util');
|
|
const chalk = require('chalk');
|
|
|
|
const declarationsPath = path.resolve(__dirname, './isaac-typescript-definitions/typings/');
|
|
const outPath = path.resolve(__dirname, './out/');
|
|
|
|
function transpile(parsed, indent, prefix) {
|
|
let global = false;
|
|
indent = indent || 0;
|
|
const type = parsed[0];
|
|
const d = parsed[1];
|
|
let n = '\n' + ' '.repeat(indent); // newline
|
|
switch (type) {
|
|
case 'namespace':
|
|
global = true;
|
|
case 'interface':
|
|
return `---@class ${d.name}\n${global ? '' : '__class_'}${d.name} = {}${n}${d.contents.map(c => transpile(c, indent, `${global ? '' : '__class_'}${d.name}`)).join(n)}\n`;
|
|
case 'function':
|
|
return `${d.arguments.map(p => `---@param ${p.name.replace('?', '')} ${p.type}${n}`).join('')}---@return ${d.returns}${n}function ${prefix ? prefix + ':' : ''}${d.name}(${d.arguments.map(a => a.name.replace('?', '')).join(', ')}) end`;
|
|
case 'const':
|
|
return `---@type ${d.type}${n}${prefix ? prefix + '.' : ''}${d.name} = nil`;
|
|
case 'enum':
|
|
return `${prefix ? prefix + '.' : ''}${d.name} = {${n} ${d.contents.map(c => `${c.name} = ${c.value}`).join(`,${n} `)}${n}}`;
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* @param {string} code
|
|
*/
|
|
function parse(code) {
|
|
// console.log(code);
|
|
|
|
// we're left with code containing "declare" statements
|
|
// other code will pass stuff in here recursively, but the declare doesnt _really_ matter
|
|
// were gonna end up with an ast of sorts anyways as a result, which we can then use instead of declares
|
|
|
|
// sanitize stuff
|
|
code = code.split('\n').filter(c => !c.trim().startsWith('//')).join('\n');
|
|
// remove /* */ style comments, for now
|
|
code = code.replace(/\/\*(.*?)(?=\*\/)/gs, '');
|
|
|
|
const elements = [];
|
|
|
|
// look for interfaces
|
|
let interfaceFreeCode = code;
|
|
const interface = /interface (\w+) ?{([^}]*)}/g;
|
|
while ((match = interface.exec(code)) !== null) {
|
|
interfaceFreeCode = interfaceFreeCode.replace(match[0], '');
|
|
elements.push(['interface', {name: match[1], contents: parse(match[2])}]);
|
|
}
|
|
code = interfaceFreeCode;
|
|
|
|
// look for namespaces
|
|
let namespaceFreeCode = code;
|
|
const namespace = /namespace (\w+) ?{([^}]*)}/g;
|
|
while ((match = namespace.exec(code)) !== null) {
|
|
namespaceFreeCode = namespaceFreeCode.replace(match[0], '');
|
|
elements.push(['namespace', {name: match[1], contents: parse(match[2])}]);
|
|
}
|
|
code = namespaceFreeCode;
|
|
|
|
// look for functions
|
|
let functionFreeCode = code;
|
|
const functions = /(function)?\s(\w+)\s*\(([^;]*)\)(\s*:\s*([\w\[\]]+))?/g; // wow this sucks
|
|
while ((match = functions.exec(code)) !== null) {
|
|
functionFreeCode = functionFreeCode.replace(match[0], '');
|
|
let arguments = match[3].split(',').map(s => {
|
|
const split = s.split(':').map(s => s.trim());
|
|
return {type: split[1], name: split[0]}
|
|
}).filter(c => c.type && c.name);
|
|
elements.push(['function', {name: match[2], arguments: arguments, returns: match[5]}]);
|
|
}
|
|
code = functionFreeCode;
|
|
|
|
// looks for enums
|
|
let enumFreeCode = code;
|
|
const enums = /enum (\w+) ?{([^}]*)}/g;
|
|
while ((match = enums.exec(code)) !== null) {
|
|
enumFreeCode = enumFreeCode.replace(match[0], '');
|
|
elements.push(['enum', {name: match[1], contents: match[2].split(',').filter(c => c.split('=').length === 2).map(c => {return {name: c.split('=')[0].trim(), value: c.split('=')[1].trim()}})}]);
|
|
}
|
|
code = enumFreeCode;
|
|
|
|
// looks for constants / properties
|
|
let constFreeCode = code;
|
|
const consts = /(\w+)\s*:\s*(\w+)/g;
|
|
while ((match = consts.exec(code)) !== null) {
|
|
constFreeCode = constFreeCode.replace(match[0], '');
|
|
elements.push(['const', {name: match[1], type: match[2]}]);
|
|
}
|
|
code = constFreeCode;
|
|
|
|
return elements;
|
|
}
|
|
|
|
(async () => {
|
|
let timeParsing = 0;
|
|
let timeTotal = 0;
|
|
|
|
try {
|
|
const d = await fs.lstat(outPath)
|
|
if (!d.isDirectory()) throw new Error(`${outPath} exists, and isn't a directory!`);
|
|
} catch(err) {
|
|
if (err.code === 'ENOENT') {
|
|
await fs.mkdir(outPath);
|
|
} else {
|
|
console.error(err);
|
|
process.exit();
|
|
}
|
|
}
|
|
|
|
const files = await fs.readdir(declarationsPath);
|
|
for (const f of files) {
|
|
const filePath = path.resolve(declarationsPath, f);
|
|
|
|
const stat = await fs.lstat(filePath);
|
|
if (stat.isFile() && f) {
|
|
const file = await fs.readFile(filePath, 'utf8');
|
|
|
|
let startParse = Date.now();
|
|
const parsed = parse(file);
|
|
console.log(` parsed ${chalk.magentaBright(parsed.length)} elements from ${chalk.cyanBright(f)}`);
|
|
timeParsing += Date.now() - startParse;
|
|
|
|
if (parsed.length === 0) {
|
|
console.log(`${chalk.redBright('!')} no elements parsed, ${chalk.gray('not writing anything')}`);
|
|
continue;
|
|
}
|
|
|
|
const transpiled = parsed.map(p => transpile(p)).join('\n').trim();
|
|
console.log(` transpiled w/ final length of ${chalk.magentaBright(transpiled.length + ' chars')}`);
|
|
timeTotal += Date.now() - startParse;
|
|
|
|
if (transpiled.length === 0) {
|
|
console.log(`${chalk.redBright('!')} nothing transpiled, ${chalk.gray('not writing anything')}`);
|
|
continue;
|
|
}
|
|
|
|
//console.log(inspect(parsed, false, 10));
|
|
const luaFilename = f.replace('.d.ts', '.lua');
|
|
await fs.writeFile(path.resolve(outPath, luaFilename), '--[[\n' + inspect(parsed, false, null) + '\n]]\n\n' + transpiled);
|
|
console.log(` wrote ${chalk.cyanBright(luaFilename)}`);
|
|
}
|
|
}
|
|
|
|
console.log(`\nfinished transpiling in ${chalk.magentaBright(timeTotal + 'ms')} ${chalk.gray(`(${Math.floor(timeParsing/timeTotal * 1000 + 0.5) / 10}% spent parsing)`)}`);
|
|
console.log(`check ${chalk.cyanBright(outPath)}`);
|
|
})(); |