├── .github └── workflows │ ├── linux.yml │ └── macos.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── META6.json ├── README.md ├── bin └── jupyter-kernel.raku ├── eg ├── comms.ipynb ├── hello-world.ipynb ├── magics.ipynb ├── math.ipynb ├── pinter-5.F.ipynb └── svg.ipynb ├── lib └── Jupyter │ ├── Kernel.rakumod │ └── Kernel │ ├── Comm.rakumod │ ├── Comms.rakumod │ ├── Handler.rakumod │ ├── History.rakumod │ ├── Magics.rakumod │ ├── Paths.rakumod │ ├── Response.rakumod │ ├── Sandbox.rakumod │ ├── Sandbox │ └── Autocomplete.rakumod │ └── Service.rakumod ├── resources ├── kernel.json ├── logo-32x32.png └── logo-64x64.png └── t ├── 01-basic.rakutest ├── 02-sandbox.rakutest ├── 03-service.rakutest ├── 04-completions.rakutest ├── 05-autocomplete.rakutest ├── 06-magic.rakutest ├── 07-comms.rakutest ├── 08-paths.rakutest ├── 09-history.rakutest ├── 20-end-to-end.rakutest ├── 99-meta.rakutest └── lib ├── Jupyter └── Client.rakumod └── Slang └── TestWhatIs.rakumod /.github/workflows/linux.yml: -------------------------------------------------------------------------------- 1 | name: Linux 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | tags-ignore: 8 | - '*' 9 | pull_request: 10 | 11 | jobs: 12 | raku: 13 | strategy: 14 | matrix: 15 | os: 16 | - ubuntu-latest 17 | raku-version: 18 | - '2024.05' 19 | - 'latest' 20 | runs-on: ${{ matrix.os }} 21 | steps: 22 | - uses: actions/checkout@v3 23 | - uses: Raku/setup-raku@v1 24 | with: 25 | raku-version: ${{ matrix.raku-version }} 26 | - name: Install native deps 27 | run: sudo apt-get install -y libzmq5-dev 28 | - name: Install Dependencies 29 | run: zef install --deps-only . 30 | - name: Run Tests 31 | run: zef test -v . 32 | -------------------------------------------------------------------------------- /.github/workflows/macos.yml: -------------------------------------------------------------------------------- 1 | name: MacOS 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | tags-ignore: 8 | - '*' 9 | pull_request: 10 | 11 | jobs: 12 | raku: 13 | strategy: 14 | matrix: 15 | os: 16 | - macos-latest 17 | raku-version: 18 | - 'latest' 19 | runs-on: ${{ matrix.os }} 20 | steps: 21 | - uses: actions/checkout@v3 22 | - name: Install native deps 23 | run: | 24 | brew install czmq zmq 25 | echo "DYLD_LIBRARY_PATH=/opt/homebrew/lib:/opt/homebrew/Cellar/zeromq/4.3.5_1/lib:$DYLD_LIBRARY_PATH" >> $GITHUB_ENV 26 | brew list zmq 27 | - uses: Raku/setup-raku@v1 28 | with: 29 | raku-version: ${{ matrix.raku-version }} 30 | - name: Install Dependencies 31 | run: zef install --deps-only . 32 | - name: Run Tests 33 | run: zef test -v . 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .ipynb_checkpoints 2 | Untitled* 3 | .precomp 4 | *.log 5 | sdist 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM sumankhanal/rakudo:daily 2 | LABEL maintainer="Dr Suman Khanal " 3 | 4 | 5 | #Enabling Binder.................................. 6 | ENV NB_USER suman 7 | ENV NB_UID 1000 8 | ENV HOME /home/${NB_USER} 9 | RUN adduser --disabled-password \ 10 | --gecos "Default user" \ 11 | --uid ${NB_UID} \ 12 | ${NB_USER} 13 | 14 | #.............................................. 15 | 16 | ENV PATH=$PATH:/usr/share/perl6/site/bin 17 | 18 | RUN apt-get update \ 19 | && apt-get install -y build-essential \ 20 | wget libzmq3-dev ca-certificates \ 21 | python3-pip python3-setuptools \ 22 | && rm -rf /var/lib/apt/lists/* && pip3 install jupyter notebook asciinema jupyterlab pyscaffold --no-cache-dir \ 23 | && zef -v install git://github.com/bduggan/raku-jupyter-kernel.git@1.0.2 --force-test \ 24 | && zef install SVG::Plot --force-test \ 25 | && jupyter-kernel.raku --generate-config \ 26 | && ln -s /usr/share/perl6/site/bin/* /usr/local/bin 27 | 28 | ENV TINI_VERSION v0.18.0 29 | ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /usr/bin/tini 30 | RUN chmod +x /usr/bin/tini 31 | ENTRYPOINT ["/usr/bin/tini", "--"] 32 | 33 | 34 | 35 | #For enabling binder.......................... 36 | COPY eg ${HOME} 37 | 38 | USER root 39 | RUN chown -R ${NB_UID} ${HOME} 40 | USER ${NB_USER} 41 | WORKDIR ${HOME} 42 | #.............................................. 43 | 44 | 45 | EXPOSE 8888 46 | 47 | CMD ["jupyter", "notebook", "--port=8888", "--no-browser", "--ip=0.0.0.0", "--allow-root"] 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The Artistic License 2.0 2 | 3 | Copyright (c) 2000-2006, The Perl Foundation. 4 | 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | This license establishes the terms under which a given free software 11 | Package may be copied, modified, distributed, and/or redistributed. 12 | The intent is that the Copyright Holder maintains some artistic 13 | control over the development of that Package while still keeping the 14 | Package available as open source and free software. 15 | 16 | You are always permitted to make arrangements wholly outside of this 17 | license directly with the Copyright Holder of a given Package. If the 18 | terms of this license do not permit the full use that you propose to 19 | make of the Package, you should contact the Copyright Holder and seek 20 | a different licensing arrangement. 21 | 22 | Definitions 23 | 24 | "Copyright Holder" means the individual(s) or organization(s) 25 | named in the copyright notice for the entire Package. 26 | 27 | "Contributor" means any party that has contributed code or other 28 | material to the Package, in accordance with the Copyright Holder's 29 | procedures. 30 | 31 | "You" and "your" means any person who would like to copy, 32 | distribute, or modify the Package. 33 | 34 | "Package" means the collection of files distributed by the 35 | Copyright Holder, and derivatives of that collection and/or of 36 | those files. A given Package may consist of either the Standard 37 | Version, or a Modified Version. 38 | 39 | "Distribute" means providing a copy of the Package or making it 40 | accessible to anyone else, or in the case of a company or 41 | organization, to others outside of your company or organization. 42 | 43 | "Distributor Fee" means any fee that you charge for Distributing 44 | this Package or providing support for this Package to another 45 | party. It does not mean licensing fees. 46 | 47 | "Standard Version" refers to the Package if it has not been 48 | modified, or has been modified only in ways explicitly requested 49 | by the Copyright Holder. 50 | 51 | "Modified Version" means the Package, if it has been changed, and 52 | such changes were not explicitly requested by the Copyright 53 | Holder. 54 | 55 | "Original License" means this Artistic License as Distributed with 56 | the Standard Version of the Package, in its current version or as 57 | it may be modified by The Perl Foundation in the future. 58 | 59 | "Source" form means the source code, documentation source, and 60 | configuration files for the Package. 61 | 62 | "Compiled" form means the compiled bytecode, object code, binary, 63 | or any other form resulting from mechanical transformation or 64 | translation of the Source form. 65 | 66 | 67 | Permission for Use and Modification Without Distribution 68 | 69 | (1) You are permitted to use the Standard Version and create and use 70 | Modified Versions for any purpose without restriction, provided that 71 | you do not Distribute the Modified Version. 72 | 73 | 74 | Permissions for Redistribution of the Standard Version 75 | 76 | (2) You may Distribute verbatim copies of the Source form of the 77 | Standard Version of this Package in any medium without restriction, 78 | either gratis or for a Distributor Fee, provided that you duplicate 79 | all of the original copyright notices and associated disclaimers. At 80 | your discretion, such verbatim copies may or may not include a 81 | Compiled form of the Package. 82 | 83 | (3) You may apply any bug fixes, portability changes, and other 84 | modifications made available from the Copyright Holder. The resulting 85 | Package will still be considered the Standard Version, and as such 86 | will be subject to the Original License. 87 | 88 | 89 | Distribution of Modified Versions of the Package as Source 90 | 91 | (4) You may Distribute your Modified Version as Source (either gratis 92 | or for a Distributor Fee, and with or without a Compiled form of the 93 | Modified Version) provided that you clearly document how it differs 94 | from the Standard Version, including, but not limited to, documenting 95 | any non-standard features, executables, or modules, and provided that 96 | you do at least ONE of the following: 97 | 98 | (a) make the Modified Version available to the Copyright Holder 99 | of the Standard Version, under the Original License, so that the 100 | Copyright Holder may include your modifications in the Standard 101 | Version. 102 | 103 | (b) ensure that installation of your Modified Version does not 104 | prevent the user installing or running the Standard Version. In 105 | addition, the Modified Version must bear a name that is different 106 | from the name of the Standard Version. 107 | 108 | (c) allow anyone who receives a copy of the Modified Version to 109 | make the Source form of the Modified Version available to others 110 | under 111 | 112 | (i) the Original License or 113 | 114 | (ii) a license that permits the licensee to freely copy, 115 | modify and redistribute the Modified Version using the same 116 | licensing terms that apply to the copy that the licensee 117 | received, and requires that the Source form of the Modified 118 | Version, and of any works derived from it, be made freely 119 | available in that license fees are prohibited but Distributor 120 | Fees are allowed. 121 | 122 | 123 | Distribution of Compiled Forms of the Standard Version 124 | or Modified Versions without the Source 125 | 126 | (5) You may Distribute Compiled forms of the Standard Version without 127 | the Source, provided that you include complete instructions on how to 128 | get the Source of the Standard Version. Such instructions must be 129 | valid at the time of your distribution. If these instructions, at any 130 | time while you are carrying out such distribution, become invalid, you 131 | must provide new instructions on demand or cease further distribution. 132 | If you provide valid instructions or cease distribution within thirty 133 | days after you become aware that the instructions are invalid, then 134 | you do not forfeit any of your rights under this license. 135 | 136 | (6) You may Distribute a Modified Version in Compiled form without 137 | the Source, provided that you comply with Section 4 with respect to 138 | the Source of the Modified Version. 139 | 140 | 141 | Aggregating or Linking the Package 142 | 143 | (7) You may aggregate the Package (either the Standard Version or 144 | Modified Version) with other packages and Distribute the resulting 145 | aggregation provided that you do not charge a licensing fee for the 146 | Package. Distributor Fees are permitted, and licensing fees for other 147 | components in the aggregation are permitted. The terms of this license 148 | apply to the use and Distribution of the Standard or Modified Versions 149 | as included in the aggregation. 150 | 151 | (8) You are permitted to link Modified and Standard Versions with 152 | other works, to embed the Package in a larger work of your own, or to 153 | build stand-alone binary or bytecode versions of applications that 154 | include the Package, and Distribute the result without restriction, 155 | provided the result does not expose a direct interface to the Package. 156 | 157 | 158 | Items That are Not Considered Part of a Modified Version 159 | 160 | (9) Works (including, but not limited to, modules and scripts) that 161 | merely extend or make use of the Package, do not, by themselves, cause 162 | the Package to be a Modified Version. In addition, such works are not 163 | considered parts of the Package itself, and are not subject to the 164 | terms of this license. 165 | 166 | 167 | General Provisions 168 | 169 | (10) Any use, modification, and distribution of the Standard or 170 | Modified Versions is governed by this Artistic License. By using, 171 | modifying or distributing the Package, you accept this license. Do not 172 | use, modify, or distribute the Package, if you do not accept this 173 | license. 174 | 175 | (11) If your Modified Version has been derived from a Modified 176 | Version made by someone other than you, you are nevertheless required 177 | to ensure that your Modified Version complies with the requirements of 178 | this license. 179 | 180 | (12) This license does not grant you the right to use any trademark, 181 | service mark, tradename, or logo of the Copyright Holder. 182 | 183 | (13) This license includes the non-exclusive, worldwide, 184 | free-of-charge patent license to make, have made, use, offer to sell, 185 | sell, import and otherwise transfer the Package with respect to any 186 | patent claims licensable by the Copyright Holder that are necessarily 187 | infringed by the Package. If you institute patent litigation 188 | (including a cross-claim or counterclaim) against any party alleging 189 | that the Package constitutes direct or contributory patent 190 | infringement, then this Artistic License to you shall terminate on the 191 | date that such litigation is filed. 192 | 193 | (14) Disclaimer of Warranty: 194 | THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS 195 | IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED 196 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR 197 | NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL 198 | LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL 199 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 200 | DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF 201 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /META6.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Jupyter::Kernel", 3 | "source-url" : "git://github.com/bduggan/raku-jupyter-kernel.git", 4 | "raku" : "6.*", 5 | "auth" : "zef:bduggan", 6 | "build-depends" : [ ], 7 | "provides" : { 8 | "Jupyter::Kernel" : "lib/Jupyter/Kernel.rakumod", 9 | "Jupyter::Kernel::Comm" : "lib/Jupyter/Kernel/Comm.rakumod", 10 | "Jupyter::Kernel::Comms" : "lib/Jupyter/Kernel/Comms.rakumod", 11 | "Jupyter::Kernel::Handler" : "lib/Jupyter/Kernel/Handler.rakumod", 12 | "Jupyter::Kernel::History" : "lib/Jupyter/Kernel/History.rakumod", 13 | "Jupyter::Kernel::Magics" : "lib/Jupyter/Kernel/Magics.rakumod", 14 | "Jupyter::Kernel::Paths" : "lib/Jupyter/Kernel/Paths.rakumod", 15 | "Jupyter::Kernel::Response" : "lib/Jupyter/Kernel/Response.rakumod", 16 | "Jupyter::Kernel::Sandbox" : "lib/Jupyter/Kernel/Sandbox.rakumod", 17 | "Jupyter::Kernel::Sandbox::Autocomplete" : "lib/Jupyter/Kernel/Sandbox/Autocomplete.rakumod", 18 | "Jupyter::Kernel::Service" : "lib/Jupyter/Kernel/Service.rakumod" 19 | }, 20 | "resources" : [ "logo-32x32.png", "logo-64x64.png", "kernel.json" ], 21 | "tags" : [ ], 22 | "depends" : [ 23 | "UUID", "Log::Async", "Net::ZMQ:ver<0.8>", "JSON::Tiny", 24 | "Digest::HMAC", "Digest::SHA256::Native" 25 | ], 26 | "description" : "Raku Kernel for Jupyter Notebooks", 27 | "test-depends" : [ ], 28 | "version" : "1.0.3", 29 | "license" : "Artistic-2.0", 30 | "authors" : [ 31 | "Brian Duggan" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Jupyter::Kernel for Raku 2 | ---------------- 3 | [![Actions Status](https://github.com/bduggan/raku-jupyter-kernel/actions/workflows/linux.yml/badge.svg)](https://github.com/bduggan/raku-jupyter-kernel/actions/workflows/linux.yml) 4 | [![Actions Status](https://github.com/bduggan/raku-jupyter-kernel/actions/workflows/macos.yml/badge.svg)](https://github.com/bduggan/raku-jupyter-kernel/actions/workflows/macos.yml) 5 | 6 | [![Binder](https://binder.pangeo.io/badge_logo.svg)](https://mybinder.org/v2/gh/bduggan/p6-jupyter-kernel/master?filepath=hello-world.ipynb) 7 | 8 | ![autocomplete](https://user-images.githubusercontent.com/58956/29986517-c6a2020e-8f31-11e7-83da-086ad18bc662.gif) 9 | 10 | This is a pure Raku implementation of a Raku kernel for Jupyter clients¹. 11 | 12 | Jupyter notebooks provide a web-based (or console-based) 13 | Read Eval Print Loop (REPL) for running code and serializing input and output. 14 | 15 | REALLY QUICK START 16 | ------------------- 17 | 18 | [Binder](https://mybinder.org/) provides a way to instantly launch a Docker 19 | image and open a notebook². Click `launch | binder` above 20 | to start this kernel with a sample notebook. (See below 21 | for similar alternatives.) 22 | 23 | QUICK START 24 | ----------- 25 | 26 | * [Installation](#Installation) 27 | * [Configuration](#Configuration) 28 | * [Running](#Running) 29 | 30 | ### Installation 31 | You'll need to install zmq. Note that currently, version 4.1 is 32 | recommended by Net::ZMQ (though 4.2 is installed by, e.g. homebrew). 33 | If you run into stability issues, you may need to downgrade. 34 | 35 | ``` 36 | brew install zmq # on OS/X 37 | apt-get install libzmq-dev # on Ubuntu 38 | ``` 39 | 40 | You'll also want jupyter, for the front end: 41 | 42 | ``` 43 | pip install jupyter 44 | ``` 45 | 46 | Finally, install `Jupyter::Kernel`: 47 | 48 | ``` 49 | zef install 'Jupyter::Kernel:auth' 50 | ``` 51 | 52 | At the end of the above installation, you'll see the location 53 | of the `bin/` directory which has `jupyter-kernel.raku`. Make 54 | sure that is in your `PATH`. 55 | 56 | ### Configuration 57 | #### Server Configuration 58 | To generate a configuration directory, and to install a kernel 59 | config file and icons into the default location: 60 | ``` 61 | jupyter-kernel.raku --generate-config 62 | ``` 63 | * Use `--location=XXX` to specify another location. 64 | * Use `--force` to override an existing configuration. 65 | 66 | #### Logging 67 | By default a log file `jupyter.log` will be written in the 68 | current directory. An option `--logfile=XXX` argument can be 69 | added to the argv argument of the server configuration file 70 | (located at `$(jupyter --data)/kernels/raku/kernel.json`) 71 | to change this. 72 | 73 | #### Client configuration 74 | The jupyter documentation describes the client configuration. 75 | To start, you can generate files for the notebook or 76 | console clients like this: 77 | ``` 78 | jupyter notebook --generate-config 79 | jupyter console --generate-config 80 | ``` 81 | Some suggested configuration changes for the console client: 82 | 83 | * set `kernel_is_complete_timeout` to a high number. Otherwise, 84 | if the kernel takes more than 1 second to respond, then from 85 | then on, the console client uses internal (non-Raku) heuristics 86 | to guess when a block of code is complete. 87 | 88 | * set `highlighting_style` to `vim`. This avoids having dark blue 89 | on a black background in the console client. 90 | 91 | ### Running 92 | Start the web UI with: 93 | ``` 94 | jupyter-notebook 95 | Then select New -> Raku. 96 | ``` 97 | 98 | You can also use it in the console like this: 99 | ``` 100 | jupyter-console --kernel=raku 101 | ``` 102 | 103 | Or make a handy shell alias: 104 | 105 | ``` 106 | alias iraku='jupyter-console --kernel=raku' 107 | ``` 108 | 109 | FEATURES 110 | ----------- 111 | 112 | * __Autocompletion:__ Typing `[tab]` in the client will send an autocomplete request. Possible autocompletions are: 113 | 114 | * methods: after a `.` the invocant will be evaluated to find methods 115 | 116 | * set operators: after a ` (`, set operators (unicode and texas) will be shown (note the whitespace before the `(`)). 117 | 118 | * equality/inequality operators: after `=`, ` <`, or ` >`, related operators will be shown. 119 | 120 | * autocompleting ` *` or ` /` will give `×` or `÷` respectively. 121 | 122 | * autocompleting ` **` or a superscript will give you superscripts (for typing exponents). 123 | 124 | * the word 'atomic' autocompletes to the [atomic operators](https://docs.raku.org/type/atomicint#Operators). (Use `atomic-` or `atom` to get the subroutines with their ASCII names). 125 | 126 | * a colon followed by a sequence of word characters will autocomplete 127 | to characters whose unicode name contains that string. Dashes are 128 | treated as spaces. 129 | e.g. :straw will find 🍓 ("STRAWBERRY") or 🥤 ("CUP WITH STRAW") and :smiling-face-with-smiling-eye will find 😊 ("SMILING FACE WITH SMILING EYES") 130 | 131 | * __Keep output:__ All cells are evaluated in item context. Outputs are then saved to an array 132 | named `$Out`. You can read from this directly or: 133 | 134 | * via the subroutine `Out` (e.g. `Out[3]`) 135 | 136 | * via an underscore and the output number (e.g. `_3`) 137 | 138 | * for the most recent output: via a plain underscore (`_`). 139 | 140 | * __Keep input:__ Similiarly, the input text can be accessed via `In[N]` (e.g. `In[3].EVAL` or `In[3].AST` would eval or produce the ast for a cell) 141 | 142 | * __Magics:__ There is some support for jupyter "magics". If the first line 143 | of a code cell starts with `#%` or `%%`, it may be interpreted as a directive 144 | by the kernel. See EXAMPLES. The following magics are supported: 145 | 146 | * `#% javascript`: interpret the cell as javascript; i.e. run it in the browser 147 | 148 | * `#% js`: return the output as javascript 149 | 150 | * `#% > js`: return stdout as javascript 151 | 152 | * `#% html`: return the output as html 153 | 154 | * `#% latex`: return the output as LaTeX. Use `latex(equation)` to wrap 155 | the output in `\begin{equation}` and `\end{equation}`. (Or replace 156 | "`equation`" with another string to use something else.) 157 | 158 | * `#% markdown` (or `md`): the output will be interpreted as markdown. 159 | Note that this is for generating markdown as the output of a cell, not for 160 | writing markdown, which can be done without magics. Also, this simply 161 | sends the data with the markdown mime-type, and the notebook does the rendering. 162 | 163 | * `#% > markdown` (or `md`): interpret stdout as markdown 164 | 165 | * `#% html > latex`: The above can be combined to render, for instance, 166 | the output cell as HTML, but stdout as LaTeX. The word before the `>` 167 | indicates the type of the output cell. The word after the `>` indictes 168 | the type of stdout. 169 | 170 | * `%% bash`: Interpret the cell as bash. stdout becomes the contents of 171 | the next cell. Behaves like Raku's built-in `shell`. 172 | 173 | * `%% run FILENAME`: Prepend the contents of FILENAME to the 174 | contents of the current cell (if any) before execution. 175 | Note this is different from the built-in `EVALFILE` in that 176 | if any lexical variables, subroutines, etc. are declared in FILENAME, 177 | they will become available in the notebook execution context. 178 | 179 | * `%% always [SUBCOMMAND] CODE`: SUBCOMMAND defaults to `prepend` but can be: 180 | * `prepend`: Prepend each cell by `CODE;\n` 181 | * `append`: Append `;\nCODE` after each command 182 | * `clear`: Clear all `always` registered actions 183 | * `show`: Show `always` registered actions 184 | You can combine it with another magic. For example: 185 | `%% always prepend %% run file.raku` 186 | 187 | * __Comms:__ Comms allow for asynchronous communication between a notebook 188 | and the kernel. For an example of using comms, see [this notebook](eg/comms.ipynb) 189 | 190 | ### Usage notes 191 | 192 | * In the console, pressing return will execute the code in a cell. If you want 193 | a cell to span several lines, put a `\` at the end of the line, like so: 194 | 195 | ``` 196 | In [1]: 42 197 | Out[1]: 42 198 | 199 | In [2]: 42 + 200 | Out[2]: Missing required term after infix 201 | 202 | In [3]: 42 + \ 203 | : 10 + \ 204 | : 3 + \ 205 | : 12 206 | Out[3]: 67 207 | ``` 208 | 209 | Note that this is not the same as the raku 'unspace' -- a backslash followed 210 | by a newline will be replaced with a newline before the code is executed. To 211 | create an unspace at the end of the line, you can use two backslashes. 212 | 213 | DOCKER 214 | ------- 215 | 216 | [This blog post](https://sumankhanal.netlify.com/post/raku_notebook/) provides 217 | a tutorial for running this kernel with Docker. [This one](https://sumdoc.wordpress.com/2018/01/04/using-perl-6-notebooks-in-binder/) describes using [Binder](https://mybinder.org/). 218 | 219 | EXAMPLES 220 | -------- 221 | 222 | The [eg/](eg/) directory of this repository has some 223 | example notebooks: 224 | 225 | * [Hello, world](eg/hello-world.ipynb). 226 | 227 | * [Generating an SVG](eg/svg.ipynb). 228 | 229 | * [Some unicodey math examples](http://nbviewer.jupyter.org/github/bduggan/p6-jupyter-kernel/blob/master/eg/math.ipynb) 230 | 231 | * [magics](http://nbviewer.jupyter.org/github/bduggan/p6-jupyter-kernel/blob/master/eg/magics.ipynb) 232 | 233 | SEE ALSO 234 | -------- 235 | * [Docker image for Raku](https://hub.docker.com/r/sumankhanal/raku-notebook/) 236 | 237 | * [iperl6kernel](https://github.com/timo/iperl6kernel) 238 | 239 | KNOWN ISSUES 240 | --------- 241 | * Newly declared methods might not be available in autocompletion unless SPESH is disabled (see tests in [this PR](https://github.com/bduggan/p6-jupyter-kernel/pull/11)). 242 | 243 | THANKS 244 | -------- 245 | Matt Oates 246 | 247 | Suman Khanal 248 | 249 | Timo Paulssen 250 | 251 | Tinmarino 252 | 253 | Anton Antonov 254 | 255 | FOOTNOTES 256 | -------- 257 | 258 | ¹ Jupyter clients are user interfaces to interact with an interpreter kernel like `Jupyter::Kernel`. 259 | Jupyter [Lab | Notebook | Console | QtConsole ] are the jupyter maintained clients. 260 | More info in the [jupyter documentations site](https://jupyter.org/documentation). 261 | 262 | ² mybinder.org provides a way to instantly launch a Docker image and open a notebook. 263 | -------------------------------------------------------------------------------- /bin/jupyter-kernel.raku: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env raku 2 | 3 | use Log::Async; 4 | use Jupyter::Kernel; 5 | use Jupyter::Kernel::Paths; 6 | 7 | multi MAIN($spec-file, :$logfile = './jupyter.log') { 8 | logger.send-to($logfile); 9 | Jupyter::Kernel.new.run($spec-file); 10 | } 11 | 12 | 13 | multi MAIN(Bool :$generate-config!, 14 | Str :$location = ~raku-dir; 15 | Bool :$force) { 16 | 17 | # Retrieve color code 18 | # nb: Colored output can be disabled with RAKUDO_ERROR_COLOR environment variable 19 | my ($red, $clear, $green, $yellow, $eject) = Rakudo::Internals.error-rcgye; 20 | 21 | # Check if need to work 22 | my $dest-spec = $location.IO.child('kernel.json'); 23 | $dest-spec.f and !$force and do { 24 | say "File $dest-spec already exists => exiting the configuration."; 25 | say "You can force the configuration with '" ~ $red~"--force"~$clear ~ "'"; 26 | exit; 27 | } 28 | 29 | # Declare kernel.json content 30 | my $spec = q:to/DONE/; 31 | { 32 | "display_name": "Raku", 33 | "language": "raku", 34 | "argv": [ 35 | "jupyter-kernel.raku", 36 | "{connection_file}" 37 | ] 38 | } 39 | DONE 40 | 41 | # Create kernel file system 42 | note "Creating directory $location"; 43 | mkdir $location; 44 | note "Writing kernel.json to $dest-spec"; 45 | $dest-spec.spurt($spec); 46 | for <32 64> { 47 | my $file = "logo-{ $_ }x{ $_ }.png"; 48 | my $resources = Jupyter::Kernel.resources; 49 | my $resource = $resources{ $file } // $?FILE.IO.parent.parent.child('resources').child($file); 50 | $resource.IO.e or do { 51 | say "Can't find resource $file"; 52 | next; 53 | } 54 | note "Copying $file to $location"; 55 | copy $resource.IO, $location.IO.child($file) or die "Failed to copy $file to $location."; 56 | } 57 | 58 | # Say Success 59 | say "Congratulations, configuration files have been " 60 | ~ $green~"successfully"~$clear ~ " written!"; 61 | say $green~"Happy Perling!"~$clear 62 | ~ " <- " ~ $yellow~"jupyter console --kernel=raku"~$clear; 63 | say ''; 64 | } 65 | -------------------------------------------------------------------------------- /eg/comms.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "$*JUPYTER.register-comm('counter', -> :$in, :$out, :$data {\n", 12 | " my atomicint $num = 0;\n", 13 | " start react whenever Supply.interval(2) -> $i {\n", 14 | " $out.send($num + $i);\n", 15 | " done if $i > 4;\n", 16 | " }\n", 17 | " start while $in.receive -> $i {\n", 18 | " $num ⚛= $i;\n", 19 | " }\n", 20 | " }\n", 21 | ")" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": 2, 27 | "metadata": {}, 28 | "outputs": [ 29 | { 30 | "data": { 31 | "application/javascript": [ 32 | "var comm = Jupyter.notebook.kernel.comm_manager.new_comm('counter');\n", 33 | "var el = $('
---
');\n", 34 | "this.element.append(el);\n", 35 | "var x = 10;\n", 36 | "comm.on_msg(function(msg) {\n", 37 | " el.html(msg.content.data);\n", 38 | " comm.send({'num': x});\n", 39 | " x = x + 10;\n", 40 | "});" 41 | ] 42 | }, 43 | "metadata": {}, 44 | "output_type": "display_data" 45 | } 46 | ], 47 | "source": [ 48 | "%% javascript\n", 49 | "var comm = Jupyter.notebook.kernel.comm_manager.new_comm('counter');\n", 50 | "var el = $('
---
');\n", 51 | "this.element.append(el);\n", 52 | "var x = 10;\n", 53 | "comm.on_msg(function(msg) {\n", 54 | " el.html(msg.content.data);\n", 55 | " comm.send({'num': x});\n", 56 | " x = x + 10;\n", 57 | "});" 58 | ] 59 | } 60 | ], 61 | "metadata": { 62 | "kernelspec": { 63 | "display_name": "Raku", 64 | "language": "raku", 65 | "name": "raku" 66 | }, 67 | "language_info": { 68 | "file_extension": ".raku", 69 | "mimetype": "text/x-raku", 70 | "name": "raku", 71 | "version": "6.d" 72 | } 73 | }, 74 | "nbformat": 4, 75 | "nbformat_minor": 2 76 | } 77 | -------------------------------------------------------------------------------- /eg/hello-world.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "### Welcome to this sample Raku Jupyter notebook!\n", 8 | "\n", 9 | "To run a cell: select it, and press ctrl-return.\n", 10 | "\n", 11 | "To add a new empty cell, press the `+` button on the menu bar.\n", 12 | "\n", 13 | "To run a cell and add a new one after it: press alt-return.\n", 14 | "\n", 15 | "For documentation about Raku, go to http://docs.raku.org.\n", 16 | "\n", 17 | "For documentation about this Jupyter Kernel, go to http://github.com/bduggan/p6-jupyter-kernel.\n", 18 | "\n", 19 | "Have fun!" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": 1, 25 | "metadata": { 26 | "scrolled": true 27 | }, 28 | "outputs": [ 29 | { 30 | "data": { 31 | "text/plain": [ 32 | "hello, world" 33 | ] 34 | }, 35 | "execution_count": 1, 36 | "metadata": {}, 37 | "output_type": "execute_result" 38 | } 39 | ], 40 | "source": [ 41 | "\"hello, world\"" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": 2, 47 | "metadata": {}, 48 | "outputs": [ 49 | { 50 | "data": { 51 | "text/plain": [ 52 | "170141183460469231731687303715884105727" 53 | ] 54 | }, 55 | "execution_count": 2, 56 | "metadata": {}, 57 | "output_type": "execute_result" 58 | } 59 | ], 60 | "source": [ 61 | "# To type this, you can type '2 **' and then press tab to help type exponents.\n", 62 | "my $n = 2¹²⁷ - 1;" 63 | ] 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": 3, 68 | "metadata": {}, 69 | "outputs": [ 70 | { 71 | "data": { 72 | "text/plain": [ 73 | "True" 74 | ] 75 | }, 76 | "execution_count": 3, 77 | "metadata": {}, 78 | "output_type": "execute_result" 79 | } 80 | ], 81 | "source": [ 82 | "# _ refers to the last output\n", 83 | "is-prime( _ )" 84 | ] 85 | }, 86 | { 87 | "cell_type": "code", 88 | "execution_count": 4, 89 | "metadata": {}, 90 | "outputs": [ 91 | { 92 | "data": { 93 | "text/plain": [ 94 | "127" 95 | ] 96 | }, 97 | "execution_count": 4, 98 | "metadata": {}, 99 | "output_type": "execute_result" 100 | } 101 | ], 102 | "source": [ 103 | "( Out[2] + 1).log(2); # you can also refer to Out[...]" 104 | ] 105 | }, 106 | { 107 | "cell_type": "code", 108 | "execution_count": 5, 109 | "metadata": {}, 110 | "outputs": [ 111 | { 112 | "data": { 113 | "text/plain": [ 114 | "[...]" 115 | ] 116 | }, 117 | "execution_count": 5, 118 | "metadata": {}, 119 | "output_type": "execute_result" 120 | } 121 | ], 122 | "source": [ 123 | "my @fib = 1, 1, * + * ... ∞" 124 | ] 125 | }, 126 | { 127 | "cell_type": "code", 128 | "execution_count": 6, 129 | "metadata": {}, 130 | "outputs": [ 131 | { 132 | "data": { 133 | "text/plain": [ 134 | "(1 1 2 3 5 8 13 21 34 55 89)" 135 | ] 136 | }, 137 | "execution_count": 6, 138 | "metadata": {}, 139 | "output_type": "execute_result" 140 | } 141 | ], 142 | "source": [ 143 | "@fib[0..10]" 144 | ] 145 | }, 146 | { 147 | "cell_type": "code", 148 | "execution_count": 7, 149 | "metadata": {}, 150 | "outputs": [ 151 | { 152 | "name": "stdout", 153 | "output_type": "stream", 154 | "text": [ 155 | "this goes to stdout\n" 156 | ] 157 | }, 158 | { 159 | "data": { 160 | "text/plain": [ 161 | "42" 162 | ] 163 | }, 164 | "execution_count": 7, 165 | "metadata": {}, 166 | "output_type": "execute_result" 167 | } 168 | ], 169 | "source": [ 170 | "say \"this goes to stdout\";\n", 171 | "42 # this is an output expression" 172 | ] 173 | } 174 | ], 175 | "metadata": { 176 | "kernelspec": { 177 | "display_name": "Raku", 178 | "language": "raku", 179 | "name": "raku" 180 | }, 181 | "language_info": { 182 | "file_extension": ".raku", 183 | "mimetype": "text/x-raku", 184 | "name": "raku", 185 | "version": "6.d" 186 | } 187 | }, 188 | "nbformat": 4, 189 | "nbformat_minor": 2 190 | } 191 | -------------------------------------------------------------------------------- /eg/magics.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "This notebook has examples of using magics." 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "If the first line of a cell starts with %% or #%, the kernel treats it as a \"magic\".\n", 15 | "`javascript` will just send the contents as javascript to the browser. In this context, you can use `element` to manipulate the output area." 16 | ] 17 | }, 18 | { 19 | "cell_type": "code", 20 | "execution_count": 1, 21 | "metadata": { 22 | "scrolled": true 23 | }, 24 | "outputs": [ 25 | { 26 | "data": { 27 | "application/javascript": [ 28 | "element.append('foo')" 29 | ] 30 | }, 31 | "metadata": {}, 32 | "output_type": "display_data" 33 | } 34 | ], 35 | "source": [ 36 | "%% javascript\n", 37 | "element.append('foo')" 38 | ] 39 | }, 40 | { 41 | "cell_type": "code", 42 | "execution_count": 2, 43 | "metadata": {}, 44 | "outputs": [ 45 | { 46 | "data": { 47 | "application/javascript": [ 48 | "for (var i=0;i<4;i++) {\n", 49 | " element.append(i)\n", 50 | "}" 51 | ] 52 | }, 53 | "metadata": {}, 54 | "output_type": "display_data" 55 | } 56 | ], 57 | "source": [ 58 | "#% javascript\n", 59 | "for (var i=0;i<4;i++) {\n", 60 | " element.append(i)\n", 61 | "}" 62 | ] 63 | }, 64 | { 65 | "cell_type": "markdown", 66 | "metadata": {}, 67 | "source": [ 68 | "To generate output of type latex, use the `#% latex` magic." 69 | ] 70 | }, 71 | { 72 | "cell_type": "code", 73 | "execution_count": 3, 74 | "metadata": {}, 75 | "outputs": [ 76 | { 77 | "data": { 78 | "text/latex": [ 79 | "\\begin{equation*} \\frac{1}{2} \\end{equation*}" 80 | ] 81 | }, 82 | "execution_count": 3, 83 | "metadata": {}, 84 | "output_type": "execute_result" 85 | } 86 | ], 87 | "source": [ 88 | "#% latex\n", 89 | "my $x = '\\begin{equation*} \\frac{1}{2} \\end{equation*}';\n", 90 | "$x" 91 | ] 92 | }, 93 | { 94 | "cell_type": "code", 95 | "execution_count": 4, 96 | "metadata": {}, 97 | "outputs": [ 98 | { 99 | "data": { 100 | "text/latex": [ 101 | "\\begin{equation}\n", 102 | "\\frac{1}{2}\n", 103 | "\\end{equation}\n" 104 | ] 105 | }, 106 | "execution_count": 4, 107 | "metadata": {}, 108 | "output_type": "execute_result" 109 | } 110 | ], 111 | "source": [ 112 | "%% latex(equation)\n", 113 | "'\\frac{1}{2}'" 114 | ] 115 | }, 116 | { 117 | "cell_type": "code", 118 | "execution_count": 5, 119 | "metadata": {}, 120 | "outputs": [ 121 | { 122 | "data": { 123 | "text/html": [ 124 | "½\n" 125 | ] 126 | }, 127 | "metadata": {}, 128 | "output_type": "display_data" 129 | }, 130 | { 131 | "data": { 132 | "text/latex": [ 133 | "\\begin{equation}\n", 134 | "\\frac{1}{2}\n", 135 | "\\end{equation}\n" 136 | ] 137 | }, 138 | "execution_count": 5, 139 | "metadata": {}, 140 | "output_type": "execute_result" 141 | } 142 | ], 143 | "source": [ 144 | "#% latex(equation) > html\n", 145 | "say '½';\n", 146 | "'\\frac{1}{2}';" 147 | ] 148 | }, 149 | { 150 | "cell_type": "code", 151 | "execution_count": 6, 152 | "metadata": {}, 153 | "outputs": [ 154 | { 155 | "data": { 156 | "text/plain": [ 157 | "(Routine::WrapHandle.new Routine::WrapHandle.new)" 158 | ] 159 | }, 160 | "execution_count": 6, 161 | "metadata": {}, 162 | "output_type": "execute_result" 163 | } 164 | ], 165 | "source": [ 166 | "Rat.^find_method($_)\n", 167 | " .wrap( sub ($x) {\n", 168 | " '\\frac{' ~ $x.numerator ~ '}{' ~ $x.denominator ~ '}'\n", 169 | " } ) for ;" 170 | ] 171 | }, 172 | { 173 | "cell_type": "code", 174 | "execution_count": 7, 175 | "metadata": {}, 176 | "outputs": [ 177 | { 178 | "data": { 179 | "text/latex": [ 180 | "\\begin{equation}\n", 181 | "\\frac{1}{5} + \\frac{9}{10} - \\frac{1}{10}\n", 182 | "\\end{equation}\n" 183 | ] 184 | }, 185 | "execution_count": 7, 186 | "metadata": {}, 187 | "output_type": "execute_result" 188 | } 189 | ], 190 | "source": [ 191 | "%% latex(equation)\n", 192 | "\"{0.2} + {0.9} - {0.1}\"" 193 | ] 194 | }, 195 | { 196 | "cell_type": "code", 197 | "execution_count": 8, 198 | "metadata": {}, 199 | "outputs": [ 200 | { 201 | "data": { 202 | "text/latex": [ 203 | "\\begin{equation}\n", 204 | "\\frac{2}{3} + \\frac{1}{8} == \\frac{19}{24}\n", 205 | "\\end{equation}\n" 206 | ] 207 | }, 208 | "execution_count": 8, 209 | "metadata": {}, 210 | "output_type": "execute_result" 211 | } 212 | ], 213 | "source": [ 214 | "%% latex(equation)\n", 215 | "my $x = 2/3;\n", 216 | "my $y = 1/8;\n", 217 | "\"$x + $y == { $x + $y }\"" 218 | ] 219 | } 220 | ], 221 | "metadata": { 222 | "kernelspec": { 223 | "display_name": "Raku", 224 | "language": "raku", 225 | "name": "raku" 226 | }, 227 | "language_info": { 228 | "file_extension": ".raku", 229 | "mimetype": "text/x-raku", 230 | "name": "raku", 231 | "version": "6.d" 232 | } 233 | }, 234 | "nbformat": 4, 235 | "nbformat_minor": 2 236 | } 237 | -------------------------------------------------------------------------------- /eg/math.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Math examples\n", 8 | "With attempts to use Unicode operators where possible to make the Raku code look like the math." 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "metadata": {}, 14 | "source": [ 15 | "## The Cauchy-Schwarz Inequality\n", 16 | " from the [jupyter docs](http://jupyter-notebook.readthedocs.io/en/latest/examples/Notebook/Typesetting%20Equations.html)\n", 17 | "\\begin{equation*}\n", 18 | "\\left( \\sum_{k=1}^n a_k b_k \\right)^2 \\leq \\left( \\sum_{k=1}^n a_k^2 \\right) \\left( \\sum_{k=1}^n b_k^2 \\right)\n", 19 | "\\end{equation*}" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": 1, 25 | "metadata": {}, 26 | "outputs": [ 27 | { 28 | "data": { 29 | "text/plain": [ 30 | "True" 31 | ] 32 | }, 33 | "execution_count": 1, 34 | "metadata": {}, 35 | "output_type": "execute_result" 36 | } 37 | ], 38 | "source": [ 39 | "sub cauchy-schwarz(@a,@b) {\n", 40 | " ( [+] @a Z× @b )² ≤ ( [+] @a»² ) × ( [+] @b»² )\n", 41 | "}\n", 42 | "\n", 43 | "cauchy-schwarz( ( ^100 ).pick(5), ( ^100 ).pick(5) )" 44 | ] 45 | }, 46 | { 47 | "cell_type": "markdown", 48 | "metadata": {}, 49 | "source": [ 50 | "## The Cubic formula\n", 51 | "see this [discussion](http://www.perlmonks.org/?node_id=1189383) and [wikipedia](https://en.wikipedia.org/wiki/Cubic_function#Algebraic_solution)\n", 52 | "\\begin{equation*}\n", 53 | "\\Delta_0 = b^2 - 3ac\n", 54 | "\\\\\n", 55 | "\\Delta_1 = 2b^3 - 9abc + 27a^2d\n", 56 | "\\\\\n", 57 | "C = \\sqrt[3]{ \\frac{ \\Delta_1 \\pm \\sqrt{\\Delta_1^2 - 4 \\Delta_0^3 } }{2} }\n", 58 | "\\\\\n", 59 | "x_k = - \\frac{1}{3a}( b + ς^k C + \\frac{\\Delta_0}{ς^k C}), k ∊ {0,1,2}\n", 60 | "\\end{equation*}" 61 | ] 62 | }, 63 | { 64 | "cell_type": "code", 65 | "execution_count": 2, 66 | "metadata": {}, 67 | "outputs": [ 68 | { 69 | "data": { 70 | "text/plain": [ 71 | "[True True True]" 72 | ] 73 | }, 74 | "execution_count": 2, 75 | "metadata": {}, 76 | "output_type": "execute_result" 77 | } 78 | ], 79 | "source": [ 80 | "sub cubic(\\a,\\b,\\c,\\d) {\n", 81 | " my \\Δ0 = b²\t- 3 × a × c;\n", 82 | " # note: special case when Δ0 == 0\n", 83 | " my \\Δ1 = 2 * b³ - 9 × a × b × c + 27 × a² × d;\n", 84 | " my \\C = ( ( Δ1 + sqrt( Δ1² - 4 × Δ0³ + 0i) ) / 2 ).roots(3)[0];\n", 85 | " my \\ς = 1.roots(3); # cubic roots of unity\n", 86 | " return [0,1,2].map: -> \\k {\n", 87 | " ( -1 / ( 3 × a ) ) × ( b + ς[k] × C + Δ0 / ( C × ς[k] ) )\n", 88 | " }\n", 89 | "}\n", 90 | "\n", 91 | "my @vals = cubic(1,10,10,-10);\n", 92 | "my $f = -> \\x { x³ + 10 * x² + 10 * x - 10 };\n", 93 | "\n", 94 | "my $*TOLERANCE = 1e-10;\n", 95 | "\n", 96 | "[ $f( $_ ) ≅ 0 for @vals ]" 97 | ] 98 | } 99 | ], 100 | "metadata": { 101 | "kernelspec": { 102 | "display_name": "Raku", 103 | "language": "raku", 104 | "name": "raku" 105 | }, 106 | "language_info": { 107 | "file_extension": ".raku", 108 | "mimetype": "text/x-raku", 109 | "name": "raku", 110 | "version": "6.d" 111 | } 112 | }, 113 | "nbformat": 4, 114 | "nbformat_minor": 2 115 | } 116 | -------------------------------------------------------------------------------- /eg/pinter-5.F.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "Exercises from *A Book of Abstract Algebra* by *Charles C Pinter* solved in Raku.\n", 8 | "\n", 9 | "# 5.F. Groups Determined by Generators and Defining Equations" 10 | ] 11 | }, 12 | { 13 | "cell_type": "markdown", 14 | "metadata": {}, 15 | "source": [ 16 | "# Helper function `zip-many`" 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": 1, 22 | "metadata": {}, 23 | "outputs": [ 24 | { 25 | "data": { 26 | "text/plain": [ 27 | "sub zip-many (@arrs) { #`(Sub|139734349541608) ... }" 28 | ] 29 | }, 30 | "execution_count": 1, 31 | "metadata": {}, 32 | "output_type": "execute_result" 33 | } 34 | ], 35 | "source": [ 36 | "sub zip-many (@arrs) { @arrs == 1 ?? @arrs[0][] !! [Z] @arrs }" 37 | ] 38 | }, 39 | { 40 | "cell_type": "markdown", 41 | "metadata": {}, 42 | "source": [ 43 | "# The heart of the program: `generate`" 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": 2, 49 | "metadata": {}, 50 | "outputs": [ 51 | { 52 | "data": { 53 | "text/plain": [ 54 | "sub generate (%eqs, $s) { #`(Sub|139734349547384) ... }" 55 | ] 56 | }, 57 | "execution_count": 2, 58 | "metadata": {}, 59 | "output_type": "execute_result" 60 | } 61 | ], 62 | "source": [ 63 | "sub generate(%eqs, $s)\n", 64 | "{\n", 65 | " my @results;\n", 66 | "\n", 67 | " for %eqs.kv -> $key, $val {\n", 68 | " if $s ~~ /$key/ { @results.push($s.subst(/$key/, $val)); }\n", 69 | " if $s ~~ /$val/ { @results.push($s.subst(/$val/, $key)); }\n", 70 | " }\n", 71 | "\n", 72 | " .take for @results;\n", 73 | "\n", 74 | " .take for flat zip-many @results.map({ gather generate(%eqs, $_) });\n", 75 | "}" 76 | ] 77 | }, 78 | { 79 | "cell_type": "markdown", 80 | "metadata": {}, 81 | "source": [ 82 | "# Subroutine to display the table" 83 | ] 84 | }, 85 | { 86 | "cell_type": "code", 87 | "execution_count": 3, 88 | "metadata": {}, 89 | "outputs": [ 90 | { 91 | "data": { 92 | "text/plain": [ 93 | "sub table (@G, %eqs) { #`(Sub|139734349553312) ... }" 94 | ] 95 | }, 96 | "execution_count": 3, 97 | "metadata": {}, 98 | "output_type": "execute_result" 99 | } 100 | ], 101 | "source": [ 102 | "sub table(@G, %eqs)\n", 103 | "{\n", 104 | " printf \" |\"; for @G -> $y { printf \"%-5s|\", $y; }; say '';\n", 105 | "\n", 106 | " printf \"-----|\"; for @G -> $y { printf \"-----|\"; }; say '';\n", 107 | "\n", 108 | " for @G -> $x {\n", 109 | "\n", 110 | " printf \"%-5s|\", $x;\n", 111 | "\n", 112 | " for @G -> $y {\n", 113 | " my $result = (gather generate(%eqs, \"$x$y\")).first(* ∈ @G);\n", 114 | "\n", 115 | " printf \"%-5s|\", $result;\n", 116 | " }\n", 117 | " say ''\n", 118 | " } \n", 119 | "}" 120 | ] 121 | }, 122 | { 123 | "cell_type": "markdown", 124 | "metadata": {}, 125 | "source": [ 126 | "# Exercises" 127 | ] 128 | }, 129 | { 130 | "cell_type": "markdown", 131 | "metadata": {}, 132 | "source": [ 133 | "** 5.G.1 **\n", 134 | "\n", 135 | "Let $G$ be the group:\n", 136 | "\n", 137 | "\\begin{align*}\n", 138 | "\\{e,a,b, b^2, ab, ab^2\\}\n", 139 | "\\end{align*}\n", 140 | "\n", 141 | "whose generators satisfy: \n", 142 | "\n", 143 | "\\begin{align*}\n", 144 | "a^2 & = e \\\\\n", 145 | "b^3 & = e \\\\\n", 146 | "ba & = ab^2\n", 147 | "\\end{align*}\n", 148 | "\n", 149 | "Write the table of $G$." 150 | ] 151 | }, 152 | { 153 | "cell_type": "code", 154 | "execution_count": 4, 155 | "metadata": {}, 156 | "outputs": [ 157 | { 158 | "name": "stdout", 159 | "output_type": "stream", 160 | "text": [ 161 | " |e |a |b |bb |ab |abb |\n", 162 | "-----|-----|-----|-----|-----|-----|-----|\n", 163 | "e |e |a |b |bb |ab |abb |\n", 164 | "a |a |e |ab |abb |b |bb |\n", 165 | "b |b |abb |bb |e |a |ab |\n", 166 | "bb |bb |ab |e |b |abb |a |\n", 167 | "ab |ab |bb |abb |a |e |b |\n", 168 | "abb |abb |b |a |ab |bb |e |\n" 169 | ] 170 | } 171 | ], 172 | "source": [ 173 | "my @G = ;\n", 174 | "\n", 175 | "my %eqs = ; %eqs = '';\n", 176 | "\n", 177 | "table @G, %eqs;" 178 | ] 179 | }, 180 | { 181 | "cell_type": "markdown", 182 | "metadata": {}, 183 | "source": [ 184 | "**5.G.2**\n", 185 | "\n", 186 | "Let $G$ be the group\n", 187 | "\n", 188 | "\\begin{align*}\n", 189 | "\\{ e, a, b, b^2, b^3, ab, ab^2, ab^3 \\}\n", 190 | "\\end{align*}\n", 191 | "\n", 192 | "whose generators satisfy\n", 193 | "\n", 194 | "\\begin{align*}\n", 195 | "a^2 & = e \\\\\n", 196 | "b^4 & = e \\\\\n", 197 | "ba & = ab^3\n", 198 | "\\end{align*}\n", 199 | "\n", 200 | "Write the table of $G$." 201 | ] 202 | }, 203 | { 204 | "cell_type": "code", 205 | "execution_count": 5, 206 | "metadata": {}, 207 | "outputs": [ 208 | { 209 | "name": "stdout", 210 | "output_type": "stream", 211 | "text": [ 212 | " |e |a |b |bb |bbb |ab |abb |abbb |\n", 213 | "-----|-----|-----|-----|-----|-----|-----|-----|-----|\n", 214 | "e |e |a |b |bb |bbb |ab |abb |abbb |\n", 215 | "a |a |e |ab |abb |abbb |b |bb |bbb |\n", 216 | "b |b |abbb |bb |bbb |e |a |ab |abb |\n", 217 | "bb |bb |abb |bbb |e |b |abbb |a |ab |\n", 218 | "bbb |bbb |ab |e |b |bb |abb |abbb |a |\n", 219 | "ab |ab |bbb |abb |abbb |a |e |b |bb |\n", 220 | "abb |abb |bb |abbb |a |ab |bbb |e |b |\n", 221 | "abbb |abbb |b |a |ab |abb |bb |bbb |e |\n" 222 | ] 223 | } 224 | ], 225 | "source": [ 226 | "my @G = ;\n", 227 | "\n", 228 | "my %eqs = ; %eqs = '';\n", 229 | "\n", 230 | "table @G, %eqs;" 231 | ] 232 | }, 233 | { 234 | "cell_type": "markdown", 235 | "metadata": {}, 236 | "source": [ 237 | "**5.G.3**\n", 238 | "\n", 239 | "Let $G$ be the group\n", 240 | "\n", 241 | "\\begin{align*}\n", 242 | "\\{ e, a, b, b^2, b^3, ab, ab^2, ab^3 \\}\n", 243 | "\\end{align*}\n", 244 | "\n", 245 | "whose generators satisfy\n", 246 | "\n", 247 | "\\begin{align*}\n", 248 | "a^4 & = e \\\\\n", 249 | "a^2 & = b^2 \\\\\n", 250 | "ba & = ab^3\n", 251 | "\\end{align*}\n", 252 | "\n", 253 | "Write the table of $G$." 254 | ] 255 | }, 256 | { 257 | "cell_type": "code", 258 | "execution_count": 6, 259 | "metadata": {}, 260 | "outputs": [ 261 | { 262 | "name": "stdout", 263 | "output_type": "stream", 264 | "text": [ 265 | " |e |a |b |bb |bbb |ab |abb |abbb |\n", 266 | "-----|-----|-----|-----|-----|-----|-----|-----|-----|\n", 267 | "e |e |a |b |bb |bbb |ab |abb |abbb |\n", 268 | "a |a |bb |ab |abb |abbb |bbb |e |b |\n", 269 | "b |b |abbb |bb |bbb |e |a |ab |abb |\n", 270 | "bb |bb |abb |bbb |e |b |abbb |a |ab |\n", 271 | "bbb |bbb |ab |e |b |bb |abb |abbb |a |\n", 272 | "ab |ab |b |abb |abbb |a |bb |bbb |e |\n", 273 | "abb |abb |e |abbb |a |ab |b |bb |bbb |\n", 274 | "abbb |abbb |bbb |a |ab |abb |e |b |bb |\n" 275 | ] 276 | } 277 | ], 278 | "source": [ 279 | "my @G = ;\n", 280 | "\n", 281 | "my %eqs = ; %eqs = '';\n", 282 | "\n", 283 | "table @G, %eqs;" 284 | ] 285 | } 286 | ], 287 | "metadata": { 288 | "kernelspec": { 289 | "display_name": "Raku", 290 | "language": "raku", 291 | "name": "raku" 292 | }, 293 | "language_info": { 294 | "file_extension": ".raku", 295 | "mimetype": "text/x-raku", 296 | "name": "raku", 297 | "version": "6.d" 298 | } 299 | }, 300 | "nbformat": 4, 301 | "nbformat_minor": 2 302 | } 303 | -------------------------------------------------------------------------------- /eg/svg.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Two examples from SVG::Plot" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "## Example 1\n", 15 | "stdout is svg, result is text/plain." 16 | ] 17 | }, 18 | { 19 | "cell_type": "code", 20 | "execution_count": 1, 21 | "metadata": {}, 22 | "outputs": [ 23 | { 24 | "data": { 25 | "image/svg+xml": [ 26 | "\n", 27 | "\n", 28 | "\n", 29 | "\n", 30 | "\n", 31 | "\n", 32 | "\n", 33 | "\n", 34 | "\n", 35 | "\n", 36 | "\n", 37 | "\n", 38 | "\n", 39 | "\n", 40 | "\n", 41 | "\n", 42 | "\n", 43 | "\n", 44 | "\n", 45 | "\n", 46 | "\n", 47 | "\n", 48 | "\n", 49 | "\n", 50 | "\n", 51 | "\n", 52 | "\n", 53 | "\n", 54 | "\n", 55 | "\n", 56 | "\n", 57 | "\n", 58 | "\n", 59 | "\n", 60 | "\n", 61 | "\n", 62 | "\n", 63 | "\n", 64 | "\n", 65 | "\n", 66 | "\n", 67 | "\n", 68 | "\n", 69 | "\n", 70 | "\n", 71 | "\n", 72 | "\n", 73 | "\n", 74 | "\n", 75 | "\n", 76 | "\n", 77 | "\n", 78 | "\n", 79 | "\n", 80 | "\n", 81 | "\n", 82 | "\n", 83 | "\n", 84 | "\n", 85 | "\n", 86 | "\n", 87 | "\n", 88 | "\n", 89 | "\n", 90 | "\n", 91 | "\n", 92 | "\n", 93 | "\n", 94 | "\n", 95 | "\n", 96 | "\n", 97 | "\n", 98 | "\n", 99 | "\n", 100 | "\n", 101 | "\n", 102 | "\n", 103 | "\n", 104 | "\n", 105 | "\n", 106 | "\n", 107 | "\n", 108 | "\n", 109 | "\n", 110 | "\n", 111 | "\n", 112 | "\n", 113 | "\n", 114 | "\n", 115 | "\n", 116 | "\n", 117 | "\n", 118 | "\n", 119 | "\n", 120 | "\n", 121 | "\n", 122 | "\n", 123 | "\n", 124 | "\n", 125 | "\n", 126 | "\n", 127 | "-0.5\n", 128 | "\n", 129 | "0\n", 130 | "\n", 131 | "0.5\n", 132 | "\n", 133 | "-2\n", 134 | "\n", 135 | "-1.5\n", 136 | "\n", 137 | "-1\n", 138 | "\n", 139 | "-0.5\n", 140 | "\n", 141 | "0\n", 142 | "\n", 143 | "0.5\n", 144 | "\n", 145 | "1\n", 146 | "\n", 147 | "1.5\n", 148 | "\n", 149 | "2\n", 150 | "\n", 151 | "\n", 152 | "sin(x/10), cos(x/10)\n", 153 | "\n", 154 | "\n", 155 | "\n" 156 | ] 157 | }, 158 | "metadata": {}, 159 | "output_type": "display_data" 160 | }, 161 | { 162 | "data": { 163 | "text/plain": [ 164 | "12" 165 | ] 166 | }, 167 | "execution_count": 1, 168 | "metadata": {}, 169 | "output_type": "execute_result" 170 | } 171 | ], 172 | "source": [ 173 | "use SVG;\n", 174 | "use SVG::Plot;\n", 175 | "\n", 176 | "my $points = 50;\n", 177 | "my @x = (0..$points).map: { sin(2 * pi * $_ / $points) };\n", 178 | "my @d1 = (0..$points).map: { 2 * cos(2 * pi * $_ / $points) };\n", 179 | "my @d2 = (0..$points).map: { cos(2 * pi * $_ / $points) };\n", 180 | "say SVG.serialize: SVG::Plot.new(\n", 181 | " width => 400,\n", 182 | " height => 250,\n", 183 | " :@x,\n", 184 | " values => (@d1, @d2),\n", 185 | " title => 'sin(x/10), cos(x/10)',\n", 186 | ").plot(:xy-lines);\n", 187 | "12;" 188 | ] 189 | }, 190 | { 191 | "cell_type": "markdown", 192 | "metadata": {}, 193 | "source": [ 194 | "# Example 2\n", 195 | "Result is svg, stdout is text/plain." 196 | ] 197 | }, 198 | { 199 | "cell_type": "code", 200 | "execution_count": 2, 201 | "metadata": {}, 202 | "outputs": [ 203 | { 204 | "name": "stdout", 205 | "output_type": "stream", 206 | "text": [ 207 | "this is plain text\n" 208 | ] 209 | }, 210 | { 211 | "data": { 212 | "image/svg+xml": [ 213 | "\n", 214 | "\n", 215 | "\n", 216 | "\n", 217 | "\n", 218 | "\n", 219 | "\n", 220 | "\n", 221 | "\n", 222 | "0\n", 223 | "\n", 224 | "0.5\n", 225 | "\n", 226 | "1\n", 227 | "\n", 228 | "1.5\n", 229 | "\n", 230 | "2\n", 231 | "\n", 232 | "2.5\n", 233 | "\n", 234 | "3\n", 235 | "\n", 236 | "3.5\n", 237 | "\n", 238 | "4\n", 239 | "\n", 240 | "4.5\n", 241 | "\n", 242 | "5\n", 243 | "\n", 244 | "5.5\n", 245 | "\n", 246 | "6\n", 247 | "\n", 248 | "0\n", 249 | "\n", 250 | "0.5\n", 251 | "\n", 252 | "1\n", 253 | "\n", 254 | "1.5\n", 255 | "\n", 256 | "2\n", 257 | "\n", 258 | "2.5\n", 259 | "\n", 260 | "3\n", 261 | "\n", 262 | "3.5\n", 263 | "\n", 264 | "4\n", 265 | "\n", 266 | "4.5\n", 267 | "\n", 268 | "5\n", 269 | "\n", 270 | "5.5\n", 271 | "\n", 272 | "6\n", 273 | "\n", 274 | "\n", 275 | "Bubbles!\n", 276 | "\n" 277 | ] 278 | }, 279 | "execution_count": 2, 280 | "metadata": {}, 281 | "output_type": "execute_result" 282 | } 283 | ], 284 | "source": [ 285 | "use SVG;\n", 286 | "use SVG::Plot;\n", 287 | "\n", 288 | "# cw: Sensible storage method\n", 289 | "my @data1 = (\n", 290 | "\t# [x, y, mag]\n", 291 | "\t[ 1, 5, 1],\n", 292 | "\t[ 3, 6, 1],\n", 293 | "\t[ 5, 4, 1],\n", 294 | "\t[ 2, 2, 1],\n", 295 | ");\n", 296 | "\n", 297 | "my @data2 = (\n", 298 | "\t[ 2, 5, 0.5],\n", 299 | "\t[ 4, 4, 0.5],\n", 300 | "\t[ 3, 2.5, 0.5],\n", 301 | "\t[ 5, 4, 0.5],\n", 302 | ");\n", 303 | "\n", 304 | "my $svg = SVG::Plot.new(\n", 305 | " width \t\t=> 500,\n", 306 | " height \t\t=> 300,\n", 307 | " values \t\t=> [ $(@data1), $(@data2) ],\n", 308 | " background\t\t=> 'gray',\n", 309 | " title \t\t=> 'Bubbles!',\n", 310 | " min-y-axis \t\t=> 0\n", 311 | ").plot(:bubbles, :opacity(0.8));\n", 312 | "say \"this is plain text\";\n", 313 | "SVG.serialize($svg);" 314 | ] 315 | } 316 | ], 317 | "metadata": { 318 | "kernelspec": { 319 | "display_name": "Raku", 320 | "language": "raku", 321 | "name": "raku" 322 | }, 323 | "language_info": { 324 | "file_extension": ".raku", 325 | "mimetype": "text/x-raku", 326 | "name": "raku", 327 | "version": "6.d" 328 | } 329 | }, 330 | "nbformat": 4, 331 | "nbformat_minor": 2 332 | } 333 | -------------------------------------------------------------------------------- /lib/Jupyter/Kernel.rakumod: -------------------------------------------------------------------------------- 1 | unit class Jupyter::Kernel; 2 | 3 | use JSON::Tiny; 4 | use Log::Async; 5 | use Net::ZMQ4::Constants; 6 | use UUID; 7 | 8 | use Jupyter::Kernel::Service; 9 | use Jupyter::Kernel::Sandbox; 10 | use Jupyter::Kernel::Magics; 11 | use Jupyter::Kernel::Comms; 12 | use Jupyter::Kernel::History; 13 | 14 | has $.engine-id = ~UUID.new: :version(4); 15 | has $.kernel-info = { 16 | status => 'ok', 17 | protocol_version => '5.0', 18 | implementation => 'raku-jupyter-kernel', 19 | implementation_version => '1.0.3', 20 | language_info => { 21 | name => 'raku', 22 | version => ~$*RAKU.version, 23 | mimetype => 'text/x-raku', 24 | file_extension => '.raku', 25 | }, 26 | banner => "Welcome to Raku 🦋 ({ $*RAKU.compiler.name } { $*RAKU.compiler.version })." 27 | } 28 | has $.magics = Jupyter::Kernel::Magics.new; 29 | has Int $.execution_count = 1; 30 | has $.sandbox; 31 | has $.handler = Jupyter::Kernel::Handler.new; 32 | 33 | method resources { 34 | return %?RESOURCES; 35 | } 36 | 37 | method run($spec-file!) { 38 | info 'starting jupyter kernel'; 39 | 40 | my $spec = from-json($spec-file.IO.slurp); 41 | my $url = "$spec://$spec"; 42 | my $key = $spec or die "no key"; 43 | my Jupyter::Kernel::History $history; 44 | 45 | debug "read $spec-file"; 46 | debug "listening on $url"; 47 | 48 | sub svc($name, $type) { 49 | Jupyter::Kernel::Service.new( :$name, :socket-type($type), 50 | :port($spec{"{ $name }_port"}), :$key, :$url).setup; 51 | } 52 | 53 | my $ctl = svc('control', ZMQ_ROUTER); 54 | my $shell = svc('shell', ZMQ_ROUTER); 55 | my $iopub = svc('iopub', ZMQ_PUB); 56 | my $hb = svc('hb', ZMQ_REP); 57 | my $iopub_supplier = Supplier.new; 58 | my $iopub_promise = Promise.new; 59 | my $sigint = Promise.new; 60 | 61 | start react whenever signal(SIGINT) { 62 | info "Got sigint"; 63 | $iopub_supplier.emit: ('stream', { :text("Trapped interrupt. Please restart the kernel to abort execution.\n"), :name }); 64 | $sigint.keep; 65 | } 66 | 67 | method ciao (Bool $restart=False) { 68 | if $restart { 69 | info "Restarting\n"; 70 | $iopub_supplier.emit: ('stream', { :text( "The kernel is dead, long live the kernel!\n" ), :name }); 71 | 72 | # Reset context 73 | $!handler = Jupyter::Kernel::Handler.new; 74 | $!sandbox = Jupyter::Kernel::Sandbox.new(:$.handler, :$iopub_supplier); 75 | $!execution_count = 1; 76 | 77 | $ctl.send: 'shutdown_reply', { :$restart } 78 | self.register-ciao; 79 | $iopub_supplier.emit: ('stream', { :text( "Raku kernel restarted\n" ), :name }); 80 | } else { 81 | info "Exiting\n"; 82 | $iopub_supplier.emit: ('stream', { :text( "Exiting Raku kernel (you may close client)\n" ), :name }); 83 | $ctl.send: 'shutdown_reply', { :$restart } 84 | exit; 85 | } 86 | } 87 | 88 | method register-ciao { 89 | my $*CIAO = sub ($restart=0) { self.ciao(so $restart); } 90 | my $eval-code = q:to/DONE/; 91 | my &quit := my &QUIT := my &exit := my &EXIT := $*CIAO; 92 | 42; 93 | DONE 94 | my $result = $.sandbox.eval($eval-code); 95 | $iopub_supplier.emit: ('execute_input', { :$eval-code, :execution_count(0), :metadata(Hash.new()) }); 96 | } 97 | 98 | start { 99 | $hb.start-heartbeat; 100 | } 101 | 102 | # Control 103 | start loop { 104 | my $msg = try $ctl.read-message; 105 | error "error reading data: $!" if $!; 106 | debug "ctl got a message: { $msg
// $msg.raku }"; 107 | given $msg
{ 108 | when 'shutdown_request' { 109 | self.ciao($msg); 110 | } 111 | } 112 | } 113 | 114 | # Iostream 115 | $iopub_supplier.Supply.tap( -> ($tag, %dic) { 116 | $iopub.send: $tag, %dic; 117 | try $iopub_promise.break if $tag eq 'status' 118 | and %dic{'execution_state'} and %dic{'execution_state'} eq 'idle'; 119 | }); 120 | 121 | # Shell 122 | my $promise = start { 123 | $!sandbox = Jupyter::Kernel::Sandbox.new(:$.handler, :$iopub_supplier); 124 | self.register-ciao; 125 | loop { 126 | try { 127 | my $msg = $shell.read-message; 128 | $iopub.parent = $msg; 129 | $shell.parent = $msg; 130 | debug "shell got a message: { $msg
}"; 131 | given $msg
{ 132 | when 'kernel_info_request' { 133 | $shell.send: 'kernel_info_reply', $.kernel-info; 134 | $iopub_supplier.emit: ('status', { :execution_state }); 135 | } 136 | when 'execute_request' { 137 | $iopub_supplier.emit: ('status', { :execution_state }); 138 | my $code = ~ $msg.subst(:g, / \\ $$/, ''); 139 | my $silent = $msg; 140 | .append($code,:$!execution_count) with $history; 141 | my $status = 'ok'; 142 | my $magic = $.magics.find-magic($code); 143 | my $result; 144 | $result = .preprocess($code) with $magic; 145 | my $out-mime-type = .?stdout.?mime-type with $magic; 146 | without $result { 147 | my $p = start $.sandbox.eval($code, :store($!execution_count), :$out-mime-type); 148 | my $r = await Promise.anyof($p, $sigint); 149 | if $sigint { 150 | $result = Jupyter::Kernel::Response::Abort.new; 151 | $sigint = Promise.new; 152 | } else { 153 | $result = $p.result; 154 | } 155 | } 156 | if $magic { 157 | with $magic.postprocess(:$code,:$result) -> $new-result { 158 | $result = $new-result; 159 | } 160 | } 161 | my %extra; 162 | $status = 'error' with $result.exception; 163 | $iopub_supplier.emit: ('execute_input', { :$code, :$!execution_count, :metadata(Hash.new()) }) unless $silent; 164 | unless $silent || $result.output-raw === Nil { 165 | $iopub_supplier.emit: ('execute_result', 166 | { :$!execution_count, 167 | :data( $result.output-mime-type => $result.output ), 168 | :metadata(Hash.new()); 169 | }); 170 | } 171 | # Lock / Send / Unlock 172 | $iopub_promise = Promise.new; 173 | $iopub_supplier.emit: ('status', { :execution_state, }); 174 | # Wait for the iopub to be delivered before reply 175 | my $content = { :$status, |%extra, :$!execution_count, 176 | payload => [], user_expressions => {} } 177 | # Wait iopub 178 | await Promise.anyof($iopub_promise, Promise.in(0.2)); 179 | $shell.send: 'execute_reply', 180 | $content, 181 | :metadata({ 182 | "dependencies_met" => True, 183 | "engine" => $.engine-id, 184 | :$status, 185 | "started" => ~DateTime.new(now), 186 | }) unless $silent; 187 | $!execution_count++; 188 | } 189 | when 'is_complete_request' { 190 | my $code = ~ $msg; 191 | my $status = 'complete'; 192 | if $code.ends-with('\\') { 193 | $status = 'incomplete'; 194 | } 195 | # invalid? 196 | debug "sending is_complete_reply: $status"; 197 | $shell.send: 'is_complete_reply', { :$status } 198 | } 199 | when 'complete_request' { 200 | my $code = ~$msg; 201 | my Int:D $cursor_pos = $msg; 202 | my (Int:D $cursor_start, Int:D $cursor_end, $completions) 203 | = $.sandbox.completions($code,$cursor_pos); 204 | if $completions { 205 | $shell.send: 'complete_reply', 206 | { matches => $completions, 207 | :$cursor_end, 208 | :$cursor_start, 209 | metadata => {}, 210 | status => 'ok' 211 | } 212 | } else { 213 | $shell.send: 'complete_reply', 214 | { :matches([]), :cursor_end($cursor_pos), :0cursor_start, metadata => {}, :status } 215 | } 216 | } 217 | when 'shutdown_request' { 218 | self.ciao($msg); 219 | } 220 | when 'history_request' { 221 | $history = Jupyter::Kernel::History.new.init; 222 | $shell.send: 'history_reply', { :history($history.read) }; 223 | } 224 | when 'comm_open' { 225 | my ($comm_id,$data,$name) = $msg; 226 | with $.handler.comms.add-comm(:id($comm_id), :$data, :$name) { 227 | start react whenever .out -> $data { 228 | debug "sending a message from $name"; 229 | $iopub.send: 'comm_msg', { :$comm_id, :$data } 230 | } 231 | } else { 232 | $iopub.send( 'comm_close', {} ); 233 | } 234 | } 235 | when 'comm_msg' { 236 | my ($comm_id, $data) = $msg; 237 | debug "comm_msg for $comm_id"; 238 | $.handler.comms.send-to-comm(:id($comm_id), :$data); 239 | } 240 | when 'comm_info_request' { 241 | debug "comm_info_request " ~ $msg.raku; 242 | my $name = $msg; 243 | $shell.send: 'comm_info_reply', $.handler.comms.comm-ids; 244 | } 245 | default { 246 | warning "unimplemented message type: $_"; 247 | } 248 | } 249 | CATCH { 250 | error "shell: $_"; 251 | error "trace: { .backtrace.list.map: ~* }"; 252 | } 253 | }}} 254 | await $promise; 255 | } 256 | -------------------------------------------------------------------------------- /lib/Jupyter/Kernel/Comm.rakumod: -------------------------------------------------------------------------------- 1 | unit class Jupyter::Kernel::Comm; 2 | use Log::Async; 3 | 4 | has $.id; 5 | has $.data; 6 | has $.name; 7 | has Channel $.out .= new; 8 | has Channel $.in .= new; 9 | has &.cb is required; 10 | 11 | method run($data) { 12 | my %args; 13 | given &.cb.signature.params { 14 | %args = $.out if .grep: *.name eq '$out'; 15 | %args = $.in if .grep: *.name eq '$in'; 16 | %args = $data if .grep: *.name eq '$data'; 17 | } 18 | &.cb()(|%args); 19 | } 20 | -------------------------------------------------------------------------------- /lib/Jupyter/Kernel/Comms.rakumod: -------------------------------------------------------------------------------- 1 | unit class Jupyter::Kernel::Comms; 2 | use Jupyter::Kernel::Comm; 3 | use Log::Async; 4 | 5 | our %COMM-CALLBACKS; # keyed on global names 6 | has %.comms; # keyed on id 7 | has %.running; 8 | 9 | method add-comm-callback($name,&callback) { 10 | %COMM-CALLBACKS{ $name } = &callback; 11 | } 12 | 13 | method add-comm(Str:D :$id, :$name, :$data) { 14 | %COMM-CALLBACKS{ $name }:exists or return; 15 | my &cb = %COMM-CALLBACKS{ $name }; 16 | my $new = Jupyter::Kernel::Comm.new(:$id,:$data,:$name,:&cb); 17 | %.running{ $id } = start $new.run($data); 18 | %.comms{ $id } = $new; 19 | return $new; 20 | } 21 | 22 | method comm-names { 23 | %COMM-CALLBACKS.keys; 24 | } 25 | 26 | method comm-ids { 27 | Hash.new( %.comms.map: -> ( :$key, :$value ) { $key => $value.name } ) 28 | } 29 | 30 | method send-to-comm(:$id,:$data) { 31 | debug "sending $data to $id"; 32 | without %.comms{ $id } { 33 | warning "could not find comm for $id, dropping message"; 34 | return; 35 | } 36 | %.comms{ $id }.in.send: $data; 37 | } 38 | -------------------------------------------------------------------------------- /lib/Jupyter/Kernel/Handler.rakumod: -------------------------------------------------------------------------------- 1 | #= Lexical variable container for completion 2 | 3 | unit class Jupyter::Kernel::Handler; 4 | use Jupyter::Kernel::Comms; 5 | use Log::Async; 6 | 7 | has SetHash $.lexicals is rw = SetHash.new; 8 | has $.comms handles 9 | = Jupyter::Kernel::Comms.new; 10 | has Array $.imports is rw; 11 | has Array $.keywords is rw; 12 | my Mu $lang = $?LANG; 13 | 14 | method set-lang(Mu $arg-lang) { $lang := $arg-lang; } 15 | 16 | method update-compiler(Mu $compiler) { 17 | $compiler.parsegrammar($lang.slang_grammar('MAIN')); 18 | $compiler.parseactions($lang.slang_actions('MAIN')); 19 | } 20 | 21 | method register-comm($name, &callback --> Nil) { 22 | $.comms.add-comm-callback($name,&callback); 23 | } 24 | 25 | method add-lexicals(@list) { 26 | $!lexicals{ |@list }»++; 27 | } 28 | 29 | #| Returns: list of loaded module strings 30 | # https://docs.raku.org/language/5to6-perlvar#%INC 31 | method loaded(:$sandbox) { 32 | return ($*REPO.repo-chain 33 | ==> map( *.loaded.map(*.Str.split(' ')) ) 34 | ==> grep( * ne '' ) 35 | ==> flat).Array; 36 | } 37 | 38 | #| Returns: list of all usable modules 39 | method imports { 40 | if $!imports { return $!imports; } 41 | 42 | # Get usaable module list 43 | my @module = ($*REPO.repo-chain 44 | ==> grep(* ~~ CompUnit::Repository::Installable) 45 | ==> map(*.installed) 46 | ==> map( *.map(*.meta.keys ) ) 47 | ==> flat); 48 | # Add pragmas 49 | @module.push(|self.pragmas); 50 | 51 | # Uniq 52 | $!imports = (@module.unique).Array; 53 | return $!imports; 54 | } 55 | 56 | #| List of existing pragmas 57 | # From: https://github.com/rakudo/rakudo/blob/master/src/Perl6/World.nqp 58 | method pragmas { 59 | return ('C++', 'MONKEY', 'MONKEY-BARS', 'MONKEY-BRAINS', 'MONKEY-BUSINESS', 'MONKEY-GUTS', 'MONKEY-SEE-NO-EVAL', 'MONKEY-SHINE', 'MONKEY-TRAP', 'MONKEY-TYPING', 'MONKEY-WRENCH', 'Perl5', 'attributes', 'dynamic-scope', 'fatal', 'internals', 'invocant', 'isms', 'lib', 'nqp', 'parameters', 'precompilation', 'soft', 'strict', 'trace', 'variables', 'worries'); 60 | } 61 | 62 | # List bare raku keywords 63 | # From: https://github.com/Raku/vim-raku/blob/master/syntax/raku.vim 64 | method keywords { 65 | if $!keywords { return $!keywords; } 66 | $!keywords = []; 67 | for ( 68 | # Include 69 | , 70 | # Conditional 71 | , 72 | # VarStorage 73 | , 74 | # Repeat 75 | , 77 | # FlowControl 78 | , 81 | # ClosureTrait 82 | , 85 | # Exception 86 | , 87 | # Pragma 88 | , 91 | # Operator 92 |
, 95 | # Native Type 96 | , 103 | # Types 104 | , 117 | # Predeclare 118 | , 119 | # Declare 120 | , 121 | # Type constraint 122 | , 123 | # Property 124 | # XXX: when prefixed by 'is' 125 | , 129 | ) -> @word { 130 | $!keywords.push(|@word); 131 | } 132 | $!keywords = ($!keywords.sort.squish).Array; 133 | } 134 | -------------------------------------------------------------------------------- /lib/Jupyter/Kernel/History.rakumod: -------------------------------------------------------------------------------- 1 | unit class Jupyter::Kernel::History; 2 | use JSON::Tiny; 3 | use Log::Async; 4 | use Jupyter::Kernel::Paths; 5 | 6 | has IO::Path $.history-file = history-file; 7 | has Int $.session-count = 1; 8 | has Int $.execution-count = 0; 9 | has $!h_history; 10 | 11 | method init { 12 | info "opening history file $.history-file"; 13 | if self.read -> $lines { 14 | $!session-count = $lines[*-1][0] + 1; 15 | } 16 | $!h_history = try $.history-file.open(:a, :!out-buffer); 17 | warning "$!" if $!; 18 | self; 19 | } 20 | 21 | method read { 22 | return [] unless $.history-file.e; 23 | from-json('[' ~ $.history-file.lines.join(',') ~ ']'); 24 | } 25 | 26 | method append($code, Int :$execution_count!) { 27 | return without $!h_history; 28 | if $execution_count != $!execution-count + 1 { 29 | warning "history count is wrong ($execution_count vs $!execution-count)" 30 | } 31 | $!execution-count = $execution_count; 32 | my $str = to-json([$.session-count, $.execution-count, $code]); 33 | start $!h_history.print( "$str\n" ) 34 | } 35 | -------------------------------------------------------------------------------- /lib/Jupyter/Kernel/Magics.rakumod: -------------------------------------------------------------------------------- 1 | unit class Jupyter::Kernel::Magics; 2 | use Jupyter::Kernel::Response; 3 | 4 | my class Result does Jupyter::Kernel::Response { 5 | has $.output is default(Nil); 6 | method output-raw { $.output } 7 | has $.output-mime-type is rw; 8 | has $.stdout; 9 | has $.stdout-mime-type; 10 | has $.stderr; 11 | has $.exception; 12 | has $.incomplete; 13 | method Bool { 14 | True; 15 | } 16 | } 17 | 18 | #| Container of always magics registered 19 | my class Always { 20 | has @.prepend is rw; 21 | has @.append is rw; 22 | } 23 | 24 | #| Globals 25 | my $always = Always.new; 26 | 27 | class Magic::Filter { 28 | method transform($str) { 29 | # no transformation by default 30 | $str; 31 | } 32 | method mime-type { 33 | 'text/plain' 34 | } 35 | } 36 | class Magic::Filter::HTML is Magic::Filter { 37 | has $.mime-type = 'text/html'; 38 | } 39 | class Magic::Filter::Markdown is Magic::Filter { 40 | has $.mime-type = 'text/markdown'; 41 | } 42 | class Magic::Filter::Javascript is Magic::Filter { 43 | has $.mime-type = 'application/javascript'; 44 | } 45 | class Magic::Filter::Latex is Magic::Filter { 46 | has $.mime-type = 'text/latex'; 47 | has Str $.enclosure; 48 | method transform($str) { 49 | if $.enclosure { 50 | return 51 | '\begin{' ~ $.enclosure ~ "}\n" 52 | ~ $str ~ "\n" ~ 53 | '\end{' ~ $.enclosure ~ "}\n"; 54 | } 55 | return $str; 56 | } 57 | } 58 | 59 | class Magic { 60 | method preprocess($code! is rw) { Nil } 61 | method postprocess(:$result! ) { $result } 62 | } 63 | 64 | my class Magic::JS is Magic { 65 | method preprocess($code!) { 66 | return Result.new: 67 | output => $code, 68 | output-mime-type => 'application/javascript'; 69 | } 70 | } 71 | 72 | my class Magic::Bash is Magic { 73 | method preprocess($code!) { 74 | my $cmd = (shell $code, :out, :err); 75 | 76 | return Result.new: 77 | output => $cmd.out.slurp(:close), 78 | output-mime-type => 'text/plain', 79 | stdout => $cmd.err.slurp(:close), 80 | stdout-mime-type => 'text/plain', 81 | ; 82 | } 83 | } 84 | 85 | my class Magic::Run is Magic { 86 | has Str:D $.file is required; 87 | method preprocess($code! is rw) { 88 | $.file or return Result.new: 89 | stdout => "Missing filename to run.", 90 | stdout-mime-type => 'text/plain'; 91 | $.file.IO.e or 92 | return Result.new: 93 | stdout => "Could not find file: {$.file}", 94 | stdout-mime-type => 'text/plain'; 95 | given $code { 96 | $_ = $.file.IO.slurp 97 | ~ ( "\n" x so $_ ) 98 | ~ ( $_ // '') 99 | } 100 | return; 101 | } 102 | } 103 | class Magic::Filters is Magic { 104 | # Attributes match magic-params in grammar. 105 | has Magic::Filter $.out; 106 | has Magic::Filter $.stdout; 107 | method postprocess(:$result) { 108 | my $out = $.out; 109 | my $stdout = $.stdout; 110 | return $result but role { 111 | method stdout-mime-type { $stdout.mime-type } 112 | method output-mime-type { $out.mime-type } 113 | method output { $out.transform(callsame) } 114 | method stdout { $stdout.transform(callsame) } 115 | } 116 | } 117 | } 118 | 119 | class Magic::Always is Magic { 120 | has Str:D $.subcommand = ''; 121 | has Str:D $.rest = ''; 122 | 123 | method preprocess($code! is rw) { 124 | my $output = ''; 125 | given $.subcommand { 126 | when 'prepend' { $always.prepend.push($.rest.trim); } 127 | when 'append' { $always.append.push($.rest.trim); } 128 | when 'clear' { 129 | $always = Always.new; 130 | $output = 'Always actions cleared'; 131 | } 132 | when 'show' { 133 | for $always.^attributes -> $attr { 134 | $output ~= $attr.name.substr(2)~" = "~$attr.get_value($always).join('; ')~"\n"; 135 | } 136 | } 137 | } 138 | return Result.new: 139 | output => $output, 140 | output-mime-type => 'text/plain'; 141 | } 142 | } 143 | 144 | class Magic::AlwaysWorker is Magic { 145 | #= Applyer for always magics on each line 146 | method unmagicify($code! is rw) { 147 | my $magic-action = Jupyter::Kernel::Magics.new.parse-magic($code); 148 | return $magic-action.preprocess($code) if $magic-action; 149 | return Nil; 150 | } 151 | 152 | method preprocess($code! is rw) { 153 | my $pre = ''; my $post = ''; 154 | for $always.prepend -> $magic-code { 155 | my $container = $magic-code; 156 | self.unmagicify($container); 157 | $pre ~= $container ~ ";\n"; 158 | } 159 | for $always.append -> $magic-code { 160 | my $container = $magic-code; 161 | self.unmagicify($container); 162 | $post ~= ";\n" ~ $container; 163 | } 164 | $code = $pre ~ $code ~ $post; 165 | return Nil; 166 | } 167 | } 168 | 169 | grammar Magic::Grammar { 170 | rule TOP { } 171 | rule magic { 172 | [ '%%' | '#%' ] 173 | [ || || || ] 174 | } 175 | token simple { 176 | $=[ 'javascript' | 'bash' ] 177 | } 178 | token args { 179 | $='run' $=.* 180 | } 181 | rule filter { 182 | [ 183 | | $= ['>' $=]? 184 | | '>' $= 185 | ] 186 | } 187 | token always { 188 | $='always' <.ws> $=[ '' | 'prepend' | 'append' | 'show' | 'clear' ] $=.* 189 | } 190 | token mime { 191 | | 192 | | 193 | | 194 | | 195 | } 196 | token html { 197 | 'html' 198 | } 199 | token markdown { 200 | 'markdown' || 'md' 201 | } 202 | token javascript { 203 | 'javascript' || 'js' 204 | } 205 | token latex { 206 | 'latex' [ '(' $=[ \w | '*' ]+ ')' ]? 207 | } 208 | } 209 | 210 | class Magic::Actions { 211 | method TOP($/) { $/.make: $.made } 212 | method magic($/) { 213 | $/.make: $.made // $.made // $.made // $.made; 214 | } 215 | method simple($/) { 216 | given "$" { 217 | when 'javascript' { 218 | $/.make: Magic::JS.new; 219 | } 220 | when 'bash' { 221 | $/.make: Magic::Bash.new; 222 | } 223 | } 224 | } 225 | method args($/) { 226 | given ("$") { 227 | when 'run' { 228 | $/.make: Magic::Run.new(file => trim ~$); 229 | } 230 | } 231 | } 232 | method always($/) { 233 | my $subcommand = ~$ || 'prepend'; 234 | my $rest = $ ?? ~$ !! ''; 235 | $/.make: Magic::Always.new( 236 | subcommand => $subcommand, 237 | rest => $rest); 238 | } 239 | method filter($/) { 240 | my %args = 241 | |($ ?? |(out => $.made) !! Empty), 242 | |($ ?? |(stdout => $.made) !! Empty); 243 | $/.make: Magic::Filters.new: |%args; 244 | } 245 | method mime($/) { 246 | $/.make: $.made // $.made // $.made // $.made; 247 | } 248 | method html($/) { 249 | $/.make: Magic::Filter::HTML.new; 250 | } 251 | method markdown($/) { 252 | $/.make: Magic::Filter::Markdown.new; 253 | } 254 | method javascript($/) { 255 | $/.make: Magic::Filter::Javascript.new; 256 | } 257 | method latex($/) { 258 | my %args = :enclosure(''); 259 | %args = ~$_ with $; 260 | $/.make: Magic::Filter::Latex.new(|%args); 261 | } 262 | } 263 | 264 | method parse-magic($code is rw) { 265 | my $magic-line = $code.lines[0] or return Nil; 266 | $magic-line ~~ /^ [ '#%' | '%%' ] / or return Nil; 267 | my $actions = Magic::Actions.new; 268 | my $match = Magic::Grammar.new.parse($magic-line,:$actions) or return Nil; 269 | # Parse full cell if always 270 | if $match { 271 | $match = Magic::Grammar.new.parse($code,:$actions); 272 | $code = ''; 273 | # Parse only first line otherwise 274 | } else { 275 | $code .= subst( $magic-line, ''); 276 | $code .= subst( /\n/, ''); 277 | } 278 | return $match.made; 279 | } 280 | 281 | method find-magic($code is rw) { 282 | # Parse 283 | my $magic-action = self.parse-magic($code); 284 | # If normal line and there is always magics -> activate them 285 | if !$magic-action && ($always.prepend || $always.append) { 286 | return Magic::AlwaysWorker.new; 287 | } 288 | return $magic-action; 289 | } -------------------------------------------------------------------------------- /lib/Jupyter/Kernel/Paths.rakumod: -------------------------------------------------------------------------------- 1 | unit module Jupyter::Kernel::Paths; 2 | # see https://github.com/jupyter/jupyter_core/blob/master/jupyter_core/paths.py 3 | 4 | sub data-dir is export { 5 | # Same as qqx{ jupyter --data-dir } 6 | if %*ENV { 7 | return %*ENV; 8 | } 9 | 10 | do given ($*DISTRO) { 11 | when .is-win { 12 | '%APPDATA%'.IO.child('jupyter') 13 | } 14 | when .name ~~ /macos/ { 15 | %*ENV.IO.child('Library').child('Jupyter') 16 | } 17 | default { 18 | %*ENV.IO.child('.local').child('share').child('jupyter') 19 | } 20 | } 21 | } 22 | 23 | 24 | sub raku-dir is export { 25 | data-dir.child('kernels').child('raku'); 26 | } 27 | 28 | 29 | sub runtime-dir is export { 30 | if %*ENV { 31 | return %*ENV; 32 | } 33 | data-dir.child('runtime'); 34 | } 35 | 36 | 37 | sub history-file is export { 38 | raku-dir.child('history.json'); 39 | } 40 | -------------------------------------------------------------------------------- /lib/Jupyter/Kernel/Response.rakumod: -------------------------------------------------------------------------------- 1 | role Jupyter::Kernel::Response { 2 | method output { ... } 3 | method output-mime-type { ... } 4 | method exception { ... } 5 | method incomplete { ... } 6 | method output-raw { ... } 7 | } 8 | 9 | class Jupyter::Kernel::Response::Abort does Jupyter::Kernel::Response { 10 | method output { "[got sigint on thread {$*THREAD.id}]" } 11 | method output-mime-type { 'text/plain' } 12 | method exception { True } 13 | method incomplete { False } 14 | method output-raw { 'aborted' } 15 | } 16 | -------------------------------------------------------------------------------- /lib/Jupyter/Kernel/Sandbox.rakumod: -------------------------------------------------------------------------------- 1 | #!raku 2 | 3 | use Log::Async; 4 | use Jupyter::Kernel::Sandbox::Autocomplete; 5 | use Jupyter::Kernel::Response; 6 | use Jupyter::Kernel::Handler; 7 | use nqp; 8 | 9 | %*ENV = 'none'; 10 | %*ENV = 0; 11 | 12 | state $iopub_supplier; 13 | 14 | 15 | sub mime-type($str) { 16 | return do given $str { 17 | when /:i ^ ' $text ), 50 | :metadata(Hash.new()); 51 | }); 52 | } 53 | return True but role { method __hide { True } } 54 | } 55 | method say(*@args) { 56 | self.print(@args.map: * ~ "\n"); 57 | } 58 | method flush { } 59 | method stream_name { ... } 60 | } 61 | 62 | class Out is Std { method stream_name { 'stdout' } } 63 | class Err is Std { method stream_name { 'stderr' } } 64 | 65 | class Jupyter::Kernel::Sandbox is export { 66 | has $.save_ctx; 67 | has $.compiler; 68 | has $.repl; 69 | has Jupyter::Kernel::Sandbox::Autocomplete $.completer; 70 | has $.handler; 71 | 72 | method TWEAK (:$!handler, :$iopub_supplier) { 73 | $!handler = Jupyter::Kernel::Handler.new unless $.handler; 74 | $OUTERS::iopub_supplier = $iopub_supplier; 75 | $!compiler := nqp::getcomp("Raku") || nqp::getcomp('perl6'); 76 | $*IN.^find_method('t').wrap( { True } ); 77 | $!repl = REPL.new($!compiler, {}); 78 | $!completer = Jupyter::Kernel::Sandbox::Autocomplete.new(:$.handler); 79 | self.eval(q:to/INIT/); 80 | my $Out = []; 81 | my $In = []; 82 | sub Out { $Out }; 83 | sub In { $In }; 84 | my \_ = do { 85 | state $last; 86 | Proxy.new( FETCH => method () { $last }, 87 | STORE => method ($x) { $last = $x } ); 88 | } 89 | INIT 90 | } 91 | 92 | method eval(Str $code, Bool :$no-persist, Int :$store, :$out-mime-type) { 93 | my $*CTXSAVE = $!repl; 94 | my $*MAIN_CTX; 95 | # without setting $PROCESS:: variants, output from Test.pm6 96 | # is not visible in the notebook. 97 | $PROCESS::OUT = $*OUT = Out.new(mime-type => $out-mime-type); 98 | $PROCESS::ERR = $*ERR = Err.new; 99 | my $exception; 100 | my $eval-code = $code; 101 | my $*JUPYTER = $.handler; 102 | if $store { 103 | $eval-code = qq:to/DONE/ 104 | my \\_$store = \$( 105 | $code 106 | ); 107 | \$*JUPYTER.set-lang( \$?LANG ); 108 | \$*JUPYTER.add-lexicals( MY::.keys ); 109 | \$Out[$store] := _$store; 110 | \$In[$store] := q⁅⁅⁅⁅⁅⁅$code⁆⁆⁆⁆⁆⁆; 111 | _ = _$store; 112 | DONE 113 | } 114 | if $no-persist { 115 | # use a temporary package 116 | $eval-code = qq:to/DONE/; 117 | my \$out is default(Nil); 118 | package JupTemp \{ 119 | \$out = $( $code ) 120 | \} 121 | for (JupTemp::).keys \{ 122 | (JupTemp::)\{\$_\}:delete; 123 | \} 124 | \$out; 125 | DONE 126 | } 127 | 128 | my $output is default(Nil); 129 | my $gist; 130 | try { 131 | $output = $!repl.repl-eval( 132 | $eval-code, 133 | $exception, 134 | :outer_ctx($!save_ctx), 135 | :interactive(1) 136 | ); 137 | $gist = $output.gist; 138 | $!handler.update-compiler($!compiler); 139 | CATCH { 140 | default { 141 | $exception = $_; 142 | } 143 | } 144 | } 145 | given $output { 146 | $_ = Nil if .?__hide; 147 | $_ = Nil if try { $_ ~~ List and .elems and .[*-1].?__hide } 148 | $_ = Nil if $_ === Any; 149 | } 150 | 151 | if $*MAIN_CTX and !$no-persist { 152 | $!save_ctx := $*MAIN_CTX; 153 | } 154 | 155 | with $exception { 156 | $output = ~$_; 157 | $gist = $output; 158 | } 159 | my $incomplete = so $!repl.input-incomplete($output); 160 | my $result = Result.new: 161 | :output($gist), 162 | :output-raw($output), 163 | :$exception, 164 | :$incomplete; 165 | 166 | $result; 167 | } 168 | 169 | method completions($str, $cursor-pos = $str.chars ) { 170 | try { 171 | return self.completer.complete($str,$cursor-pos,self); 172 | CATCH { 173 | error "Error completing <$str> at $cursor-pos: " ~ .message; 174 | return $cursor-pos,$cursor-pos,(); 175 | } 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /lib/Jupyter/Kernel/Sandbox/Autocomplete.rakumod: -------------------------------------------------------------------------------- 1 | #= Autocompletion for the sandbox. 2 | unit class Jupyter::Kernel::Sandbox::Autocomplete; 3 | use Log::Async; 4 | use Jupyter::Kernel::Handler; 5 | 6 | has $.handler; 7 | 8 | method BUILD (:$!handler){ 9 | $!handler = Jupyter::Kernel::Handler.new unless $.handler; 10 | }; 11 | 12 | constant set-operators = << 13 | & ^ | ∈ ∉ ∋ ∌ ∖ ∩ ∪ ⊂ ⊃ ⊄ ⊅ ⊆ ⊇ ⊈ ⊍ ⊎ ⊖ ≼ ≽ 14 | (&) (+) (-) (.) (<) (>) (^) (|) 15 | (<=) (>=) (>+) (<+) 16 | (cont) (elem) >>; 17 | constant equality-operators = << ≠ ≅ == != <=> =:= === =~= >>; 18 | constant less-than-operators = << < ≤ <= >>; 19 | constant greater-than-operators = << > ≥ >= >>; 20 | constant superscripts = <⁰ ¹ ² ³ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹ ⁱ ⁺ ⁻ ⁼ ⁽ ⁾ ⁿ>; 21 | constant atomic-operators = <⚛= ⚛ ++⚛ ⚛++ --⚛ ⚛-- ⚛+= ⚛-= ⚛−=>; 22 | constant magic-words = ['javascript', 'html', 'latex', 'bash', 'run', 23 | 'always', 'always prepend', 'always append', 'always show', 'always clear']; 24 | constant mop = ; 25 | 26 | method !find-methods(:$sandbox, Bool :$all, :$var) { 27 | my $eval-str = $var ~ '.^methods(' ~ (':all' x $all) ~ ').map({.name}).join(" ")'; 28 | my $res = $sandbox.eval($eval-str, :no-persist ); 29 | unless $res and !$res.exception and !$res.incomplete { 30 | debug 'autocomplete produced an error'; 31 | return (); 32 | } 33 | return $res.output-raw.split(' ').unique.Array.append(mop); 34 | } 35 | 36 | my @CANDIDATES; 37 | my $loading; 38 | unless @CANDIDATES { 39 | $loading = start @CANDIDATES = (1 .. 0x1ffff).map({.chr}) 40 | .grep({ not .uniprop.starts-with('C') 41 | and not .uniprop.starts-with('Z') 42 | and not .uniprop.starts-with('M') }); 43 | } 44 | 45 | method !unisearch($word) { 46 | state %cache; 47 | await $loading; 48 | %cache{$word} //= do { 49 | my $alt; 50 | $alt = $word.subst('-',' ', :global) if $word.contains('-'); 51 | my @chars = @CANDIDATES 52 | .hyper 53 | .grep({ 54 | my $u = .uniname.fc; 55 | $u.contains($word) 56 | or ($alt and $u.contains(' ') and $u.subst('-',' ', :g).contains($alt)) 57 | }).head(30); 58 | @chars; 59 | } 60 | my $chars = %cache{$word}; 61 | %cache{$word} 62 | } 63 | 64 | my sub find-dynamics($str) { 65 | # Is there a way to get these programmatically? 66 | # Otherwise: 67 | # find src -type f | \ 68 | # xargs perl -ln -e "/REGISTER-DYNAMIC: '(.*)'.*\$/ and print \$1 =~ s/'.*$//r" 69 | my @dyns = ; 72 | return @dyns.grep: { .fc.starts-with($str.fc) } 73 | } 74 | 75 | #| Returns: i_start_repl_pos, i_end_repl_pos, a_possible_repl_strings 76 | method complete($str,$cursor-pos=$str.chars,$sandbox = Nil) { 77 | my regex identifier { [ \w | '-' | '_' | '::' ]+ } 78 | my regex sigil { <[&$@%]> | '$*' } 79 | my regex method-call { } 80 | my regex how-call { '^' ? } 81 | my regex invocant { 82 | | '"' <-["]>+ '"' 83 | | [ \S+ ] 84 | } 85 | my regex uniname { [ \w | '-' ]+ } 86 | my regex import { [ use | need | require ] } 87 | my regex pragma-no { 'no' } 88 | my regex modul { [ \w | '-' | '_' | ':' ]+ } 89 | my regex magic-pre { ['#%' | '%%'] <.ws>} 90 | 91 | my $p = $cursor-pos; 92 | given $str.substr(0,$p) { 93 | when / [\s|^] '(' $/ { return $p-1, $p, set-operators; } 94 | when / [\s|^] '='? '=' $/ { return $p-1, $p, equality-operators } 95 | when / [\s|^] '<' $/ { return $p-1, $p, less-than-operators } 96 | when / [\s|^] '>' $/ { return $p-1, $p, greater-than-operators } 97 | when / [\s|^] '*' $/ { return $p-1, $p, << * × >> } 98 | when / [\s|^] '/' $/ { return $p-1, $p, << / ÷ >> } 99 | when / 'atomic' $/ { return $p - 'atomic'.chars, $p, atomic-operators; } 100 | when / '**' $/ { return $p-2, $p, superscripts } 101 | when / <[⁰¹²³⁴⁵⁶⁷⁸⁹ⁱ⁺⁻⁼⁽⁾ⁿ]> $/ { return ($p-"$/".chars, $p, [ "$/" X~ superscripts ]); } 102 | when /^ '%%' \s+ 'run' \s+ (.*) $/ { 103 | info "Completion: dir"; 104 | my $path = (~$/[0] || './').IO; 105 | my $ds = $path.SPEC.dir-sep; 106 | my $dir = $path.ends-with($ds) ?? $path !! $path.dirname.IO; 107 | my $found = ($dir.dir ==> map {~$^a ~ ($ds if $^a.d)} ==> grep(/^ $path/) ==> sort); 108 | return $p - $path.chars, $p, $found; 109 | } 110 | when /^ / 111 | and (my $rest = $_.subst(/^ /, ''); True) 112 | and (my $match = $/) 113 | and (so (my @words = magic-words.grep(*.starts-with($rest)))) { 114 | info "Completion: magic"; 115 | my $found = @words.map($match ~ *).sort.Array; 116 | return 0, $p, $found; 117 | } 118 | when / '.' ? $/ { 119 | info "Completion: method call"; 120 | my @methods = self!find-methods(:$sandbox, var => "$", all => so $); 121 | my $meth = ~( $ // "" ); 122 | my $len = $p - $meth.chars; 123 | return $len, $p, @methods.grep( { / ^ "$meth" / } ).sort.unique; 124 | } 125 | when / '.' $/ { 126 | info "Completion: method how call"; 127 | my @methods = Metamodel::ClassHOW.^methods(:all).map({"^" ~ .name}); 128 | my $meth = ~( $ // "" ); 129 | return $p-$.chars, $p, @methods.grep({ / ^ "{$}" / }).sort.unique; 130 | } 131 | when / \s* ? $/ { 132 | info "Completion: module import"; 133 | my $modul = $ // ''; 134 | my $found = ( grep { / $modul / }, $.handler.imports.Seq).sort.Array; 135 | return $p - $modul.chars, $p, $found; 136 | } 137 | when / \s* ? $/ { 138 | info "Completion: pragma no"; 139 | my $modul = $ // ''; 140 | my $found = ( grep { / $modul / }, $.handler.pragmas.Seq).sort.Array; 141 | return $p - $modul.chars, $p, $found; 142 | } 143 | when / ':' $/ { 144 | info "Completion: named parameter"; 145 | my $word = ~ $; 146 | if self!unisearch( $word.fc ) -> @chars { 147 | my $pos = $str.chars - $word.chars - 1; 148 | return ( $pos, $pos + $word.chars + 1, @chars ); 149 | } 150 | } 151 | when / $/ { 152 | info "Completion: lexical variable"; 153 | my $identifier = "$/"; 154 | my $possible = $.handler.lexicals; 155 | my $found = ( |($possible.keys), |( CORE::.keys ) ).grep( { /^ "$identifier" / } ).sort; 156 | return $p - $identifier.chars, $p, $found; 157 | } 158 | when / '$*' ? $/ { 159 | info "Completion: dynamic scalar"; 160 | my $identifier = $ // ''; 161 | my $found = map { '$*' ~ $_ }, find-dynamics($identifier); 162 | return $p - $identifier.chars - 2, $p, $found; 163 | } 164 | when /<|w> > $/ { 165 | info "Completion: bare word"; 166 | my $last = ~ $; 167 | my %barewords = 168 | pi => 'π', 'Inf' => '∞', tau => 'τ', 169 | e => '𝑒', set => '∅', o=> '∘', 170 | self => 'self', now => 'now', time => 'time', rand => 'rand'; 171 | my @bare; 172 | @bare.push($_) with %barewords{ $last }; 173 | my $possible = $.handler.lexicals; 174 | my $found = ( |($possible.keys), 175 | |( CORE::.keys ), 176 | |($.handler.keywords), 177 | # disabled: not working on raku >= 2022.12 178 | # |($.handler.loaded), 179 | ).grep( { /^ '&'? "$last" / } 180 | ).sort.map: { .subst('&', '') } 181 | @bare.append: @$found if $found; 182 | return $p - $last.chars, $p, @bare; 183 | } 184 | default { 185 | info "Completion: default"; 186 | my $found = ( |( CORE::.keys ) ).sort; 187 | return $p, $p, $found; 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /lib/Jupyter/Kernel/Service.rakumod: -------------------------------------------------------------------------------- 1 | unit class Jupyter::Kernel::Service; 2 | 3 | use Net::ZMQ4; 4 | use Net::ZMQ4::Constants; 5 | use Net::ZMQ4::Proxy; 6 | use UUID; 7 | use Log::Async; 8 | use Digest::HMAC; 9 | use Digest::SHA256::Native; 10 | use JSON::Tiny; 11 | use NativeCall; 12 | 13 | my $session = ~UUID.new: :version(4); 14 | 15 | has $.url is required; 16 | has $.name is required; 17 | has $.socket-type is required; 18 | has $.port is required; 19 | has $.key is required; 20 | has $.ctx; 21 | has $.socket; 22 | has $!session = $session; 23 | has $.is-client = False; # for testing 24 | has $.parent is rw; 25 | 26 | method setup { 27 | debug "setting up $.name on $.port"; 28 | $!ctx = Net::ZMQ4::Context.new; 29 | $!socket = Net::ZMQ4::Socket.new( $!ctx, $.socket-type ); 30 | if $!is-client { 31 | $!socket.connect("$!url:$!port") 32 | } else { 33 | $!socket.bind("$!url:$!port") 34 | } 35 | self 36 | } 37 | 38 | method !hmac(@m) { 39 | hmac-hex $!key, @m[0] ~ @m[1] ~ @m[2] ~ @m[3], &sha256; 40 | } 41 | 42 | method read-message($flags=0) { 43 | my @identities; 44 | my @message; 45 | my $separated = False; 46 | my $separator = buf8.new: "".encode; 47 | while $!socket.receive($flags).data -> $data { 48 | if !$separated { 49 | if $data eqv $separator { 50 | $separated = True; 51 | next; 52 | } 53 | @identities.push: $data; 54 | next; 55 | } 56 | @message.push: $data; 57 | last if not $!socket.getopt: ZMQ_RCVMORE; 58 | } 59 | error "No message received" unless @message; 60 | die "HMAC verification failed." if self!hmac(@message[1..4]) ne @message.shift.decode; 61 | my %msg; 62 | %msg{$_} = from-json @message.shift.decode for
; 63 | %msg = @identities; 64 | %msg = @message; 65 | $!parent = %msg; 66 | %msg; 67 | } 68 | 69 | method send($type, $message, :$metadata = {} ) { 70 | info "{ $.name }: sending { $type } message"; 71 | my $identities = $!parent; 72 | 73 | my $header = { 74 | date => ~DateTime.new(now), 75 | msg_id => ~UUID.new(:version(4)), 76 | msg_type => $type, 77 | session => $!session, 78 | username => 'kernel', 79 | version => '5.3', 80 | }; 81 | 82 | my @parts = ( 83 | $header, 84 | $!parent
// {}, 85 | $metadata, 86 | $message 87 | ).map({ 88 | to-json($_) 89 | }); 90 | my $hmac = self!hmac(@parts); 91 | 92 | my $sep = ""; 93 | $!socket.sendmore: 94 | |( @$identities.grep(*.defined) ), 95 | $sep, $hmac, |@parts; 96 | } 97 | 98 | method start-heartbeat { 99 | loop { 100 | try Net::ZMQ4::Proxy::zmq_proxy($!socket, $!socket, Nil); 101 | error ~$! if $!; 102 | debug 'heartbeat'; 103 | sleep 1; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /resources/kernel.json: -------------------------------------------------------------------------------- 1 | { 2 | "display_name": "Raku", 3 | "language": "raku", 4 | "argv": [ 5 | "jupyter-kernel.raku", 6 | "{connection_file}" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /resources/logo-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bduggan/raku-jupyter-kernel/4d9417fb6443a7bcfe7a171076e90e83149b02fa/resources/logo-32x32.png -------------------------------------------------------------------------------- /resources/logo-64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bduggan/raku-jupyter-kernel/4d9417fb6443a7bcfe7a171076e90e83149b02fa/resources/logo-64x64.png -------------------------------------------------------------------------------- /t/01-basic.rakutest: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env raku 2 | 3 | use lib 'lib'; 4 | 5 | use Test; 6 | 7 | plan 1; 8 | 9 | use-ok 'Jupyter::Kernel'; 10 | -------------------------------------------------------------------------------- /t/02-sandbox.rakutest: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env raku 2 | 3 | use lib 'lib'; 4 | use lib 't/lib'; 5 | 6 | use Test; 7 | use Jupyter::Kernel::Sandbox; 8 | use Log::Async; 9 | 10 | plan 54; 11 | 12 | my $VERBOSE = %*ENV; 13 | my @log; 14 | logger.add-tap: { 15 | @log.push($_); 16 | note "# $_" if $VERBOSE; 17 | }; 18 | 19 | 20 | my $iopub_supplier = Supplier.new; 21 | my $sandbox = Jupyter::Kernel::Sandbox.new(:$iopub_supplier); 22 | my $iopub_channel = $iopub_supplier.Supply.Channel; 23 | ok defined($sandbox), 'make a new sandbox'; 24 | 25 | sub stream { 26 | my (@out, @err, @out-mime); 27 | while my $msg = $iopub_channel.poll { 28 | if $msg[0] eq 'display_data' { 29 | @out.push($msg[1].values[0]); 30 | @out-mime.push($msg[1].keys[0]); 31 | } 32 | next unless $msg[0] eq 'stream'; 33 | if $msg[1] eq 'stdout' { 34 | @out.push($msg[1]); 35 | } elsif $msg[1] eq 'stderr' { 36 | @err.push($msg[1]); 37 | } 38 | } 39 | return {:@out, :@err, :@out-mime} 40 | } 41 | 42 | sub qa (Str $code, Bool :$no-persist, Int :$store){ 43 | return {res => $sandbox.eval($code, :$no-persist, :$store), std=>stream}; 44 | } 45 | 46 | my ($a, $res); 47 | 48 | is qa(q["hello"]).output, "hello", 'simple eval'; 49 | is qa("12").output, "12", 'stringify'; 50 | is qa('my $x = 12; 123;').output, '123', 'made a var'; 51 | is qa('$x + 10;').output, "22", 'saved state'; 52 | 53 | $a = qa('say "hello"'); 54 | ok !$a.output-raw, 'no output, sent to stdout'; 55 | is $a[0], "hello\n", 'right value on stdout'; 56 | 57 | ok !$a.incomplete, 'not incomplete'; 58 | is $a.output-mime-type, 'text/plain', 'right mime-type on stdout'; 59 | 60 | $a = qa('note "goodbye"'); 61 | ok !$a.output-raw, 'no output, sent to stderr'; 62 | is $a[0], "goodbye\n", 'correct value on stderr'; 63 | 64 | $res = qa('floobody doop'); 65 | ok $res.exception, 'caught exception'; 66 | like ~$res.exception, /'Undeclared routines'/, 'error message'; 67 | like ~$res.exception, /'doop'/, 'error message somewhat useful'; 68 | is $res.exception.^name, 'X::Undeclared::Symbols', 'exception type'; 69 | 70 | $res = qa('for (1..10) {'); 71 | ok $res.incomplete, 'identified incomplete input'; 72 | 73 | $res = qa('my @ints = <1 2 3>;'); 74 | ok !$res.exception, 'made an array'; 75 | $res = qa('@ints[1]'); 76 | is $res.output, "2", 'array'; 77 | 78 | $res = qa('my @bound := <1 2 3>;'); 79 | ok !$res.exception, 'bound an array'; 80 | $res = qa('@bound[1]'); 81 | is $res.output, "2", 'bound array'; 82 | is $res.output-mime-type, 'text/plain', 'mime type'; 83 | 84 | $a = qa('say ""'); 85 | is $a[0], "\n", 'generated svg on stdout'; 86 | is $a[0], "image/svg+xml", 'svg mime type on stdout'; 87 | 88 | $res = qa('"";'); 89 | is $res.output, '', 'generated svg output'; 90 | is $res.output-mime-type, 'image/svg+xml', 'svg output mime type'; 91 | 92 | $res = qa('Int'); 93 | is $res.output.perl, '"(Int)"', 'Any works'; 94 | 95 | $res = qa('die'); 96 | is $res.output, 'Died', 'Die trapped'; 97 | 98 | $res = qa('sub foo { ... }; foo;'); 99 | is $res.output, 'Stub code executed', 'trapped sub call that died'; 100 | 101 | is qa('123', :store(1)).output, "123", 'store eval in Out[1]'; 102 | is qa('Out[1]', :store(2)).output, "123", 'get Out[1]'; 103 | is qa('_2', :store(3)).output, "123", 'get _2'; 104 | is qa('_', :store(4)).output, "123", 'get _'; 105 | 106 | is qa('my $y = 3; my $x = 99; $x + 1').output, "100", 'two statements'; 107 | is qa('my $yy = 3; my $xx = 99; $xx + 1', :store(5)).output, "100", 'two statements'; 108 | is qa('_').output, "100", 'saved the right thing'; 109 | is qa('_ + 1').output, "101", 'used _ in an expression'; 110 | 111 | is qa('class Foo { method bar { ... } }', :no-persist).output, '(Foo)', 'class decl'; 112 | is qa('class Foo { method bar { ... } }', :no-persist).output, '(Foo)', 'class decl'; 113 | is qa('class Foo { method bar { ... } }').output, '(Foo)', 'no-persist a class'; 114 | 115 | $res = qa(q['hi'; # foo], :store(6)); 116 | ok $res, "Produced output when ending with a comment"; 117 | is $res.output, "hi", "got right output when ending with a comment"; 118 | 119 | ok 1, 'still here'; 120 | 121 | ok qa('Any').output-raw === Nil, "Any becomes Nil"; 122 | ok qa('Nil').output-raw === Nil, "Nil becomes Nil"; 123 | ok qa('say 12').output-raw === Nil, "say becomes Nil"; 124 | is qa('my Int $x;').output, "(Int)", "Output from a type (undefined)"; 125 | 126 | $a = qa('.say for 1..10', :store(7)); 127 | ok $a.output-raw === Nil, "No output for multiple say's"; 128 | is $a.join, (1..10).join("\n") ~ "\n", "right stdout for multiple say's"; 129 | 130 | $res = qa('1/0'); 131 | ok $res, 'survived exception'; 132 | like $res.output, /:i 'attempt to divide' .* 'by zero' /, 'trapped 1/0 error'; 133 | 134 | # Operator overload persistence 135 | is qa('sub infix:($a, $b){return ">" ~ $a ~ ":" ~ $b ~ "<"}; "to" test-op "ti";', :store(1)).output, '>to:ti<', 'Operator: test-op'; 136 | is qa('12 test-op 13 test-op 14').output, '>>12:13<:14<', 'Operator: test-op'; 137 | 138 | # Slang persistence 139 | is qa('use Slang::TestWhatIs; what-is-test', :store(1)).output, 'test is nice', 'Slang: use'; 140 | is qa('what-is-test', :store(1)).output, 'test is nice', 'Slang: persistance'; 141 | -------------------------------------------------------------------------------- /t/03-service.rakutest: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env raku 2 | 3 | use lib 'lib'; 4 | 5 | use Net::ZMQ4::Constants; 6 | use Jupyter::Kernel::Service; 7 | use Log::Async; 8 | 9 | use Test; 10 | 11 | plan 5; 12 | 13 | my $VERBOSE = %*ENV; 14 | 15 | my @log; 16 | logger.add-tap: { 17 | @log.push($_); 18 | note "# $_" if $VERBOSE; 19 | }; 20 | 21 | my $s = Jupyter::Kernel::Service.new: 22 | :url('tcp://127.0.0.1'), 23 | :name, 24 | :port<9099>, 25 | :key, 26 | :socket-type(ZMQ_ROUTER) 27 | ; 28 | 29 | ok $s.setup, 'setup worked'; 30 | 31 | my $d = Jupyter::Kernel::Service.new: 32 | :url('tcp://127.0.0.1'), 33 | :name, 34 | :port<9099>, 35 | :key, 36 | :socket-type(ZMQ_DEALER) 37 | :is-client, 38 | ; 39 | 40 | ok $d.setup, 'setup worked for dealer'; 41 | 42 | my $msg = Channel.new; 43 | 44 | start loop { 45 | $msg.send: try $s.read-message; 46 | note $! if $!; 47 | } 48 | 49 | $d.send('other', 'xyzzy'); 50 | $d.send('other', 'hello'); 51 | is $msg.receive, "xyzzy", 'router-dealer message sent and received'; 52 | is $msg.receive, "hello", 'router-dealer message sent and received'; 53 | 54 | $d.send('other','π'); 55 | is $msg.receive, "π", 'router-dealer message sent and received'; 56 | 57 | # vim: ft=raku 58 | -------------------------------------------------------------------------------- /t/04-completions.rakutest: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env raku 2 | use lib 'lib'; 3 | use Test; 4 | use Jupyter::Kernel::Sandbox; 5 | use Jupyter::Kernel::Handler; 6 | use Log::Async; 7 | 8 | logger.add-tap( -> $msg { diag $msg } ); 9 | 10 | if %*ENV { 11 | plan 27; 12 | } else { 13 | plan :skip-all; 14 | } 15 | 16 | unless %*ENV { 17 | diag "You may need to set MVM_SPESH_DISABLE=1 for these to pass"; 18 | } 19 | 20 | my $iopub_channel = Channel.new; 21 | my $r = Jupyter::Kernel::Sandbox.new(:$iopub_channel); 22 | 23 | my ($pos, $end, $completions) = $r.completions('sa', 2); 24 | is-deeply $completions, [ ], 'completions for "sa"'; 25 | is $pos, 0, 'offset'; 26 | 27 | ($pos, $end, $completions) = $r.completions(' sa'); 28 | is-deeply $completions, [ ], 'completions for "sa"'; 29 | is $pos, 1, 'offset'; 30 | 31 | my $res = $r.eval(q[my $x = 'hello'; $x]); 32 | is $res.output, 'hello', 'output'; 33 | ($pos,$end,$completions) = $r.completions('$x.pe'); 34 | is-deeply $completions, , 'autocomplete for a string'; 35 | 36 | $res = $r.eval(q|class Foo { method barglefloober { ... } }; my $y = Foo.new;|); 37 | is $res.output, 'Foo.new', 'declared class'; 38 | ($pos,$end,$completions) = $r.completions('$y.barglefl'); 39 | is-deeply $completions, $( 'barglefloober', ) , 'Declared a class and completion was a method'; 40 | 41 | $res = $r.eval('my $abc = 12;'); 42 | ($pos,$end,$completions) = $r.completions('$abc.is-prim'); 43 | is-deeply $completions, $('is-prime', ), 'method with a -'; 44 | 45 | ($pos,$end,$completions) = $r.completions('if 15.is-prim'); 46 | is-deeply $completions, $( 'is-prime', ), 'is-prime for a number'; 47 | 48 | ($pos,$end,$completions) = $r.completions('if "hello world".sa'); 49 | is-deeply $completions, $("samecase", "samemark", "samespace", "say"), 'string methods'; 50 | 51 | ($pos,$end,$completions) = $r.completions('if "hello world".WHA'); 52 | is-deeply $completions, $("WHAT", ), 'WHAT method'; 53 | 54 | ($pos,$end,$completions) = $r.completions('if "hello world".^add_metho'); 55 | is-deeply $completions, $("^add_method", ), 'ClassHOW method'; 56 | 57 | $res = $r.eval('my $ghostbusters = 99', :store); 58 | is $res.output, 99, 'made a var'; 59 | ($pos,$end,$completions) = $r.completions('say $ghost'); 60 | is-deeply $completions, $( '$ghostbusters', ), 'completed a variable'; 61 | is $pos, 4, 'position is correct'; 62 | 63 | # Generate an error but still get something sane 64 | $r.eval('class Flannel { }; my $d = Flannel.new;', :11store); 65 | my $from-here = q[$d.c].chars; 66 | my $str = q[$d.c and say 'ok']; 67 | ($pos,$end,$completions) = $r.completions($str,$from-here); 68 | is $completions, , 'Mu class'; 69 | 70 | $res = $r.eval(q|sub flubber { 99 };|, :12store ); 71 | ($pos,$end,$completions) = $r.completions('flubb'); 72 | is-deeply $completions, [ , ], 'found a subroutine declaration'; 73 | 74 | { 75 | my $str = '(1..100).'; 76 | $res = $r.eval(q|(1..100).|, :13store ); 77 | ($pos,$end,$completions) = $r.completions($str); 78 | ok 'max' ∈ $completions, 'complete an expression'; 79 | } 80 | { 81 | my ($pos,$end,$completions) = $r.completions('wp'); 82 | ok $completions.defined, 'did not get undef'; 83 | } 84 | { 85 | my ($pos,$end,$completions) = $r.completions('2'); 86 | ok $completions.defined, 'did not get undef'; 87 | } 88 | 89 | # Module 90 | ($pos,$end,$completions) = $r.completions('use Log:'); 91 | ok 'Log::Async' ∈ $completions, 'module Log::Async'; 92 | 93 | # Pragma 94 | ($pos,$end,$completions) = $r.completions('no st'); 95 | ok 'strict' ∈ $completions, 'pragma no strict'; 96 | 97 | ($pos,$end,$completions) = $r.completions('use li'); 98 | ok 'lib' ∈ $completions, 'pragma use lib'; 99 | 100 | # Magic 101 | ($pos,$end,$completions) = $r.completions('%% al'); 102 | ok '%% always prepend' ∈ $completions, 'magic always prepend'; 103 | 104 | ($pos,$end,$completions) = $r.completions('#% r'); 105 | ok '#% run' ∈ $completions, 'magic run spaces'; 106 | 107 | ($pos,$end,$completions) = $r.completions('%%javas'); 108 | ok '%%javascript' ∈ $completions, 'magic javascript no spaces'; 109 | 110 | 111 | done-testing; 112 | 113 | # vim: syn=raku 114 | -------------------------------------------------------------------------------- /t/05-autocomplete.rakutest: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env raku 2 | use lib 'lib'; 3 | use Test; 4 | use Log::Async; 5 | use Jupyter::Kernel::Sandbox::Autocomplete; 6 | 7 | logger.add-tap( -> $msg { diag $msg } ); 8 | 9 | my $c = Jupyter::Kernel::Sandbox::Autocomplete.new; 10 | 11 | ok $c.complete('prin')[2].grep(/print/), 'print'; 12 | ok $c.complete('(')[2] ⊃ <∩ ∪ ⊂ ⊃>, 'found set ops'; 13 | ok $c.complete('( ',1)[2] ⊃ <∩ ∪ ⊂ ⊃>, 'found set ops in the middle'; 14 | ok $c.complete('(1..10) (')[0,1] eqv (8,9), 'right position'; 15 | ok $c.complete('<1 2 3> (')[2] ⊃ <∩ ∪ ⊂ ⊃>, 'found set ops'; 16 | ok $c.complete('**')[2] ⊃ <³ ⁴ ⁵ ⁶ ⁷>, 'got some exponents'; 17 | is $c.complete('*'), (0, 1, << * × >>), 'multiplication'; 18 | is $c.complete('<'), (0, 1, << < ≤ <= >>), 'less than'; 19 | 20 | { 21 | my ($pos,$offset,$atomic) = $c.complete('$a atomic','$a atomic'.chars,Nil); 22 | ok @$atomic > 0, 'got some atomic ops'; 23 | ok <⚛= ⚛> ⊂ $atomic, 'atomic ops contains ⚛= ⚛'; 24 | } 25 | 26 | { 27 | my ($pos,$end,$beer) = $c.complete('some :beer','some :beer'.chars,Nil); 28 | ok @$beer > 0, 'got some beer'; 29 | ok <🍺 🍻> ⊆ $beer, 'beer contains🍺 and 🍻 '; 30 | is $pos, 5, 'got right start'; 31 | is $end, '10', 'got right end'; 32 | } 33 | { 34 | my ($pos,$end,$beer) = $c.complete('some :b','some :b'.chars,Nil); 35 | ok $beer.elems ≤ 30, '30 or fewer results'; 36 | } 37 | { 38 | my ($pos,$end,$got) = $c.complete(':less-than',':less-than'.chars,Nil); 39 | ok '≤' ∈ @$got, 'found less-than'; 40 | } 41 | { 42 | my ($pos,$end,$got) = $c.complete(':less-than-or-equal',':less-than'.chars,Nil); 43 | ok '≤' ∈ @$got, 'found ≤'; 44 | } 45 | { 46 | my ($pos,$end,$got) = $c.complete('say pi','say pi'.chars,Nil); 47 | ok 'π' ∈ @$got, 'found π'; 48 | } 49 | { 50 | my ($pos,$end,$got) = $c.complete('pi','pi'.chars,Nil); 51 | ok 'π' ∈ @$got, 'found π'; 52 | } 53 | { 54 | my ($pos,$end,$got) = $c.complete('tau','tau'.chars,Nil); 55 | ok 'τ' ∈ @$got, 'found τ'; 56 | } 57 | { 58 | my ($pos,$end,$got) = $c.complete('1..Inf','1..Inf'.chars,Nil); 59 | ok '∞' ∈ @$got, 'found ∞'; 60 | } 61 | 62 | done-testing; 63 | # vim: syn=raku 64 | -------------------------------------------------------------------------------- /t/06-magic.rakutest: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env raku 2 | use lib 'lib'; 3 | use Test; 4 | use Log::Async; 5 | use Jupyter::Kernel::Magics; 6 | 7 | logger.add-tap( -> $msg { diag $msg } ); 8 | 9 | plan 56; 10 | 11 | my $m = Jupyter::Kernel::Magics.new; 12 | class MockResult { 13 | has $.output; 14 | has $.output-mime-type; 15 | has $.stdout; 16 | has $.stdout-mime-type; 17 | has $.stderr; 18 | has $.exception; 19 | has $.incomplete; 20 | } 21 | 22 | { 23 | my $code = q:to/DONE/; 24 | no magic 25 | DONE 26 | ok !$m.find-magic($code), 'no magic'; 27 | } 28 | 29 | { 30 | my $code = q:to/DONE/; 31 | %% javascript 32 | hello world 33 | DONE 34 | 35 | ok my $magic = $m.find-magic($code), 'preprocess recognized %% javascript'; 36 | is $code, "hello world\n", 'find-magic removed magic line'; 37 | my $r = $magic.preprocess($code); 38 | is $r.output-mime-type, 'application/javascript', 'js magic set the mime type'; 39 | } 40 | { 41 | my $code = q:to/DONE/; 42 | %% latex 43 | "hello latex"; 44 | DONE 45 | 46 | ok my $magic = $m.find-magic($code), 'find-magic recognized %% latex'; 47 | is $code, qq["hello latex";\n], 'find-magic removed magic line'; 48 | ok !$magic.preprocess($code), "preprocess did not return a result"; 49 | is $code, qq["hello latex";\n], 'preprocess did not change code'; 50 | my $result = $magic.postprocess(:result(MockResult.new)); 51 | is $result.output-mime-type, 'text/latex', 'latex magic set the output mime type'; 52 | } 53 | { 54 | my $code = q:to/DONE/; 55 | %% latex(equation*) 56 | "hello latex"; 57 | DONE 58 | 59 | ok my $magic = $m.find-magic($code), 'find-magic recognized %% latex(equation*)'; 60 | is $code, qq["hello latex";\n], 'find-magic removed magic line'; 61 | ok !$magic.preprocess($code), "preprocess did not return a result"; 62 | is $code, qq["hello latex";\n], 'preprocess did not change code'; 63 | my $result = $magic.postprocess(:result(MockResult.new(:output))); 64 | is $result.output-mime-type, 'text/latex', 'latex magic set the output mime type'; 65 | is $result.output, q:to/LATEX/, 'latex magic enclosed the output'; 66 | \begin{equation*} 67 | foo 68 | \end{equation*} 69 | LATEX 70 | } 71 | { 72 | my $code = q:to/DONE/; 73 | %% html 74 | say "this is stdout"; 75 | 'output'; 76 | DONE 77 | 78 | ok my $magic = $m.find-magic($code), 'find-magic recognized %% html'; 79 | is $code, q:to/DONE/, 'find-magic removed magic line'; 80 | say "this is stdout"; 81 | 'output'; 82 | DONE 83 | ok !$magic.preprocess($code), "preprocess did not return a result"; 84 | my $result = $magic.postprocess(:result(MockResult.new( 85 | :output('output'), 86 | :stdout("this is stdout\n"), 87 | ))); 88 | is $result.output-mime-type, 'text/html', 'html magic set the output mime type'; 89 | is $result.output, 'output', 'html unchanged'; 90 | is $result.stdout-mime-type, 'text/plain', 'stdout is text/plain'; 91 | is $result.stdout, "this is stdout\n", 'stdout worked'; 92 | } 93 | { 94 | my $code = q:to/DONE/; 95 | %% > html 96 | say 'this is stdout'; 97 | 'output'; 98 | DONE 99 | 100 | ok my $magic = $m.find-magic($code), 'find-magic recognized %% html'; 101 | is $code, q:to/DONE/, 'find-magic removed magic line'; 102 | say 'this is stdout'; 103 | 'output'; 104 | DONE 105 | ok !$magic.preprocess($code), "preprocess did not return a result"; 106 | my $result = $magic.postprocess(:result(MockResult.new( 107 | :output('output'), 108 | :stdout("this is stdout\n"), 109 | ))); 110 | is $result.output-mime-type, 'text/plain', 'html magic did not set output mime type'; 111 | is $result.output, 'output', 'html unchanged'; 112 | is $result.stdout-mime-type, 'text/html', 'stdout is text/html'; 113 | is $result.stdout, "this is stdout\n", 'stdout worked'; 114 | } 115 | { 116 | my $code = '#% html > html'; 117 | ok my $magic = $m.find-magic($code), 'found magic for mime'; 118 | is $magic.^name, 'Jupyter::Kernel::Magics::Magic::Filters', 'right magic'; 119 | ok !$magic.preprocess($code), 'preprocess does not return true'; 120 | my $result = MockResult.new(:output('going out'),:stdout('going to stdout')); 121 | ok $result = $magic.postprocess(:$result), 'postprocess returned a result'; 122 | is $result.output-mime-type, 'text/html', 'set output mime type'; 123 | is $result.stdout-mime-type, 'text/html', 'set stdout mime type'; 124 | } 125 | { 126 | my $code = '#% html > latex'; 127 | given $m.find-magic($code) 128 | .postprocess(:result( MockResult.new(:output,:stdout) )) { 129 | is .output-mime-type, 'text/html', 'generated html output'; 130 | is .stdout-mime-type, 'text/latex', 'but latex on stdout'; 131 | } 132 | } 133 | { 134 | my $code = '#% latex > html'; 135 | given $m.find-magic($code) 136 | .postprocess(:result( MockResult.new(:output,:stdout) )) { 137 | is .output-mime-type, 'text/latex', 'generated latex output'; 138 | is .stdout-mime-type, 'text/html', 'but html on stdout'; 139 | } 140 | } 141 | { 142 | my $code = '#% latex(equation) > html'; 143 | given $m.find-magic($code) 144 | .postprocess(:result( MockResult.new(:output,:stdout) )) { 145 | is .output-mime-type, 'text/latex', 'generated latex output'; 146 | is .stdout-mime-type, 'text/html', 'but html on stdout'; 147 | } 148 | } 149 | { 150 | my $cell = ( '%% bash', 'echo hello').join("\n"); 151 | my $magic = $m.find-magic($cell); 152 | ok $magic, 'found bash magic'; 153 | given $magic.preprocess("echo hello") { 154 | is .output, "hello\n", 'got output'; 155 | is .output-mime-type, 'text/plain', 'got right mime type'; 156 | } 157 | } 158 | { 159 | my $cmd = 'LANG=C LC_ALL=C ls /no/such/file/i/hope'; 160 | my $cell = ( '%% bash', $cmd).join("\n"); 161 | my $magic = $m.find-magic($cell); 162 | ok $magic, 'found bash magic'; 163 | given $magic.preprocess($cmd) { 164 | is .output, "", 'no output'; 165 | is .output-mime-type, 'text/plain', 'got right mime type'; 166 | like .stdout, /:i 'no such file'/, 'errors on stdout'; 167 | } 168 | } 169 | { 170 | my $code = q:to/DONE/; 171 | my $x = 1; 172 | my $y = 2; 173 | my $z = $x + $y; 174 | 42; 175 | DONE 176 | my $file will leave {.unlink} = $*TMPDIR.child("test.$*PID"); 177 | $file.spurt: $code; 178 | my $cell = "%% run $file"; 179 | my $magic = $m.find-magic($cell); 180 | ok $magic, 'found run magic'; 181 | nok $magic.preprocess($cell), 'no return value from preprocess'; 182 | is $cell, $code, "Cell now has code"; 183 | } 184 | { 185 | my $code = q:to/CODE/; 186 | my $x = 1; 187 | my $y = 2; 188 | my $z = $x + $y; 189 | 42; 190 | CODE 191 | my $more = q:to/MORE/; 192 | my $pdq = 99; 193 | MORE 194 | my $file will leave {.unlink} = $*TMPDIR.child("test.$*PID"); 195 | $file.spurt: $code; 196 | my $cell = qq:to/CELL/; 197 | %% run $file 198 | $more 199 | CELL 200 | my $magic = $m.find-magic($cell); 201 | ok $magic, 'found run magic'; 202 | nok $magic.preprocess($cell), 'no return value from preprocess'; 203 | is $cell, ($code,$more).join("\n") ~ "\n", "Cell now has more code"; 204 | } 205 | 206 | { 207 | my $cell = '%% run nosuchfile.abc'; 208 | my $magic = $m.find-magic($cell); 209 | my $result = $magic.preprocess($cell); 210 | ok $result, 'Handled missing file'; 211 | like $result.stdout, / 'not find' /, 'error message'; 212 | } 213 | 214 | # vim: syn=raku 215 | -------------------------------------------------------------------------------- /t/07-comms.rakutest: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env raku 2 | use lib 'lib'; 3 | use Test; 4 | use Jupyter::Kernel::Comms; 5 | use Log::Async; 6 | logger.add-tap( -> $msg { diag $msg } ); 7 | 8 | plan 11; 9 | 10 | my $m = Jupyter::Kernel::Comms.new; 11 | ok $m, 'made comm manager'; 12 | 13 | $m.add-comm-callback('hello', 14 | -> :$out, :$data { $out.send: "hello, $data" }); 15 | my $c = $m.add-comm(:id<1>, :name, :data); 16 | ok $c, 'made a comm'; 17 | is $c.out.receive, "hello, world", 'received message back from comm'; 18 | 19 | $m.add-comm-callback('goodbye', 20 | -> :$out { $out.send: "bye, world" }); 21 | my $d = $m.add-comm(:id<2>, :name); 22 | ok $d, 'made a comm'; 23 | is $d.out.receive, "bye, world", 'comm with no data'; 24 | 25 | # Replace 26 | $m.add-comm-callback('hello', 27 | -> :$out, :$data { $out.send: "hello, $data" }); 28 | $c = $m.add-comm(:id<3>, :name, :data); 29 | ok $c, 'made a comm'; 30 | is $c.out.receive, "hello, world", 'received message back from comm'; 31 | 32 | is $m.comm-names.sort, .sort, 'List comms'; 33 | 34 | is $m.comm-ids.keys.sort, <1 2 3>.sort, '3 ids'; 35 | 36 | $m.add-comm-callback('howareyou', 37 | -> :$in, :$out, :$data { 38 | start { 39 | my $name = $in.receive; 40 | $out.send: "hello, $name" 41 | } 42 | }); 43 | $c = $m.add-comm(:id<3>, :name); 44 | ok $c, 'made a comm'; 45 | $c.in.send('bob'); 46 | is $c.out.receive, "hello, bob", 'in and out for comm'; 47 | -------------------------------------------------------------------------------- /t/08-paths.rakutest: -------------------------------------------------------------------------------- 1 | use lib 'lib'; 2 | use Jupyter::Kernel::Paths; 3 | use Test; 4 | 5 | plan 1; 6 | 7 | like data-dir, /:i jupyter/; 8 | -------------------------------------------------------------------------------- /t/09-history.rakutest: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env raku 2 | use lib 'lib'; 3 | use Test; 4 | use Log::Async; 5 | use Jupyter::Kernel::History; 6 | 7 | plan 7; 8 | 9 | logger.add-tap( -> $msg { diag $msg } ); 10 | 11 | my $history-file will leave {.unlink} = $*TMPDIR.child('history-test.json'); 12 | my $history = Jupyter::Kernel::History.new(:$history-file); 13 | ok $history, 'made a history object'; 14 | ok $history.init, 'init'; 15 | is-deeply $history.read, [], 'nothing there yet'; 16 | ok (await $history.append("2.say", :1execution_count)), 'append'; 17 | is-deeply $history.read, [ [ 1, 1, '2.say'], ], 'read the history'; 18 | ok (await $history.append("2.is-prime", :2execution_count)), 'append'; 19 | is-deeply $history.read, [ [ 1, 1, '2.say'], [ 1, 2, '2.is-prime'], ], 'read the history'; 20 | -------------------------------------------------------------------------------- /t/20-end-to-end.rakutest: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env raku 2 | 3 | use lib 'lib'; 4 | use lib 't/lib'; 5 | 6 | use JSON::Tiny; 7 | use Net::ZMQ4::Constants; 8 | use Jupyter::Client; 9 | use Jupyter::Kernel; 10 | use Jupyter::Kernel::Paths; 11 | use Jupyter::Kernel::Service; 12 | use Log::Async; 13 | 14 | use Test; 15 | 16 | my $res; 17 | 18 | # Only for dev <- Heavy tests 19 | if %*ENV { 20 | # We must make directory for travis 21 | raku-dir.mkdir; 22 | } else { 23 | plan :skip-all; 24 | } 25 | 26 | # Define logger 27 | my $VERBOSE = %*ENV; 28 | my @log; 29 | logger.add-tap: { 30 | @log.push($_); 31 | note "# $_" if $VERBOSE; 32 | }; 33 | 34 | # Create connection file 35 | my $s_connection = Q[{ 36 | "shell_port": 9099, 37 | "iopub_port": 9100, 38 | "stdin_port": 9101, 39 | "control_port": 9102, 40 | "hb_port": 9103, 41 | "ip": "127.0.0.1", 42 | "key": "abcd", 43 | "transport": "tcp", 44 | "signature_scheme": "hmac-sha256", 45 | "kernel_name": "raku" 46 | }]; 47 | my $spec-file = $*TMPDIR.child("kernel_test.json"); 48 | $spec-file.spurt($s_connection); 49 | my $spec = from-json($s_connection); 50 | 51 | # Launch a new kernel <- run jupyter-kernel.raku 52 | sub spawn-kernel { 53 | my $lib = $?FILE.IO.parent.sibling('lib').Str; 54 | my $script = $?FILE.IO.parent.sibling('bin').child('jupyter-kernel.raku').Str; 55 | return Proc::Async.new("raku", "-I$lib", $script, $spec-file).start; 56 | } 57 | 58 | # Remove all 'GREP-TEST' in history <- run raku 59 | sub clean-history { 60 | my $res = ''; 61 | my rule todel { GREP\-TEST }; 62 | for history-file.lines -> $line { 63 | $res ~= $line ~ "\n" unless $line ~~ / /; 64 | } 65 | history-file.spurt($res); 66 | } 67 | 68 | sub spawn-both { 69 | my $proc1 = spawn-kernel; 70 | my $client1 = Jupyter::Client.new(:$spec); 71 | return ($client1, $proc1); 72 | } 73 | 74 | my ($cl, $ke) = spawn-both; 75 | 76 | # Test types 77 | is $cl.qa('my $a = "GREP-TEST"; 42'), '42', 'Int'; 78 | is $cl.qa(42), '42', 'Int'; 79 | is $cl.qa('my $a = "GREP-TEST"; 1/2'), '0.5', 'Rat'; 80 | is $cl.qa('my $a = "GREP-TEST"; "toto"'), 'toto', 'Str'; 81 | is $cl.qa('my $a = "GREP-TEST"; [1, 2, 3]'), '[1 2 3]', 'Array'; 82 | is $cl.qa('my $a = "GREP-TEST"; { 1 => 2, 3 => 4}'), '{1 => 2, 3 => 4}', 'Hash'; 83 | 84 | # Test order 85 | ## 1 Wait request 86 | $cl.wait-request('my $a = "GREP-TEST"; say "Raku Order" gt "Jedy Order"; 42'); 87 | ## 2 Read stdio, without waiting 88 | my @stdout = $cl.read-stdio(ZMQ_DONTWAIT); 89 | ## 3 Check order: 90 | ### 3.1 busy 91 | my $msg = @stdout.shift; 92 | is $msg{'header'}{'msg_type'}, 'status', 'Order: 1. type = status'; 93 | is $msg{'content'}{'execution_state'}, 'busy', 'Order: 1. content = busy'; 94 | ### 3.2 stream <- True 95 | $msg = @stdout.shift; 96 | is $msg{'header'}{'msg_type'}, 'stream', 'Order: 2. type = stream'; 97 | is $msg{'content'}{'text'}, "True\n", 'Order: 2. content = True\n'; 98 | ### 3.3 code <- repeat input 99 | $msg = @stdout.shift; 100 | is $msg{'header'}{'msg_type'}, 'execute_input', 'Order: 3. type = execute_input'; 101 | is $msg{'content'}{'code'}, 'my $a = "GREP-TEST"; say "Raku Order" gt "Jedy Order"; 42', 'Order: 3. content = Some code'; 102 | ### 3.4 result <- 42 103 | $msg = @stdout.shift; 104 | is $msg{'header'}{'msg_type'}, 'execute_result', 'Order: 4. type = execute_result'; 105 | is $msg{'content'}{'data'}{'text/plain'}, 42, 'Order: 4. content = 42'; 106 | ### 3.5 idle 107 | $msg = @stdout.shift; 108 | is $msg{'header'}{'msg_type'}, 'status', 'Order: 5. type = status'; 109 | is $msg{'content'}{'execution_state'}, 'idle', 'Order: 5. content = idle'; 110 | ### 3.*-1 No more 111 | is @stdout.elems, 0, 'Order: *. No more element in iopub'; 112 | 113 | # Test always: I did it my way 114 | ## Pre: ... Yes, there were times, I'm sure you knew 115 | $cl.qa('my $way = ""; # GREP-TEST'); 116 | is $cl.qa('%% always $way ~= "pre1-"; # GREP-TEST'), '', 'Always: register pre1'; 117 | is $cl.qa('%% always prepend $way ~= "pre2-"; # GREP-TEST'), '', 'Always: register pre2'; 118 | ## Show: ... When I bit off more than I could chew 119 | ok $cl.qa('%% always show me the way # GREP-TEST') ~~ / 'pre1' / , 'Always: show'; 120 | is $cl.qa('$way; # GREP-TEST'), 'pre1-pre2-', 'Always: test1'; 121 | ## Clear: ... But through it all, when there was doubt 122 | $cl.qa('$way = ""; # GREP-TEST'); 123 | $cl.qa('%% always clear my way # GREP-TEST'); 124 | is $cl.qa('$way; # GREP-TEST'), '', 'New var'; 125 | ## Post: ... I ate it up and spit it out 126 | is $cl.qa('%% always append $way ~= "-post1"; # GREP-TEST'), '', 'Always: register post1'; 127 | is $cl.qa('%% always append $way ~= "-post2"; # GREP-TEST'), '', 'Always: register post2'; 128 | is $cl.qa('my $no-warn = $way; # GREP-TEST'), '-post1-post2', 'Always: test post1'; 129 | is $cl.qa('$no-warn = $way; # GREP-TEST'), '-post1-post2-post1-post2', 'Always: test post2'; 130 | $cl.qa('%% always clear my way # GREP-TEST'); 131 | $cl.qa('$way = "";'); 132 | ## Combine: ... I faced it all and I stood tall 133 | { 134 | my $file will leave {.unlink} = $*TMPDIR.child("test.$*PID"); 135 | $file.spurt: 'my $imported = "I traveled each and every highway";'; 136 | # Cannot make a GREP-TEST Here or it is considered as path 137 | is $cl.qa("%% always prepend %% run $file"), '', 'Always: Combining with run'; 138 | is $cl.qa('$imported; # GREP-TEST'), 'I traveled each and every highway', 'Always: Combined with run'; 139 | $cl.qa('%% always clear my way # GREP-TEST'); 140 | $cl.qa('$way = "";'); 141 | } 142 | $cl.qa('%% always clear my way # GREP-TEST'); 143 | $cl.qa('$way = "";'); 144 | ## Multiline: ... And did it my way 145 | is $cl.qa("%% always prepend # GREP-TEST\n\$way ~= 'multi1-'; # GREP-TEST\n \n\$way ~= 'multi2-'; \n\n"), '', 'Always: register multiline'; 146 | is $cl.qa('$way ~= "mid-"; # GREP-TEST'), 'multi1-multi2-mid-', 'Always: multiline 1'; 147 | is $cl.qa('$way ~= "last"; # GREP-TEST'), 'multi1-multi2-mid-multi1-multi2-last', 'Always: multiline 2'; 148 | $cl.qa('%% always clear my way # GREP-TEST'); 149 | 150 | # Test history 151 | $cl.wait-history; 152 | is $cl.qa('my $a = "GREP-TEST";'), 'GREP-TEST', 'History Pre'; 153 | $cl.wait-shutdown; 154 | await $ke; 155 | ($cl, $ke) = spawn-both; 156 | my $hist = $cl.wait-history; 157 | my $last-cmd = $hist[*-1][2]; 158 | is $last-cmd, 'my $a = "GREP-TEST";', 'History Post'; 159 | $cl.wait-shutdown; 160 | await $ke; 161 | 162 | ## Clean history file 163 | clean-history; 164 | 165 | done-testing; 166 | -------------------------------------------------------------------------------- /t/99-meta.rakutest: -------------------------------------------------------------------------------- 1 | use v6; 2 | use lib 'lib'; 3 | use Test; 4 | plan 1; 5 | 6 | constant AUTHOR = ?%*ENV; 7 | 8 | if AUTHOR { 9 | require Test::META <&meta-ok>; 10 | meta-ok; 11 | done-testing; 12 | } 13 | else { 14 | skip-rest "Skipping author test"; 15 | exit; 16 | } 17 | -------------------------------------------------------------------------------- /t/lib/Jupyter/Client.rakumod: -------------------------------------------------------------------------------- 1 | =begin pod 2 | 3 | Small client for test 4 | Very similar to Kernel.pm6 5 | 6 | =end pod 7 | 8 | unit class Jupyter::Client; 9 | 10 | use JSON::Tiny; 11 | use Net::ZMQ4::Constants; 12 | use Jupyter::Kernel::Service; 13 | 14 | has Jupyter::Kernel::Service $.ctl is rw; 15 | has Jupyter::Kernel::Service $.shell is rw; 16 | has Jupyter::Kernel::Service $.iopub is rw; 17 | has Jupyter::Kernel::Service $.hb is rw; 18 | has Hash $.spec; 19 | 20 | submethod BUILD(:$!spec) { 21 | sub clt($name, $type, $is-client=True) { 22 | Jupyter::Kernel::Service.new( 23 | :$name, 24 | :url('tcp://127.0.0.1'), 25 | :socket-type($type), 26 | :port($!spec{"{ $name }_port"}), 27 | :key, 28 | :$is-client, 29 | ).setup; 30 | } 31 | $!ctl = clt('control', ZMQ_DEALER); 32 | $!shell = clt('shell', ZMQ_DEALER); 33 | $!iopub = clt('iopub', ZMQ_SUB); 34 | $!hb = clt('hb', ZMQ_REP); 35 | # Subscribe stdstreams to all messages 36 | $!iopub.socket.setopt(ZMQ_SUBSCRIBE, Blob.new); 37 | return self; 38 | } 39 | 40 | method wait-request (Str(Cool) $request) { 41 | $.shell.send('execute_request', { :code($request) }); 42 | return $.shell.read-message; 43 | } 44 | 45 | method wait-shutdown { 46 | $.ctl.send('shutdown_request', { :restart(False) }); 47 | return $.ctl.read-message; 48 | } 49 | 50 | method wait-history (Str(Cool) $pattern="") { 51 | $.shell.send('history_request', { :$pattern }); 52 | return $.shell.read-message; 53 | } 54 | 55 | method read-stdio ($flags = 0) { 56 | # Call me after a request 57 | my @res; my %msg; 58 | repeat { 59 | %msg = $.iopub.read-message($flags); 60 | my %topush = %msg; 61 | @res.push(%topush); 62 | } until %msg
eq 'status' and %msg eq 'idle'; 63 | return @res; 64 | } 65 | 66 | method wait-result { 67 | # Call me after a request 68 | my @msg = self.read-stdio; 69 | my %result-msg = @msg.grep(*
eq 'execute_result')[0]; 70 | return %result-msg 71 | } 72 | 73 | method qa (Str(Cool) $request){ 74 | # Question / Answer 75 | self.wait-request($request); 76 | return self.wait-result; 77 | } 78 | -------------------------------------------------------------------------------- /t/lib/Slang/TestWhatIs.rakumod: -------------------------------------------------------------------------------- 1 | =begin pod 2 | 3 | use Slang::TestWhatIs; 4 | say what-is-test; # OUTPUT: "test is nice" 5 | 6 | =end pod 7 | 8 | use nqp; 9 | use QAST:from; 10 | 11 | role TestGrammar { 12 | token term:sym { }; 13 | } 14 | 15 | role TestActions { 16 | method term:sym (Mu $/) { 17 | return make QAST::SVal.new: :value('test is nice'); 18 | } 19 | } 20 | 21 | sub EXPORT(|) { 22 | $*LANG.refine_slang('MAIN', TestGrammar, TestActions); 23 | return {}; 24 | } 25 | --------------------------------------------------------------------------------