0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Jupyterのcell magicでcellのC/C++ソースをgcc/g++でコンパイルしよう3(自分専用のkernelを作ろう)

Last updated at Posted at 2025-04-23

概要

上記の投稿記事で作成したCustom Magicの機能を専用のkernelを作成して実現しようとする投稿記事です。なので、上記の記事の内容を知っているものとして書きます。さらに、ソースコードも流用します。
kernelを作ったとしても使い方は上記の記事とほぼ同じです。違いはmagicの表し方とmagicを指定していない時の動作です。
なら、何のためにわざわざkernelを作るかというと、Cell内でのコードのハイライトがC/C++に出来て入力時のインデントもC/C++になり、 {}のペアでインデントされます。
後は、kernel作成の勉強にもなりました。

実行環境

> sw_vers
ProductName:		macOS
ProductVersion:		15.4.1
BuildVersion:		24E263

-- 以下 anaconda3-2024.10-1 ----

> python  --version
Python 3.12.7

> jupyter --version
Selected Jupyter core packages...
IPython          : 8.27.0
ipykernel        : 6.28.0
ipywidgets       : 7.8.1
jupyter_client   : 8.6.0
jupyter_core     : 5.7.2
jupyter_server   : 2.14.1
jupyterlab       : 4.2.5
nbclient         : 0.8.0
nbconvert        : 7.16.4
nbformat         : 5.10.4
notebook         : 7.2.2
qtconsole        : 5.5.1
traitlets        : 5.14.3

テストはJupyterLabで行います。

Custom Kernelを作ろう

サンプルKernel

上記に記載されているechokernel.pyをそのまま実装します。

echokernel.py
from ipykernel.kernelbase import Kernel

class EchoKernel(Kernel):
    implementation = 'Echo'
    implementation_version = '1.0'
    language = 'no-op'
    language_version = '0.1'
    language_info = {'mimetype': 'text/plain'}
    banner = "Echo kernel - as useful as a parrot"

    def do_execute(self, code, silent, store_history=True, user_expressions=None,
                   allow_stdin=False):
        if not silent:
            stream_content = {'name': 'stdout', 'text': code}
            self.send_response(self.iopub_socket, 'stream', stream_content)

        return {'status': 'ok',
                # The base class increments the execution count
                'execution_count': self.execution_count,
                'payload': [],
                'user_expressions': {},
               }

if __name__ == '__main__':
    from ipykernel.kernelapp import IPKernelApp
    IPKernelApp.launch_instance(kernel_class=EchoKernel)

cellの内容はdo_executecodeに渡されます。
ブラウザの出力するには、基本的にstream_contentの内容を作って、send_responseするだけのようです。

kernel.json

kernelの起動等に必要な情報であるkernel.jsonを以下のように作成します。

kernel.json
{
    "argv":["python","-m","echokernel", "-f", "{connection_file}"],
    "display_name":"Echo",
    "env": {
        "PYTHONPATH": "/Users/xxxx/qiita/kernel"
    }
}
  • echokernel.py無しで指定
  • display_nameJupyter notebookkernel名として表示
  • PYTHONPATHechokernel.pyがある場所にパスが通っていないときに必要
    • パスが通っていれば不要

echoディレクトリ(名前は何でも良い)を作ってkernel.jsonをその下に移します。

echo/
└── kernel.json

kernelをインストールします。

> jupyter kernelspec install echo --user
[InstallKernelSpec] Installed kernelspec echo in /Users/xxxx/Library/Jupyter/kernels/echo

installの後はディレクトリ名(echo)を指定します。

--userを付けた時のkernelのインストール先は次のディレクトリです。

  • Linux
    • ~/.local/share/jupyter/kernels
  • Max
    • ~/Library/Jupyter/kernels
  • Windows
    • %APPDATA%\jupyter\kernels

Pythonやその他のpackageの組み合わせによっては以下のメッセージが出ますが動きます。
私が今現在使っている環境ではこれらのメッセージは出ません。

/Users/xxxx/qiita/kernel/echokernel.py:1: DeprecationWarning: Parsing dates
involving a day of month without a year specified is ambiguious
and fails to parse leap day. The default behavior will change in Python 3.15
to either always raise an exception or to use a different default year (TBD).
To avoid trouble, add a specific year to the input & format.
See https://github.com/python/cpython/issues/70647.
  from ipykernel.kernelbase import Kernel
~~省略~~
[IPKernelApp] WARNING | Unknown message type: 'comm_open'
[IPKernelApp] WARNING | Unknown message type: 'comm_msg'

テストをします。 kernel名が右上にちゃんと表示されてます。

echokernel01.png

Costuom KernelなのでPythonのkernelで使えた %lsmagic%%scriptCustom magicなどのmagicコマンドは一切使えません

Jupyter consoleではプログラムのbannerで設定した内容がトップに表示されています。しかし、Jupyter consoleは使うことはないと思います。
echokernel02.png

gcc Kernel

簡単なgcc Kernel

上記のサンプルを元にgccでコンパイル&実行だけの簡単なkernelをつくります。前回の投稿記事のgccでコンパイル&実行部分のコードはCustom magicとほぼ同じコードになります。
後はstderrがサンプルに無いコードです。

gcckernel.py
import os
import sys
import subprocess
from ipykernel.kernelbase import Kernel

OBJFILE = "./a.out"
COMPILE_EXEC_CMD = "gcc -xc - -o {0} && {0}".format(OBJFILE)

class GccKernel(Kernel):
    implementation = 'gcc'
    implementation_version = '1.0'
    language_info = {
        'name': 'c',
        'mimetype': 'text/x-csrc',
        'file_extension': '.c',
        'version': '17.0.0',
    }
    banner = "Compile by gcc and execute binary"
 
    def do_execute(self, code, silent, store_history=True, user_expressions=None,
                   allow_stdin=False):
        if not silent:
            rc = self.compile_and_exec(code)
            stream_content = {'name': 'stdout', 'text': rc.stdout}
            self.send_response(self.iopub_socket, 'stream', stream_content)
            if rc.stderr != "":
                stream_content = {'name': 'stderr', 'text': rc.stderr}
                self.send_response(self.iopub_socket, 'stream', stream_content)
                
        return {'status': 'ok',
                'execution_count': self.execution_count,
                'payload': [],
                'user_expressions': {},
               }

    def compile_and_exec(self, src):
        rc = subprocess.run(COMPILE_EXEC_CMD, text=True, 
                         capture_output=True, shell=True, input=src)
        if os.path.isfile(OBJFILE):
            os.remove(OBJFILE)
        return rc 

if __name__ == '__main__':
    from ipykernel.kernelapp import IPKernelApp
    IPKernelApp.launch_instance(kernel_class=GccKernel)

implementationからbannerについては以下のサイトを参照してください。

また、language_infonamemimetypefile_extensionについては以下のサイトを参照してください。これが一致していない(特にname)とハイライトなどがC言語になりません。他のいろいろプログラミング言語についても載っています。

kernel.json

gcc用のkernel.jsonを作ります。
languagecにします。C++のときはC++にしてください。もしかしたら、無くても良いかもしれません。これよりはプログラムのlanguage_infonameが大事です。

kernel.json
{
    "argv":["python","-m","gcckernel", "-f", "{connection_file}"],
    "display_name":"gcc",
    "language": "c",
    "env": {
        "PYTHONPATH": "/Users/xxxx/qiita/kernel"
    }
}

gccディレクトリを作ってkernel.jsonをその下に移します。

gcc/
└── kernel.json

kernelをインストールします。

jupyter kernelspec install gcc --user
[InstallKernelSpec] Installed kernelspec gcc in /Users/xxxx/Library/Jupyter/kernels/gcc

テストです。
コンパイルエラーと実行結果がちゃんと表示されています。また、ハイライトがC言語になっています。cell magicでは#includeがpythonのコメントに該当するのでイタリック体になっていましたが、ちゃんと普通に表示されています。

gcckernel01.png

Custom Magicと同等の機能をもったgcc Kernel

今度はコンパイラをg++にしますが、gccとそんなに違いはありません。
また、Custom Magicで作成したclass GCCMagicsを一部変更して流用します。
magicの表現を%%gccから//gcc%/に変更します。

ソースを前半と後半部分に分けて解説します。2つを一緒のソースにして使ってください。

gcckernel.py 前半部分
import re
import sys
import subprocess
from ipykernel.kernelbase import Kernel

OBJFILE = "./a.out"
COMPILE_CMD = "g++ -xc++ - -std=c++17"
COMPILE_EXEC_CMD = "{0} -o {1} && {1}".format(COMPILE_CMD, OBJFILE)

ALL_SOURCE = """
#include <iostream>
{0}

int main(int argc, char *argv[]) {{

{1}
    
    return 0;
}}    
"""

def CompletedProcess(stdout='', stderr=''):
    return subprocess.CompletedProcess("", 0, stdout=stdout, stderr=stderr) 
    
class GCCMagics:
    main_source = {}  # main関数内のソース
    other_source = {} # main関数外のソース
    
    def gcc(self, line, cell):
        rc = self.compile_and_exec(cell)
        return rc

    def gmain(self, line, cell):
        src_key = line.strip()
        if src_key != "":
            self.main_source[src_key] = cell                
            rc = self.compile_and_exec(self.make_source())
        else:
            rc = self.compile_and_exec(ALL_SOURCE.format("", cell))           
        return rc
    
    def gfunc(self, line, cell):
        src_key = line.strip()
        if src_key == "":
            rc = CompletedProcess(stderr="lineパラメータがありません")
        else:
            self.other_source[src_key] = cell
            rc = self.compile_and_exec(self.make_source(), exec=False)
        return rc
    
    def glist(self, line):
        return CompletedProcess(stdout=self.make_source())

    def grun(self, line):
        return self.compile_and_exec(self.make_source())
        
    def make_source(self, src_key=None):
        other_src = ""
        main_src = ""
        for key in sorted(self.other_source):
            other_src += self.other_source[key]
                
        for key in sorted(self.main_source):
            main_src += self.main_source[key]

        src = ALL_SOURCE.format(other_src, main_src)
        return src
               
    def compile_and_exec(self, src, exec=True):
        if exec:
            rc = subprocess.run(COMPILE_EXEC_CMD, text=True, capture_output=True, shell=True, input=src)
        else:
            rc = subprocess.run(COMPILE_CMD, text=True, capture_output=True, shell=True, input=src)            
        if os.path.isfile(OBJFILE):
            os.remove(OBJFILE)
        return rc        

上記のコードの解説

  • def CompletedProcesssubprocess.runが返す戻り値のクラスであるので、subprocess.runを使わない処理でもstdoutstderrを返すためと、簡略化のための関数
  • GCCMagicsクラスはCustom Magicで作成したクラスを流用したもので以下の主な変更点
    • Magicsの継承をしない
    • @cell_magicなどのデコレーションやDocstringを消す
    • print文をなくしてrcなどのCompletedProcessstdout,stderrを設定してretrunで返すように変更

実はGCCMagicsを全く変更しなくても(デコレーションを消さない)使えますが、Custom Magicには依存していないと示したかったので消しました。そのまま使ったコードはAppendixに記載します。

gcckernel.py 後半部分
# GCC用Custom Kernel
class GccKernel(Kernel):
    # cellデータの解析結果
    class MagicData:
        def __init__(self, magic="gcc", cell="", line="", iscell=True):
            self.magic = magic
            self.cell = cell
            self.line = line
            self.iscell = iscell
            
    implementation = 'gcc'
    implementation_version = '1.0'
    language_info = {
        'name': 'c++',
        'mimetype': 'text/x-c++src',
        'file_extension': '.cpp',
        'version': '17.0.0',
    }
    banner = "Compile by gcc and execute binary"

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        magic_obj = GCCMagics()
        self.line_magics = {"glist": magic_obj.glist,
                            "grun": magic_obj.grun}
        self.cell_magics = {"gcc": magic_obj.gcc,
                            "gmain": magic_obj.gmain,
                            "gfunc": magic_obj.gfunc}    
        self.magic_obj = magic_obj
        
    def parse(self, code):
        mline = re.match("^.*$", code, re.MULTILINE)
        pos = mline.span()[1] + 1  # +1は改行コードを除くため
        cell = code[pos:]
        line = mline.group()
        magic_re = re.match(r" *(/{1,2})(\w+)", line)
        iscell = True            
        if magic_re == None:
            # 先頭行がmagicでないとき
            magic = "gcc"
            cell = code
            line = ''
        else:    
            line_pos = magic_re.span()[1] + 1
            line = line[line_pos:]
            magic_names = magic_re.groups()
            magic = magic_names[1]
            if magic_names[0] == "/":
                iscell = False
        return self.MagicData(magic, cell, line, iscell)
                
    def exec_magic(self, magic_data):
        if magic_data.iscell:
            if magic_data.magic in self.cell_magics:
                func = self.cell_magics[magic_data.magic]
                return func(magic_data.line, magic_data.cell)
            else:
                return CompletedProcess(stderr="該当するcell magicはありません")
        else:
            if magic_data.magic in self.line_magics:
                func = self.line_magics[magic_data.magic]
                return func(magic_data.line)
            else:
                return CompletedProcess(stderr="該当するline magicはありません")
        
    def do_execute(self, code, silent, store_history=True, user_expressions=None,
                   allow_stdin=False):
        if not silent:
            src = code if code[-1] == "\n" else code + "\n"
            magic_data = self.parse(src)
            rc = self.exec_magic(magic_data)
            
            stream_content = {'name': 'stdout', 'text': rc.stdout}
            self.send_response(self.iopub_socket, 'stream', stream_content)
            if rc.stderr != "":
                stream_content = {'name': 'stderr', 'text': rc.stderr}
                self.send_response(self.iopub_socket, 'stream', stream_content)
                
        return {'status': 'ok',
                'execution_count': self.execution_count,
                'payload': [],
                'user_expressions': {},
               }

if __name__ == '__main__':
    from ipykernel.kernelapp import IPKernelApp
    IPKernelApp.launch_instance(kernel_class=GccKernel)

上記コードの解説

  • def __init__はクラスGCCMagicsで定義されているline magic、cell magicの処理メソッドの辞書を作る

  • def parseはcellのデータから1行目をmagicとして解析する

    • 解析した結果は以下の項目をMagicDataクラスで返す
      • magic名
      • cellデータ
      • lineデータ
      • line magicかcell magicのフラグ
    • re.match("^.*$", code, re.MULTILINE)は1行目を取り出すために正規表現
    • re.match(r" *(/{1,2})(\w+)", line)///で始まるmagic名を取り出すための正規表現
      • groups()で取り出すと例えば'//gcc arg1 arg2'('//', 'gcc')のタプルになるのでgroups()[1]がmagic名になる
  • def exec_magicdef parse解析したデータをもとにGCCMagicsクラスの各magicメソッドを実行

    • 解析したmagicがline magicかcell magicによって対応する辞書から処理メソッドを取得し、処理を実行
    • 対応する処理メソッドが無いときはエラーメッセージを表示

kernel.json

gcc用でC++のkernel.jsonを作ります。

kernel.json
{
    "argv":["python","-m","gcckernel", "-f", "{connection_file}"],
    "display_name":"C++",
    "language": "c++",
    "env": {
        "PYTHONPATH": "/Users/xxxx/qiita/kernel"
    }
}

gccディレクトリを作ってkernel.jsonをその下に移します。

gcc/
└── kernel.json

kernelをインストールします。

jupyter kernelspec install gcc --user
[InstallKernelSpec] Installed kernelspec gcc in /Users/xxxx/Library/Jupyter/kernels/gcc

テストも下記の通り大丈夫なようです。

gcckernel02.png

gcckernel03.png

gcckernel04.png

終わりに

全部でCell Magicの3部作となりましたが、Pythonはメイン言語ではないので時間が掛かったところもあります。しかし、知らないことを学びそれなりの成果がでると嬉しいものです。自己満足ですが。

Appendix

自分ために以下の2つのコードを残します。

GCCMagicsをそのまま使ったコード

  • printは使えないので自分で再定義
  • line magic、cell magicのメソッド名とメソッドオブジェクトの辞書はGCCMagicsmagics変数に登録されているので、自分で辞書を作らなくてもOK
    • @cell_magic@line_magicとデコレーションすることで、辞書が作成されるようです

Custom Magicとしては使っていないので、親クラスのMagicsやデコレータが何も悪さしないと良いのですが、とにかく動きました。

ソースコード1
gcckernel.py
import os
import re
import sys
import subprocess
from ipykernel.kernelbase import Kernel
from IPython.core.magic import (Magics, magics_class, line_magic,
                                cell_magic, line_cell_magic)


OBJFILE = "./a.out"
COMPILE_CMD = "g++ -xc++ - -std=c++17"
COMPILE_EXEC_CMD = "{0} -o {1} && {1}".format(COMPILE_CMD, OBJFILE)

ALL_SOURCE = """
#include <iostream>
{0}

int main(int argc, char *argv[]) {{

{1}
    
    return 0;
}}    
"""

def CompletedProcess(stdout='', stderr=''):
    return subprocess.CompletedProcess("", 0, stdout=stdout, stderr=stderr) 

GccKernel_obj = None

def print(str, file=None, end="\n"):
    if file == sys.stderr:
        stream_content = {'name': 'stderr', 'text': str+end}
    else:
        stream_content = {'name': 'stdout', 'text': str+end}
    GccKernel_obj.send_response(GccKernel_obj.iopub_socket, 
                                'stream', stream_content)
    
@magics_class
class GCCMagics(Magics):
    main_source = {}  # main関数内のソース
    other_source = {} # main関数外のソース
    
    @cell_magic
    def gcc(self, line, cell):
        """
        %%gcc コメント
        """      
        rc = self.compile_and_exec(cell)
        print(rc.stdout, end="")
        if rc.stderr != "":
            print(rc.stderr, file=sys.stderr, end="")

    @cell_magic
    def gmain(self, line, cell):
        """
        %%gmain コメント
        """
        src_key = line.strip()
        if src_key != "":
            self.main_source[src_key] = cell                
            rc = self.compile_and_exec(self.make_source())
        else:
            rc = self.compile_and_exec(ALL_SOURCE.format("", cell))           
        print(rc.stdout, end="")
        if rc.stderr != "":
            print(rc.stderr, file=sys.stderr, end="")

    @cell_magic
    def gfunc(self, line, cell):
        """
        %%gfunc コメント
        """
        src_key = line.strip()
        if src_key == "":
            print("lineパラメータがありません", file=sys.stderr)
            return
        else:
            self.other_source[src_key] = cell
        rc = self.compile_and_exec(self.make_source(), exec=False)
        print(rc.stdout, end="")
        if rc.stderr != "":
            print(rc.stderr, file=sys.stderr, end="")

    @line_magic
    def glist(self, line):
        """
        %glist コメント
        """
        print(self.make_source(), end="")

    @line_magic
    def grun(self, line):
        """
        %grun コメント
        """
        rc = self.compile_and_exec(self.make_source())
        print(rc.stdout, end="")
        if rc.stderr != "":
            print(rc.stderr, file=sys.stderr, end="")
        
    def make_source(self, src_key=None):
        other_src = ""
        main_src = ""
        for key in sorted(self.other_source):
            other_src += self.other_source[key]
                
        for key in sorted(self.main_source):
            main_src += self.main_source[key]

        src = ALL_SOURCE.format(other_src, main_src)
        return src
               
    def compile_and_exec(self, src, exec=True):
        if exec:
            rc = subprocess.run(COMPILE_EXEC_CMD, text=True, capture_output=True, shell=True, input=src)
        else:
            rc = subprocess.run(COMPILE_CMD, text=True, capture_output=True, shell=True, input=src)            
        if os.path.isfile(OBJFILE):
            os.remove(OBJFILE)
        return rc        
    
# GCC用Custom Kernel
class GccKernel(Kernel):
    # cellデータの解析結果
    class MagicData:
        def __init__(self, magic="gcc", cell="", line="", iscell=True):
            self.magic = magic
            self.cell = cell
            self.line = line
            self.iscell = iscell
            
            
    implementation = 'gcc'
    implementation_version = '1.0'
    language_info = {
        'name': 'c++',
        'mimetype': 'text/x-c++src',
        'file_extension': '.cpp',
        'version': '17.0.0',
    }
    banner = "Compile by gcc and execute binary"

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.magic_obj = GCCMagics()
        self.line_magics = self.magic_obj.magics["line"]
        self.cell_magics = self.magic_obj.magics["cell"]        
        global GccKernel_obj
        GccKernel_obj = self

    def parse(self, code):
        mline = re.match("^.*$", code, re.MULTILINE)
        pos = mline.span()[1] + 1  # +1は改行コードを除くため
        cell = code[pos:]
        line = mline.group()
        magic_re = re.match(r" *(/{1,2})(\w+)", line)
        iscell = True            
        if magic_re == None:
            # 先頭行がmagicでないとき
            magic = "gcc"
            cell = code
            line = ''
        else:    
            line_pos = magic_re.span()[1] + 1
            line = line[line_pos:]
            magic_names = magic_re.groups()
            magic = magic_names[1]
            if magic_names[0] == "/":
                iscell = False
        return self.MagicData(magic, cell, line, iscell)
                
    def exec_magic(self, magic_data):
        if magic_data.iscell:
            if magic_data.magic in self.cell_magics:
                func = self.cell_magics[magic_data.magic]
                return func(magic_data.line, magic_data.cell)
            else:
                return CompletedProcess(stderr="該当するcell magicはありません")
        else:
            if magic_data.magic in self.line_magics:
                func = self.line_magics[magic_data.magic]
                return func(magic_data.line)
            else:
                return CompletedProcess(stderr="該当するline magicはありません")
                
    def do_execute(self, code, silent, store_history=True, user_expressions=None,
                   allow_stdin=False):
        if not silent:
            src = code if code[-1] == "\n" else code + "\n"
            magic_data = self.parse(src)
            rc = self.exec_magic(magic_data)
            if rc != None:
                stream_content = {'name': 'stdout', 'text': rc.stdout}
                self.send_response(self.iopub_socket, 'stream', stream_content)
                if rc.stderr != "":
                    stream_content = {'name': 'stderr', 'text': rc.stderr}
                    self.send_response(self.iopub_socket, 'stream', stream_content)    
        return {'status': 'ok',
                'execution_count': self.execution_count,
                'payload': [],
                'user_expressions': {},
               }

if __name__ == '__main__':
    from ipykernel.kernelapp import IPKernelApp
    IPKernelApp.launch_instance(kernel_class=GccKernel)

GCCMagicsをそのまま使うが、親クラスやデコレータを自分で作ってみる

gccをでコンパイルする目的としてはこれを作るメリットがあまりないが、デコレーションの勉強にはなるので作ってみた。デコレーションについては使ったことはあるが、どう動作するかまでは調べなかったので今回調べてみて勉強になった。

ソースコード1からの変更点はPython.core.magicimportを削除して##--------で囲った箇所に自作したデコレータのコードを追加した。

デコレータについて

  • クラスのインスタンスを生成しなくても関数のデコレータ、クラスのデコレータの順に呼び出される
  • 関数デコレータに渡されるfuncはクラスメソッドでオブジェクトメソッドではないのでオブジェクトメソッドとしては使えないのでメソッド名だけ取って、オブジェクトが生成されたときにオブジェクトメソッドとして取得した

その他として、getattrcallableを始めて知った。

ソースコード2
gcckernel.py
import os
import re
import sys
import subprocess
from ipykernel.kernelbase import Kernel


OBJFILE = "./a.out"
COMPILE_CMD = "g++ -xc++ - -std=c++17"
COMPILE_EXEC_CMD = "{0} -o {1} && {1}".format(COMPILE_CMD, OBJFILE)

ALL_SOURCE = """
#include <iostream>
{0}

int main(int argc, char *argv[]) {{

{1}
    
    return 0;
}}    
"""

##----------------------------------------------------------------------------------
g_line_magics = []
g_cell_magics = []

def cell_magic(func):
    g_cell_magics.append(func.__name__)
    def _wrapper(self,*args, **kwargs):
        return func(self,*args,**kwargs)
    return _wrapper
def line_magic(func):
    g_line_magics.append(func.__name__)
    def _wrapper(self,*args, **kwargs):
        return func(self,*args,**kwargs)
    return _wrapper

class Magics:
    pass

def magics_class(cls):
    class NewClass(cls):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            cell_magics = {}
            line_magics = {}
            for fname in g_cell_magics:
                func = getattr(self, fname, False)
                if callable(func):
                    cell_magics[fname] = func 
                
            for fname in g_line_magics:
                func = getattr(self, fname, False)
                if callable(func):
                    line_magics[fname] = func

            self.magics = {'cell': cell_magics, 'line': line_magics}
    return NewClass

##----------------------------------------------------------------------------------

def CompletedProcess(stdout='', stderr=''):
    return subprocess.CompletedProcess("", 0, stdout=stdout, stderr=stderr) 

GccKernel_obj = None

def print(str, file=None, end="\n"):
    if file == sys.stderr:
        stream_content = {'name': 'stderr', 'text': str+end}
    else:
        stream_content = {'name': 'stdout', 'text': str+end}
    GccKernel_obj.send_response(GccKernel_obj.iopub_socket, 
                                'stream', stream_content)

    
@magics_class
class GCCMagics(Magics):
    main_source = {}  # main関数内のソース
    other_source = {} # main関数外のソース
    
    @cell_magic
    def gcc(self, line, cell):
        """
        %%gcc コメント
        """      
        rc = self.compile_and_exec(cell)
        print(rc.stdout, end="")
        if rc.stderr != "":
            print(rc.stderr, file=sys.stderr, end="")

    @cell_magic
    def gmain(self, line, cell):
        """
        %%gmain コメント
        """
        src_key = line.strip()
        if src_key != "":
            self.main_source[src_key] = cell                
            rc = self.compile_and_exec(self.make_source())
        else:
            rc = self.compile_and_exec(ALL_SOURCE.format("", cell))           
        print(rc.stdout, end="")
        if rc.stderr != "":
            print(rc.stderr, file=sys.stderr, end="")

    @cell_magic
    def gfunc(self, line, cell):
        """
        %%gfunc コメント
        """
        src_key = line.strip()
        if src_key == "":
            print("lineパラメータがありません", file=sys.stderr)
            return
        else:
            self.other_source[src_key] = cell
        rc = self.compile_and_exec(self.make_source(), exec=False)
        print(rc.stdout, end="")
        if rc.stderr != "":
            print(rc.stderr, file=sys.stderr, end="")

    @line_magic
    def glist(self, line):
        """
        %glist コメント
        """
        print(self.make_source(), end="")

    @line_magic
    def grun(self, line):
        """
        %grun コメント
        """
        rc = self.compile_and_exec(self.make_source())
        print(rc.stdout, end="")
        if rc.stderr != "":
            print(rc.stderr, file=sys.stderr, end="")
        
    def make_source(self, src_key=None):
        other_src = ""
        main_src = ""
        for key in sorted(self.other_source):
            other_src += self.other_source[key]
                
        for key in sorted(self.main_source):
            main_src += self.main_source[key]

        src = ALL_SOURCE.format(other_src, main_src)
        return src
               
    def compile_and_exec(self, src, exec=True):
        if exec:
            rc = subprocess.run(COMPILE_EXEC_CMD, text=True, capture_output=True, shell=True, input=src)
        else:
            rc = subprocess.run(COMPILE_CMD, text=True, capture_output=True, shell=True, input=src)            
        if os.path.isfile(OBJFILE):
            os.remove(OBJFILE)
        return rc        
    
# GCC用Custom Kernel
class GccKernel(Kernel):
    # cellデータの解析結果
    class MagicData:
        def __init__(self, magic="gcc", cell="", line="", iscell=True):
            self.magic = magic
            self.cell = cell
            self.line = line
            self.iscell = iscell
            
            
    implementation = 'gcc'
    implementation_version = '1.0'
    language_info = {
        'name': 'c++',
        'mimetype': 'text/x-c++src',
        'file_extension': '.cpp',
        'version': '17.0.0',
    }
    banner = "Compile by gcc and execute binary"

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.magic_obj = GCCMagics()
        self.line_magics = self.magic_obj.magics["line"]
        self.cell_magics = self.magic_obj.magics["cell"]        
        global GccKernel_obj
        GccKernel_obj = self

    def parse(self, code):
        mline = re.match("^.*$", code, re.MULTILINE)
        pos = mline.span()[1] + 1  # +1は改行コードを除くため
        cell = code[pos:]
        line = mline.group()
        magic_re = re.match(r" *(/{1,2})(\w+)", line)
        iscell = True            
        if magic_re == None:
            # 先頭行がmagicでないとき
            magic = "gcc"
            cell = code
            line = ''
        else:    
            line_pos = magic_re.span()[1] + 1
            line = line[line_pos:]
            magic_names = magic_re.groups()
            magic = magic_names[1]
            if magic_names[0] == "/":
                iscell = False
        return self.MagicData(magic, cell, line, iscell)
                
    def exec_magic(self, magic_data):
        if magic_data.iscell:
            if magic_data.magic in self.cell_magics:
                func = self.cell_magics[magic_data.magic]
                return func(magic_data.line, magic_data.cell)
            else:
                return CompletedProcess(stderr="該当するcell magicはありません")
        else:
            if magic_data.magic in self.line_magics:
                func = self.line_magics[magic_data.magic]
                return func(magic_data.line)
            else:
                return CompletedProcess(stderr="該当するline magicはありません")
                
    def do_execute(self, code, silent, store_history=True, user_expressions=None,
                   allow_stdin=False):
        if not silent:
            src = code if code[-1] == "\n" else code + "\n"
            magic_data = self.parse(src)
            rc = self.exec_magic(magic_data)
            if rc != None:
                stream_content = {'name': 'stdout', 'text': rc.stdout}
                self.send_response(self.iopub_socket, 'stream', stream_content)
                if rc.stderr != "":
                    stream_content = {'name': 'stderr', 'text': rc.stderr}
                    self.send_response(self.iopub_socket, 'stream', stream_content)    
        return {'status': 'ok',
                'execution_count': self.execution_count,
                'payload': [],
                'user_expressions': {},
               }

if __name__ == '__main__':
    from ipykernel.kernelapp import IPKernelApp
    IPKernelApp.launch_instance(kernel_class=GccKernel)
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?