├── Build.pm ├── META.info ├── README.md ├── bin └── iperl6kernel.nqp ├── heartbeat.py ├── inline-wrapper ├── kernel.json └── kernel.p6 ├── lib ├── IPerl6.pm6 └── IPerl6 │ ├── Gobble.pm6 │ └── ZMQ.pm6 ├── pperl6 ├── kernel.json └── test.json └── wrapper.py /Build.pm: -------------------------------------------------------------------------------- 1 | use Panda::Common; 2 | use Panda::Builder; 3 | 4 | my $nqp = 'nqp'; 5 | my $parrot = 'parrot'; 6 | my $pbc_to_exe = 'pbc_to_exe'; 7 | my $executable = $*OS eq 'MSWin32' ?? 'iperl6kernel.exe' !! 'iperl6kernel'; 8 | 9 | class Build is Panda::Builder { 10 | method build(Pies::Project $p) { 11 | my $workdir = $.resources.workdir($p); 12 | 13 | shell "$nqp --vmlibs=perl6_group,perl6_ops --target=pir " 14 | ~ "--output=iperl6kernel.pir bin/iperl6kernel.nqp"; 15 | shell "$parrot -o iperl6kernel.pbc iperl6kernel.pir"; 16 | shell "$pbc_to_exe --output=bin/$executable iperl6kernel.pbc" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /META.info: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "IPerl6", 3 | "version" : "*", 4 | "description" : "an ipython kernel for rakudo", 5 | "depends" : ["Digest", "Digest::HMAC", "JSON::Fast", "Net::ZMQ"], 6 | "source-url" : "git://github.com/timo/iperl6kernel.git" 7 | } 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | iperl6kernel 2 | ============ 3 | 4 | This is the attempt to expose an "ipython kernel" interface for a rakudo process. 5 | 6 | ipython kernels communicate with one or more frontends (terminals) using ZeroMQ sockets. 7 | 8 | Links and stuff 9 | --------------- 10 | 11 | IPython ZeroMQ Protocol: http://ipython.org/ipython-doc/rel-0.13.1/development/messaging.html 12 | -------------------------------------------------------------------------------- /bin/iperl6kernel.nqp: -------------------------------------------------------------------------------- 1 | # vi: ft=perl6 2 | 3 | use Perl6::Compiler; 4 | use Perl6::Actions; 5 | 6 | # pass on some handler object that allows interfacing 7 | # with the ZMQ wrapper 8 | class ProtocolGetter { 9 | has $!proto; 10 | 11 | method set($foo) { 12 | nqp::say("setting protocol instance"); 13 | $*W.add_object($foo); 14 | $!proto := $foo; 15 | } 16 | 17 | method protocol() { 18 | $!proto; 19 | } 20 | }; 21 | 22 | class ChattyRepl is Perl6::Compiler { 23 | method interactive(*%adverbs) { 24 | # copied 1:1 from HLL::Compiler {{{ 25 | # blank_context() serves as a cumulative "outer context" 26 | # for code executed in this interactive REPL instance. 27 | # It's invoked once to obtain a context, and the LexPad 28 | # of that context is replaced with an empty Hash that 29 | # we can use to cumulatively store lexicals. 30 | sub blank_context() { 31 | # transform context's pad into a Hash 32 | my %blank_pad; 33 | pir::copy__vPP( 34 | pir::getattribute__PPs(pir::getinterp__P(){'context'}, 'lex_pad'), 35 | %blank_pad); 36 | pir::getinterp__P(){'context'}; 37 | } 38 | &blank_context.set_outer(nqp::null()); 39 | my $interactive_ctx := blank_context(); 40 | my %interactive_pad := 41 | pir::getattribute__PPs($interactive_ctx, 'lex_pad'); 42 | 43 | my $target := nqp::lc(%adverbs); 44 | 45 | pir::getinterp__P().stderr_handle().print(self.interactive_banner); 46 | 47 | my $stdin := pir::getinterp__P().stdin_handle(); 48 | my $encoding := ~%adverbs; 49 | if $encoding && $encoding ne 'fixed_8' { 50 | $stdin.encoding($encoding); 51 | } 52 | 53 | my $save_ctx; 54 | while 1 { 55 | last unless $stdin; 56 | 57 | #my $code := $stdin.readline_interactive(~$prompt); 58 | my $result := $*ZMQ_PROTOCOL.protocol.get_command(); 59 | my $command := $result.shift; 60 | my $code := $result.shift; 61 | my $callback := $result.shift; 62 | 63 | last if nqp::isnull($code); 64 | unless nqp::defined($code) { 65 | nqp::print("\n"); 66 | last; 67 | } 68 | 69 | # Set the current position of stdout for autoprinting control 70 | #my $*AUTOPRINTPOS := (pir::getinterp__P()).stdout_handle().tell(); 71 | my $*CTXSAVE := self; 72 | my $*MAIN_CTX; 73 | 74 | my $pretty-out := ""; 75 | if $code { 76 | $code := $code ~ "\n"; 77 | my $output; 78 | { 79 | $output := self.eval($code, :outer_ctx($save_ctx), |%adverbs); 80 | CATCH { 81 | nqp::print(~$! ~ "\n"); 82 | next; 83 | } 84 | }; 85 | if nqp::defined($*MAIN_CTX) { 86 | my $cur_ctx := $*MAIN_CTX; 87 | my %seen; 88 | until nqp::isnull($cur_ctx) { 89 | my $pad := nqp::ctxlexpad($cur_ctx); 90 | unless nqp::isnull($pad) { 91 | for $pad { 92 | my str $key := ~$_; 93 | unless nqp::existskey(%seen, $key) { 94 | %interactive_pad{$key} := nqp::atkey($pad, $key); 95 | %seen{$key} := 1; 96 | } 97 | } 98 | } 99 | $cur_ctx := nqp::ctxouter($cur_ctx); 100 | } 101 | $save_ctx := $interactive_ctx; 102 | } 103 | 104 | if !nqp::isnull($output) && nqp::can($output, 'dump') { 105 | $pretty-out := ($output.dump()); 106 | } 107 | } 108 | $callback($pretty-out, "", ""); 109 | } 110 | } 111 | } 112 | 113 | sub hll-config($config) { 114 | $config := 'rakudo'; 115 | $config := ''; 116 | $config := ''; 117 | $config := ''; 118 | $config := '2013-02-24T16:57:45Z'; 119 | } 120 | 121 | sub MAIN(@ARGS) { 122 | #copied from jnthn/rakudo-debugger 123 | 124 | # initialize dynops 125 | pir::rakudo_dynop_setup__v(); 126 | 127 | # bump up the recursion limit 128 | pir::getinterp__P().recursion_limit(100000); 129 | 130 | # create and configure a compiler object 131 | my $comp := ChattyRepl.new(); 132 | $comp.language('perl6'); 133 | $comp.parsegrammar(Perl6::Grammar); 134 | $comp.parseactions(Perl6::Actions); 135 | $comp.addstage('syntaxcheck', :before); 136 | $comp.addstage('optimize', :before); 137 | hll-config($comp.config); 138 | my $COMPILER_CONFIG := $comp.config; 139 | 140 | # add extra commandline options 141 | my @clo := $comp.commandline_options(); 142 | @clo.push('setting=s'); 143 | @clo.push('c'); 144 | @clo.push('I=s'); 145 | @clo.push('M=s'); 146 | 147 | # Set up module loading trace 148 | my @*MODULES := []; 149 | 150 | # Set up END block list, which we'll run at exit. 151 | my @*END_PHASERS := []; 152 | 153 | # Force loading of the debugger module. 154 | my $pname := @ARGS.shift(); 155 | @ARGS.unshift('-Ilib'); 156 | @ARGS.unshift('-MIPerl6::ZMQ'); 157 | @ARGS.unshift($pname); 158 | 159 | my $*ZMQ_PROTOCOL := ProtocolGetter.new(); 160 | 161 | # Enter the compiler. 162 | $comp.command_line(@ARGS, :encoding('utf8'), :transcode('ascii iso-8859-1')); 163 | 164 | # Run any END blocks before exiting. 165 | for @*END_PHASERS { $_() } 166 | } 167 | -------------------------------------------------------------------------------- /heartbeat.py: -------------------------------------------------------------------------------- 1 | import zmq 2 | from IPython.zmq.heartbeat import Heartbeat 3 | from IPython.utils.localinterfaces import LOCALHOST 4 | 5 | context = zmq.Context() 6 | 7 | beat = Heartbeat(context, (LOCALHOST, 5554)) 8 | beat.start() 9 | 10 | raw_input("input anything and hit return to stop the heart beating\n"); 11 | -------------------------------------------------------------------------------- /inline-wrapper/kernel.json: -------------------------------------------------------------------------------- 1 | { 2 | "argv": ["perl6", "/home/arne/programming/perl/iperl6kernel/inline-wrapper/kernel.p6", "{connection_file}"], 3 | "display_name": "Perl 6" 4 | } 5 | -------------------------------------------------------------------------------- /inline-wrapper/kernel.p6: -------------------------------------------------------------------------------- 1 | use nqp; 2 | use Inline::Python; 3 | 4 | my $program = q:to; 5 | from ipykernel.kernelapp import IPKernelApp 6 | from ipykernel.kernelbase import Kernel 7 | 8 | executor = None 9 | 10 | class Perl6Kernel (Kernel): 11 | implementation = 'python-perl6' 12 | implementation_version = '0.1' 13 | language = 'Perl' 14 | language_version = '6' 15 | banner = 'Welcome to the gloriously hacky Perl 6 kernel!' 16 | 17 | def do_execute(self, code, silent, store_history=True, user_expressions=None, allow_stdin=False): 18 | global executor 19 | if not silent: 20 | stream_content = {'name': 'stdout', 'text': executor(code)} 21 | self.send_response(self.iopub_socket, 'stream', stream_content) 22 | 23 | return {'status': 'ok', 24 | 'execution_count': self.execution_count, 25 | 'payload': [], 26 | 'user_expressions': {}} 27 | 28 | def run(e, connection): 29 | #print "Running..." 30 | global executor 31 | #print "Setting executor..." 32 | executor = e 33 | #print "Starting..." 34 | IPKernelApp.launch_instance(kernel_class=Perl6Kernel, connection_file=connection) 35 | PYTHON 36 | 37 | my $save_ctx := nqp::null(); 38 | my $compiler := nqp::getcomp('perl6'); 39 | sub executor($code) { 40 | my $*CTXSAVE := $compiler; 41 | my $*MAIN_CTX; 42 | 43 | my $output; 44 | { 45 | CATCH { return "SHENANIGANS!" } # TODO: Error handling. 46 | $output := $compiler.eval($code, :outer_ctx($save_ctx)); 47 | } 48 | 49 | if nqp::defined($*MAIN_CTX) { $save_ctx := $*MAIN_CTX } 50 | ~$output; 51 | } 52 | #sub executor($code) { $code } 53 | 54 | # XXX: For some reason, this results in an ambiguous multidispatch inside 55 | # Inline::Python... 56 | #sub MAIN(Str $connection) { 57 | # my Inline::Python $python .= new; 58 | # $python.run: $program; 59 | # $python.call('__main__', 'run', &executor, $connection) 60 | #} 61 | 62 | my Inline::Python $python .= new; 63 | $python.run: $program; 64 | $python.call: '__main__', 'run', sub ($code) { executor($code) }, @*ARGS[0] 65 | -------------------------------------------------------------------------------- /lib/IPerl6.pm6: -------------------------------------------------------------------------------- 1 | unit class IPerl6; 2 | 3 | use nqp; 4 | 5 | use Digest::HMAC; 6 | use Digest::SHA; 7 | use JSON::Fast; 8 | use Net::ZMQ; 9 | use Net::ZMQ::Constants; 10 | use Net::ZMQ::Poll; 11 | use UUID; 12 | 13 | use IPerl6::Gobble; 14 | 15 | has Net::ZMQ::Context $!ctx; 16 | has $!transport; 17 | has $!ip; 18 | has $!key; 19 | has $!session = ~UUID.new: :version(4); 20 | has $!stdout = IPerl6::Gobble.new; 21 | has $!stderr = IPerl6::Gobble.new; 22 | has $!exec_counter = 1; 23 | has @!history = (); 24 | submethod BUILD(:%connection) { 25 | $!ctx .= new; 26 | $!transport = %connection; 27 | $!ip = %connection; 28 | $!key = %connection; 29 | 30 | self!setup_sockets(%connection); 31 | self!setup_heartbeat(%connection); 32 | self!setup_compiler; 33 | } 34 | 35 | has Net::ZMQ::Socket $!shell; 36 | has Net::ZMQ::Socket $!iopub; 37 | has Net::ZMQ::Socket $!stdin; 38 | has Net::ZMQ::Socket $!control; 39 | method !setup_sockets(%connection) { 40 | say "# Setting up sockets..."; 41 | $!shell = self!socket: ZMQ_ROUTER, %connection; 42 | $!iopub = self!socket: ZMQ_PUB, %connection; 43 | $!stdin = self!socket: ZMQ_ROUTER, %connection; 44 | $!control = self!socket: ZMQ_ROUTER, %connection; 45 | 46 | self!starting; 47 | } 48 | 49 | method !socket($type, $port) { 50 | my Net::ZMQ::Socket $s .= new: $!ctx, $type; 51 | my $addr = self!make_address: $port; 52 | $s.bind: $addr; 53 | return $s; 54 | } 55 | 56 | has $!save_ctx; 57 | has $!compiler; 58 | method !setup_compiler() { 59 | $!save_ctx := nqp::null(); 60 | $!compiler := nqp::getcomp('perl6'); 61 | } 62 | 63 | #has Thread $!hb_thread; 64 | has Net::ZMQ::Socket $!hb_socket; 65 | method !setup_heartbeat(%connection) { 66 | $!hb_socket = self!socket: ZMQ_REP, %connection; 67 | #$!hb_socket = self!socket: ZMQ_ROUTER, %connection; 68 | 69 | # There's a bug on Rakudo/Moar where running the heartbeat in a separate 70 | # thread hangs the program. So for the time being we interleave things. 71 | # Not optimal, but at least it gets us something that works (mostly). 72 | #my $hb_addr = self!make_address: %connection; 73 | #$!hb_thread .= start: { 74 | # my Net::ZMQ::Context $ctx .= new; 75 | # my Net::ZMQ::Socket $s .= new: $ctx, ZMQ_ROUTER; 76 | # # XXX: The IPython base kernel code sets linger 1000 on the socket. 77 | # # Maybe we want that too? 78 | # $s.bind: $hb_addr; 79 | # loop { 80 | # my $ret = device($s, $s, :queue); 81 | # last if $ret != 4; # XXX: 4 is the value of EINTR. On my machine anyways... 82 | # } 83 | #}; 84 | } 85 | 86 | method !make_address($port) { 87 | my $sep = $!transport eq "tcp" ?? ":" !! "-"; 88 | return "$!transport://$!ip$sep$port"; 89 | } 90 | 91 | has $!running = False; 92 | method start() { 93 | # There are two things making the main run loop here weirder than 94 | # necessary. First is the hang bug described in !setup heartbeat; thus we 95 | # do a blocking poll on the heartbeat to make sure we reply to heartbeat 96 | # requests, and between each heartbeat poll we make non-blocking polls on 97 | # the other sockets. This means we should work fairly well, as long as 98 | # users don't start computations that take *too* long. 99 | # 100 | # Second is a limitation in Net::ZMQ (due in turn to a limitation in 101 | # NativeCall), where we can only poll single sockets at a time, not all of 102 | # them at once. Thus we have to cascade the polls, one after the other. 103 | $!running = True; 104 | while $!running { 105 | if poll_one($!hb_socket, 500_000, :in) { 106 | $!hb_socket.send: $!hb_socket.receive; 107 | } 108 | if poll_one($!shell, 0, :in) { 109 | self!shell_message 110 | } 111 | if poll_one($!control, 0, :in) { 112 | say "# Message on control:"; 113 | say self!read_message: $!control; 114 | } 115 | if poll_one($!iopub, 0, :in) { 116 | say "# Message on iopub:"; 117 | say self!read_message: $!iopub; 118 | } 119 | if poll_one($!stdin, 0, :in) { 120 | say "# Message on stdin:"; 121 | say self!read_message: $!stdin; 122 | } 123 | } 124 | } 125 | 126 | method !shell_message() { 127 | my $parent = self!read_message: $!shell; 128 | say "# Message on shell: $parent
"; 129 | given $parent
{ 130 | when 'kernel_info_request' { 131 | my $reply = { 132 | protocol_version => '5.0', 133 | implementation => 'IPerl6', 134 | implementation_version => '0.0.1', 135 | language_info => { 136 | name => 'perl6', 137 | version => '0.1.0', 138 | mimetype => 'text/plain', 139 | file_extension => '.p6', 140 | }, 141 | banner => 'Welcome to IPerl6!', 142 | }; 143 | self!send: $!shell, $reply, type => 'kernel_info_reply', :$parent 144 | } 145 | when 'history_request' { 146 | self!history_request: $parent; 147 | } 148 | when 'execute_request' { 149 | self!execute: $parent; 150 | } 151 | when 'inspect_request' { die 'Inspection NYI' } 152 | when 'complete_request' { die 'Completion NYI' } 153 | when 'is_complete_request' { die 'Is-complete NYI' } 154 | when 'connect_request' { die 'Connect NYI' } 155 | when 'shutdown_request' { 156 | # TODO: Handle restart requests correctly. 157 | self!send: $!shell, {restart => False}, type => 'shutdown_reply', :$parent; 158 | $!running = False; 159 | } 160 | # Comms are a way for kernels and frontends to extend the IPython 161 | # protocol with custom messages. Since we currently don't support any 162 | # extensions, when can just ignore the messages related to them and 163 | # return a hardcoded empty list of currently open comms. 164 | when 'comm_info_request' { self!send: $!shell, {comms => {}}, type => 'comm_info_reply', :$parent } 165 | when 'comm_open' {} 166 | when 'comm_msg' {} 167 | when 'comm_close' {} 168 | default { die "Unknown message type: {to-json $parent
}\n{to-json $parent}" } 169 | } 170 | } 171 | 172 | method !history_request($parent) { 173 | # TODO: Actually implement this. 174 | self!send: $!shell, {history => []}, type => 'history_reply', :$parent; 175 | } 176 | 177 | method !execute($parent) { 178 | my $code = $parent; 179 | my $*CTXSAVE := $!compiler; 180 | my $*MAIN_CTX; 181 | 182 | # TODO: Handle silent, store_history and user_expressions parameters 183 | # TODO: Rebroadcast code input on the IOpub socket (looking at the 184 | # ipykernel/kernelbase.py code, looks like it should have the original 185 | # request as its parent). 186 | 187 | @!history.push: $code; 188 | 189 | my $result; 190 | my $success = True; 191 | my $e; 192 | my $reply = {execution_count => $!exec_counter}; 193 | say "# Executing `$code'"; 194 | self!busy; 195 | { 196 | # Catch any exception, and store it away to be processed into the 197 | # reply. 198 | CATCH { 199 | default { 200 | $e = $_; 201 | $success = False; 202 | } 203 | } 204 | my $*OUT = $!stdout; 205 | my $*ERR = $!stderr; 206 | $result := $!compiler.eval($code, :outer_ctx($!save_ctx)); 207 | } 208 | $reply = $success ?? 'ok' !! 'error'; 209 | if $success { 210 | # TODO: Handle user_expressions here. 211 | } 212 | else { 213 | $result = $e.^name; 214 | $result = ~$e; 215 | $result = $e.backtrace.list.map: ~*; 216 | } 217 | 218 | if nqp::defined($*MAIN_CTX) { $!save_ctx := $*MAIN_CTX } 219 | 220 | say $result; 221 | 222 | self!send: $!shell, $reply, type => 'execute_reply', :$parent; 223 | 224 | self!flush_output: $!stdout, 'stdout', $parent; 225 | self!flush_output: $!stderr, 'stderr', $parent; 226 | 227 | self!send: $!iopub, {execution_count => $!exec_counter, data => {'text/plain' => $result.gist}}, 228 | type => 'execute_result', :$parent; 229 | self!idle; 230 | $!exec_counter++; 231 | } 232 | 233 | method !flush_output($stream, $name, $parent) { 234 | my $output = $stream.get-output; 235 | self!send($!iopub, {:$name, text => $output}, type => 'stream', :$parent) 236 | if $output; 237 | } 238 | 239 | method !starting() { 240 | self!send: $!iopub, {execution_state => 'starting'}, type => 'status'; 241 | } 242 | 243 | method !busy() { 244 | self!send: $!iopub, {execution_state => 'busy'}, type => 'status'; 245 | } 246 | 247 | method !idle() { 248 | self!send: $!iopub, {execution_state => 'idle'}, type => 'status'; 249 | } 250 | 251 | # Str.encode returns a Blob[unit8], whereas we want a Buf[uint8] in the eqv 252 | # check below, so we have to construct the appropriate thing by hand here. 253 | my buf8 $separator = buf8.new: "".encode; 254 | method !read_message(Net::ZMQ::Socket $s) { 255 | my buf8 @routing; 256 | my buf8 @message; 257 | my $separated = False; 258 | 259 | # A message from the IPython frontend is sent in several parts. First is a 260 | # sequence of socket ids for the originating sockets; several because ZMQ 261 | # supports routing messages over many sockets. Next is the "" 262 | # separator to signal the start of the IPython part of the message. 263 | # 264 | # The IPython message consists of a HMAC, a header, a parent header, 265 | # metadata, a message body, and possibly some additional data blobs; in 266 | # that order. 267 | loop { 268 | my buf8 $data = $s.receive.data; 269 | 270 | if not $separated and not $data eqv $separator { @routing.push: $data } 271 | elsif not $separated and $data eqv $separator { 272 | $separated = True; 273 | next; 274 | } 275 | else { @message.push: $data } 276 | 277 | last if not $s.getopt: ZMQ_RCVMORE; 278 | } 279 | 280 | my $hmac = hmac-hex $!key, @message[1] ~ @message[2] ~ @message[3] ~ @message[4], &sha256; 281 | die "HMAC verification failed!" if $hmac ne @message.shift.decode; 282 | 283 | my $header = from-json @message.shift.decode; 284 | my $parent = from-json @message.shift.decode; 285 | my $metadata = from-json @message.shift.decode; 286 | my $content = from-json @message.shift.decode; 287 | 288 | #say to-json $header; 289 | 290 | return {ids => @routing, header => $header, parent => $parent, 291 | metadata => $metadata, content => $content, extra_data => @message}; 292 | } 293 | 294 | method !send($socket, $message, :$type!, :$parent = {}) { 295 | say "# Sending ($type)"; 296 | 297 | if $parent { 298 | for $parent.list { 299 | $socket.send($_, ZMQ_SNDMORE); 300 | } 301 | } 302 | $socket.send: "", ZMQ_SNDMORE; 303 | 304 | #say(to-json $message);# if $type eq 'execute_result'; 305 | 306 | my $header = { 307 | date => ~DateTime.new(now), 308 | msg_id => ~UUID.new(:version(4)), 309 | msg_type => $type, 310 | session => $!session, 311 | username => 'bogus', # TODO: Set this correctly. 312 | version => '5.0', 313 | }; 314 | my $metadata = {}; 315 | 316 | my $header_bytes = to-json($header).encode; 317 | my $parent_bytes = to-json($parent
).encode; 318 | my $meta_bytes = to-json($metadata).encode; 319 | my $content_bytes = to-json($message).encode; 320 | 321 | my $hmac = hmac-hex $!key, $header_bytes ~ $parent_bytes ~ $meta_bytes ~ $content_bytes, &sha256; 322 | 323 | $socket.send: $hmac, ZMQ_SNDMORE; 324 | $socket.send: $header_bytes, ZMQ_SNDMORE; 325 | $socket.send: $parent_bytes, ZMQ_SNDMORE; 326 | $socket.send: $meta_bytes, ZMQ_SNDMORE; 327 | $socket.send: $content_bytes; 328 | } 329 | 330 | sub MAIN(Str $connection) is export { 331 | my IPerl6 $kernel .= new: connection => from-json($connection.IO.slurp); 332 | $kernel.start; 333 | } 334 | -------------------------------------------------------------------------------- /lib/IPerl6/Gobble.pm6: -------------------------------------------------------------------------------- 1 | unit class IPerl6::Gobble; 2 | 3 | my $nl = "\n"; 4 | has $!output = ""; 5 | 6 | method print(Str $data --> True) { $!output ~= $data } 7 | method put(Str $data --> True) { $!output ~= $data ~ $nl } 8 | method print-nl(--> True) { $!output ~= $nl } 9 | 10 | method get-output() { 11 | my $data = $!output; 12 | $!output = ""; 13 | return $data; 14 | } 15 | 16 | # Reading from STDIN NYI. 17 | method get() { die "IPerl6::Gobble.read NYI" } 18 | method read() { die "IPerl6::Gobble.read NYI" } 19 | method readchars() { die "IPerl6::Gobble.readchars NYI" } 20 | method slurp-rest() { die "IPerl6::Gobble.read NYI" } 21 | -------------------------------------------------------------------------------- /lib/IPerl6/ZMQ.pm6: -------------------------------------------------------------------------------- 1 | module IPerl6::ZMQ; 2 | 3 | say "i'm being run!"; 4 | 5 | $*ZMQ_PROTOCOL.set(IPerl6::ZMQ::Protocol.new()); 6 | 7 | use JSON::Tiny; 8 | use Net::ZMQ; 9 | use Net::ZMQ::Constants; 10 | 11 | my Net::ZMQ::Context $zmqctx .= new; 12 | my Net::ZMQ::Socket $pubsock .= new($zmqctx, ZMQ_PUB); 13 | my Net::ZMQ::Socket $shellsock .= new($zmqctx, ZMQ_ROUTER); 14 | my Net::ZMQ::Socket $stdinsock .= new($zmqctx, ZMQ_ROUTER); 15 | 16 | $pubsock.bind: "tcp://*:5551"; 17 | $shellsock.bind: "tcp://*:5552"; 18 | $stdinsock.bind: "tcp://*:5553"; 19 | 20 | my \DELIM := ""; 21 | 22 | my class Stdin { 23 | has $.protocol; 24 | 25 | method read($num) { 26 | return substr("reading currently unsupported", 0, $num); 27 | } 28 | } 29 | 30 | multi send-zmq($data, :$SNDMORE?, :$PUBLISH?) { 31 | state @send-buf; 32 | push @send-buf, $data; 33 | unless $SNDMORE { 34 | my Str $result = to-json(@send-buf); 35 | $*ZMQOUT.write($result.bytes ~ "\n"); 36 | $*ZMQOUT.write($result); 37 | @send-buf = []; 38 | } 39 | } 40 | 41 | multi send-zmq(@data-parts) { 42 | send-zmq($_, :SNDMORE) for @data-parts[0..*-2]; 43 | send-zmq($_) for @data-parts[*-1]; 44 | } 45 | 46 | sub recv-zmq($socket --> List) { 47 | my @res; 48 | @res.push($socket.recv); 49 | while $socket.getsockopt(ZMQ_RCVMORE) { 50 | @res.push($socket.recv); 51 | } 52 | return @res; 53 | } 54 | 55 | my class Message { 56 | has $.id; 57 | has $.parent-header; 58 | has $.header; 59 | has $.content; 60 | has $.msg-id; 61 | has $.msg-type; 62 | has $.hmac; 63 | 64 | submethod recv(--> Message) { 65 | my $data = recv-zmq($shellsock); 66 | my $id = $data.shift; 67 | die "did not find a delimiter after 1 id" if $data.shift ne DELIM; 68 | my &d = { $data.shift }; 69 | return Message.new( 70 | :$id, 71 | :hmac(d), 72 | :header(d), 73 | :parent_header(d), 74 | :content(d)); 75 | } 76 | 77 | method answer returns Message { 78 | return Message.new(:id($.id), :parent-header($.header)); 79 | } 80 | 81 | method sign returns Str { 82 | "" 83 | } 84 | 85 | method verify returns Bool { 86 | True; 87 | } 88 | 89 | method send { 90 | $!header = DateTime.now().Str; 91 | $!header = q:x{uuidgen}; 92 | send-zmq([$.id, DELIM, self.sign, $.header, $.parent-header, $.content]); 93 | } 94 | } 95 | 96 | our class IPerl6::ZMQ::Protocol { 97 | has $.username = "camilla"; 98 | has $.session = q:x{uuidgen}; 99 | has $.ident = q:x{uuidgen}; 100 | 101 | has $.IN; 102 | has $.OUT; 103 | 104 | method BUILD { 105 | $.session = q:x{uuidgen}.trim; 106 | $.ident = q:x{uuidgen}.trim; 107 | } 108 | 109 | method bind(Message $msg) { 110 | $msg.header = $.username; 111 | $msg.header = $.session; 112 | } 113 | 114 | method get_command { 115 | say "going to receive a command now"; 116 | my $msg = Message.recv; 117 | given my $msgtype = $msg.header { 118 | when "execute_request" { 119 | say "going to do an execute request"; 120 | sub exec_req_cb ($result, $stdout, $stderr) { 121 | say "i've done it!"; 122 | } 123 | return ($msgtype, $msg.content, &exec_req_cb); 124 | } 125 | } 126 | } 127 | } 128 | 129 | -------------------------------------------------------------------------------- /pperl6/kernel.json: -------------------------------------------------------------------------------- 1 | { 2 | "argv": ["perl6", "-M", "IPerl6", "-e", "", "{connection_file}"], 3 | "display_name": "Perl 6" 4 | } 5 | -------------------------------------------------------------------------------- /pperl6/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "stdin_port": 51621, 3 | "ip": "127.0.0.1", 4 | "control_port": 35936, 5 | "hb_port": 38248, 6 | "signature_scheme": "hmac-sha256", 7 | "key": "89320f4b-5433-495e-a2ed-30c6b1b3bcf2", 8 | "shell_port": 42695, 9 | "transport": "tcp", 10 | "iopub_port": 43625 11 | } -------------------------------------------------------------------------------- /wrapper.py: -------------------------------------------------------------------------------- 1 | import zmq 2 | #import uuid 3 | #import hmac 4 | 5 | #from datetime import datetime 6 | #from subprocess import Popen, PIPE 7 | 8 | import json 9 | 10 | from zmq.utils import jsonapi 11 | 12 | from IPython.utils.jsonutil import extract_dates, date_default 13 | from IPython.utils.localinterfaces import LOCALHOST 14 | from IPython.zmq.heartbeat import Heartbeat 15 | 16 | json_packer = lambda obj: jsonapi.dumps(obj, default=date_default) 17 | json_unpacker = lambda s: extract_dates(jsonapi.loads(s)) 18 | 19 | pack = json_packer 20 | unpack = json_unpacker 21 | 22 | #DELIM = "" 23 | #my_ident = str(uuid.uuid4()) 24 | #my_session = str(uuid.uuid4()) 25 | 26 | #hmac_key = "e8da7b2b-75a6-46d9-902e-ebd13b3c98f2" 27 | my_username = "camilla" 28 | 29 | #HMAC_O = hmac.HMAC(hmac_key) 30 | #def sign(messages): 31 | #h = HMAC_O.copy() 32 | #for msg in messages: 33 | #h.update(msg) 34 | #return h.hexdigest() 35 | 36 | #my_metadata = { 37 | #'dependencies_met' : True, 38 | #'engine' : my_ident, 39 | #'started' : datetime.now(), 40 | #} 41 | 42 | #rakudo = Popen("perl6", stdin=PIPE, stdout=PIPE) 43 | #if rakudo.stdout.read(2) != "> ": 44 | #print "ERROR!" 45 | #rakudo_counter = [0] 46 | 47 | context = zmq.Context() 48 | stdin_sock = context.socket(zmq.ROUTER) 49 | stdin_sock.bind("tcp://*:5551") 50 | 51 | shell_sock = context.socket(zmq.ROUTER) 52 | shell_sock.bind("tcp://*:5552") 53 | 54 | iopub= context.socket(zmq.PUB) 55 | iopub.bind("tcp://*:5553") 56 | 57 | beat = Heartbeat(context, (LOCALHOST, 5554)) 58 | beat.start() 59 | 60 | poller = zmq.Poller() 61 | poller.register(stdin_sock, zmq.POLLIN) 62 | poller.register(shell_sock, zmq.POLLIN) 63 | 64 | def msg_header(msg_id, msg_type): 65 | username = my_username 66 | session = my_session 67 | date = datetime.now() 68 | return locals() 69 | 70 | def send_answer(to_ident, parent_header, header, content): 71 | parent_header = pack(parent_header) 72 | header = pack(header) 73 | content = pack(content) 74 | signature = sign([header, parent_header, content]) 75 | shell_sock.send(to_ident, zmq.SNDMORE) 76 | shell_sock.send(DELIM, zmq.SNDMORE) 77 | shell_sock.send(signature, zmq.SNDMORE) 78 | shell_sock.send(header, zmq.SNDMORE) 79 | shell_sock.send(parent_header, zmq.SNDMORE) 80 | shell_sock.send(content) 81 | 82 | print "publishing answer:" 83 | print parent_header 84 | print header 85 | print content 86 | 87 | def publish(msg_type, topic, content, parent_header): 88 | parent_header = pack(parent_header) 89 | header = pack(msg_header(str(uuid.uuid4()), msg_type)) 90 | content = pack(content) 91 | signature = sign([header, parent_header, content]) 92 | iopub.send("kernel.%s.%s" % (my_ident, topic), zmq.SNDMORE) 93 | iopub.send(DELIM, zmq.SNDMORE) 94 | iopub.send(signature, zmq.SNDMORE) 95 | iopub.send(header, zmq.SNDMORE) 96 | iopub.send(parent_header, zmq.SNDMORE) 97 | iopub.send(content) 98 | 99 | def handle_shell_sock_message(messages): 100 | delimpos = messages.index(DELIM) 101 | idents = messages[:delimpos] 102 | hmac = messages[delimpos+1] 103 | header = unpack(messages[delimpos+2]) 104 | parent_header = unpack(messages[delimpos+3]) 105 | content = unpack(messages[delimpos+4]) 106 | streams = messages[delimpos+5:] 107 | 108 | msg_type = header["msg_type"] 109 | msg_id = header["msg_id"] 110 | 111 | print(" ".join(idents) + " sent a " + header["msg_type"]) 112 | print header 113 | print content 114 | 115 | if globals().get("handle_" + msg_type, None) is not None: 116 | globals()["handle_"+msg_type](idents[0], header, content, streams) 117 | 118 | def handle_execute_request(to_ident, header, content, streams): 119 | print "handling execute request" 120 | 121 | silent = content["silent"] 122 | 123 | if not silent: 124 | rakudo_counter[0] += 1 125 | 126 | if not content["code"].endswith("\n"): 127 | content["code"] += "\n" 128 | 129 | rakudo.stdin.write(content["code"]) 130 | newlines = content["code"].count("\n") 131 | while True: 132 | read = rakudo.stdout.read(1) 133 | if read == "\n": 134 | newlines -= 1 135 | if newlines == 0: 136 | break 137 | 138 | if not silent: 139 | publish("pyin", "pyin", 140 | {"execution_count": rakudo_counter[0], 141 | "code": content["code"]}, 142 | header) 143 | 144 | result = "" 145 | while True: 146 | read = rakudo.stdout.read(1) 147 | if result.endswith("\n>") and read == " ": 148 | result = result[:-2] 149 | break 150 | else: 151 | result += read 152 | print repr(result), " <- execution result" 153 | new_header = msg_header(str(uuid.uuid4()), "execute_reply") 154 | content = { 155 | "status": "OK", 156 | "execution_count": rakudo_counter[0], 157 | "payload": [] 158 | } 159 | send_answer(to_ident, header, new_header, content) 160 | 161 | if not silent: 162 | publish("pyout", "pyout", 163 | {"execution_count": rakudo_counter[0], 164 | "data": { 165 | "text/plain": result, 166 | } 167 | }, 168 | header) 169 | 170 | def handle_shell_sock_message(parts): 171 | data = json.dumps(["shellsock"]+parts) 172 | datlen = len(data) + 1 173 | print datlen 174 | print data 175 | 176 | shell_messages = [] 177 | stdin_messages = [] 178 | while True: 179 | socks = dict(poller.poll()) 180 | 181 | if socks.get(shell_sock) == zmq.POLLIN: 182 | message = shell_sock.recv() 183 | more = shell_sock.getsockopt(zmq.RCVMORE) 184 | shell_messages.append(message) 185 | if not more: 186 | handle_shell_sock_message(shell_messages) 187 | shell_messages = [] 188 | 189 | if socks.get(stdin_sock) == zmq.POLLIN: 190 | message = stdin_sock.recv() 191 | print("stdin socket received:") 192 | print(repr(message)) 193 | print() 194 | --------------------------------------------------------------------------------