Edited at

PythonコードからC++コードを作りたい!

More than 1 year has passed since last update.

GoogleによってPythonコードをGoに変換するGrumpyが公開されました。

Google Open Source Blog: Grumpy: Go running Python!

google/grumpy: Grumpy is a Python to Go source code transcompiler and runtime.

Pythonには標準でastモジュールがあり、構文解析結果に直接触ることが可能です。

import ast

node = ast.parse('''
def main() -> int:
print("Hello World!")
return 0
'''
)

print(ast.dump(node))
# Module(body=[FunctionDef(name='main', args=arguments(args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Expr(value=Call(func=Name(id='print', ctx=Load()), args=[Str(s='Hello World!')], keywords=[])), Return(value=Num(n=0))], decorator_list=[], returns=Name(id='int', ctx=Load()))])

Grumpyでも構文木をNodeVisitorで訪問しながらコードを生成しているようです。

ざっくりと読むと、Visitorで訪問した結果を文字列で返しているようです。


py2cpp

https://github.com/mugwort-rc/py2cpp

C++でも同じようなことがしたいと思って少し前にpy2cppというツールを作りかけていたことを思い出しました。

個人的には移植コードを書く際、移植先言語のお約束の構文に読み替えることが多いのですが、その処理をどう表現するか悩み、NodeVisitorで訪問時にTransformer(先処理)とHook(後処理)の2段階で変換し、wrapped Nodeを出力することで後からいろいろこねくり回せるようにしてみました。

tuplestd::make_tupleで出力したり、**演算子をstd::powに置き換えたり…。)


Hello World

$ cat samples/helloworld.py

def main() -> int:
print("Hello World!")
return 0

$ python -m py2cpp samples/helloworld.py
#include "py2cpp/py2cpp.hpp"

int main() {
std::cout << "Hello World!" << std::endl;
return 0;
}


range-based for

$ cat samples/range.py

def main() -> int:
x = 0
for i in range(100):
x += i
print(x)
return 0

$ python -m py2cpp samples/range.py
#include "py2cpp/py2cpp.hpp"

int main() {
x = 0;
for (auto i : py2cpp::range(100)) {
x += i;
}
std::cout << x << std::endl;
return 0;
}

一番最初のx = 0の型が抜けているのが問題ですが、それ以外は違和感のないC++コードが吐けていると思います。

型の情報については後から情報を与えるか、この部分に関してはautoでも十分だと思うので、改良したいと思います。

py2cpp::rangeはVariadic Templatesを使用してboost::irangeを呼び出すaliasです。


型ヒント

$ cat samples/add.py

def add(x: float, y: float) -> float:
return x + y

def main() -> int:
print("2.0 + 3.0 =", add(2.0, 3.0))
return 0

$ python -m py2cpp samples/add.py
#include "py2cpp/py2cpp.hpp"

double add(double x, double y) {
return x + y;
}

int main() {
std::cout << "2.0 + 3.0 =" << add(2.0, 3.0) << std::endl;
return 0;
}

Python 3.5からPEP483の型ヒントが取り入れられました。

これにより、関数の引数と返り値に型情報を付与することが出来ます。

かなりよさげな挙動をしているのではないでしょうか?

単純なロジックのプログラムであれば、Pythonで書いてC++に変換し、boost.pythonやpybind11を用いてPythonで呼び出す未来も近いかもしれません。


ライセンス

py2cpp自体はGPLv3で公開しています。

py2cppを用いて出力された生成物はGCCの生成物と同様にGPL感染することはありません

include下のpy2cpp.hppはBoost Software Licenseで公開しています。


プルリク募集中

型の決定やビルトイン関数の実装など、やることいっぱいだなーと思いながら放置していたのを思い出したところなので、私と同様にPythonコードをC++にしたい人はばしばし編集してプルリクを送っていただけると助かります。

プルリクは日本語だと助かります。Gitのコメントを見ていただければ分かるように英語は不自由です…。


参考

本の虫:GoogleがGoによるPython実装、Grumpyを発表