Pythonインタプリタをショボーンインタプリタに改造する

53, 2019-07-19

目次

Pythonインタプリタをショボーンインタプリタにする

Pythonインタプリタも長いあいだ使ってると飽きるものです。
そこでインタプリタを改造して気分を変えることにしました。

インタプリタに(´・ω・`)ショボーンを表示させればインタプリタが可愛くなるかも?!
果たして本当にそうなるのでしょうか。

やってみたいと思います。

CPythonをゲットする

Pythonには色々な実装がありますが、今回改造するのは最も一般的と思われるCPythonの実装です。

Gitを使ってCPythonのプロジェクトをクローンします。

$ git clone https://github.com/python/cpython.git

あとはconfigureとやってmakeしてPythonをビルドします。
環境はWindows10のWSLを使っています。Ubuntu16.04ですね。なので生成されるPythonがpython.exeになってますね。

$ cd cpython
$ ./configure
$ make
$ python.exe

CPythonを改造する

Programs/python.cにPythonプログラムのエントリーポイントがありました。

:::c
#ifdef MS_WINDOWS
int
wmain(int argc, wchar_t **argv)
{
    return Py_Main(argc, argv);
}
#else
int
main(int argc, char **argv)
{
    return Py_BytesMain(argc, argv);
}
#endif

Windows10のWSL(Ubuntu16.04)の環境では後者のmainが呼ばれます。

Py_BytesMainModules/main.cで定義されています。

:::c
int
Py_BytesMain(int argc, char **argv)
{
    _PyArgv args = {
        .argc = argc,
        .use_bytes_argv = 1,
        .bytes_argv = argv,
        .wchar_argv = NULL};
    return pymain_main(&args);
}

pymain_mainも同ファイル内に定義されています。

:::c
static int
pymain_main(_PyArgv *args)
{
    PyStatus status = pymain_init(args);
    if (_PyStatus_IS_EXIT(status)) {
        pymain_free();
        return status.exitcode;
    }
    if (_PyStatus_EXCEPTION(status)) {
        pymain_exit_error(status);
    }

    return Py_RunMain();
}

Py_RunMainも同様です。

:::c
int
Py_RunMain(void)
{
    int exitcode = 0;

    pymain_run_python(&exitcode);

    if (Py_FinalizeEx() < 0) {
        /* Value unlikely to be confused with a non-error exit status or
           other special meaning */
        exitcode = 120;
    }

    pymain_free();

    if (_Py_UnhandledKeyboardInterrupt) {
        exitcode = exit_sigint();
    }

    return exitcode;
}

pymain_run_pythonの中身はなんじゃらほい?

:::c
static void
pymain_run_python(int *exitcode)
{
    ...
    pymain_header(config);
    ...
    else {
        *exitcode = pymain_run_stdin(config, &cf);
    }
}

pymain_headerが対話モードのバージョンとかを表示している関数です。
対話モードではpymain_run_stdinが呼ばれるようです。

先にpymain_headerを見てみます。

:::c
static void
pymain_header(const PyConfig *config)
{
    if (config->quiet) {
        return;
    }

    if (!config->verbose && (config_run_code(config) || !stdin_is_interactive(config))) {
        return;
    }

    fprintf(stderr, "Python %s on %s\n", Py_GetVersion(), Py_GetPlatform());
    if (config->site_import) {
        fprintf(stderr, "%s\n", COPYRIGHT);
    }
}

バージョンとかコピーライトを表示していますね。stderrに出力しているようです。
ここのPythonをショボーンに変えておきましょう。

:::c
    fprintf(stderr, "(´・ω・`)ショボーン %s on %s\n", Py_GetVersion(), Py_GetPlatform());

pymain_run_stdinに戻ります。

:::c
static int
pymain_run_stdin(PyConfig *config, PyCompilerFlags *cf)
{
    ...
    int run = PyRun_AnyFileExFlags(stdin, "<stdin>", 0, cf);
    ...
}

PyRun_AnyFileExFlagsを見てみましょう。
Python/pythonrun.cで定義されています。

:::c
int
PyRun_AnyFileExFlags(FILE *fp, const char *filename, int closeit,
                     PyCompilerFlags *flags)
{
    ...
        int err = PyRun_InteractiveLoopFlags(fp, filename, flags);
    ...
}

PyRun_InteractiveLoopFlagsが呼ばれています。この関数を見てみましょう。

:::c
int
PyRun_InteractiveLoopFlags(FILE *fp, const char *filename_str, PyCompilerFlags *flags)
{
    ...
    do {
        ret = PyRun_InteractiveOneObjectEx(fp, filename, flags);
    } while (ret != E_EOF);
    ...
}

do while文の中でstdinを監視してループしているようです。
ここがPythonの対話モードでループされている所ですね。

ループの所にショボーンを追加しときましょう。

:::c
    ...
    do {
        fprintf(stderr, ">>> (´・ω・`)ショボーン\n");
        ret = PyRun_InteractiveOneObjectEx(fp, filename, flags);
        ...
    } while (ret != E_EOF);
    ...

これでショボーンインタプリタができました。
エラー時のカワイソスも追加します。

:::c
    ...
    do {
        fprintf(stderr, ">>> (´・ω・`)ショボーン\n");
        ret = PyRun_InteractiveOneObjectEx(fp, filename, flags);
        if (ret == -1 && PyErr_Occurred()) {
            /* Prevent an endless loop after multiple consecutive MemoryErrors
             * while still allowing an interactive command to fail with a
             * MemoryError. */
            if (PyErr_ExceptionMatches(PyExc_MemoryError)) {
                if (++nomem_count > 16) {
                    PyErr_Clear();
                    err = -1;
                    break;
                }
            } else {
                nomem_count = 0;
            }
            PyErr_Print();

            // エラー出力のあとにカワイソスを表示
            fprintf(stderr, ">>> (´・ω・`)カワイソス\n");

            flush_io();
        } else {
            nomem_count = 0;
        }
#ifdef Py_REF_DEBUG
        if (show_ref_count) {
            _PyDebug_PrintTotalRefs();
        }
#endif
    } while (ret != E_EOF);
    ...

動作風景

↓がショボーンインタプリタの動作風景です。

ショボーンインタプリタの動作風景

おわりに

しばらくこのインタプリタを使ってみた感想ですが、ショボーンがうざかったです。
みなさんもインタプリタを改造して気分を変えてみましょう。

以上、narupoでした。