├── .github └── workflows │ ├── publish.yml │ └── test.yml ├── .gitignore ├── API.md ├── CHANGELOG.md ├── LICENSE ├── README.md ├── basilisp.edn ├── basilisp_kernel ├── __init__.py ├── __main__.py ├── kernel.py └── nrepl_server.lpy ├── bb.edn ├── hatch_build.py ├── notebooks ├── clojure-flow-control.ipynb ├── nb-plot.png ├── pandas-03-select.ipynb ├── pandas-04-plot.ipynb └── python-interop.ipynb ├── pyproject.toml ├── test_basilisp.py └── tests ├── __init__.py └── basilisp_kernel ├── __init__.py ├── integration ├── __init__.py └── notebook_test.lpy └── nrepl_server_test.lpy /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # https://github.com/anze3db/words-tui/blob/main/.github/workflows/publish.yml 2 | name: Publish to PyPI 3 | 4 | on: 5 | release: 6 | types: [published] 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | deploy: 13 | 14 | runs-on: ubuntu-latest 15 | 16 | environment: release 17 | permissions: 18 | id-token: write # IMPORTANT: this permission is mandatory for trusted publishing 19 | contents: write # github release upload 20 | 21 | steps: 22 | - uses: actions/checkout@v4 23 | - name: Set up Python 24 | uses: actions/setup-python@v4 25 | with: 26 | python-version: '3.12' 27 | cache: 'pip' 28 | - name: Install dependencies 29 | run: | 30 | python -m pip install --upgrade pip 31 | pip install hatch 32 | - name: Build package 33 | run: hatch build 34 | - name: Upload release artifacts 35 | run: gh release upload ${{ github.event.release.tag_name }} dist/*.{tar.gz,whl} 36 | env: 37 | GH_TOKEN: ${{ github.token }} 38 | - name: Publish package distributions to PyPI 39 | uses: pypa/gh-action-pypi-publish@release/v1 40 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Basilisp Jupyter Kernel package 2 | 3 | on: 4 | pull_request: 5 | types: [ opened, synchronize, reopened ] 6 | push: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | os: [ubuntu-latest] 16 | python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "pypy-3.10"] 17 | include: 18 | - os: windows-latest 19 | python-version: "3.13" 20 | - os: macos-latest 21 | python-version: "3.13" 22 | steps: 23 | - uses: actions/checkout@v4 24 | - uses: actions/setup-python@v5 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | - name: Install dependencies 28 | run: | 29 | pipx install hatch 30 | hatch env create test 31 | hatch env find 32 | - name: Test 33 | run: | 34 | hatch run test:basilisp test -- -v 35 | - name: Check Kernel 36 | run: | 37 | hatch run test:jupyter kernelspec list | grep -E "jupyter/kernels/basilisp|jupyter\\kernels\\basilisp" 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | MANIFEST 2 | build 3 | dist 4 | _build 5 | *.py[co] 6 | __pycache__ 7 | *.egg-info 8 | *~ 9 | *.bak 10 | .ipynb_checkpoints 11 | .tox 12 | .DS_Store 13 | \#*# 14 | .#* 15 | .coverage 16 | htmlcov 17 | .cache 18 | .idea 19 | data_kernelspec 20 | 21 | .ipynb_checkpoints 22 | /notebooks/new 23 | /notebooks/no2_concentrations.png 24 | 25 | .nrepl-port 26 | .calva/ 27 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | - [`basilisp-kernel.nrepl-server`](#basilisp-kernel.nrepl-server) 3 | - [`server-shut`](#basilisp-kernel.nrepl-server/server-shut) - Convenience function to shutdown the SERVER. 4 | - [`server-start`](#basilisp-kernel.nrepl-server/server-start) - Starts the nrepl-server in async mode according to OPTS, using a asyncio task to schedule any pending client work every 100ms. 5 | 6 | ----- 7 | # basilisp-kernel.nrepl-server 8 | 9 | 10 | 11 | 12 | 13 | 14 | ## `server-shut` 15 | ``` clojure 16 | 17 | (server-shut server) 18 | ``` 19 | Function. 20 | 21 | Convenience function to shutdown the `SERVER`. 22 |

Source

23 | 24 | ## `server-start` 25 | ``` clojure 26 | 27 | (server-start) 28 | (server-start {:keys [host port dir interval-sec], :as opts, :or {port 0, interval-sec 0.1}}) 29 | ``` 30 | Function. 31 | 32 | Starts the nrepl-server in async mode according to `OPTS`, using a 33 | asyncio task to schedule any pending client work every 100ms. 34 | 35 | `OPTS` is a map that can have the following keys. It defaults to {}. 36 | 37 | `:dir` The directory where the `.nrepl-port` file should be created 38 | at. It defaults to the current working directory if not given or 39 | empty. 40 | 41 | `:host` The interface address the server should be bound to. It 42 | defaults to 127.0.0.1 if not given or empty. 43 | 44 | `:port` The port number the server should listen to. It defaults to 45 | 0, which indicates a random available port number. 46 | 47 | It return the `server`, which is a map with the following keys 48 | 49 | `:host` The host interface to which the server is bound. 50 | 51 | `:nrepl-port-file` The path to the `.nrepl-port` file created by the 52 | server. 53 | 54 | `:port` The port on the host interface the server listens for client 55 | connections. 56 | 57 | `:shutdown!` A function to shutdown the server. 58 |

Source

59 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Unreleased 4 | 5 | ## 1.2.0 6 | 7 | - Added nREPL Server support. 8 | - Dropped Python 3.8 support and added 3.13 in CI testing. 9 | - Set minimum Basilisp version to >=0.3.2. 10 | 11 | ## 1.1.0 12 | 13 | - Optimized output to stdout/stderr by removing proxy string intermediary. 14 | - Improved Exception formatting (requires basilisp>=0.1.0b2) 15 | 16 | ## 1.0.0 17 | 18 | - Support Basilisp code execution and code completion. 19 | 20 | 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, Project Jupyter Contributors 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![PyPI](https://img.shields.io/pypi/v/basilisp-kernel.svg?style=flat-square)](https://pypi.org/project/basilisp-kernel/) [![CI](https://github.com/ikappaki/basilisp-kernel/actions/workflows/test.yml/badge.svg)](https://github.com/ikappaki/basilisp-kernel/actions/workflows/test.yml) 2 | 3 | # Basilisp Kernel for Jupyter 4 | 5 | [Basilisp](https://github.com/basilisp-lang/basilisp) is a Python-based Lisp implementation that offers broad compatibility with Clojure. Refer to [documentation](https://basilisp.readthedocs.io/en/latest/index.html) to get started. 6 | 7 | 8 | # Overview 9 | 10 | The Basilisp Kernel enables running Basilisp Clojure code directly within Jupyter notebooks. 11 | 12 | ## Features 13 | 14 | - Full integration with Jupyter Notebook and JupyterLab. 15 | - Enhanced autocompletion features using the `Tab` key. 16 | - Seamless interoperability with Python libraries. 17 | - Interactive development with your preferred editor, powered by the nREPL server running inside the notebook. 18 | 19 | ## Installation 20 | 21 | Ensure you have Jupyter installed. If not, install it using pip: 22 | 23 | ```shell 24 | pip install jupyter 25 | 26 | ``` 27 | 28 | To install the Basilisp Kernel, run: 29 | 30 | ```shell 31 | pip install basilisp-kernel 32 | ``` 33 | 34 | ## Usage 35 | 36 | Start your Jupyter notebook server: 37 | 38 | ```shell 39 | jupyter notebook 40 | ``` 41 | 42 | In the Jupyter interface, select the `Basilisp` Kernel when creating a new notebook. 43 | 44 | ## Examples 45 | 46 | This project includes a series of Jupyter notebooks that demonstrate various features and capabilities. You can find these notebooks in the [notebooks](notebooks) directory of this repository. 47 | 48 | ![notebook plotting example](notebooks/nb-plot.png) 49 | 50 | ## nREPL support 51 | 52 | See [API.md](API.md). 53 | 54 | The Basilisp Kernel includes an nREPL server, allowing remote interaction with notebooks using Clojure-Enabled Editors like Emacs (via [CIDER](https://docs.cider.mx/cider/platforms/basilisp.html)) and VS code (via [Calva](https://calva.io/basilisp/)). For detailed use cases, visit the [Connecting Your Editor to the nREPL Server](https://github.com/ikappaki/basilisp-kernel/wiki/Connecting-Your-Editor-to-the-nREPL-Server) page. 55 | 56 | ### Starting the nREPL Server 57 | 58 | Start the nREPL in your notebook by running: 59 | ```clojure 60 | (require '[basilisp-kernel.nrepl-server :refer [server-start server-shut]]) 61 | (def server (server-start)) 62 | => nREPL server started on port 58966 on host 127.0.0.1 - nrepl://127.0.0.1:58966 63 | => #'user/server 64 | ``` 65 | 66 | For additional configuration options, such as specifying a port with `:port` or directory for the `.nrepl-port` file with `:dir`, consult the [server-start](API.md#basilisp-kernel.nrepl-server/server-start) documentation. 67 | 68 | ### Stopping the nREPL Server 69 | 70 | ```clojure 71 | (server-shut server) 72 | => :shut 73 | ``` 74 | 75 | ## Getting Started with Basilisp Notebooks Development 76 | 77 | Below are various methods to help you start writing Basilisp code that can be loaded in a Basilisp notebook. 78 | 79 | ### 🔋 Batteries Included Project: `basilex-notebook` 80 | 81 | The [Basilisp Example Notebook](https://github.com/ikappaki/basilex-notebook) repository, is an excellent resource for exploring Basilisp notebooks. It includes Jupyter, the Basilisp kernel, a skeleton Basilisp library, and a development notebook to help you get started. 82 | 83 | 1. Install [Poetry](https://python-poetry.org/docs/) for dependency management. 84 | 85 | 2. Clone the repository, install dependencies, activate the environment and start Jupyter: 86 | 87 | ```shell 88 | $ git clone https://github.com/ikappaki/basilex-notebook.git 89 | $ cd basilex-notebook 90 | $ poetry install 91 | $ poetry shell 92 | () $ jupyter notebook 93 | ``` 94 | 95 | 3. Open the `tutorial.ipynb` notebook from the Jupyter interface. Refer to the [basilex-notebook documentation](https://github.com/ikappaki/basilex-notebook) for more details. 96 | 97 | *** 98 | ### 💾 Local Basilisp Files 99 | 100 | You can seamlessly write and use Basilisp `.lpy` files in the same directory as your Notebook. These files can be required in the Notebook just like standard Basilisp namespaces. 101 | 102 | For example, if you create a file named `dev.lpy` with the following content 103 | 104 | `./dev.lpy` 105 | ```clojure 106 | (ns dev) 107 | 108 | (defn hello [] 109 | :hi) 110 | ``` 111 | 112 | You can load and use it in your Notebook as follows 113 | 114 | ```Clojure 115 | [n]: (require 'dev) 116 | [n+1]: (dev/hello) 117 | :hi 118 | ``` 119 | 120 | #### nREPL development 121 | 122 | You can start an nREPL server directly within a Notebook and connect to it using a Clojure-enabled editor. 123 | 124 | In a notebook cell: 125 | 126 | 1. Load the nREPL server namespace: 127 | ```clojure 128 | [n]: (require '[basilisp-kernel.nrepl-server :as nrepl-server]) 129 | ``` 130 | 2. Start the nREPL server at a random port 131 | ```Clojure 132 | [n]: (def server (nrepl-server/server-start)) 133 | nREPL server started on port 59498 on host 127.0.0.1 - nrepl://127.0.0.1:59498 134 | #'user/server 135 | ``` 136 | 137 | To connect your editor to the nREPL server create a `basilisp.edn` file in the same directory as your Notebook. 138 | 139 | Open your Clojure-enabled editor and use its nREPL connection commands. The server generated a `.nrepl-port file` in the directory, which helps the editor locate the port. 140 | 141 | Both [Emacs/CIDER](https://docs.cider.mx/cider/platforms/basilisp.html) and [VSCode/Calva](https://calva.io/basilisp/) offer explicit support for Basilisp. 142 | 143 | #### CIDER (Emacs) 144 | 145 | 1. Run `M-x cider-connect-clj`. 146 | 2. Select `localhost`. 147 | 3. Select the `:` option. 148 | 149 | #### Calva (VSCode) 150 | 1. Press `Ctrl-Alt-P` to open the Command Palette. 151 | 2. Select `Calva: Connect to a Running REPL Server, in your project`>`basilisp`. 152 | 3. The editor will automatically find the port using `.nrepl-port`. 153 | 154 | The Editor should now connect seamlessly to the nREPL server. 155 | 156 | *** 157 | ### 📚 Basilisp Example Library Project: `basilex-basilib` 158 | 159 | The [Basilisp Example Library](https://github.com/ikappaki/basilex-basilib) repository provides a starting point for setting up an external Basilisp library that can be integrated with your notebooks. 160 | 161 | The instructions below assume you are working in a virtual environment where the Basilisp Notebooks will be launched (e.g. the `basiliex-notebook` setup above). This is indicated by the environment name prefix in your command prompt, such as `` in the examples below. 162 | 163 | #### Setup 164 | 165 | Clone the repository and install the `basilib` library as an editable package in in the same virtual environment as your Basilisp notebooks: 166 | 167 | ```shell 168 | () $ git clone https://github.com/ikappaki/basilex-basilib.git 169 | () $ cd basilex-basilib 170 | # install example library in virtual environment as editable package 171 | () $ pip install -e . 172 | ``` 173 | 174 | #### Usage 175 | 176 | Load and interact with the library in your notebooks: 177 | 178 | ```clojure 179 | [n]: (require '[basilib.core :as bc]) 180 | [n+1]: (bc/time-now) 181 | => '2024-11-30 14:53:00' 182 | ``` 183 | 184 | #### nREPL development 185 | 186 | For interactive coding, you can connect your preferred Clojure-Enabled Editor to a running nREPL server in your Basilisp Notebook. 187 | 188 | In a notebook cell: 189 | 190 | 1. Load the nREPL server namespace: 191 | ```clojure 192 | [n]: (require '[basilisp-kernel.nrepl-server :as nrepl-server]) 193 | ``` 194 | 2. Start the nREPL server on a fixed port: 195 | ```Clojure 196 | [n]: (def server (server-start {:port 9998})) 197 | nREPL server started on port 9998 on host 127.0.0.1 - nrepl://127.0.0.1:9998 198 | #'user/server 199 | ``` 200 | 201 | In your code Editor, open the `basilisp.edn` file located in your project root directory (e.g. in `basilex-notebook`, `basilex-basilib` or your own Basilisp Project) to enable Clojure-specific features in your editor. Then, use your editor's commands to connect to the nREPL server. 202 | 203 | Both [Emacs/CIDER](https://docs.cider.mx/cider/platforms/basilisp.html) and [VSCode/Calva](https://calva.io/basilisp/) offer explicit support for Basilisp. 204 | 205 | ###### Connecting via CIDER (Emacs) 206 | 207 | 1. Run `M-x cider-connect-clj` 208 | 2. Select `localhost`. 209 | 3. Enter the port number from the nREPL server output. 210 | 211 | ###### Connecting Calva (VSCode) 212 | 213 | 1. Press `Ctrl-Shift-P` to open the Command Palette. 214 | 2. Select `Calva: Connect to a Running REPL Server, not in your project`>`basilisp`. 215 | 3. Enter the port number from the nREPL server output. 216 | 217 | ## Acknowledgments 218 | 219 | This kernel was developed using the [echo_kernel](https://github.com/jupyter/echo_kernel) as a starting point. 220 | 221 | -------------------------------------------------------------------------------- /basilisp.edn: -------------------------------------------------------------------------------- 1 | (import [IPython.display :as id] 2 | [ipywidgets :as iw]) 3 | 4 | 5 | 6 | (def v ) 7 | 8 | (def ) 9 | 10 | (comment 11 | (import 12 | [matplotlib.pyplot :as plt]) 13 | 14 | (type (iw/Output)) 15 | ;; 16 | ) 17 | 18 | (defn abc [] 19 | (let [v (iw.widgets/IntSlider ** :value 0 :min 0 :max 100 :description "hey there") 20 | output (iw/Output)] 21 | (id/display v output) 22 | )) 23 | 24 | (def xyz (abc)) 25 | 26 | (def code-js " 27 | const notebook = document.querySelector('#notebook-container') || document.querySelector('.jp-NotebookPanel-notebook'); 28 | if (notebook) { 29 | // For both Classic Notebook and JupyterLab 30 | IPython.notebook.insert_cell_below(); 31 | IPython.notebook.select_next(); 32 | IPython.notebook.get_selected_cell().set_text("print('This content was injected!')"); 33 | IPython.notebook.get_selected_cell().execute(); 34 | } 35 | ") 36 | 37 | (def code-js2 38 | "var cell = IPython.notebook.insert_cell_below('code'); 39 | cell.set_text(\"128\"); 40 | cell.execute()" 41 | ) 42 | 43 | (id/display (id/Javascript 44 | code-js2)) -------------------------------------------------------------------------------- /basilisp_kernel/__init__.py: -------------------------------------------------------------------------------- 1 | """The Basilisp Jupyter kernel""" 2 | 3 | __version__ = '1.2.0' 4 | 5 | from .kernel import BasilispKernel 6 | -------------------------------------------------------------------------------- /basilisp_kernel/__main__.py: -------------------------------------------------------------------------------- 1 | from ipykernel.kernelapp import IPKernelApp 2 | from . import BasilispKernel 3 | 4 | IPKernelApp.launch_instance(kernel_class=BasilispKernel) 5 | -------------------------------------------------------------------------------- /basilisp_kernel/kernel.py: -------------------------------------------------------------------------------- 1 | from ipykernel.ipkernel import IPythonKernel 2 | 3 | from basilisp import cli 4 | from basilisp import main as basilisp 5 | from basilisp.lang import compiler as compiler 6 | from basilisp.lang import reader as reader 7 | from basilisp.lang import runtime as runtime 8 | from basilisp.lang import symbol as sym 9 | from basilisp.lang.exception import format_exception 10 | 11 | import re 12 | from typing import Any, Callable, Optional, Sequence, Type 13 | import sys 14 | 15 | opts = {} 16 | basilisp.init(opts) 17 | ctx = compiler.CompilerContext(filename="basilisp-kernel", opts=opts) 18 | eof = object() 19 | 20 | user_ns = runtime.Namespace.get_or_create(sym.symbol("user")) 21 | core_ns = runtime.Namespace.get(runtime.CORE_NS_SYM) 22 | cli.eval_str("(ns user (:require clojure.core))", ctx, core_ns, eof) 23 | 24 | _DELIMITED_WORD_PATTERN = re.compile(r"[\[\](){\}\s]+") 25 | 26 | def do_execute(code): 27 | with runtime.bindings({ 28 | runtime.Var.find_safe(sym.symbol("*out*", ns=runtime.CORE_NS)) : sys.stdout, 29 | runtime.Var.find_safe(sym.symbol("*err*", ns=runtime.CORE_NS)) : sys.stderr 30 | }): 31 | try: 32 | return cli.eval_str(code, ctx, user_ns, eof) 33 | except reader.SyntaxError as e: 34 | msg = "".join(format_exception(e, reader.SyntaxError, e.__traceback__)) 35 | raise reader.SyntaxError(msg) from None 36 | except compiler.CompilerException as e: 37 | msg = "".join(format_exception(e, compiler.CompilerException, e.__traceback__)) 38 | raise compiler.CompilerException(msg, phase=e.phase, filename=e.filename) from None 39 | except Exception as e: 40 | msg = "".join(format_exception(e, Exception, e.__traceback__)) 41 | raise Exception(msg) from None 42 | 43 | 44 | class BasilispKernel(IPythonKernel): 45 | implementation = 'basilisp-kernel' 46 | implementation_version = '1.0' 47 | language_info = { 48 | 'name': 'clojure', 49 | 'mimetype': 'text/x-clojure', 50 | 'file_extension': '.lpy', 51 | } 52 | banner = "Basilisp: a Clojure-compatible(-ish) Lisp dialect in Python" 53 | 54 | def __init__(self, **kwargs): 55 | super().__init__(**kwargs) 56 | self.imported = False 57 | 58 | def do_complete(self, acode, cursor_pos): 59 | code = acode[:cursor_pos] 60 | words = re.split(_DELIMITED_WORD_PATTERN, code) 61 | 62 | last_word = words[-1] 63 | completions = list(runtime.repl_completions(last_word)) or () 64 | 65 | return {"status" : "ok", 66 | "matches" : completions, 67 | "cursor_start" : cursor_pos-len(last_word), 68 | "cursor_end" : cursor_pos, 69 | "metadata" : dict() 70 | } 71 | 72 | def do_execute(self, code, silent, store_history=True, user_expressions=None, 73 | allow_stdin=False): 74 | bas_code = f'basilisp_kernel.kernel.do_execute({repr(code)})' 75 | if not self.imported: 76 | bas_code = f'import basilisp_kernel\n{bas_code}' 77 | self.imported = True 78 | return super().do_execute(code=bas_code, silent=silent, store_history=store_history, 79 | user_expressions=user_expressions, allow_stdin=allow_stdin) 80 | 81 | -------------------------------------------------------------------------------- /basilisp_kernel/nrepl_server.lpy: -------------------------------------------------------------------------------- 1 | (ns basilisp-kernel.nrepl-server 2 | (:require [basilisp-nrepl-async.nrepl-server :as nr] 3 | [basilisp-nrepl-async.utils :as u] 4 | [basilisp.string :as str]) 5 | (:import asyncio 6 | os)) 7 | 8 | (defasync work-task 9 | ;; An async task to call `WORK-FN` every `INTERVAL-SEC`onds. It 10 | ;; exits when `SHUTDOWN?*` atom becomes truthy. It calls `SHUTDOWN` 11 | ;; on exit. 12 | [interval-sec work-fn shutdown?* shutdown!] 13 | (binding [*out* sys/stdout] 14 | (try 15 | (loop [] 16 | (work-fn) 17 | (when-not @shutdown?* 18 | (await (asyncio/sleep 0.1)) 19 | (recur))) 20 | (catch python/Exception e 21 | (println ::work-task-error :exiting (str e))) 22 | (finally (shutdown!))) 23 | (println ::work-task :exited))) 24 | 25 | (defn server-start 26 | "Starts the nrepl-server in async mode according to `OPTS`, using a 27 | asyncio task to schedule any pending client work every 100ms. 28 | 29 | `OPTS` is a map that can have the following keys. It defaults to {}. 30 | 31 | `:dir` The directory where the `.nrepl-port` file should be created 32 | at. It defaults to the current working directory if not given or 33 | empty. 34 | 35 | `:host` The interface address the server should be bound to. It 36 | defaults to 127.0.0.1 if not given or empty. 37 | 38 | `:port` The port number the server should listen to. It defaults to 39 | 0, which indicates a random available port number. 40 | 41 | It return the `server`, which is a map with the following keys 42 | 43 | `:host` The host interface to which the server is bound. 44 | 45 | `:nrepl-port-file` The path to the `.nrepl-port` file created by the 46 | server. 47 | 48 | `:port` The port on the host interface the server listens for client 49 | connections. 50 | 51 | `:shutdown!` A function to shutdown the server." 52 | ([] 53 | (server-start {})) 54 | ([{:keys [host port dir interval-sec] :as opts 55 | :or {port 0 56 | interval-sec 0.1}}] 57 | (let [host (if (or (nil? host) (empty? (str/trim host))) 58 | "127.0.0.1" 59 | host) 60 | nrepl-port-dir (if (or (nil? dir) (empty? (str/trim dir))) 61 | "." 62 | dir)] 63 | 64 | (if (not (os.path/isdir nrepl-port-dir)) 65 | {:error (u/error-make [:nrepl-server-start :nrepl-port-dir-not-a-dir nrepl-port-dir])} 66 | 67 | (let [{:keys [error work-fn shutdown-fn] :as ret} 68 | (nr/server-start! {:async? true 69 | :host host 70 | :port port 71 | :nrepl-port-file (os.path/join nrepl-port-dir ".nrepl-port")})] 72 | (if error 73 | (binding [*out* sys/stderr] 74 | (println :server-start-error (u/error->str error)) 75 | {:error error}) 76 | 77 | (let [shutdown?* (volatile! false) 78 | shutdown! #(do (vreset! shutdown?* true) 79 | (shutdown-fn) 80 | :shut) 81 | loop (asyncio/get-running-loop) 82 | task (.create-task loop (work-task interval-sec work-fn shutdown?* shutdown!))] 83 | (-> (select-keys ret [:host :port :nrepl-port-file]) 84 | (assoc :shutdown! shutdown! 85 | :_task task))))))))) 86 | 87 | (defn ^:inline server-shut 88 | "Convenience function to shutdown the `SERVER`." 89 | [server] 90 | ((:shutdown! server))) 91 | -------------------------------------------------------------------------------- /bb.edn: -------------------------------------------------------------------------------- 1 | {:tasks 2 | {quickdoc {:doc "Invoke quickdoc" 3 | :extra-deps {io.github.borkdude/quickdoc {:git/sha "7e41f33d98e2ef697dd9ecdff8e84b779141c7a6"}} 4 | :task (exec 'quickdoc.api/quickdoc) 5 | :exec-args {:git/branch "master" 6 | :github/repo "https://github.com/ikappaki/basilisp-kernel" 7 | :source-paths ["basilisp_kernel/nrepl_server.lpy"]}}}} 8 | -------------------------------------------------------------------------------- /hatch_build.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from hatchling.builders.hooks.plugin.interface import BuildHookInterface 4 | 5 | 6 | import argparse 7 | import json 8 | import os 9 | import sys 10 | import shutil 11 | 12 | from jupyter_client.kernelspec import KernelSpecManager 13 | from tempfile import TemporaryDirectory 14 | 15 | kernel_json = { 16 | "argv": ["python", "-m", "basilisp_kernel", "-f", "{connection_file}"], 17 | "display_name": "Basilisp", 18 | "language": "clojure", 19 | } 20 | 21 | class CustomHook(BuildHookInterface): 22 | def initialize(self, version, build_data): 23 | here = os.path.abspath(os.path.dirname(__file__)) 24 | sys.path.insert(0, here) 25 | prefix = os.path.join(here, 'data_kernelspec') 26 | 27 | with TemporaryDirectory() as td: 28 | os.chmod(td, 0o755) # Starts off as 700, not user readable 29 | with open(os.path.join(td, 'kernel.json'), 'w') as f: 30 | json.dump(kernel_json, f, sort_keys=True) 31 | print('Installing Jupyter kernel spec') 32 | 33 | # Requires logo files in kernel root directory 34 | cur_path = os.path.dirname(os.path.realpath(__file__)) 35 | for logo in ["logo-32x32.png", "logo-64x64.png"]: 36 | try: 37 | shutil.copy(os.path.join(cur_path, logo), td) 38 | except FileNotFoundError: 39 | print("Custom logo files not found. Default logos will be used.") 40 | 41 | KernelSpecManager().install_kernel_spec(td, 'basilisp', user=False, prefix=prefix) 42 | 43 | -------------------------------------------------------------------------------- /notebooks/clojure-flow-control.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "bb2d079a-a22f-4264-b163-9a25dd6a7b03", 6 | "metadata": {}, 7 | "source": [ 8 | "## Learn Clojure - Flow Control\n", 9 | "https://clojure.org/guides/learn/flow" 10 | ] 11 | }, 12 | { 13 | "cell_type": "markdown", 14 | "id": "0c183f97-d468-41cd-a86c-7442d11f464f", 15 | "metadata": {}, 16 | "source": [ 17 | "### Flow Control Expressions" 18 | ] 19 | }, 20 | { 21 | "cell_type": "markdown", 22 | "id": "9372c969-eb5a-443f-8dcc-debf9e075853", 23 | "metadata": {}, 24 | "source": [ 25 | "#### if" 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": 1, 31 | "id": "cda50450-57fd-4d6f-9e14-395584838599", 32 | "metadata": {}, 33 | "outputs": [ 34 | { 35 | "data": { 36 | "text/plain": [ 37 | "'2 is even'" 38 | ] 39 | }, 40 | "execution_count": 1, 41 | "metadata": {}, 42 | "output_type": "execute_result" 43 | } 44 | ], 45 | "source": [ 46 | "(str \"2 is \" (if (even? 2) \"even\" \"odd\"))" 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": 2, 52 | "id": "d5cd9ef1-d92b-4b9c-8a5e-d35659446a13", 53 | "metadata": {}, 54 | "outputs": [], 55 | "source": [ 56 | "(if (true? false) \"impossible!\") ;; else is optional" 57 | ] 58 | }, 59 | { 60 | "cell_type": "markdown", 61 | "id": "753d03d9-1dfa-4a65-b3c3-42fbd8ed1982", 62 | "metadata": {}, 63 | "source": [ 64 | "#### Truth" 65 | ] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "execution_count": 3, 70 | "id": "32154053-95fc-47e2-80a4-908dd0fcceaa", 71 | "metadata": {}, 72 | "outputs": [ 73 | { 74 | "data": { 75 | "text/plain": [ 76 | ":truthy" 77 | ] 78 | }, 79 | "execution_count": 3, 80 | "metadata": {}, 81 | "output_type": "execute_result" 82 | } 83 | ], 84 | "source": [ 85 | "(if true :truthy :falsey)" 86 | ] 87 | }, 88 | { 89 | "cell_type": "code", 90 | "execution_count": 4, 91 | "id": "bae69dc8-9488-43cd-9133-d057959babad", 92 | "metadata": {}, 93 | "outputs": [ 94 | { 95 | "data": { 96 | "text/plain": [ 97 | ":truthy" 98 | ] 99 | }, 100 | "execution_count": 4, 101 | "metadata": {}, 102 | "output_type": "execute_result" 103 | } 104 | ], 105 | "source": [ 106 | "(if (object.) :truthy :falsey) ; objects are true" 107 | ] 108 | }, 109 | { 110 | "cell_type": "code", 111 | "execution_count": 5, 112 | "id": "e9c65744-b2eb-4444-8da9-60c5b1fef62d", 113 | "metadata": {}, 114 | "outputs": [ 115 | { 116 | "data": { 117 | "text/plain": [ 118 | ":truthy" 119 | ] 120 | }, 121 | "execution_count": 5, 122 | "metadata": {}, 123 | "output_type": "execute_result" 124 | } 125 | ], 126 | "source": [ 127 | "(if [] :truthy :falsey) ; empty collections are true" 128 | ] 129 | }, 130 | { 131 | "cell_type": "code", 132 | "execution_count": 6, 133 | "id": "25bca075-b3f1-4516-b336-0a54aee551e3", 134 | "metadata": {}, 135 | "outputs": [ 136 | { 137 | "data": { 138 | "text/plain": [ 139 | ":truthy" 140 | ] 141 | }, 142 | "execution_count": 6, 143 | "metadata": {}, 144 | "output_type": "execute_result" 145 | } 146 | ], 147 | "source": [ 148 | "(if 0 :truthy :falsey) ; zero is true" 149 | ] 150 | }, 151 | { 152 | "cell_type": "code", 153 | "execution_count": 7, 154 | "id": "8b48e579-2d40-467f-a7f5-3dfa7c357415", 155 | "metadata": {}, 156 | "outputs": [ 157 | { 158 | "data": { 159 | "text/plain": [ 160 | ":falsey" 161 | ] 162 | }, 163 | "execution_count": 7, 164 | "metadata": {}, 165 | "output_type": "execute_result" 166 | } 167 | ], 168 | "source": [ 169 | "(if false :truthy :falsey)" 170 | ] 171 | }, 172 | { 173 | "cell_type": "code", 174 | "execution_count": 8, 175 | "id": "1f8685bc-1bf7-4b5e-81af-3f81f0652c0d", 176 | "metadata": {}, 177 | "outputs": [ 178 | { 179 | "data": { 180 | "text/plain": [ 181 | ":falsey" 182 | ] 183 | }, 184 | "execution_count": 8, 185 | "metadata": {}, 186 | "output_type": "execute_result" 187 | } 188 | ], 189 | "source": [ 190 | "(if nil :truthy :falsey)" 191 | ] 192 | }, 193 | { 194 | "cell_type": "markdown", 195 | "id": "68821f7b-4d76-435a-a52a-903456a6a6b3", 196 | "metadata": {}, 197 | "source": [ 198 | "#### if and do" 199 | ] 200 | }, 201 | { 202 | "cell_type": "code", 203 | "execution_count": 9, 204 | "id": "a76cff05-d4ca-4462-bde7-ebd018bc2b55", 205 | "metadata": {}, 206 | "outputs": [ 207 | { 208 | "name": "stdout", 209 | "output_type": "stream", 210 | "text": [ 211 | "odd\n", 212 | "\n" 213 | ] 214 | }, 215 | { 216 | "data": { 217 | "text/plain": [ 218 | "False" 219 | ] 220 | }, 221 | "execution_count": 9, 222 | "metadata": {}, 223 | "output_type": "execute_result" 224 | } 225 | ], 226 | "source": [ 227 | "(if (even? 5)\n", 228 | " (do (println \"even\")\n", 229 | " true)\n", 230 | " (do (println \"odd\")\n", 231 | " false))" 232 | ] 233 | }, 234 | { 235 | "cell_type": "markdown", 236 | "id": "6d4a28f5-c46b-413f-8905-812dc1c0eaa3", 237 | "metadata": {}, 238 | "source": [ 239 | "#### when" 240 | ] 241 | }, 242 | { 243 | "cell_type": "code", 244 | "execution_count": 10, 245 | "id": "28a5ce11-06a5-4690-a0b9-02fdcb8f6851", 246 | "metadata": {}, 247 | "outputs": [], 248 | "source": [ 249 | "(let [x 1] ;; change to a negative num to throw exception\n", 250 | " (when (neg? x)\n", 251 | " (throw (RuntimeError. (str \"x must be positive: \" x)))))" 252 | ] 253 | }, 254 | { 255 | "cell_type": "markdown", 256 | "id": "6857b53f-9730-46cd-9dad-71fb78603142", 257 | "metadata": {}, 258 | "source": [ 259 | "#### cond" 260 | ] 261 | }, 262 | { 263 | "cell_type": "code", 264 | "execution_count": 11, 265 | "id": "35d076f4-d51c-4756-bd0b-daee3fba739b", 266 | "metadata": {}, 267 | "outputs": [ 268 | { 269 | "data": { 270 | "text/plain": [ 271 | "'x is less than 10'" 272 | ] 273 | }, 274 | "execution_count": 11, 275 | "metadata": {}, 276 | "output_type": "execute_result" 277 | } 278 | ], 279 | "source": [ 280 | "(let [x 5]\n", 281 | " (cond\n", 282 | " (< x 2) \"x is less than 2\"\n", 283 | " (< x 10) \"x is less than 10\"))" 284 | ] 285 | }, 286 | { 287 | "cell_type": "markdown", 288 | "id": "ab77af05-4be1-419b-bb31-b17d9412af98", 289 | "metadata": {}, 290 | "source": [ 291 | "#### cond and else" 292 | ] 293 | }, 294 | { 295 | "cell_type": "code", 296 | "execution_count": 12, 297 | "id": "6763a4b1-d382-4dfb-abbb-d31a85f36ed2", 298 | "metadata": {}, 299 | "outputs": [ 300 | { 301 | "data": { 302 | "text/plain": [ 303 | "'x is greater than or equal to 10'" 304 | ] 305 | }, 306 | "execution_count": 12, 307 | "metadata": {}, 308 | "output_type": "execute_result" 309 | } 310 | ], 311 | "source": [ 312 | "(let [x 11]\n", 313 | " (cond\n", 314 | " (< x 2) \"x is less than 2\"\n", 315 | " (< x 10) \"x is less than 10\"\n", 316 | " :else \"x is greater than or equal to 10\"))" 317 | ] 318 | }, 319 | { 320 | "cell_type": "markdown", 321 | "id": "777e4fd0-895a-40b1-b732-7e6117b92cc0", 322 | "metadata": {}, 323 | "source": [ 324 | "#### case" 325 | ] 326 | }, 327 | { 328 | "cell_type": "code", 329 | "execution_count": 13, 330 | "id": "d3be4b71-63a5-4c2b-9e53-9851965808b0", 331 | "metadata": {}, 332 | "outputs": [ 333 | { 334 | "data": { 335 | "text/plain": [ 336 | "#'user/foo" 337 | ] 338 | }, 339 | "execution_count": 13, 340 | "metadata": {}, 341 | "output_type": "execute_result" 342 | } 343 | ], 344 | "source": [ 345 | "(defn foo [x]\n", 346 | " (case x\n", 347 | " 5 \"x is 5\"\n", 348 | " 10 \"x is 10\"))" 349 | ] 350 | }, 351 | { 352 | "cell_type": "code", 353 | "execution_count": 14, 354 | "id": "f8996b49-da00-4d63-b8de-2bcae9e03222", 355 | "metadata": {}, 356 | "outputs": [ 357 | { 358 | "data": { 359 | "text/plain": [ 360 | "'x is 10'" 361 | ] 362 | }, 363 | "execution_count": 14, 364 | "metadata": {}, 365 | "output_type": "execute_result" 366 | } 367 | ], 368 | "source": [ 369 | "(foo 10)" 370 | ] 371 | }, 372 | { 373 | "cell_type": "code", 374 | "execution_count": 15, 375 | "id": "699416a9-5085-48f7-ac57-3652db6ee38b", 376 | "metadata": {}, 377 | "outputs": [ 378 | { 379 | "data": { 380 | "text/plain": [ 381 | "" 382 | ] 383 | }, 384 | "execution_count": 15, 385 | "metadata": {}, 386 | "output_type": "execute_result" 387 | } 388 | ], 389 | "source": [ 390 | "#_(foo 11) ;; => ValueError: No case clause matches 11" 391 | ] 392 | }, 393 | { 394 | "cell_type": "markdown", 395 | "id": "23a70b79-4f68-450a-97bd-f1d7676edb1a", 396 | "metadata": {}, 397 | "source": [ 398 | "#### case with else-expression" 399 | ] 400 | }, 401 | { 402 | "cell_type": "code", 403 | "execution_count": 16, 404 | "id": "4b301b20-9955-49b0-b673-3452d6f83099", 405 | "metadata": {}, 406 | "outputs": [ 407 | { 408 | "data": { 409 | "text/plain": [ 410 | "#'user/foo" 411 | ] 412 | }, 413 | "execution_count": 16, 414 | "metadata": {}, 415 | "output_type": "execute_result" 416 | } 417 | ], 418 | "source": [ 419 | "(defn foo [x]\n", 420 | " (case x\n", 421 | " 5 \"x is 5\"\n", 422 | " 10 \"x is 10\"\n", 423 | " \"x isn't 5 or 10\"))" 424 | ] 425 | }, 426 | { 427 | "cell_type": "code", 428 | "execution_count": 17, 429 | "id": "73477504-f023-4123-b700-0f8ee5e14d84", 430 | "metadata": {}, 431 | "outputs": [ 432 | { 433 | "data": { 434 | "text/plain": [ 435 | "\"x isn't 5 or 10\"" 436 | ] 437 | }, 438 | "execution_count": 17, 439 | "metadata": {}, 440 | "output_type": "execute_result" 441 | } 442 | ], 443 | "source": [ 444 | "(foo 11)" 445 | ] 446 | }, 447 | { 448 | "cell_type": "markdown", 449 | "id": "7b42df7a-9b1b-4e52-95ec-57012941a8a0", 450 | "metadata": {}, 451 | "source": [ 452 | "### Iteration and Side Effects" 453 | ] 454 | }, 455 | { 456 | "cell_type": "markdown", 457 | "id": "f54855b1-0d22-491c-87d6-07ce7a9378af", 458 | "metadata": {}, 459 | "source": [ 460 | "#### dotimes" 461 | ] 462 | }, 463 | { 464 | "cell_type": "code", 465 | "execution_count": 18, 466 | "id": "d20ff6c5-3022-4e3c-b801-b6e4b4e531da", 467 | "metadata": {}, 468 | "outputs": [ 469 | { 470 | "name": "stdout", 471 | "output_type": "stream", 472 | "text": [ 473 | "0\n", 474 | "1\n", 475 | "2\n", 476 | "\n" 477 | ] 478 | } 479 | ], 480 | "source": [ 481 | "(dotimes [i 3]\n", 482 | " (println i))" 483 | ] 484 | }, 485 | { 486 | "cell_type": "markdown", 487 | "id": "2a090e89-6cd6-419f-a898-e87dc5ac6623", 488 | "metadata": {}, 489 | "source": [ 490 | "#### doseq" 491 | ] 492 | }, 493 | { 494 | "cell_type": "code", 495 | "execution_count": 19, 496 | "id": "aea97718-8f2a-482f-9f6e-264991a229a2", 497 | "metadata": {}, 498 | "outputs": [ 499 | { 500 | "name": "stdout", 501 | "output_type": "stream", 502 | "text": [ 503 | "0\n", 504 | "1\n", 505 | "2\n", 506 | "\n" 507 | ] 508 | } 509 | ], 510 | "source": [ 511 | "(doseq [n (range 3)]\n", 512 | " (println n))" 513 | ] 514 | }, 515 | { 516 | "cell_type": "markdown", 517 | "id": "f024b5a2-78be-4e45-97e0-e905cbdcabb3", 518 | "metadata": {}, 519 | "source": [ 520 | "#### doseq with multiple bindings" 521 | ] 522 | }, 523 | { 524 | "cell_type": "code", 525 | "execution_count": 20, 526 | "id": "88ca0761-da8d-40a0-83b1-d14d67e0d624", 527 | "metadata": {}, 528 | "outputs": [ 529 | { 530 | "name": "stdout", 531 | "output_type": "stream", 532 | "text": [ 533 | "[:a 0]\n", 534 | "[:a 1]\n", 535 | "[:a 2]\n", 536 | "[:b 0]\n", 537 | "[:b 1]\n", 538 | "[:b 2]\n", 539 | "\n" 540 | ] 541 | } 542 | ], 543 | "source": [ 544 | "(doseq [letter [:a :b]\n", 545 | " number (range 3)] ; list of 0, 1, 2\n", 546 | " (prn [letter number]))" 547 | ] 548 | }, 549 | { 550 | "cell_type": "markdown", 551 | "id": "c1b0d09d-3f68-4619-aa60-f8c2a7feeaee", 552 | "metadata": {}, 553 | "source": [ 554 | "### Clojure's for" 555 | ] 556 | }, 557 | { 558 | "cell_type": "code", 559 | "execution_count": 21, 560 | "id": "48699c42-6361-43b8-92ba-35bb5881edb6", 561 | "metadata": {}, 562 | "outputs": [ 563 | { 564 | "data": { 565 | "text/plain": [ 566 | "([:a 0] [:a 1] [:a 2] [:b 0] [:b 1] [:b 2])" 567 | ] 568 | }, 569 | "execution_count": 21, 570 | "metadata": {}, 571 | "output_type": "execute_result" 572 | } 573 | ], 574 | "source": [ 575 | "(for [letter [:a :b]\n", 576 | " number (range 3)] ; list of 0, 1, 2\n", 577 | " [letter number])" 578 | ] 579 | }, 580 | { 581 | "cell_type": "markdown", 582 | "id": "47816937-5efa-40f6-854c-50309fb0ec78", 583 | "metadata": {}, 584 | "source": [ 585 | "### Recursion" 586 | ] 587 | }, 588 | { 589 | "cell_type": "markdown", 590 | "id": "9d9ab360-0151-49a6-99f2-847a2cbd7c97", 591 | "metadata": {}, 592 | "source": [ 593 | "#### loop and recur" 594 | ] 595 | }, 596 | { 597 | "cell_type": "code", 598 | "execution_count": 22, 599 | "id": "97befb63-bdd4-41c3-ad90-79adca795b01", 600 | "metadata": {}, 601 | "outputs": [ 602 | { 603 | "data": { 604 | "text/plain": [ 605 | "10" 606 | ] 607 | }, 608 | "execution_count": 22, 609 | "metadata": {}, 610 | "output_type": "execute_result" 611 | } 612 | ], 613 | "source": [ 614 | "(loop [i 0]\n", 615 | " (if (< i 10)\n", 616 | " (recur (inc i))\n", 617 | " i))" 618 | ] 619 | }, 620 | { 621 | "cell_type": "markdown", 622 | "id": "f61399e8-355c-4617-b240-38f3453401f6", 623 | "metadata": {}, 624 | "source": [ 625 | "### Exceptions" 626 | ] 627 | }, 628 | { 629 | "cell_type": "markdown", 630 | "id": "a5fe00ec-3363-467f-90ed-32498cf1e87d", 631 | "metadata": {}, 632 | "source": [ 633 | "#### Exception handling" 634 | ] 635 | }, 636 | { 637 | "cell_type": "code", 638 | "execution_count": 23, 639 | "id": "bcb10ea9-207a-4b43-8279-5d335352f817", 640 | "metadata": {}, 641 | "outputs": [ 642 | { 643 | "name": "stdout", 644 | "output_type": "stream", 645 | "text": [ 646 | "cleanup\n", 647 | "\n" 648 | ] 649 | }, 650 | { 651 | "data": { 652 | "text/plain": [ 653 | "Fraction(2, 1)" 654 | ] 655 | }, 656 | "execution_count": 23, 657 | "metadata": {}, 658 | "output_type": "execute_result" 659 | } 660 | ], 661 | "source": [ 662 | "(try\n", 663 | " (/ 2 1)\n", 664 | " (catch ArithmeticError e\n", 665 | " \"divide by zero\")\n", 666 | " (finally\n", 667 | " (println \"cleanup\")))\n", 668 | "\n", 669 | "(/ 2 1)" 670 | ] 671 | }, 672 | { 673 | "cell_type": "markdown", 674 | "id": "1f2ba079-a4b8-411f-91cc-0492038fafe5", 675 | "metadata": {}, 676 | "source": [ 677 | "#### Throwing Exceptions" 678 | ] 679 | }, 680 | { 681 | "cell_type": "code", 682 | "execution_count": 24, 683 | "id": "06902ddc-ac1c-4beb-ab39-5780896ac2cb", 684 | "metadata": {}, 685 | "outputs": [ 686 | { 687 | "data": { 688 | "text/plain": [ 689 | "\"Exception('something went wrong')\"" 690 | ] 691 | }, 692 | "execution_count": 24, 693 | "metadata": {}, 694 | "output_type": "execute_result" 695 | } 696 | ], 697 | "source": [ 698 | "(try\n", 699 | " (throw (Exception. \"something went wrong\"))\n", 700 | " (catch Exception e (str e)))" 701 | ] 702 | }, 703 | { 704 | "cell_type": "markdown", 705 | "id": "eb7bb565-7c8d-480d-a7d0-a321e29b0bf3", 706 | "metadata": {}, 707 | "source": [ 708 | "#### Exception with Clojure data" 709 | ] 710 | }, 711 | { 712 | "cell_type": "code", 713 | "execution_count": 25, 714 | "id": "0daa69f5-3c18-407c-af1a-df2c1453912c", 715 | "metadata": {}, 716 | "outputs": [ 717 | { 718 | "name": "stdout", 719 | "output_type": "stream", 720 | "text": [ 721 | "42\n", 722 | "\n" 723 | ] 724 | } 725 | ], 726 | "source": [ 727 | "(try\n", 728 | " (throw (ex-info \"There was a problem\" {:detail 42}))\n", 729 | " (catch Exception e\n", 730 | " (prn (:detail (ex-data e)))))" 731 | ] 732 | }, 733 | { 734 | "cell_type": "code", 735 | "execution_count": 26, 736 | "id": "6a3cff25-eaf4-4cd3-86b4-e8a01efad1b3", 737 | "metadata": {}, 738 | "outputs": [ 739 | { 740 | "data": { 741 | "text/plain": [ 742 | "9" 743 | ] 744 | }, 745 | "execution_count": 26, 746 | "metadata": {}, 747 | "output_type": "execute_result" 748 | } 749 | ], 750 | "source": [ 751 | "(let [f (python/open \"new\" ** :mode \"w\") ]\n", 752 | " (try\n", 753 | " (.write f \"some text\")\n", 754 | " (finally\n", 755 | " (.close f))))\n", 756 | "\n", 757 | ";; Can be written:\n", 758 | "(with-open [f (python/open \"new\" ** :mode \"w\")]\n", 759 | " (.write f \"some text\"))" 760 | ] 761 | } 762 | ], 763 | "metadata": { 764 | "kernelspec": { 765 | "display_name": "Basilisp", 766 | "language": "clojure", 767 | "name": "basilisp" 768 | }, 769 | "language_info": { 770 | "file_extension": ".lpy", 771 | "mimetype": "text/x-clojure", 772 | "name": "clojure" 773 | } 774 | }, 775 | "nbformat": 4, 776 | "nbformat_minor": 5 777 | } 778 | -------------------------------------------------------------------------------- /notebooks/nb-plot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikappaki/basilisp-kernel/b2dd50bd1830bd2d12fb554de34cc7f895140030/notebooks/nb-plot.png -------------------------------------------------------------------------------- /notebooks/pandas-03-select.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "c970e277-6573-484e-b2a0-0812db69fb0d", 6 | "metadata": {}, 7 | "source": [ 8 | "## How do I select a subset of a DataFrame\n", 9 | "https://web.archive.org/web/20240318204924id_/https://pandas.pydata.org/pandas-docs/stable/getting_started/intro_tutorials/03_subset_data.html" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 24, 15 | "id": "317d4286-22bf-45e2-9d6b-492888a6f6ad", 16 | "metadata": {}, 17 | "outputs": [], 18 | "source": [ 19 | ";; pip install pandas\n", 20 | "(import [pandas :as pd])\n", 21 | "nil" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": 2, 27 | "id": "5fa7ae4b-02db-40eb-9648-5a0f188790bb", 28 | "metadata": {}, 29 | "outputs": [ 30 | { 31 | "data": { 32 | "text/plain": [ 33 | "#'user/titanic" 34 | ] 35 | }, 36 | "execution_count": 2, 37 | "metadata": {}, 38 | "output_type": "execute_result" 39 | } 40 | ], 41 | "source": [ 42 | "(def titanic (pd/read_csv \"https://web.archive.org/web/20230319165431id_/https://raw.githubusercontent.com/pandas-dev/pandas/main/doc/data/titanic.csv\"))" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": 3, 48 | "id": "42844a8f-e77a-494b-a3b1-e911ec2bebb4", 49 | "metadata": {}, 50 | "outputs": [ 51 | { 52 | "data": { 53 | "text/html": [ 54 | "
\n", 55 | "\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 | " \n", 128 | " \n", 129 | " \n", 130 | " \n", 131 | " \n", 132 | " \n", 133 | " \n", 134 | " \n", 135 | " \n", 136 | " \n", 137 | " \n", 138 | " \n", 139 | " \n", 140 | " \n", 141 | " \n", 142 | " \n", 143 | " \n", 144 | " \n", 145 | " \n", 146 | " \n", 147 | " \n", 148 | " \n", 149 | " \n", 150 | " \n", 151 | " \n", 152 | " \n", 153 | " \n", 154 | " \n", 155 | " \n", 156 | " \n", 157 | " \n", 158 | " \n", 159 | " \n", 160 | " \n", 161 | " \n", 162 | " \n", 163 | " \n", 164 | " \n", 165 | " \n", 166 | " \n", 167 | " \n", 168 | " \n", 169 | " \n", 170 | " \n", 171 | " \n", 172 | " \n", 173 | " \n", 174 | " \n", 175 | " \n", 176 | " \n", 177 | " \n", 178 | " \n", 179 | " \n", 180 | " \n", 181 | " \n", 182 | " \n", 183 | " \n", 184 | " \n", 185 | " \n", 186 | " \n", 187 | " \n", 188 | " \n", 189 | " \n", 190 | " \n", 191 | " \n", 192 | " \n", 193 | " \n", 194 | " \n", 195 | " \n", 196 | " \n", 197 | " \n", 198 | " \n", 199 | " \n", 200 | " \n", 201 | " \n", 202 | " \n", 203 | " \n", 204 | " \n", 205 | " \n", 206 | " \n", 207 | " \n", 208 | " \n", 209 | " \n", 210 | " \n", 211 | " \n", 212 | " \n", 213 | " \n", 214 | " \n", 215 | " \n", 216 | " \n", 217 | " \n", 218 | " \n", 219 | " \n", 220 | " \n", 221 | " \n", 222 | " \n", 223 | " \n", 224 | " \n", 225 | " \n", 226 | " \n", 227 | " \n", 228 | " \n", 229 | " \n", 230 | " \n", 231 | " \n", 232 | " \n", 233 | " \n", 234 | " \n", 235 | " \n", 236 | " \n", 237 | " \n", 238 | " \n", 239 | " \n", 240 | " \n", 241 | " \n", 242 | " \n", 243 | " \n", 244 | " \n", 245 | " \n", 246 | " \n", 247 | " \n", 248 | " \n", 249 | " \n", 250 | " \n", 251 | " \n", 252 | " \n", 253 | "
PassengerIdSurvivedPclassNameSexAgeSibSpParchTicketFareCabinEmbarked
0103Braund, Mr. Owen Harrismale22.010A/5 211717.2500NaNS
1211Cumings, Mrs. John Bradley (Florence Briggs Th...female38.010PC 1759971.2833C85C
2313Heikkinen, Miss. Lainafemale26.000STON/O2. 31012827.9250NaNS
3411Futrelle, Mrs. Jacques Heath (Lily May Peel)female35.01011380353.1000C123S
4503Allen, Mr. William Henrymale35.0003734508.0500NaNS
.......................................
88688702Montvila, Rev. Juozasmale27.00021153613.0000NaNS
88788811Graham, Miss. Margaret Edithfemale19.00011205330.0000B42S
88888903Johnston, Miss. Catherine Helen \"Carrie\"femaleNaN12W./C. 660723.4500NaNS
88989011Behr, Mr. Karl Howellmale26.00011136930.0000C148C
89089103Dooley, Mr. Patrickmale32.0003703767.7500NaNQ
\n", 254 | "

891 rows × 12 columns

\n", 255 | "
" 256 | ], 257 | "text/plain": [ 258 | " PassengerId Survived Pclass \\\n", 259 | "0 1 0 3 \n", 260 | "1 2 1 1 \n", 261 | "2 3 1 3 \n", 262 | "3 4 1 1 \n", 263 | "4 5 0 3 \n", 264 | ".. ... ... ... \n", 265 | "886 887 0 2 \n", 266 | "887 888 1 1 \n", 267 | "888 889 0 3 \n", 268 | "889 890 1 1 \n", 269 | "890 891 0 3 \n", 270 | "\n", 271 | " Name Sex Age SibSp \\\n", 272 | "0 Braund, Mr. Owen Harris male 22.0 1 \n", 273 | "1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 \n", 274 | "2 Heikkinen, Miss. Laina female 26.0 0 \n", 275 | "3 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 \n", 276 | "4 Allen, Mr. William Henry male 35.0 0 \n", 277 | ".. ... ... ... ... \n", 278 | "886 Montvila, Rev. Juozas male 27.0 0 \n", 279 | "887 Graham, Miss. Margaret Edith female 19.0 0 \n", 280 | "888 Johnston, Miss. Catherine Helen \"Carrie\" female NaN 1 \n", 281 | "889 Behr, Mr. Karl Howell male 26.0 0 \n", 282 | "890 Dooley, Mr. Patrick male 32.0 0 \n", 283 | "\n", 284 | " Parch Ticket Fare Cabin Embarked \n", 285 | "0 0 A/5 21171 7.2500 NaN S \n", 286 | "1 0 PC 17599 71.2833 C85 C \n", 287 | "2 0 STON/O2. 3101282 7.9250 NaN S \n", 288 | "3 0 113803 53.1000 C123 S \n", 289 | "4 0 373450 8.0500 NaN S \n", 290 | ".. ... ... ... ... ... \n", 291 | "886 0 211536 13.0000 NaN S \n", 292 | "887 0 112053 30.0000 B42 S \n", 293 | "888 2 W./C. 6607 23.4500 NaN S \n", 294 | "889 0 111369 30.0000 C148 C \n", 295 | "890 0 370376 7.7500 NaN Q \n", 296 | "\n", 297 | "[891 rows x 12 columns]" 298 | ] 299 | }, 300 | "execution_count": 3, 301 | "metadata": {}, 302 | "output_type": "execute_result" 303 | } 304 | ], 305 | "source": [ 306 | "titanic" 307 | ] 308 | }, 309 | { 310 | "cell_type": "code", 311 | "execution_count": 4, 312 | "id": "2e64be7e-8d4f-4f7a-ace7-8e0d93086757", 313 | "metadata": {}, 314 | "outputs": [ 315 | { 316 | "data": { 317 | "text/html": [ 318 | "
\n", 319 | "\n", 332 | "\n", 333 | " \n", 334 | " \n", 335 | " \n", 336 | " \n", 337 | " \n", 338 | " \n", 339 | " \n", 340 | " \n", 341 | " \n", 342 | " \n", 343 | " \n", 344 | " \n", 345 | " \n", 346 | " \n", 347 | " \n", 348 | " \n", 349 | " \n", 350 | " \n", 351 | " \n", 352 | " \n", 353 | " \n", 354 | " \n", 355 | " \n", 356 | " \n", 357 | " \n", 358 | " \n", 359 | " \n", 360 | " \n", 361 | " \n", 362 | " \n", 363 | " \n", 364 | " \n", 365 | " \n", 366 | " \n", 367 | " \n", 368 | " \n", 369 | " \n", 370 | " \n", 371 | " \n", 372 | " \n", 373 | " \n", 374 | " \n", 375 | " \n", 376 | " \n", 377 | " \n", 378 | " \n", 379 | " \n", 380 | " \n", 381 | " \n", 382 | " \n", 383 | " \n", 384 | " \n", 385 | " \n", 386 | " \n", 387 | " \n", 388 | " \n", 389 | " \n", 390 | " \n", 391 | " \n", 392 | " \n", 393 | " \n", 394 | " \n", 395 | " \n", 396 | " \n", 397 | " \n", 398 | " \n", 399 | " \n", 400 | " \n", 401 | " \n", 402 | " \n", 403 | " \n", 404 | " \n", 405 | " \n", 406 | " \n", 407 | " \n", 408 | " \n", 409 | " \n", 410 | " \n", 411 | " \n", 412 | " \n", 413 | " \n", 414 | " \n", 415 | " \n", 416 | " \n", 417 | " \n", 418 | " \n", 419 | " \n", 420 | " \n", 421 | " \n", 422 | " \n", 423 | " \n", 424 | " \n", 425 | " \n", 426 | " \n", 427 | "
PassengerIdSurvivedPclassNameSexAgeSibSpParchTicketFareCabinEmbarked
0103Braund, Mr. Owen Harrismale22.010A/5 211717.2500NaNS
1211Cumings, Mrs. John Bradley (Florence Briggs Th...female38.010PC 1759971.2833C85C
2313Heikkinen, Miss. Lainafemale26.000STON/O2. 31012827.9250NaNS
3411Futrelle, Mrs. Jacques Heath (Lily May Peel)female35.01011380353.1000C123S
4503Allen, Mr. William Henrymale35.0003734508.0500NaNS
\n", 428 | "
" 429 | ], 430 | "text/plain": [ 431 | " PassengerId Survived Pclass \\\n", 432 | "0 1 0 3 \n", 433 | "1 2 1 1 \n", 434 | "2 3 1 3 \n", 435 | "3 4 1 1 \n", 436 | "4 5 0 3 \n", 437 | "\n", 438 | " Name Sex Age SibSp \\\n", 439 | "0 Braund, Mr. Owen Harris male 22.0 1 \n", 440 | "1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 \n", 441 | "2 Heikkinen, Miss. Laina female 26.0 0 \n", 442 | "3 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 \n", 443 | "4 Allen, Mr. William Henry male 35.0 0 \n", 444 | "\n", 445 | " Parch Ticket Fare Cabin Embarked \n", 446 | "0 0 A/5 21171 7.2500 NaN S \n", 447 | "1 0 PC 17599 71.2833 C85 C \n", 448 | "2 0 STON/O2. 3101282 7.9250 NaN S \n", 449 | "3 0 113803 53.1000 C123 S \n", 450 | "4 0 373450 8.0500 NaN S " 451 | ] 452 | }, 453 | "execution_count": 4, 454 | "metadata": {}, 455 | "output_type": "execute_result" 456 | } 457 | ], 458 | "source": [ 459 | "(.head titanic)" 460 | ] 461 | }, 462 | { 463 | "cell_type": "code", 464 | "execution_count": 5, 465 | "id": "6c7774ed-1a11-4ff8-98bf-3a9efd775fdc", 466 | "metadata": {}, 467 | "outputs": [ 468 | { 469 | "data": { 470 | "text/html": [ 471 | "
\n", 472 | "\n", 485 | "\n", 486 | " \n", 487 | " \n", 488 | " \n", 489 | " \n", 490 | " \n", 491 | " \n", 492 | " \n", 493 | " \n", 494 | " \n", 495 | " \n", 496 | " \n", 497 | " \n", 498 | " \n", 499 | " \n", 500 | " \n", 501 | " \n", 502 | " \n", 503 | " \n", 504 | " \n", 505 | " \n", 506 | " \n", 507 | " \n", 508 | " \n", 509 | " \n", 510 | " \n", 511 | " \n", 512 | " \n", 513 | " \n", 514 | " \n", 515 | " \n", 516 | " \n", 517 | " \n", 518 | " \n", 519 | " \n", 520 | " \n", 521 | " \n", 522 | " \n", 523 | " \n", 524 | " \n", 525 | " \n", 526 | " \n", 527 | " \n", 528 | " \n", 529 | " \n", 530 | " \n", 531 | " \n", 532 | " \n", 533 | " \n", 534 | " \n", 535 | " \n", 536 | " \n", 537 | " \n", 538 | " \n", 539 | " \n", 540 | " \n", 541 | " \n", 542 | " \n", 543 | " \n", 544 | " \n", 545 | " \n", 546 | " \n", 547 | " \n", 548 | " \n", 549 | " \n", 550 | " \n", 551 | " \n", 552 | " \n", 553 | " \n", 554 | " \n", 555 | " \n", 556 | " \n", 557 | " \n", 558 | " \n", 559 | " \n", 560 | " \n", 561 | " \n", 562 | " \n", 563 | " \n", 564 | " \n", 565 | " \n", 566 | " \n", 567 | " \n", 568 | " \n", 569 | " \n", 570 | " \n", 571 | " \n", 572 | " \n", 573 | " \n", 574 | " \n", 575 | " \n", 576 | " \n", 577 | " \n", 578 | " \n", 579 | " \n", 580 | "
PassengerIdSurvivedPclassNameSexAgeSibSpParchTicketFareCabinEmbarked
88688702Montvila, Rev. Juozasmale27.00021153613.00NaNS
88788811Graham, Miss. Margaret Edithfemale19.00011205330.00B42S
88888903Johnston, Miss. Catherine Helen \"Carrie\"femaleNaN12W./C. 660723.45NaNS
88989011Behr, Mr. Karl Howellmale26.00011136930.00C148C
89089103Dooley, Mr. Patrickmale32.0003703767.75NaNQ
\n", 581 | "
" 582 | ], 583 | "text/plain": [ 584 | " PassengerId Survived Pclass Name \\\n", 585 | "886 887 0 2 Montvila, Rev. Juozas \n", 586 | "887 888 1 1 Graham, Miss. Margaret Edith \n", 587 | "888 889 0 3 Johnston, Miss. Catherine Helen \"Carrie\" \n", 588 | "889 890 1 1 Behr, Mr. Karl Howell \n", 589 | "890 891 0 3 Dooley, Mr. Patrick \n", 590 | "\n", 591 | " Sex Age SibSp Parch Ticket Fare Cabin Embarked \n", 592 | "886 male 27.0 0 0 211536 13.00 NaN S \n", 593 | "887 female 19.0 0 0 112053 30.00 B42 S \n", 594 | "888 female NaN 1 2 W./C. 6607 23.45 NaN S \n", 595 | "889 male 26.0 0 0 111369 30.00 C148 C \n", 596 | "890 male 32.0 0 0 370376 7.75 NaN Q " 597 | ] 598 | }, 599 | "execution_count": 5, 600 | "metadata": {}, 601 | "output_type": "execute_result" 602 | } 603 | ], 604 | "source": [ 605 | "(.tail titanic)" 606 | ] 607 | }, 608 | { 609 | "cell_type": "code", 610 | "execution_count": 6, 611 | "id": "e8b485af-8801-419c-916d-727b5684b966", 612 | "metadata": {}, 613 | "outputs": [ 614 | { 615 | "name": "stdout", 616 | "output_type": "stream", 617 | "text": [ 618 | "\n", 619 | "RangeIndex: 891 entries, 0 to 890\n", 620 | "Data columns (total 12 columns):\n", 621 | " # Column Non-Null Count Dtype \n", 622 | "--- ------ -------------- ----- \n", 623 | " 0 PassengerId 891 non-null int64 \n", 624 | " 1 Survived 891 non-null int64 \n", 625 | " 2 Pclass 891 non-null int64 \n", 626 | " 3 Name 891 non-null object \n", 627 | " 4 Sex 891 non-null object \n", 628 | " 5 Age 714 non-null float64\n", 629 | " 6 SibSp 891 non-null int64 \n", 630 | " 7 Parch 891 non-null int64 \n", 631 | " 8 Ticket 891 non-null object \n", 632 | " 9 Fare 891 non-null float64\n", 633 | " 10 Cabin 204 non-null object \n", 634 | " 11 Embarked 889 non-null object \n", 635 | "dtypes: float64(2), int64(5), object(5)\n", 636 | "memory usage: 83.7+ KB\n" 637 | ] 638 | } 639 | ], 640 | "source": [ 641 | "(.info titanic)" 642 | ] 643 | }, 644 | { 645 | "cell_type": "code", 646 | "execution_count": 7, 647 | "id": "70c75282-31a3-47a9-9a05-76eddf97dd9a", 648 | "metadata": {}, 649 | "outputs": [ 650 | { 651 | "data": { 652 | "text/plain": [ 653 | "#'user/ages" 654 | ] 655 | }, 656 | "execution_count": 7, 657 | "metadata": {}, 658 | "output_type": "execute_result" 659 | } 660 | ], 661 | "source": [ 662 | "(def ages (aget titanic \"Age\"))" 663 | ] 664 | }, 665 | { 666 | "cell_type": "code", 667 | "execution_count": 8, 668 | "id": "170e9897-653e-46e4-985d-e03287a19e6a", 669 | "metadata": {}, 670 | "outputs": [ 671 | { 672 | "data": { 673 | "text/plain": [ 674 | "0 22.0\n", 675 | "1 38.0\n", 676 | "2 26.0\n", 677 | "3 35.0\n", 678 | "4 35.0\n", 679 | " ... \n", 680 | "886 27.0\n", 681 | "887 19.0\n", 682 | "888 NaN\n", 683 | "889 26.0\n", 684 | "890 32.0\n", 685 | "Name: Age, Length: 891, dtype: float64" 686 | ] 687 | }, 688 | "execution_count": 8, 689 | "metadata": {}, 690 | "output_type": "execute_result" 691 | } 692 | ], 693 | "source": [ 694 | "ages" 695 | ] 696 | }, 697 | { 698 | "cell_type": "code", 699 | "execution_count": 9, 700 | "id": "b2b45abe-a548-4040-b641-009c97a7e020", 701 | "metadata": {}, 702 | "outputs": [ 703 | { 704 | "data": { 705 | "text/plain": [ 706 | "(891,)" 707 | ] 708 | }, 709 | "execution_count": 9, 710 | "metadata": {}, 711 | "output_type": "execute_result" 712 | } 713 | ], 714 | "source": [ 715 | "(.-shape ages)" 716 | ] 717 | }, 718 | { 719 | "cell_type": "code", 720 | "execution_count": 10, 721 | "id": "2a6f9d1f-52fc-4533-bceb-0825eb4dff4d", 722 | "metadata": {}, 723 | "outputs": [ 724 | { 725 | "data": { 726 | "text/html": [ 727 | "
\n", 728 | "\n", 741 | "\n", 742 | " \n", 743 | " \n", 744 | " \n", 745 | " \n", 746 | " \n", 747 | " \n", 748 | " \n", 749 | " \n", 750 | " \n", 751 | " \n", 752 | " \n", 753 | " \n", 754 | " \n", 755 | " \n", 756 | " \n", 757 | " \n", 758 | " \n", 759 | " \n", 760 | " \n", 761 | " \n", 762 | " \n", 763 | " \n", 764 | " \n", 765 | " \n", 766 | " \n", 767 | " \n", 768 | " \n", 769 | " \n", 770 | " \n", 771 | " \n", 772 | " \n", 773 | " \n", 774 | " \n", 775 | " \n", 776 | " \n", 777 | " \n", 778 | " \n", 779 | " \n", 780 | " \n", 781 | " \n", 782 | " \n", 783 | " \n", 784 | " \n", 785 | " \n", 786 | " \n", 787 | " \n", 788 | " \n", 789 | " \n", 790 | " \n", 791 | " \n", 792 | " \n", 793 | " \n", 794 | " \n", 795 | " \n", 796 | " \n", 797 | " \n", 798 | " \n", 799 | " \n", 800 | " \n", 801 | " \n", 802 | " \n", 803 | " \n", 804 | " \n", 805 | " \n", 806 | "
AgeSex
022.0male
138.0female
226.0female
335.0female
435.0male
.........
88627.0male
88719.0female
888NaNfemale
88926.0male
89032.0male
\n", 807 | "

891 rows × 2 columns

\n", 808 | "
" 809 | ], 810 | "text/plain": [ 811 | " Age Sex\n", 812 | "0 22.0 male\n", 813 | "1 38.0 female\n", 814 | "2 26.0 female\n", 815 | "3 35.0 female\n", 816 | "4 35.0 male\n", 817 | ".. ... ...\n", 818 | "886 27.0 male\n", 819 | "887 19.0 female\n", 820 | "888 NaN female\n", 821 | "889 26.0 male\n", 822 | "890 32.0 male\n", 823 | "\n", 824 | "[891 rows x 2 columns]" 825 | ] 826 | }, 827 | "execution_count": 10, 828 | "metadata": {}, 829 | "output_type": "execute_result" 830 | } 831 | ], 832 | "source": [ 833 | "(aget titanic [\"Age\" \"Sex\"])" 834 | ] 835 | }, 836 | { 837 | "cell_type": "code", 838 | "execution_count": 11, 839 | "id": "71a18d64-2bd4-4373-85a9-5ece2cd76129", 840 | "metadata": {}, 841 | "outputs": [ 842 | { 843 | "data": { 844 | "text/plain": [ 845 | "#'user/above-35" 846 | ] 847 | }, 848 | "execution_count": 11, 849 | "metadata": {}, 850 | "output_type": "execute_result" 851 | } 852 | ], 853 | "source": [ 854 | "(def above-35 (aget titanic (> (aget titanic \"Age\") 35)))" 855 | ] 856 | }, 857 | { 858 | "cell_type": "code", 859 | "execution_count": 12, 860 | "id": "482e5a86-68a2-45ac-b8cf-0f777995e11c", 861 | "metadata": {}, 862 | "outputs": [ 863 | { 864 | "data": { 865 | "text/html": [ 866 | "
\n", 867 | "\n", 880 | "\n", 881 | " \n", 882 | " \n", 883 | " \n", 884 | " \n", 885 | " \n", 886 | " \n", 887 | " \n", 888 | " \n", 889 | " \n", 890 | " \n", 891 | " \n", 892 | " \n", 893 | " \n", 894 | " \n", 895 | " \n", 896 | " \n", 897 | " \n", 898 | " \n", 899 | " \n", 900 | " \n", 901 | " \n", 902 | " \n", 903 | " \n", 904 | " \n", 905 | " \n", 906 | " \n", 907 | " \n", 908 | " \n", 909 | " \n", 910 | " \n", 911 | " \n", 912 | " \n", 913 | " \n", 914 | " \n", 915 | " \n", 916 | " \n", 917 | " \n", 918 | " \n", 919 | " \n", 920 | " \n", 921 | " \n", 922 | " \n", 923 | " \n", 924 | " \n", 925 | " \n", 926 | " \n", 927 | " \n", 928 | " \n", 929 | " \n", 930 | " \n", 931 | " \n", 932 | " \n", 933 | " \n", 934 | " \n", 935 | " \n", 936 | " \n", 937 | " \n", 938 | " \n", 939 | " \n", 940 | " \n", 941 | " \n", 942 | " \n", 943 | " \n", 944 | " \n", 945 | " \n", 946 | " \n", 947 | " \n", 948 | " \n", 949 | " \n", 950 | " \n", 951 | " \n", 952 | " \n", 953 | " \n", 954 | " \n", 955 | " \n", 956 | " \n", 957 | " \n", 958 | " \n", 959 | " \n", 960 | " \n", 961 | " \n", 962 | " \n", 963 | " \n", 964 | " \n", 965 | " \n", 966 | " \n", 967 | " \n", 968 | " \n", 969 | " \n", 970 | " \n", 971 | " \n", 972 | " \n", 973 | " \n", 974 | " \n", 975 | "
PassengerIdSurvivedPclassNameSexAgeSibSpParchTicketFareCabinEmbarked
1211Cumings, Mrs. John Bradley (Florence Briggs Th...female38.010PC 1759971.2833C85C
6701McCarthy, Mr. Timothy Jmale54.0001746351.8625E46S
111211Bonnell, Miss. Elizabethfemale58.00011378326.5500C103S
131403Andersson, Mr. Anders Johanmale39.01534708231.2750NaNS
151612Hewlett, Mrs. (Mary D Kingcome)female55.00024870616.0000NaNS
\n", 976 | "
" 977 | ], 978 | "text/plain": [ 979 | " PassengerId Survived Pclass \\\n", 980 | "1 2 1 1 \n", 981 | "6 7 0 1 \n", 982 | "11 12 1 1 \n", 983 | "13 14 0 3 \n", 984 | "15 16 1 2 \n", 985 | "\n", 986 | " Name Sex Age SibSp \\\n", 987 | "1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 \n", 988 | "6 McCarthy, Mr. Timothy J male 54.0 0 \n", 989 | "11 Bonnell, Miss. Elizabeth female 58.0 0 \n", 990 | "13 Andersson, Mr. Anders Johan male 39.0 1 \n", 991 | "15 Hewlett, Mrs. (Mary D Kingcome) female 55.0 0 \n", 992 | "\n", 993 | " Parch Ticket Fare Cabin Embarked \n", 994 | "1 0 PC 17599 71.2833 C85 C \n", 995 | "6 0 17463 51.8625 E46 S \n", 996 | "11 0 113783 26.5500 C103 S \n", 997 | "13 5 347082 31.2750 NaN S \n", 998 | "15 0 248706 16.0000 NaN S " 999 | ] 1000 | }, 1001 | "execution_count": 12, 1002 | "metadata": {}, 1003 | "output_type": "execute_result" 1004 | } 1005 | ], 1006 | "source": [ 1007 | "(.head above-35)" 1008 | ] 1009 | }, 1010 | { 1011 | "cell_type": "code", 1012 | "execution_count": 13, 1013 | "id": "90a244f9-0a23-447a-9208-f98a9da7bcf3", 1014 | "metadata": {}, 1015 | "outputs": [ 1016 | { 1017 | "data": { 1018 | "text/plain": [ 1019 | "(217, 12)" 1020 | ] 1021 | }, 1022 | "execution_count": 13, 1023 | "metadata": {}, 1024 | "output_type": "execute_result" 1025 | } 1026 | ], 1027 | "source": [ 1028 | "(.-shape above-35)" 1029 | ] 1030 | }, 1031 | { 1032 | "cell_type": "code", 1033 | "execution_count": 14, 1034 | "id": "7e758359-a22c-4307-abf8-9a851a20a584", 1035 | "metadata": {}, 1036 | "outputs": [ 1037 | { 1038 | "data": { 1039 | "text/plain": [ 1040 | "0 False\n", 1041 | "1 True\n", 1042 | "2 False\n", 1043 | "3 False\n", 1044 | "4 False\n", 1045 | " ... \n", 1046 | "886 False\n", 1047 | "887 False\n", 1048 | "888 False\n", 1049 | "889 False\n", 1050 | "890 False\n", 1051 | "Name: Age, Length: 891, dtype: bool" 1052 | ] 1053 | }, 1054 | "execution_count": 14, 1055 | "metadata": {}, 1056 | "output_type": "execute_result" 1057 | } 1058 | ], 1059 | "source": [ 1060 | "(> (aget titanic \"Age\") 35)" 1061 | ] 1062 | }, 1063 | { 1064 | "cell_type": "code", 1065 | "execution_count": 15, 1066 | "id": "84f41bd2-beda-4c3f-b2a7-d47912a83fec", 1067 | "metadata": {}, 1068 | "outputs": [ 1069 | { 1070 | "data": { 1071 | "text/plain": [ 1072 | "#'user/class-23" 1073 | ] 1074 | }, 1075 | "execution_count": 15, 1076 | "metadata": {}, 1077 | "output_type": "execute_result" 1078 | } 1079 | ], 1080 | "source": [ 1081 | "(def class-23 (aget titanic (.isin (aget titanic \"Pclass\") [2 3])))" 1082 | ] 1083 | }, 1084 | { 1085 | "cell_type": "code", 1086 | "execution_count": 16, 1087 | "id": "89eed3be-92da-47fe-ad15-e9f3997f08e0", 1088 | "metadata": {}, 1089 | "outputs": [ 1090 | { 1091 | "data": { 1092 | "text/html": [ 1093 | "
\n", 1094 | "\n", 1107 | "\n", 1108 | " \n", 1109 | " \n", 1110 | " \n", 1111 | " \n", 1112 | " \n", 1113 | " \n", 1114 | " \n", 1115 | " \n", 1116 | " \n", 1117 | " \n", 1118 | " \n", 1119 | " \n", 1120 | " \n", 1121 | " \n", 1122 | " \n", 1123 | " \n", 1124 | " \n", 1125 | " \n", 1126 | " \n", 1127 | " \n", 1128 | " \n", 1129 | " \n", 1130 | " \n", 1131 | " \n", 1132 | " \n", 1133 | " \n", 1134 | " \n", 1135 | " \n", 1136 | " \n", 1137 | " \n", 1138 | " \n", 1139 | " \n", 1140 | " \n", 1141 | " \n", 1142 | " \n", 1143 | " \n", 1144 | " \n", 1145 | " \n", 1146 | " \n", 1147 | " \n", 1148 | " \n", 1149 | " \n", 1150 | " \n", 1151 | " \n", 1152 | " \n", 1153 | " \n", 1154 | " \n", 1155 | " \n", 1156 | " \n", 1157 | " \n", 1158 | " \n", 1159 | " \n", 1160 | " \n", 1161 | " \n", 1162 | " \n", 1163 | " \n", 1164 | " \n", 1165 | " \n", 1166 | " \n", 1167 | " \n", 1168 | " \n", 1169 | " \n", 1170 | " \n", 1171 | " \n", 1172 | " \n", 1173 | " \n", 1174 | " \n", 1175 | " \n", 1176 | " \n", 1177 | " \n", 1178 | " \n", 1179 | " \n", 1180 | " \n", 1181 | " \n", 1182 | " \n", 1183 | " \n", 1184 | " \n", 1185 | " \n", 1186 | " \n", 1187 | " \n", 1188 | " \n", 1189 | " \n", 1190 | " \n", 1191 | " \n", 1192 | " \n", 1193 | " \n", 1194 | " \n", 1195 | " \n", 1196 | " \n", 1197 | " \n", 1198 | " \n", 1199 | " \n", 1200 | " \n", 1201 | " \n", 1202 | "
PassengerIdSurvivedPclassNameSexAgeSibSpParchTicketFareCabinEmbarked
0103Braund, Mr. Owen Harrismale22.010A/5 211717.2500NaNS
2313Heikkinen, Miss. Lainafemale26.000STON/O2. 31012827.9250NaNS
4503Allen, Mr. William Henrymale35.0003734508.0500NaNS
5603Moran, Mr. JamesmaleNaN003308778.4583NaNQ
7803Palsson, Master. Gosta Leonardmale2.03134990921.0750NaNS
\n", 1203 | "
" 1204 | ], 1205 | "text/plain": [ 1206 | " PassengerId Survived Pclass Name Sex \\\n", 1207 | "0 1 0 3 Braund, Mr. Owen Harris male \n", 1208 | "2 3 1 3 Heikkinen, Miss. Laina female \n", 1209 | "4 5 0 3 Allen, Mr. William Henry male \n", 1210 | "5 6 0 3 Moran, Mr. James male \n", 1211 | "7 8 0 3 Palsson, Master. Gosta Leonard male \n", 1212 | "\n", 1213 | " Age SibSp Parch Ticket Fare Cabin Embarked \n", 1214 | "0 22.0 1 0 A/5 21171 7.2500 NaN S \n", 1215 | "2 26.0 0 0 STON/O2. 3101282 7.9250 NaN S \n", 1216 | "4 35.0 0 0 373450 8.0500 NaN S \n", 1217 | "5 NaN 0 0 330877 8.4583 NaN Q \n", 1218 | "7 2.0 3 1 349909 21.0750 NaN S " 1219 | ] 1220 | }, 1221 | "execution_count": 16, 1222 | "metadata": {}, 1223 | "output_type": "execute_result" 1224 | } 1225 | ], 1226 | "source": [ 1227 | "(.head class-23)" 1228 | ] 1229 | }, 1230 | { 1231 | "cell_type": "code", 1232 | "execution_count": 17, 1233 | "id": "7ad1815d-2554-4f3c-a1a1-b3f5a86bf7a9", 1234 | "metadata": {}, 1235 | "outputs": [ 1236 | { 1237 | "data": { 1238 | "text/plain": [ 1239 | "0 3\n", 1240 | "1 1\n", 1241 | "2 3\n", 1242 | "3 1\n", 1243 | "4 3\n", 1244 | " ..\n", 1245 | "886 2\n", 1246 | "887 1\n", 1247 | "888 3\n", 1248 | "889 1\n", 1249 | "890 3\n", 1250 | "Name: Pclass, Length: 891, dtype: int64" 1251 | ] 1252 | }, 1253 | "execution_count": 17, 1254 | "metadata": {}, 1255 | "output_type": "execute_result" 1256 | } 1257 | ], 1258 | "source": [ 1259 | "(operator/or (aget titanic \"Pclass\") (aget titanic \"Pclass\"))" 1260 | ] 1261 | }, 1262 | { 1263 | "cell_type": "code", 1264 | "execution_count": 18, 1265 | "id": "fc74c4c9-4fba-4861-9d49-aaf472918faa", 1266 | "metadata": {}, 1267 | "outputs": [ 1268 | { 1269 | "data": { 1270 | "text/plain": [ 1271 | "#'user/age-no-na" 1272 | ] 1273 | }, 1274 | "execution_count": 18, 1275 | "metadata": {}, 1276 | "output_type": "execute_result" 1277 | } 1278 | ], 1279 | "source": [ 1280 | "(def age-no-na (aget titanic (.notna (aget titanic \"Age\"))))" 1281 | ] 1282 | }, 1283 | { 1284 | "cell_type": "code", 1285 | "execution_count": 19, 1286 | "id": "6042413f-ade5-4ef7-9e66-c018067d623d", 1287 | "metadata": {}, 1288 | "outputs": [ 1289 | { 1290 | "data": { 1291 | "text/plain": [ 1292 | "(714, 12)" 1293 | ] 1294 | }, 1295 | "execution_count": 19, 1296 | "metadata": {}, 1297 | "output_type": "execute_result" 1298 | } 1299 | ], 1300 | "source": [ 1301 | "(.-shape age-no-na)" 1302 | ] 1303 | }, 1304 | { 1305 | "cell_type": "code", 1306 | "execution_count": 20, 1307 | "id": "737bb875-2669-4c89-bf4d-4633c08b1841", 1308 | "metadata": {}, 1309 | "outputs": [ 1310 | { 1311 | "data": { 1312 | "text/plain": [ 1313 | "1 Cumings, Mrs. John Bradley (Florence Briggs Th...\n", 1314 | "6 McCarthy, Mr. Timothy J\n", 1315 | "11 Bonnell, Miss. Elizabeth\n", 1316 | "13 Andersson, Mr. Anders Johan\n", 1317 | "15 Hewlett, Mrs. (Mary D Kingcome) \n", 1318 | " ... \n", 1319 | "865 Bystrom, Mrs. (Karolina)\n", 1320 | "871 Beckwith, Mrs. Richard Leonard (Sallie Monypeny)\n", 1321 | "873 Vander Cruyssen, Mr. Victor\n", 1322 | "879 Potter, Mrs. Thomas Jr (Lily Alexenia Wilson)\n", 1323 | "885 Rice, Mrs. William (Margaret Norton)\n", 1324 | "Name: Name, Length: 217, dtype: object" 1325 | ] 1326 | }, 1327 | "execution_count": 20, 1328 | "metadata": {}, 1329 | "output_type": "execute_result" 1330 | } 1331 | ], 1332 | "source": [ 1333 | "(aget (.loc titanic) (> (aget titanic \"Age\") 35) \"Name\")" 1334 | ] 1335 | }, 1336 | { 1337 | "cell_type": "code", 1338 | "execution_count": 21, 1339 | "id": "d4e2d719-0da8-4a3a-996a-fffbfab03a45", 1340 | "metadata": {}, 1341 | "outputs": [ 1342 | { 1343 | "data": { 1344 | "text/html": [ 1345 | "
\n", 1346 | "\n", 1359 | "\n", 1360 | " \n", 1361 | " \n", 1362 | " \n", 1363 | " \n", 1364 | " \n", 1365 | " \n", 1366 | " \n", 1367 | " \n", 1368 | " \n", 1369 | " \n", 1370 | " \n", 1371 | " \n", 1372 | " \n", 1373 | " \n", 1374 | " \n", 1375 | " \n", 1376 | " \n", 1377 | " \n", 1378 | " \n", 1379 | " \n", 1380 | " \n", 1381 | " \n", 1382 | " \n", 1383 | " \n", 1384 | " \n", 1385 | " \n", 1386 | " \n", 1387 | " \n", 1388 | " \n", 1389 | " \n", 1390 | " \n", 1391 | " \n", 1392 | " \n", 1393 | " \n", 1394 | " \n", 1395 | " \n", 1396 | " \n", 1397 | " \n", 1398 | " \n", 1399 | " \n", 1400 | " \n", 1401 | " \n", 1402 | " \n", 1403 | " \n", 1404 | " \n", 1405 | " \n", 1406 | " \n", 1407 | " \n", 1408 | " \n", 1409 | " \n", 1410 | " \n", 1411 | " \n", 1412 | " \n", 1413 | " \n", 1414 | " \n", 1415 | " \n", 1416 | " \n", 1417 | " \n", 1418 | " \n", 1419 | " \n", 1420 | " \n", 1421 | " \n", 1422 | " \n", 1423 | " \n", 1424 | " \n", 1425 | " \n", 1426 | " \n", 1427 | " \n", 1428 | " \n", 1429 | " \n", 1430 | " \n", 1431 | " \n", 1432 | " \n", 1433 | " \n", 1434 | " \n", 1435 | " \n", 1436 | " \n", 1437 | " \n", 1438 | " \n", 1439 | " \n", 1440 | " \n", 1441 | " \n", 1442 | " \n", 1443 | " \n", 1444 | " \n", 1445 | " \n", 1446 | " \n", 1447 | " \n", 1448 | " \n", 1449 | " \n", 1450 | " \n", 1451 | " \n", 1452 | " \n", 1453 | " \n", 1454 | " \n", 1455 | " \n", 1456 | " \n", 1457 | " \n", 1458 | " \n", 1459 | " \n", 1460 | " \n", 1461 | " \n", 1462 | " \n", 1463 | " \n", 1464 | " \n", 1465 | " \n", 1466 | "
PclassNameSex
92Nasser, Mrs. Nicholas (Adele Achem)female
103Sandstrom, Miss. Marguerite Rutfemale
111Bonnell, Miss. Elizabethfemale
123Saundercock, Mr. William Henrymale
133Andersson, Mr. Anders Johanmale
143Vestrom, Miss. Hulda Amanda Adolfinafemale
152Hewlett, Mrs. (Mary D Kingcome)female
163Rice, Master. Eugenemale
172Williams, Mr. Charles Eugenemale
183Vander Planke, Mrs. Julius (Emelia Maria Vande...female
193Masselmani, Mrs. Fatimafemale
202Fynney, Mr. Joseph Jmale
212Beesley, Mr. Lawrencemale
223McGowan, Miss. Anna \"Annie\"female
231Sloper, Mr. William Thompsonmale
243Palsson, Miss. Torborg Danirafemale
\n", 1467 | "
" 1468 | ], 1469 | "text/plain": [ 1470 | " Pclass Name Sex\n", 1471 | "9 2 Nasser, Mrs. Nicholas (Adele Achem) female\n", 1472 | "10 3 Sandstrom, Miss. Marguerite Rut female\n", 1473 | "11 1 Bonnell, Miss. Elizabeth female\n", 1474 | "12 3 Saundercock, Mr. William Henry male\n", 1475 | "13 3 Andersson, Mr. Anders Johan male\n", 1476 | "14 3 Vestrom, Miss. Hulda Amanda Adolfina female\n", 1477 | "15 2 Hewlett, Mrs. (Mary D Kingcome) female\n", 1478 | "16 3 Rice, Master. Eugene male\n", 1479 | "17 2 Williams, Mr. Charles Eugene male\n", 1480 | "18 3 Vander Planke, Mrs. Julius (Emelia Maria Vande... female\n", 1481 | "19 3 Masselmani, Mrs. Fatima female\n", 1482 | "20 2 Fynney, Mr. Joseph J male\n", 1483 | "21 2 Beesley, Mr. Lawrence male\n", 1484 | "22 3 McGowan, Miss. Anna \"Annie\" female\n", 1485 | "23 1 Sloper, Mr. William Thompson male\n", 1486 | "24 3 Palsson, Miss. Torborg Danira female" 1487 | ] 1488 | }, 1489 | "execution_count": 21, 1490 | "metadata": {}, 1491 | "output_type": "execute_result" 1492 | } 1493 | ], 1494 | "source": [ 1495 | "(aget (.iloc titanic) #py ((slice 9 25) (slice 2 5)))" 1496 | ] 1497 | }, 1498 | { 1499 | "cell_type": "code", 1500 | "execution_count": 22, 1501 | "id": "8f497c17-11c0-4d47-a07a-20b57d9131b5", 1502 | "metadata": {}, 1503 | "outputs": [ 1504 | { 1505 | "data": { 1506 | "text/plain": [ 1507 | "'anonymous'" 1508 | ] 1509 | }, 1510 | "execution_count": 22, 1511 | "metadata": {}, 1512 | "output_type": "execute_result" 1513 | } 1514 | ], 1515 | "source": [ 1516 | "(aset (.iloc titanic) #py ((slice 0 3) 3) \"anonymous\")" 1517 | ] 1518 | }, 1519 | { 1520 | "cell_type": "code", 1521 | "execution_count": 23, 1522 | "id": "bfea0826-a064-43d5-8b11-a1b1333e7bb2", 1523 | "metadata": {}, 1524 | "outputs": [ 1525 | { 1526 | "data": { 1527 | "text/html": [ 1528 | "
\n", 1529 | "\n", 1542 | "\n", 1543 | " \n", 1544 | " \n", 1545 | " \n", 1546 | " \n", 1547 | " \n", 1548 | " \n", 1549 | " \n", 1550 | " \n", 1551 | " \n", 1552 | " \n", 1553 | " \n", 1554 | " \n", 1555 | " \n", 1556 | " \n", 1557 | " \n", 1558 | " \n", 1559 | " \n", 1560 | " \n", 1561 | " \n", 1562 | " \n", 1563 | " \n", 1564 | " \n", 1565 | " \n", 1566 | " \n", 1567 | " \n", 1568 | " \n", 1569 | " \n", 1570 | " \n", 1571 | " \n", 1572 | " \n", 1573 | " \n", 1574 | " \n", 1575 | " \n", 1576 | " \n", 1577 | " \n", 1578 | " \n", 1579 | " \n", 1580 | " \n", 1581 | " \n", 1582 | " \n", 1583 | " \n", 1584 | " \n", 1585 | " \n", 1586 | " \n", 1587 | " \n", 1588 | " \n", 1589 | " \n", 1590 | " \n", 1591 | " \n", 1592 | " \n", 1593 | " \n", 1594 | " \n", 1595 | " \n", 1596 | " \n", 1597 | " \n", 1598 | " \n", 1599 | " \n", 1600 | " \n", 1601 | " \n", 1602 | " \n", 1603 | " \n", 1604 | " \n", 1605 | " \n", 1606 | " \n", 1607 | " \n", 1608 | " \n", 1609 | " \n", 1610 | " \n", 1611 | " \n", 1612 | " \n", 1613 | " \n", 1614 | " \n", 1615 | " \n", 1616 | " \n", 1617 | " \n", 1618 | " \n", 1619 | " \n", 1620 | " \n", 1621 | " \n", 1622 | " \n", 1623 | " \n", 1624 | " \n", 1625 | " \n", 1626 | " \n", 1627 | " \n", 1628 | " \n", 1629 | " \n", 1630 | " \n", 1631 | " \n", 1632 | " \n", 1633 | " \n", 1634 | " \n", 1635 | " \n", 1636 | " \n", 1637 | "
PassengerIdSurvivedPclassNameSexAgeSibSpParchTicketFareCabinEmbarked
0103anonymousmale22.010A/5 211717.2500NaNS
1211anonymousfemale38.010PC 1759971.2833C85C
2313anonymousfemale26.000STON/O2. 31012827.9250NaNS
3411Futrelle, Mrs. Jacques Heath (Lily May Peel)female35.01011380353.1000C123S
4503Allen, Mr. William Henrymale35.0003734508.0500NaNS
\n", 1638 | "
" 1639 | ], 1640 | "text/plain": [ 1641 | " PassengerId Survived Pclass \\\n", 1642 | "0 1 0 3 \n", 1643 | "1 2 1 1 \n", 1644 | "2 3 1 3 \n", 1645 | "3 4 1 1 \n", 1646 | "4 5 0 3 \n", 1647 | "\n", 1648 | " Name Sex Age SibSp Parch \\\n", 1649 | "0 anonymous male 22.0 1 0 \n", 1650 | "1 anonymous female 38.0 1 0 \n", 1651 | "2 anonymous female 26.0 0 0 \n", 1652 | "3 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 \n", 1653 | "4 Allen, Mr. William Henry male 35.0 0 0 \n", 1654 | "\n", 1655 | " Ticket Fare Cabin Embarked \n", 1656 | "0 A/5 21171 7.2500 NaN S \n", 1657 | "1 PC 17599 71.2833 C85 C \n", 1658 | "2 STON/O2. 3101282 7.9250 NaN S \n", 1659 | "3 113803 53.1000 C123 S \n", 1660 | "4 373450 8.0500 NaN S " 1661 | ] 1662 | }, 1663 | "execution_count": 23, 1664 | "metadata": {}, 1665 | "output_type": "execute_result" 1666 | } 1667 | ], 1668 | "source": [ 1669 | "(.head titanic)" 1670 | ] 1671 | } 1672 | ], 1673 | "metadata": { 1674 | "kernelspec": { 1675 | "display_name": "Basilisp", 1676 | "language": "clojure", 1677 | "name": "basilisp" 1678 | }, 1679 | "language_info": { 1680 | "file_extension": ".lpy", 1681 | "mimetype": "text/x-clojure", 1682 | "name": "clojure" 1683 | } 1684 | }, 1685 | "nbformat": 4, 1686 | "nbformat_minor": 5 1687 | } 1688 | -------------------------------------------------------------------------------- /notebooks/python-interop.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "24cec1b7-f4c8-4a53-a70f-b8dae962e426", 6 | "metadata": {}, 7 | "source": [ 8 | "## Python Interop\n", 9 | "https://basilisp.readthedocs.io/en/latest/pyinterop.html" 10 | ] 11 | }, 12 | { 13 | "cell_type": "markdown", 14 | "id": "5a33fecd-a4c3-4dba-b639-2e2d58c30da6", 15 | "metadata": {}, 16 | "source": [ 17 | "### Python builtins" 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": 1, 23 | "id": "245f8361-8aa7-4797-8179-5e9d755e3f2f", 24 | "metadata": {}, 25 | "outputs": [ 26 | { 27 | "data": { 28 | "text/plain": [ 29 | "1" 30 | ] 31 | }, 32 | "execution_count": 1, 33 | "metadata": {}, 34 | "output_type": "execute_result" 35 | } 36 | ], 37 | "source": [ 38 | "(python/abs -1)" 39 | ] 40 | }, 41 | { 42 | "cell_type": "markdown", 43 | "id": "a4f0de4a-473f-430f-8982-57b1fa890ae1", 44 | "metadata": {}, 45 | "source": [ 46 | "### Importing modules" 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": 2, 52 | "id": "bb133bf3-4014-41f5-b9c4-6475db6382aa", 53 | "metadata": {}, 54 | "outputs": [ 55 | { 56 | "data": { 57 | "text/plain": [ 58 | "False" 59 | ] 60 | }, 61 | "execution_count": 2, 62 | "metadata": {}, 63 | "output_type": "execute_result" 64 | } 65 | ], 66 | "source": [ 67 | "(import [os.path :as path])\n", 68 | "(path/exists \"tests.txt\")" 69 | ] 70 | }, 71 | { 72 | "cell_type": "markdown", 73 | "id": "47f09168-7310-4391-8ce3-455b318b8fca", 74 | "metadata": {}, 75 | "source": [ 76 | "### Referencing Module Members" 77 | ] 78 | }, 79 | { 80 | "cell_type": "code", 81 | "execution_count": 3, 82 | "id": "d0823d69-19e8-412c-94ed-72ac425162a0", 83 | "metadata": {}, 84 | "outputs": [ 85 | { 86 | "data": { 87 | "text/plain": [ 88 | "datetime.datetime(2030, 1, 1, 0, 0)" 89 | ] 90 | }, 91 | "execution_count": 3, 92 | "metadata": {}, 93 | "output_type": "execute_result" 94 | } 95 | ], 96 | "source": [ 97 | "(import datetime)\n", 98 | ";; fromisoformat is a class method of datetime\n", 99 | "(datetime.datetime/fromisoformat \"2030-01-01\")" 100 | ] 101 | }, 102 | { 103 | "cell_type": "markdown", 104 | "id": "1b082632-1052-4936-a2fd-f50cd2aa1469", 105 | "metadata": {}, 106 | "source": [ 107 | "### Accessing Object Methods and Properties" 108 | ] 109 | }, 110 | { 111 | "cell_type": "code", 112 | "execution_count": 4, 113 | "id": "5dae5dcc-30f3-4de6-9abc-c0b35ba08e85", 114 | "metadata": {}, 115 | "outputs": [ 116 | { 117 | "data": { 118 | "text/plain": [ 119 | "'2030-01-01'" 120 | ] 121 | }, 122 | "execution_count": 4, 123 | "metadata": {}, 124 | "output_type": "execute_result" 125 | } 126 | ], 127 | "source": [ 128 | "(def date (datetime.datetime/fromisoformat \"2030-01-01\"))\n", 129 | "(. date strftime \"%Y-%m-%d\")" 130 | ] 131 | }, 132 | { 133 | "cell_type": "code", 134 | "execution_count": 5, 135 | "id": "d037b257-d404-4b52-b03e-d8030f2ae07c", 136 | "metadata": {}, 137 | "outputs": [ 138 | { 139 | "data": { 140 | "text/plain": [ 141 | "'2030-01-01'" 142 | ] 143 | }, 144 | "execution_count": 5, 145 | "metadata": {}, 146 | "output_type": "execute_result" 147 | } 148 | ], 149 | "source": [ 150 | "(.strftime date \"%Y-%m-%d\")" 151 | ] 152 | }, 153 | { 154 | "cell_type": "code", 155 | "execution_count": 6, 156 | "id": "3d954ecf-eb94-4437-a6bc-f5582ddf317d", 157 | "metadata": {}, 158 | "outputs": [ 159 | { 160 | "data": { 161 | "text/plain": [ 162 | "2030" 163 | ] 164 | }, 165 | "execution_count": 6, 166 | "metadata": {}, 167 | "output_type": "execute_result" 168 | } 169 | ], 170 | "source": [ 171 | "(.- date year) " 172 | ] 173 | }, 174 | { 175 | "cell_type": "code", 176 | "execution_count": 7, 177 | "id": "a0f97754-8b5a-43aa-b2d8-d0e91b7afd90", 178 | "metadata": {}, 179 | "outputs": [ 180 | { 181 | "data": { 182 | "text/plain": [ 183 | "2030" 184 | ] 185 | }, 186 | "execution_count": 7, 187 | "metadata": {}, 188 | "output_type": "execute_result" 189 | } 190 | ], 191 | "source": [ 192 | "(.-year date)" 193 | ] 194 | }, 195 | { 196 | "cell_type": "markdown", 197 | "id": "1f0995ed-5c48-40ab-8fb3-b155f972e9ff", 198 | "metadata": {}, 199 | "source": [ 200 | "### Keyword Arguments" 201 | ] 202 | }, 203 | { 204 | "cell_type": "code", 205 | "execution_count": 8, 206 | "id": "d39b9bff-494f-4aca-a3de-09ebbc79715d", 207 | "metadata": {}, 208 | "outputs": [ 209 | { 210 | "data": { 211 | "text/plain": [ 212 | "datetime.datetime(2010, 1, 5, 0, 0)" 213 | ] 214 | }, 215 | "execution_count": 8, 216 | "metadata": {}, 217 | "output_type": "execute_result" 218 | } 219 | ], 220 | "source": [ 221 | "(.replace date ** :year 2010 :day 5)" 222 | ] 223 | }, 224 | { 225 | "cell_type": "markdown", 226 | "id": "12a13a0d-b63c-4042-86ef-0d89ee2b6de6", 227 | "metadata": {}, 228 | "source": [ 229 | "### Basilisp Functions with Keyword Arguments" 230 | ] 231 | }, 232 | { 233 | "cell_type": "code", 234 | "execution_count": 9, 235 | "id": "0c3e5dd9-2ea2-49b6-a077-136b6d75b000", 236 | "metadata": {}, 237 | "outputs": [ 238 | { 239 | "data": { 240 | "text/plain": [ 241 | "30" 242 | ] 243 | }, 244 | "execution_count": 9, 245 | "metadata": {}, 246 | "output_type": "execute_result" 247 | } 248 | ], 249 | "source": [ 250 | ";; :apply strategy (keyword arguments are applied to the fn)\n", 251 | "(defn ^{:kwargs :apply} area-calculate \n", 252 | " [scale-factor & {:keys [length width] :as kwargs}]\n", 253 | " (* scale-factor (* length width)))\n", 254 | "\n", 255 | "(area-calculate 2 ** :length 5 :width 3)" 256 | ] 257 | }, 258 | { 259 | "cell_type": "code", 260 | "execution_count": 10, 261 | "id": "bf6c46c4-393a-4c8d-b157-bcb4fdef03e2", 262 | "metadata": {}, 263 | "outputs": [ 264 | { 265 | "data": { 266 | "text/plain": [ 267 | "45" 268 | ] 269 | }, 270 | "execution_count": 10, 271 | "metadata": {}, 272 | "output_type": "execute_result" 273 | } 274 | ], 275 | "source": [ 276 | ";; :collect strategy (keyword arguments are passed as a last argument map)\n", 277 | "(defn ^{:kwargs :collect} area-calculate-2 \n", 278 | " [scale-factor {:keys [length width] :as kwargs}]\n", 279 | " (* scale-factor (* length width)))\n", 280 | "\n", 281 | "(area-calculate-2 3 ** :length 5 :width 3)" 282 | ] 283 | }, 284 | { 285 | "cell_type": "markdown", 286 | "id": "4b1404c1-442b-4d0b-90c7-b9782463614b", 287 | "metadata": {}, 288 | "source": [ 289 | "### Type Hinting" 290 | ] 291 | }, 292 | { 293 | "cell_type": "code", 294 | "execution_count": 11, 295 | "id": "c86c51a2-5476-4186-9c3a-d3460bdc05eb", 296 | "metadata": {}, 297 | "outputs": [ 298 | { 299 | "data": { 300 | "text/plain": [ 301 | "128" 302 | ] 303 | }, 304 | "execution_count": 11, 305 | "metadata": {}, 306 | "output_type": "execute_result" 307 | } 308 | ], 309 | "source": [ 310 | "(def ^python/str s \"a string\")\n", 311 | "\n", 312 | "(defn upper\n", 313 | " ^python/str [^python/str s]\n", 314 | " (.upper s))\n", 315 | "\n", 316 | "(let [^python/int i 64]\n", 317 | " (* i 2))" 318 | ] 319 | } 320 | ], 321 | "metadata": { 322 | "kernelspec": { 323 | "display_name": "Basilisp", 324 | "language": "clojure", 325 | "name": "basilisp" 326 | }, 327 | "language_info": { 328 | "file_extension": ".lpy", 329 | "mimetype": "text/x-clojure", 330 | "name": "clojure" 331 | } 332 | }, 333 | "nbformat": 4, 334 | "nbformat_minor": 5 335 | } 336 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "hatchling>=1.10.0", "ipykernel" 4 | ] 5 | build-backend = "hatchling.build" 6 | 7 | [project] 8 | name = "basilisp-kernel" 9 | dynamic = [ 10 | "version", 11 | ] 12 | description = "Basilisp kernel for Jupyter" 13 | readme = "README.md" 14 | license = { file = "LICENSE" } 15 | authors = [ 16 | { name = "ikappaki"} 17 | ] 18 | classifiers = [ 19 | "License :: OSI Approved :: BSD License", 20 | "Programming Language :: Python :: 3", 21 | ] 22 | requires-python = ">=3.9" 23 | dependencies = [ 24 | "ipykernel", 25 | "jupyter_client", 26 | "basilisp>=0.3.2", 27 | "basilisp-nrepl-async>=0.1.0", 28 | ] 29 | 30 | [project.optional-dependencies] 31 | test = [ 32 | "jupyter_kernel_test", 33 | ] 34 | 35 | [tool.hatch.envs.test] 36 | dependencies = [ 37 | "basilisp-pprint", 38 | "jupyter_client", 39 | "jupyter_kernel_test", 40 | "nbclient", 41 | "nbformat", 42 | "pytest", 43 | ] 44 | 45 | [project.urls] 46 | Homepage = "https://github.com/ikappaki/basilisp-kernel" 47 | 48 | [tool.hatch.version] 49 | path = "basilisp_kernel/__init__.py" 50 | 51 | # Used to call hatch_build.py 52 | [tool.hatch.build.hooks.custom] 53 | 54 | 55 | [tool.hatch.build.targets.sdist] 56 | include = [ 57 | "/basilisp_kernel", 58 | ] 59 | 60 | [tool.hatch.build.targets.wheel.shared-data] 61 | "data_kernelspec/share" = "share" 62 | -------------------------------------------------------------------------------- /test_basilisp.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example use of jupyter_kernel_test, with tests for the default python3 kernel 3 | (IPyKernel). This includes all the currently available tests. 4 | """ 5 | 6 | import unittest 7 | 8 | import jupyter_kernel_test as jkt 9 | 10 | 11 | class BasilispKernelTests(jkt.KernelTests): 12 | 13 | # REQUIRED 14 | 15 | # the kernel to be tested 16 | # this is the normally the name of the directory containing the 17 | # kernel.json file - you should be able to do 18 | # `jupyter console --kernel KERNEL_NAME` 19 | kernel_name = "basilisp" 20 | 21 | # Everything else is OPTIONAL 22 | 23 | # the name of the language the kernel executes 24 | # checked against language_info.name in kernel_info_reply 25 | language_name = "clojure" 26 | 27 | # the normal file extension (including the leading dot) for this language 28 | # checked against language_info.file_extension in kernel_info_reply 29 | file_extension = ".lpy" 30 | 31 | # code which should write the exact string `hello, world` to STDOUT 32 | code_hello_world = "(println \"hello, world\")" 33 | 34 | # code which should cause (any) text to be written to STDERR 35 | code_stderr = "(binding [*out* *err*] (println \"hi\"))" 36 | 37 | # samples for the autocompletion functionality 38 | # for each dictionary, `text` is the input to try and complete, and 39 | # `matches` the list of all complete matching strings which should be found 40 | completion_samples = [ 41 | { 42 | "text": "[abc print", 43 | "matches": {"printf", "println", "println-str", "print", "print-str"}, 44 | }, 45 | { 46 | "text": "(import sys)\n(sys/int", 47 | "matches": {"sys/intern", "sys/int_info"}, 48 | }, 49 | 50 | ] 51 | 52 | # code which should generate a (user-level) error in the kernel, and send 53 | # a traceback to the client 54 | code_generate_error = "(throw (Exception. \"based\"))" 55 | 56 | # a statement or block of code which generates a result (which is shown 57 | # as Out[n] instead of just something printed to stdout) 58 | # running each `code` should cause `result` to be displayed (note that the 59 | # result here will always be a string representation of whatever the actual 60 | # result type is - be careful of string formatting) 61 | code_execute_result = [ 62 | {"code": "(+ 1 2 3)", "result": "6"}, 63 | {"code": "(map #(* % %) (range 1 4))", "result": "(1 4 9)"}, 64 | ] 65 | 66 | def test_basilisp_kernel_exceptions(self): 67 | reply, output_msgs = self.execute_helper(code='#lmn') 68 | self.assertEqual("SyntaxError", output_msgs[0]['content']['ename']) 69 | self.assertIn("message: No data reader found for tag #lmn", output_msgs[0]['content']['evalue']) 70 | 71 | reply, output_msgs = self.execute_helper(code='(xyz)') 72 | self.assertEqual("CompilerException", output_msgs[0]['content']['ename']) 73 | self.assertIn("message: unable to resolve symbol 'xyz' in this context", output_msgs[0]['content']['evalue']) 74 | 75 | reply, output_msgs = self.execute_helper(code='(/ 2 0)') 76 | self.assertEqual("Exception", output_msgs[0]['content']['ename']) 77 | self.assertIn("Traceback (most recent call last):", output_msgs[0]['content']['evalue']) 78 | 79 | # code which generates some sort of rich output 80 | # for each `code` input a single rich display object with the specified 81 | # `mime` type should be sent to the frontend 82 | # note that this expects a `display_data` message rather than 83 | # `execute_result`; this test might be a little too inflexible in some cases 84 | # code_display_data = [ 85 | # { 86 | # "code": "(import [IPython.display :as d]) (println (d/display (d/HTML \"A\")))", 87 | # # "code": "from IPython.display import HTML, display; display(HTML('test'))", 88 | # "mime": "text/html", 89 | # }, 90 | # { 91 | # "code": "from IPython.display import Math, display; display(Math('\\frac{1}{2}'))", 92 | # "mime": "text/latex", 93 | # }, 94 | # ] 95 | 96 | 97 | if __name__ == "__main__": 98 | unittest.main() 99 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikappaki/basilisp-kernel/b2dd50bd1830bd2d12fb554de34cc7f895140030/tests/__init__.py -------------------------------------------------------------------------------- /tests/basilisp_kernel/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikappaki/basilisp-kernel/b2dd50bd1830bd2d12fb554de34cc7f895140030/tests/basilisp_kernel/__init__.py -------------------------------------------------------------------------------- /tests/basilisp_kernel/integration/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikappaki/basilisp-kernel/b2dd50bd1830bd2d12fb554de34cc7f895140030/tests/basilisp_kernel/integration/__init__.py -------------------------------------------------------------------------------- /tests/basilisp_kernel/integration/notebook_test.lpy: -------------------------------------------------------------------------------- 1 | (ns tests.basilisp-kernel.integration.notebook-test 2 | (:require 3 | [basilisp-kernel.nrepl-server :as nr] 4 | [tests.basilisp-kernel.nrepl-server-test :as nt] 5 | [basilisp.io :as bio] 6 | [basilisp.contrib.bencode :as bc] 7 | [basilisp-pprint.pprint :as pp] 8 | [basilisp.test :refer [deftest are is testing use-fixtures]] 9 | [basilisp.test.fixtures :as fixtures :refer [*tempdir*]]) 10 | (:import [jupyter_client :as jc] 11 | nbformat 12 | nbclient)) 13 | 14 | (use-fixtures :each fixtures/tempdir) 15 | 16 | (defn notebook-make 17 | "Writes the `FORMS` to a `nbformat.v4/new-notebook` and returns it." 18 | [forms] 19 | (let [nb (nbformat.v4/new-notebook)] 20 | (doseq [form forms] 21 | (-> nb 22 | .-cells 23 | (.append (nbformat.v4/new-code-cell (str form))))) 24 | nb)) 25 | 26 | (deftest notebook-execute-test 27 | (let [nb (notebook-make ['(+ 4 9)]) 28 | km (jc/KernelManager ** :kernel-name "basilisp") 29 | client (nbclient/NotebookClient nb km)] 30 | 31 | (try 32 | (with [_ (.setup-kernel client)] 33 | (let [{:keys [cells metadata] :as all} (py->lisp (.execute client))] 34 | (is (= {:language_info 35 | {:mimetype "text/x-clojure", 36 | :name "clojure", 37 | :file_extension ".lpy"}} 38 | metadata)) 39 | 40 | (is (= [{:cell_type "code", 41 | :outputs 42 | [{:metadata {}, 43 | :output_type "execute_result", 44 | :execution_count 1, 45 | :data {(keyword nil "text/plain") "13"}}] 46 | :execution_count 1, 47 | :source "(+ 4 9)",}] 48 | (for [cell cells] 49 | (reduce dissoc cell [:id :metadata])))))) 50 | (finally 51 | (when (.is-alive km) (.shutdown-kernel km ** :now true)))))) 52 | #_(pp/pprint (notebook-execute-test)) 53 | 54 | (def ^:dynamic *notebook* 55 | "It will be bound to the new notebook that `with-notebook` created." 56 | nil) 57 | 58 | (def ^:dynamic *client* 59 | "It Will be bound to the `nbclient/NotebookClient` connected to the 60 | Notebook that `with-notebook` created." 61 | nil) 62 | 63 | 64 | (defmacro with-notebook 65 | "Writes `CELLS` to a new `*notebook*` loaded in a `basilisp` kernel 66 | connected to a NoteBook `*client*`. It executes `BODY` in that 67 | context." 68 | [cells & body] 69 | `(let [nb# (notebook-make ~cells) 70 | km# (jc/KernelManager ~'** :kernel-name "basilisp") 71 | client# (nbclient/NotebookClient nb# km#)] 72 | 73 | (try 74 | (with [_ (.setup-kernel client#)] 75 | (binding [*notebook* nb# 76 | *client* client#] 77 | ~@body)) 78 | (finally 79 | (when (.is-alive km#) (.shutdown-kernel km# ~'** :now true)))))) 80 | 81 | #_(pp/pprint (macroexpand-1 '(with-notebook []))) 82 | 83 | (deftest with-notebook-test 84 | (with-notebook '[(+ 19 20)] 85 | (is *notebook*) 86 | (is *client*) 87 | (let [{:keys [cells]} (py->lisp (.execute *client*))] 88 | (is (= {(keyword nil "text/plain") "39"} (get-in cells [0 :outputs 0 :data])) cells)))) 89 | #_(pp/pprint (with-notebook-test)) 90 | 91 | (defn code-output-text-get 92 | [cell] 93 | (get-in cell [:outputs 0 :data (keyword nil "text/plain")])) 94 | 95 | (defn cell-output-get 96 | "Returns the `CELL`'s :outputs in the following format according to their `output_type`: 97 | 98 | ``` 99 | \"stream\" {:name name1 :text text1} ... -> :stream {:name1 text :name2 text 100 | 101 | \"execute_result\" {:data {:text/plain text}} -> :result {:text text} 102 | ```" 103 | [cell] 104 | (reduce (fn [acc {:keys [output_type] :as output}] 105 | ;; (pp/pprint output) 106 | (case output_type 107 | "stream" 108 | (let [{nm :name text :text} output] 109 | (assoc-in acc [:stream nm] text)) 110 | "execute_result" 111 | (let [text (get-in output [:data (keyword nil "text/plain")])] 112 | (assoc-in acc [:result :text] text)) 113 | )) 114 | {} (:outputs cell))) 115 | 116 | (deftest basilisp-notebook-test 117 | (testing "multiple cells with cross references" 118 | (with-notebook '[(+ 19 20) 119 | (def abc (atom 5)) 120 | @abc] 121 | (let [{:keys [cells]} (py->lisp (.execute *client*))] 122 | (are [i result] (= result (cell-output-get (aget cells i))) 123 | 0 {:result {:text "39"}} 124 | 1 {:result {:text "#'user/abc"}} 125 | 2 {:result {:text "5"}}))))) 126 | #_(pp/pprint (basilisp-notebook-test)) 127 | 128 | 129 | (deftest nrepl-server-test 130 | (testing "starting server in notebook and remote changing atom" 131 | (with-notebook (concat [(list 'def 'tempdir *tempdir*)] 132 | '[(require '[basilisp-kernel.nrepl-server :as bkns]) 133 | (def value* (atom 0)) 134 | (def server (bkns/server-start {:dir tempdir})) 135 | (:port server) 136 | (deref value*) 137 | 138 | (deref value*)]) 139 | (let [cells (.-cells *notebook*) 140 | [out-tempdir _out-req out-value* out-server out-port out-value :as executed] 141 | (map-indexed (fn [i cell] 142 | (-> (.execute-cell *client* cell ** :cell-index i) 143 | py->lisp 144 | cell-output-get)) 145 | (butlast cells))] 146 | (is (= {:text "#'user/tempdir"} (:result out-tempdir)) executed) 147 | (is (= {:text "#'user/value*"} (:result out-value*)) executed) 148 | (let [port-txt (get-in out-port [:result :text]) 149 | port (int port-txt)] 150 | (is (int? port) out-port) 151 | (nt/with-connect port 152 | (is (= ["done"] (nt/client-do nt/*nrepl-client* 1 :clone))) 153 | (is (= {:value (pr-str *tempdir*) :status ["done"]} (nt/client-do nt/*nrepl-client* 2 :eval "tempdir"))) 154 | (is (= {:value "5" :status ["done"]} (nt/client-do nt/*nrepl-client* 2 :eval "(+ 2 3)"))) 155 | 156 | (is (= {:text "0"} (:result out-value))) 157 | 158 | (is (= {:value "1" :status ["done"]} (nt/client-do nt/*nrepl-client* 3 :eval "(swap! value* inc)"))) 159 | 160 | (let [out-value-after (-> (.execute-cell *client* (last cells) ** :cell-index 6) 161 | py->lisp 162 | cell-output-get)] 163 | (is (= {:text "1"} (:result out-value-after)) out-value-after)) 164 | ) 165 | ) 166 | ) 167 | ))) 168 | #_(pp/pprint (nrepl-server-test)) 169 | 170 | -------------------------------------------------------------------------------- /tests/basilisp_kernel/nrepl_server_test.lpy: -------------------------------------------------------------------------------- 1 | (ns tests.basilisp-kernel.nrepl-server-test 2 | (:require 3 | [basilisp-kernel.nrepl-server :as nr] 4 | [basilisp.io :as bio] 5 | [basilisp.contrib.bencode :as bc] 6 | ;; [basilisp-pprint.pprint :as pp] 7 | [basilisp.test :refer [deftest are is testing use-fixtures]] 8 | [basilisp.test.fixtures :as fixtures :refer [*tempdir*]] 9 | ) 10 | (:import asyncio 11 | os 12 | socket)) 13 | 14 | (use-fixtures :each fixtures/tempdir) 15 | 16 | (defn client-make! 17 | "Creates and returns an nREPL client." 18 | [] 19 | (let [sock (socket/socket socket/AF_INET socket/SOCK_STREAM)] 20 | {:sock sock 21 | :backlog* (atom {:items [] :fraction nil}) 22 | :last-id* (atom 0)})) 23 | 24 | (defn client-close! 25 | "Closes the nREPL `CLIENT` connection." 26 | [client] 27 | (.close (:sock client))) 28 | 29 | (defn client-send! 30 | "Sends `VALUE` (a map) to the nREPL server from the given `CLIENT`." 31 | [client value] 32 | (let [{:keys [sock last-id*]} client 33 | id (swap! last-id* inc) 34 | v (bc/encode (assoc value :id id))] 35 | (.sendall sock v))) 36 | 37 | (defn client-recv! 38 | "Receives data from the server via `client`. Processes any backlog or 39 | incoming data, decodes it, and returns the first item. Updates the 40 | backlog with remaining data." 41 | [client] 42 | (let [{:keys [sock backlog*]} client 43 | zero-bytes #b ""] 44 | (loop [{:keys [items fraction]} @backlog*] 45 | (if-let [item (first items)] 46 | (do (reset! backlog* {:items (drop 1 items) :fraction fraction}) 47 | item) 48 | 49 | (let [data (.recv sock 8192)] 50 | (if (= data zero-bytes) 51 | (throw (python/IOError (str [:client-recv!-error :socket-closed]))) 52 | (let [data (if fraction (+ fraction data) data) 53 | [items-d remaining :as response] (bc/decode-all data {:keywordize-keys true 54 | :string-fn #(.decode % "utf-8")}) 55 | items (concat items items-d) 56 | [item & _items-left] items] 57 | (recur {:items items :fraction remaining})))))))) 58 | 59 | (def ^:dynamic *nrepl-client* 60 | "Will be bound to the nREPL client `with-connect` is connected to the 61 | nREPL server with." 62 | nil) 63 | 64 | (defmacro with-connect 65 | "A macro that establishes a connection to the specified `port` on 66 | the local interface using a new nREPL client. It binds the client to 67 | `*nrepl-client*` and executes the `body` forms within this context." 68 | [port & body] 69 | `(let [{sock# :sock :as client#} (client-make!)] 70 | (if-not ~port 71 | (throw (python/ValueError [:with-connect-error :invalid-port ~port])) 72 | 73 | (try 74 | (binding [*nrepl-client* client#] 75 | (.connect sock# (python/tuple ["127.0.0.1" ~port])) 76 | (.settimeout sock# 1) 77 | ~@body) 78 | (finally 79 | (client-close! client#)))))) 80 | 81 | (defn client-do 82 | [client id op & args] 83 | (case op 84 | :clone 85 | (do (client-send! client {:id id :op "clone"}) 86 | (let [{:keys [status]} (client-recv! client)] 87 | status)) 88 | :eval 89 | (do (client-send! client {:id id :op "eval" :code (first args)}) 90 | (let [{:keys [value]} (client-recv! client) 91 | {:keys [status ex] :as all} 92 | (client-recv! client)] 93 | (cond-> {:value value :status status} 94 | ex 95 | (assoc :ex ex)))))) 96 | 97 | (defasync server-run 98 | [opts server*] 99 | (let [{:keys [_task] :as server} (nr/server-start opts)] 100 | (deliver server* server) 101 | (await _task))) 102 | 103 | 104 | (deftest server-run-test 105 | (let [server* (promise) 106 | fut (future 107 | (asyncio/run (server-run {} server*))) 108 | {:keys [error host nrepl-port-file port shutdown!] :as server} (deref server* 1000 ::timed-out)] 109 | (is (not= ::timed-out server)) 110 | (is (nil? error) error) 111 | (is (= host "127.0.0.1")) 112 | (is (os.path/exists nrepl-port-file )) 113 | (is port server) 114 | (is shutdown! server) 115 | (is (not= :timed-out (deref server* 100 :timed-out))) 116 | (try 117 | (with-connect port 118 | (is *nrepl-client*) 119 | (is (= ["done"] (client-do *nrepl-client* 1 :clone))) 120 | (is (= {:value "4" :status ["done"]} (client-do *nrepl-client* 2 :eval "(+ 1 3)"))) 121 | (nr/server-shut server) 122 | (is (thrown? python/IOError (client-do *nrepl-client* 3 :eval "(+ 1 4)")))) 123 | (finally 124 | (when shutdown! (shutdown!)))))) 125 | 126 | #_(pp/pprint (server-run-test)) 127 | 128 | (def ^:dynamic *nrepl-server* 129 | nil) 130 | 131 | (defmacro with-server 132 | [opts & body] 133 | `(let [server*# (promise) 134 | fut (future 135 | (asyncio/run (server-run ~opts server*#))) 136 | {port# :port 137 | shutdown!# :shutdown! :as server#} 138 | (deref server*# 1000 :timed-out)] 139 | (try 140 | (binding [*nrepl-server* @server*#] 141 | ~@body) 142 | (finally (when shutdown!# (shutdown!#)))))) 143 | 144 | (deftest with-server-test 145 | (with-server {} 146 | (let [{:keys [error port]} *nrepl-server*] 147 | (is (nil? error) error) 148 | (with-connect port 149 | (is (= ["done"] (client-do *nrepl-client* 1 :clone))) 150 | (is (= {:value "4" :status ["done"]} (client-do *nrepl-client* 2 :eval "(+ 1 3)"))))))) 151 | 152 | #_(with-server-test) 153 | 154 | (deftest nrepl-server-async-test 155 | (testing "host option" 156 | (with-server {:host "0.0.0.0"} 157 | (let [{:keys [host]} *nrepl-server*] 158 | (is (= "0.0.0.0" host))))) 159 | 160 | (testing "port option" 161 | (with-server {:port -1} 162 | (let [{:keys [error ]} *nrepl-server*] 163 | (is (= "(:server-make-error OverflowError('bind(): port must be 0-65535.'))" (str error)))))) 164 | 165 | (testing "nrepl-port-dir option" 166 | (with-server {:dir *tempdir*} 167 | (let [{:keys [nrepl-port-file]} *nrepl-server*] 168 | (is (= (str (bio/path *tempdir* ".nrepl-port")) nrepl-port-file)))))) 169 | 170 | #_(pp/pprint (nrepl-server-async-test)) 171 | --------------------------------------------------------------------------------