2015-05-19 13:34:15 -07:00
#!/usr/bin/env python
2015-12-16 11:28:32 +01:00
#------------------------------------------------------------------------------
# CLING - the C++ LLVM-based InterpreterG :)
# author: Min RK
# Copyright (c) Min RK
#
# This file is dual-licensed: you can choose to license it under the University
# of Illinois Open Source License or the GNU Lesser General Public License. See
# LICENSE.TXT for details.
#------------------------------------------------------------------------------
2015-12-09 19:00:47 +01:00
"""
Cling Kernel for Jupyter
Talks to Cling via ctypes
"""
2015-05-19 13:34:15 -07:00
from __future__ import print_function
2015-12-09 19:00:47 +01:00
__version__ = ' 0.0.2 '
import ctypes
from contextlib import contextmanager
from fcntl import fcntl , F_GETFL , F_SETFL
2015-05-19 13:37:50 -07:00
import os
2015-12-10 11:43:47 +01:00
import shutil
2015-12-09 19:00:47 +01:00
import select
2015-12-10 16:54:14 +00:00
import struct
2015-05-19 13:34:15 -07:00
import sys
2015-12-09 19:00:47 +01:00
import threading
2015-05-19 13:34:15 -07:00
2015-12-10 11:02:36 +01:00
from traitlets import Unicode , Float
from ipykernel . kernelapp import IPKernelApp
from ipykernel . kernelbase import Kernel
2015-05-19 13:34:15 -07:00
2015-12-12 09:51:40 +01:00
class my_void_p ( ctypes . c_void_p ) :
pass
2015-05-19 13:34:15 -07:00
2015-12-09 19:00:47 +01:00
libc = ctypes . CDLL ( None )
2015-06-16 12:12:02 -07:00
try :
2015-12-09 19:00:47 +01:00
c_stdout_p = ctypes . c_void_p . in_dll ( libc , ' stdout ' )
2015-12-10 11:42:43 +01:00
c_stderr_p = ctypes . c_void_p . in_dll ( libc , ' stderr ' )
2015-12-09 19:00:47 +01:00
except ValueError :
# libc.stdout is has a funny name on OS X
c_stdout_p = ctypes . c_void_p . in_dll ( libc , ' __stdoutp ' )
2015-12-10 11:42:43 +01:00
c_stderr_p = ctypes . c_void_p . in_dll ( libc , ' __stderrp ' )
2015-06-16 12:12:11 -07:00
2015-05-19 13:34:15 -07:00
class ClingKernel ( Kernel ) :
2015-12-09 19:00:47 +01:00
""" Cling Kernel for Jupyter """
2015-05-19 13:34:15 -07:00
implementation = ' cling_kernel '
implementation_version = __version__
2015-12-09 19:00:47 +01:00
language_version = ' X '
2015-05-19 13:34:15 -07:00
banner = Unicode ( )
def _banner_default ( self ) :
return ' cling- %s ' % self . language_version
return self . _banner
2015-12-10 15:47:34 +01:00
# codemirror_mode='clike' *should* work but doesn't, using the mimetype instead
2015-06-08 11:52:54 -05:00
language_info = { ' name ' : ' c++ ' ,
2015-09-09 17:18:50 +01:00
' codemirror_mode ' : ' text/x-c++src ' ,
2015-05-19 13:34:15 -07:00
' mimetype ' : ' text/x-c++src ' ,
' file_extension ' : ' .c++ ' }
2016-02-16 00:52:30 +01:00
2015-12-09 19:00:47 +01:00
flush_interval = Float ( 0.25 , config = True )
2015-12-10 11:43:47 +01:00
def __init__ ( self , * * kwargs ) :
super ( ClingKernel , self ) . __init__ ( * * kwargs )
2016-04-09 10:44:44 +02:00
try :
whichCling = shutil . which ( ' cling ' )
except AttributeError :
from distutils . spawn import find_executable
whichCling = find_executable ( ' cling ' )
2015-12-10 11:43:47 +01:00
if whichCling :
clingInstDir = os . path . dirname ( os . path . dirname ( whichCling ) )
2015-12-12 09:48:38 +01:00
llvmResourceDir = clingInstDir
2015-12-10 11:43:47 +01:00
else :
2015-12-16 15:43:19 +01:00
raise RuntimeError ( ' Cannot find cling in $PATH. No cling, no fun. ' )
2016-02-08 11:38:48 +01:00
for ext in [ ' so ' , ' dylib ' , ' dll ' ] :
libFilename = clingInstDir + " /lib/libclingJupyter. " + ext
2016-02-16 00:52:30 +01:00
if os . access ( libFilename , os . R_OK ) :
2016-02-08 11:38:48 +01:00
self . libclingJupyter = ctypes . CDLL ( clingInstDir + " /lib/libclingJupyter. " + ext ,
mode = ctypes . RTLD_GLOBAL )
break
2016-02-16 00:52:30 +01:00
if not getattr ( self , ' libclingJupyter ' , None ) :
2016-02-08 11:38:48 +01:00
raise RuntimeError ( ' Cannot find ' + clingInstDir + ' /lib/libclingJupyter. { so,dylib,dll} ' )
2015-12-12 09:51:40 +01:00
self . libclingJupyter . cling_create . restype = my_void_p
self . libclingJupyter . cling_eval . restype = my_void_p
2015-12-10 11:43:47 +01:00
strarr = ctypes . c_char_p * 4
argv = strarr ( b " clingJupyter " , b " " , b " " , b " " )
2015-12-12 09:48:38 +01:00
llvmResourceDirCP = ctypes . c_char_p ( llvmResourceDir . encode ( ' utf8 ' ) )
2015-12-10 16:54:14 +00:00
self . output_pipe , pipe_in = os . pipe ( )
2015-12-12 09:48:38 +01:00
self . interp = self . libclingJupyter . cling_create ( 4 , argv , llvmResourceDirCP , pipe_in )
2015-12-10 11:43:47 +01:00
2015-12-12 09:51:40 +01:00
self . libclingJupyter . cling_complete_start . restype = my_void_p
self . libclingJupyter . cling_complete_next . restype = my_void_p #c_char_p
2015-12-10 16:54:14 +00:00
self . output_thread = threading . Thread ( target = self . publish_pipe_output )
self . output_thread . daemon = True
self . output_thread . start ( )
2016-02-16 00:52:30 +01:00
2015-12-10 16:54:14 +00:00
def _recv_dict ( self , pipe ) :
""" Receive a serialized dict on a pipe
2016-02-16 00:52:30 +01:00
2015-12-10 16:54:14 +00:00
Returns the dictionary .
"""
# Wire format:
# // Pipe sees (all numbers are longs, except for the first):
# // - num bytes in a long (sent as a single unsigned char!)
# // - num elements of the MIME dictionary; Jupyter selects one to display.
# // For each MIME dictionary element:
# // - length of MIME type key
# // - MIME type key
# // - size of MIME data buffer (including the terminating 0 for
# // 0-terminated strings)
# // - MIME data buffer
data = { }
2015-12-12 09:47:29 +01:00
b1 = os . read ( pipe , 1 )
2015-12-10 16:54:14 +00:00
sizeof_long = struct . unpack ( ' B ' , b1 ) [ 0 ]
if sizeof_long == 8 :
fmt = ' Q '
else :
fmt = ' L '
2015-12-12 09:47:29 +01:00
buf = os . read ( pipe , sizeof_long )
2015-12-10 16:54:14 +00:00
num_elements = struct . unpack ( fmt , buf ) [ 0 ]
for i in range ( num_elements ) :
2015-12-12 09:47:29 +01:00
buf = os . read ( pipe , sizeof_long )
2015-12-10 16:54:14 +00:00
len_key = struct . unpack ( fmt , buf ) [ 0 ]
2015-12-12 09:47:29 +01:00
key = os . read ( pipe , len_key ) . decode ( ' utf8 ' )
buf = os . read ( pipe , sizeof_long )
2015-12-10 16:54:14 +00:00
len_value = struct . unpack ( fmt , buf ) [ 0 ]
2015-12-12 09:47:29 +01:00
value = os . read ( pipe , len_value ) . decode ( ' utf8 ' )
2015-12-10 16:54:14 +00:00
data [ key ] = value
return data
2016-02-16 00:52:30 +01:00
2015-12-10 16:54:14 +00:00
def publish_pipe_output ( self ) :
""" Watch output_pipe for display-data messages
2016-02-16 00:52:30 +01:00
2015-12-10 16:54:14 +00:00
and publish them on IOPub when they arrive
"""
2016-02-16 00:52:30 +01:00
2015-12-10 16:54:14 +00:00
while True :
select . select ( [ self . output_pipe ] , [ ] , [ ] )
data = self . _recv_dict ( self . output_pipe )
self . session . send ( self . iopub_socket , ' display_data ' ,
content = {
' data ' : data ,
' metadata ' : { } ,
} ,
parent = self . _parent_header ,
)
2015-12-10 16:01:07 +01:00
2015-12-09 19:00:47 +01:00
@contextmanager
2015-12-10 11:42:43 +01:00
def forward_stream ( self , name ) :
2015-12-09 19:00:47 +01:00
""" Capture stdout and forward it as stream messages """
# create pipe for stdout
2015-12-10 11:42:43 +01:00
if name == ' stdout ' :
c_flush_p = c_stdout_p
elif name == ' stderr ' :
c_flush_p = c_stderr_p
else :
raise ValueError ( " Name must be stdout or stderr, not %r " % name )
2016-02-16 00:52:30 +01:00
2015-12-10 11:42:43 +01:00
real_fd = getattr ( sys , ' __ %s __ ' % name ) . fileno ( )
save_fd = os . dup ( real_fd )
2015-12-09 19:00:47 +01:00
pipe_out , pipe_in = os . pipe ( )
2015-12-10 11:42:43 +01:00
os . dup2 ( pipe_in , real_fd )
2015-12-09 19:00:47 +01:00
os . close ( pipe_in )
2016-02-16 00:52:30 +01:00
2015-12-09 19:00:47 +01:00
# make pipe_out non-blocking
flags = fcntl ( pipe_out , F_GETFL )
fcntl ( pipe_out , F_SETFL , flags | os . O_NONBLOCK )
2015-12-10 11:42:43 +01:00
def forwarder ( pipe ) :
2015-12-09 19:00:47 +01:00
""" Forward bytes on a pipe to stream messages """
while True :
r , w , x = select . select ( [ pipe ] , [ ] , [ ] , self . flush_interval )
if not r :
# nothing to read, flush libc's stdout and check again
2015-12-10 11:42:43 +01:00
libc . fflush ( c_flush_p )
2015-12-09 19:00:47 +01:00
continue
data = os . read ( pipe , 1024 )
if not data :
# pipe closed, we are done
break
# send output
self . session . send ( self . iopub_socket , ' stream ' , {
' name ' : name ,
' text ' : data . decode ( ' utf8 ' , ' replace ' ) ,
} , parent = self . _parent_header )
t = threading . Thread ( target = forwarder , args = ( pipe_out , ) )
t . start ( )
2015-05-19 13:34:15 -07:00
try :
2015-12-09 19:00:47 +01:00
yield
2015-05-19 13:34:15 -07:00
finally :
2015-12-09 19:00:47 +01:00
# flush the pipe
2015-12-10 11:42:43 +01:00
libc . fflush ( c_flush_p )
os . close ( real_fd )
2015-12-09 19:00:47 +01:00
t . join ( )
2016-02-16 00:52:30 +01:00
2015-12-09 19:00:47 +01:00
# and restore original stdout
os . close ( pipe_out )
2015-12-10 11:42:43 +01:00
os . dup2 ( save_fd , real_fd )
os . close ( save_fd )
2015-12-09 19:00:47 +01:00
def run_cell ( self , code , silent = False ) :
2015-12-10 17:02:36 +01:00
return self . libclingJupyter . cling_eval ( self . interp , ctypes . c_char_p ( code . encode ( ' utf8 ' ) ) )
2015-12-10 11:43:47 +01:00
2015-05-19 13:34:15 -07:00
def do_execute ( self , code , silent , store_history = True ,
user_expressions = None , allow_stdin = False ) :
if not code . strip ( ) :
return {
' status ' : ' ok ' ,
' execution_count ' : self . execution_count ,
' payload ' : [ ] ,
' user_expressions ' : { } ,
}
status = ' ok '
2016-02-16 00:52:30 +01:00
2015-12-10 11:42:43 +01:00
with self . forward_stream ( ' stdout ' ) , self . forward_stream ( ' stderr ' ) :
2015-12-10 17:02:36 +01:00
stringResult = self . run_cell ( code , silent )
2015-12-12 09:51:40 +01:00
if not stringResult :
2015-12-10 17:02:36 +01:00
status = ' error '
else :
2015-12-10 17:04:00 +01:00
self . session . send (
self . iopub_socket ,
2015-12-12 09:51:40 +01:00
' execute_result ' ,
2015-12-10 17:04:00 +01:00
content = {
' data ' : {
2015-12-12 09:51:40 +01:00
' text/plain ' : ctypes . cast ( stringResult , ctypes . c_char_p ) . value . decode ( ' utf8 ' , ' replace ' ) ,
2015-12-10 17:04:00 +01:00
} ,
2015-12-10 16:54:14 +00:00
' metadata ' : { } ,
2015-12-12 09:51:40 +01:00
' execution_count ' : self . execution_count ,
2015-12-10 17:04:00 +01:00
} ,
2015-12-10 16:54:14 +00:00
parent = self . _parent_header
2015-12-10 17:04:00 +01:00
)
self . libclingJupyter . cling_eval_free ( stringResult )
2015-12-10 17:02:36 +01:00
2015-05-19 13:34:15 -07:00
reply = {
' status ' : status ,
' execution_count ' : self . execution_count ,
}
2015-12-09 19:00:47 +01:00
if status == ' error ' :
2015-05-19 13:34:15 -07:00
err = {
' ename ' : ' ename ' ,
' evalue ' : ' evalue ' ,
2015-12-09 19:00:47 +01:00
' traceback ' : [ ] ,
2015-05-19 13:34:15 -07:00
}
self . send_response ( self . iopub_socket , ' error ' , err )
reply . update ( err )
elif status == ' ok ' :
reply . update ( {
2015-12-12 09:50:48 +01:00
' THIS DOES NOT WORK: payload ' : [ {
2015-12-10 17:04:26 +01:00
' source ' : ' set_next_input ' ,
' replace ' : True ,
' text ' : ' //THIS IS MAGIC \n ' + code
} ] ,
2015-05-19 13:34:15 -07:00
' user_expressions ' : { } ,
} )
else :
raise ValueError ( " Invalid status: %r " % status )
2016-02-16 00:52:30 +01:00
2015-05-19 13:34:15 -07:00
return reply
2015-12-10 16:54:14 +00:00
def do_complete ( self , code , cursor_pos ) :
""" Provide completions here """
# if cursor_pos = cursor_start = cursor_end,
# matches should be a list of strings to be appended after the cursor
return { ' matches ' : [ ] ,
' cursor_end ' : cursor_pos ,
' cursor_start ' : cursor_pos ,
' metadata ' : { } ,
' status ' : ' ok ' }
2015-12-09 19:00:47 +01:00
class ClingKernelApp ( IPKernelApp ) :
kernel_class = ClingKernel
def init_io ( self ) :
# disable io forwarding
pass
2015-05-19 13:34:15 -07:00
def main ( ) :
""" launch a cling kernel """
2015-12-09 19:00:47 +01:00
ClingKernelApp . launch_instance ( )
2015-05-19 13:34:15 -07:00
if __name__ == ' __main__ ' :
main ( )