結論
めっちゃつらい
やってみたこと
TypeScriptのファイルをC++にトランスパイルする。
test.ts
function main() {
const txt = 'world';
const num = 100;
console.log('hello', txt, 2, num);
}
を
test.cpp
#include <string>
#include <iostream>
int main () {
const char* txt = "world";
const int num = 100;
std::cout << "hello" << txt << 2 << num << std::endl;
return 0;
};
printf
にするつもりで始めましたが、書式指定子に手間がかかるのでやめました...
ちなみに、実行結果は(TSはmainを実行するよう書き換える必要があるが)下記
- TS:
hello world 2 100
- cpp:
helloworld2100
環境
- MacBook Air(M1)
- Node.js: 18系(古かった...)
- g++: Apple clang version 15(clangがエイリアスになってる?)
準備
npm init
npm install typescript
npm install --save-dev @types/node
やったこと
Nodeをconsole.logで出力しながら、typeをtypescript.d.tsとにらめっこしてどのts.isXXXX
で判定できるのか推測しながら実装。
実装結果
import * as ts from 'typescript';
import * as fs from 'node:fs';
const FILE = 'test.ts';
function visitAll(node: ts.Node, callback: (child: ts.Node) => void) {
callback(node);
if (ts.isFunctionDeclaration(node)) return;
node.forEachChild(child => visitAll(child, callback));
}
const DEBUG = false;
function dbgLog(...args: unknown[]) {
if (DEBUG) console.log(...args);
}
const output: string[] = [];
function main () {
// 変換対象のファイルを読み込み
const program = ts.createProgram([FILE], {});
const sourceFile = program.getSourceFile(FILE);
if (!sourceFile) return;
// C++のinclude
output.push('#include <string>');
output.push('#include <iostream>');
output.push('');
// TS解析後のNodeごとの処理
visitAll(sourceFile, (child) => {
// ソースファイルなら終了
if (ts.isSourceFile(child)) return;
// 関数定義のとき
if (ts.isFunctionDeclaration(child)) {
if (child.name) dbgLog(`関数: ${child.name.text}`);
if (!child.body) return;
// C++メイン関数準備
if (child.name?.text == 'main') output.push('int main () {');
// 各ステートメントでループ
for (const statement of child.body.statements) {
// 変数定義
if (ts.isVariableStatement(statement)) {
dbgLog('-- variable statement --');
const dl = statement.declarationList;
let type = '';
if (dl.flags == ts.NodeFlags.Const) type = 'const';
else if (dl.flags == ts.NodeFlags.Let) type = 'let';
else {
dbgLog(dl);
continue;
}
for (const dec of dl.declarations) {
const name = dec.name;
if (!ts.isIdentifier(name)) {
dbgLog(dec);
continue;
}
// TODO: TS変数定義時の型情報
if (dec.type) {
dbgLog(dec.type);
if (ts.isTypeLiteralNode(dec.type)) {
for (const member of dec.type.members) dbgLog(member);
}
}
// 変数定義のC++置き換え準備
const val = (() => {
const init = dec.initializer;
if (!init) return;
if (ts.isStringLiteral(init)) return {
type: type == 'const' ? 'const char*' : 'std::string',
val: `"${init.text}"`,
};
if (ts.isNumericLiteral(init)) {
const num = Number(init.text);
if (isNaN(num)) throw new Error('invalid');
const typePrefix = type == 'const' ? 'const ' : '';
return {
type: `${typePrefix}${Number.isInteger(num) ? 'int' : 'double'}`,
val: init.text,
};
}
})();
dbgLog(`${type} ${name.escapedText}${val !== undefined ? ` = ${val.val}` : ''};`);
// 変数定義をC++として出力(型は初期値から推測)
if (val) output.push(` ${val.type} ${name.escapedText} = ${val.val};`);
}
}
// 処理
else if (ts.isExpressionStatement(statement)) {
const expression = statement.expression;
// 関数呼び出し
if (ts.isCallExpression(expression)) {
let call = '';
dbgLog('-- call expression --');
dbgLog(expression.expression);
if (ts.isPropertyAccessExpression(expression.expression)) {
const callExpExp = expression.expression;
if (ts.isIdentifier(callExpExp.expression) && ts.isIdentifier(callExpExp.name)) {
call = `${callExpExp.expression.escapedText}.${callExpExp.name.escapedText}`;
}
}
// 引数解析
const args: string[] = [];
for (const arg of expression.arguments) {
if (ts.isStringLiteral(arg)) args.push(`"${arg.text}"`);
else if (ts.isNumericLiteral(arg) || ts.isIdentifier(arg)) args.push(arg.text);
else dbgLog(arg);
}
dbgLog(`${call}(${args.join(', ')});`);
// console.logの引数内容をC++の出力に置き換える
if (call == 'console.log') {
output.push(` std::cout << ${args.join(' << ')} << std::endl;`);
}
} else {
dbgLog('-- unknown expression --')
dbgLog(expression);
}
}
else {
dbgLog('-- unknown statement --')
dbgLog(statement);
continue;
}
}
// C++メイン関数の終わり
if (child.name?.text == 'main') output.push(' return 0;\n};');
return;
}
// console.log(child);
});
try { fs.unlinkSync('test.cpp'); } catch(_) {}
fs.writeFileSync('test.cpp', output.join('\n'));
}
main();
大したことやってないのに100ステップ以上ある...