├── .gitattributes ├── .github └── FUNDING.yml ├── .gitignore ├── .gitlab-ci.yml ├── ABOUT.org ├── CONTRIBUTING.markdown ├── Dockerfile ├── Makefile ├── README.md ├── build-config.lisp ├── build-image.lisp ├── check-asdf-version.lisp ├── ciel.asd ├── docs ├── .nojekyll ├── CNAME ├── FAQ.md ├── README.md ├── _coverpage.md ├── _navbar.md ├── _sidebar.md ├── after-plus.jpeg ├── alexandria-control-flow.md ├── alexandria.md ├── assets │ └── vgplot.png ├── before.jpeg ├── dependencies.md ├── index.html ├── install.md ├── language-extensions.md ├── libraries.md ├── repl.md ├── scripting.md ├── see-also.md ├── serapeum.md └── trivial-types.md ├── find-dependencies.lisp ├── repl-utils.lisp ├── repl.lisp ├── scripting.lisp ├── src ├── ciel.lisp ├── csv.lisp ├── gui.lisp ├── json-pointer-minus.lisp ├── more-docstrings │ ├── README.md │ ├── docstrings.lisp │ └── more-docstrings.asd ├── packages.lisp └── scripts │ ├── README.md │ ├── apipointer.lisp │ ├── finder.lisp │ ├── quicksearch.lisp │ ├── simpleHTTPserver.lisp │ ├── webapp-notify.lisp │ └── webapp.lisp └── utils.lisp /.gitattributes: -------------------------------------------------------------------------------- 1 | + docs/serapeum.md linguist-vendored 2 | + docs/dependencies.md linguist-vendored 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/en/github/administering-a-repository/displaying-a-sponsor-button-in-your-repository#displaying-a-sponsor-button-in-your-repository 2 | 3 | github: [vindarel,] # we can put 4 handles here. 4 | # we can only put 1 handle below: 5 | ko_fi: vindarel 6 | liberapay: vindarel 7 | # and again, up to 4 custom links: 8 | # custom: [link1, …, link4] 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.fasl 2 | ciel 3 | ciel-core 4 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | build debian: 2 | stage: build 3 | 4 | # Build on Debian and SBCL. 5 | # We want to build CIEL in a sufficiently old Debian version: 6 | # the glibc will be compatible with lots of Debian and Ubuntu systems, 7 | # however building on the latest (Bullseye) would lead to incompatible glibc errors 8 | # when running on newer systems. 9 | # Buster: released in July, 2019. 10 | # Bullseye: released in August, 2021, supported until July, 2024. 11 | image: clfoundation/sbcl:2.1.5-buster 12 | 13 | # We need to install some system dependencies, 14 | # to clone libraries not in Quicklisp, 15 | # and to update ASDF to >= 3.3.5 in order to use local-package-nicknames. 16 | before_script: 17 | - apt-get update -qy 18 | - apt-get install -y git-core tar 19 | 20 | # The image doesn't have Quicklisp installed by default. 21 | - QUICKLISP_ADD_TO_INIT_FILE=true /usr/local/bin/install-quicklisp 22 | 23 | # Upgrade ASDF (UIOP) to 3.3.5 because we want package-local-nicknames. 24 | - mkdir -p ~/common-lisp/asdf/ 25 | - ( cd ~/common-lisp/ && wget https://asdf.common-lisp.dev/archives/asdf-3.3.5.tar.gz && tar -xvf asdf-3.3.5.tar.gz && mv asdf-3.3.5 asdf ) 26 | - echo "Content of ~/common-lisp/asdf/:" && ls ~/common-lisp/asdf/ 27 | 28 | # Install system dependencies 29 | - make debian-deps 30 | # Clone upstream QL libraries. 31 | - make ql-deps 32 | script: 33 | # build a ciel binary on cwd: 34 | - make build 35 | 36 | artifacts: 37 | name: "ciel" 38 | paths: 39 | - ciel 40 | 41 | build void: 42 | stage: build 43 | # Use custom docker image since the official ones 44 | # can't be used in gitlab CI pipelines 45 | image: cinerion/ciel-sbcl-voidlinux 46 | 47 | # We need to install some system dependencies, 48 | # to clone libraries not in Quicklisp, 49 | # and to update ASDF to >= 3.3.5 in order to use local-package-nicknames. 50 | before_script: 51 | - xbps-install -S 52 | - xbps-install -uy xbps 53 | - xbps-install -uy 54 | 55 | # The image doesn't have Quicklisp installed by default. 56 | - QUICKLISP_ADD_TO_INIT_FILE=true /usr/local/bin/install-quicklisp 57 | 58 | # Upgrade ASDF (UIOP) to 3.3.5 because we want package-local-nicknames. 59 | - mkdir -p ~/common-lisp/asdf/ 60 | - ( cd ~/common-lisp/ && wget https://asdf.common-lisp.dev/archives/asdf-3.3.5.tar.gz && tar -xvf asdf-3.3.5.tar.gz && mv asdf-3.3.5 asdf ) 61 | - echo "Content of ~/common-lisp/asdf/:" && ls ~/common-lisp/asdf/ 62 | 63 | # Clone upstream QL libraries. 64 | - make ql-deps 65 | script: 66 | # build a ciel binary on cwd: 67 | - make build 68 | 69 | artifacts: 70 | name: "ciel" 71 | paths: 72 | - ciel 73 | -------------------------------------------------------------------------------- /ABOUT.org: -------------------------------------------------------------------------------- 1 | 2 | * vindarel 3 | 4 | I got hooked into Lisp circa 2017, after around ten years in working 5 | in Python and JS. I contribute to collaborative resources (the Common 6 | Lisp Coobook) and I am now running an open-source web application in 7 | production©. 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.markdown: -------------------------------------------------------------------------------- 1 | 2 | Thanks for contributing to CIEL. We hope it is useful to you and will 3 | be now more useful to everybody. 4 | 5 | Please follow these short guidelines. They'll help the maintainer(s) 6 | craft release notes and they make for a clearer commits log, *IMO*. Thank you! 7 | 8 | ## Commit messages 9 | 10 | Please say at the beginning what your commit is changing: is it about 11 | dependencies? The Makefile, the .asd? The terminal/readline interface? 12 | 13 | ` README:` for the README 14 | - `docs:` is for documentation 15 | - `deps:` for the Lisp dependencies (be more explicit for system-wide dependencies) 16 | - `CI:` 17 | - `asd:` 18 | - `terminal`: for the terminal REPL 19 | 20 | Example: 21 | 22 | > deps: libmagic-dev is no more required 23 | 24 | If your change is about a domain, you can say it up front too. For example: 25 | 26 | > database: mention the need of db drivers for binaries 27 | 28 | If your change is adding or removing something, you can say this action up front. 29 | 30 | If your change is wider or doesn't fit here, don't think harder, just contribute. Thanks. 31 | 32 | 33 | ### Minor commit messages 34 | 35 | I like to see the `(minor)` mention when the change is really trivial 36 | and not worth looking at. Likewise, we can grep-it out from the 37 | release notes. 38 | 39 | For example: 40 | 41 | > (minor) add site icon 42 | 43 | > (minor) make run typo 44 | 45 | ## Avoid small and useless commits, squash them 46 | 47 | Please avoid small commits that say "fix" "fix" and again 48 | "fix". Squash them into one with a good commit message (see above), 49 | thank you. 50 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM clfoundation/sbcl:2.2.4 AS build 2 | 3 | WORKDIR /home 4 | 5 | RUN apt-get update \ 6 | && apt-get install -y libmagic-dev libc6-dev gcc wget git make 7 | 8 | COPY . . 9 | 10 | # install CIEL dependencies 11 | RUN mkdir -p ~/common-lisp \ 12 | && ( cd ~/common-lisp/ && wget https://asdf.common-lisp.dev/archives/asdf-3.3.5.tar.gz && tar -xvf asdf-3.3.5.tar.gz && mv asdf-3.3.5 asdf ) 13 | 14 | RUN QUICKLISP_ADD_TO_INIT_FILE=true /usr/local/bin/install-quicklisp 15 | 16 | RUN make ql-deps \ 17 | && make build \ 18 | && cp bin/* /usr/local/bin/ 19 | 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | LISP ?= sbcl 2 | 3 | all: build 4 | 5 | # can use bespoke dir like 'QLDIR=~/nostandard/local-projects make' 6 | QLDIR ?= $(HOME)/quicklisp/local-projects 7 | 8 | # make will exit early if git clone errors b/c dir already exists 9 | define git-clone-pull = 10 | if test -d $(QLDIR)/$(notdir $1); then cd $(QLDIR)/$(notdir $1) && git pull; else git clone $1 $(QLDIR)/$(notdir $1); fi 11 | endef 12 | 13 | $(QLDIR)/asdf: 14 | # 2024-08: building with older asdf fails 15 | # unrecognized define-package keyword :LOCAL-NICKNAMES 16 | # https://github.com/ciel-lang/CIEL/issues/58 17 | mkdir -p $(QLDIR) 18 | cd $(QLDIR) && \ 19 | curl -sL https://asdf.common-lisp.dev/archives/asdf-3.3.5.tar.gz | \ 20 | tar -xvzf - && \ 21 | mv asdf-3.3.5 asdf 22 | 23 | asdf: $(QLDIR)/asdf 24 | @echo "New ASDF version installed to " $(QLDIR) 25 | 26 | check-asdf-version: 27 | sbcl --script check-asdf-version.lisp || echo "Your ASDF version is too old. You can update it with 'make asdf'. It will be downloaded to " $(QLDIR) ". You can set QLDIR." 28 | 29 | # Install some Quicklisp dependencies. 30 | ql-deps: check-asdf-version 31 | # termp, little utility, it is NOT in Quicklisp as of <2025-02-02>. 32 | # I asked for inclusion in Quicklisp. 33 | $(call git-clone-pull,https://github.com/vindarel/termp) 34 | 35 | # 2023-11: The symbol SB-INT:TRULY-DYNAMIC-EXTENT is absent since at least 36 | # SBCL v2.3.10, which was required in older versions of cl-environments 37 | # and cl-form-types. 38 | # See issue https://github.com/ciel-lang/CIEL/issues/38 39 | # This has been fixed upstream, it is in Quicklisp 2024-08 40 | $(call git-clone-pull,https://github.com/alex-gutev/cl-environments) 41 | $(call git-clone-pull,https://github.com/alex-gutev/cl-form-types) 42 | 43 | # 2024-08: Moira needs moira/light, added <2023-11-23 Thu>, 44 | # it is in Quicklisp 2024-08 45 | # moira/light doesn't depend on Osicat. 46 | $(call git-clone-pull,https://github.com/ruricolist/moira) 47 | 48 | # 2024-08: simple progress bar, it is in Quicklisp 2024-08. 49 | $(call git-clone-pull,https://github.com/vindarel/progressons) 50 | 51 | # In Quicklisp 2024-08. 52 | $(call git-clone-pull,https://github.com/lisp-maintainers/file-finder) 53 | 54 | # <2024-08-30> error with SBCL: Lock on package SB-DI violated… 55 | # fixed https://github.com/Shinmera/dissect/issues/18 on March, 2024, in Quicklisp 2024-08. 56 | $(call git-clone-pull,https://github.com/Shinmera/dissect) 57 | 58 | # fix fset on latest SBCL 59 | # "Lock on package SB-EXT violated when interning ONCE-ONLY while in package FSET" 60 | # see https://github.com/slburson/fset/pull/46 61 | $(call git-clone-pull,https://gitlab.common-lisp.net/misc-extensions/misc-extensions) 62 | $(call git-clone-pull,https://github.com/slburson/fset) 63 | 64 | # Install some system dependencies. 65 | debian-deps: 66 | apt-get install -y libinotifytools0 67 | 68 | macos-deps: 69 | echo "please install fsevent (for file-notify)" 70 | 71 | run: 72 | $(LISP) --load ciel.asd \ 73 | --eval '(asdf:load-system :ciel)' \ 74 | --eval '(in-package :ciel-user)' 75 | 76 | run-repl: 77 | $(LISP) --load ciel.asd \ 78 | --eval '(asdf:load-system :ciel)' \ 79 | --eval '(asdf:load-system :ciel/repl)' \ 80 | --eval '(sbcli:repl)' 81 | 82 | image: 83 | $(LISP) --load build-image.lisp 84 | 85 | build: 86 | $(LISP) --non-interactive \ 87 | --eval '(ql:quickload "cl+ssl")' \ 88 | --load ciel.asd \ 89 | --eval '(ql:quickload :swank)' \ 90 | --eval '(ql:quickload :ciel)' \ 91 | --eval '(ql:quickload :ciel/repl)' \ 92 | --eval '(asdf:make :ciel/repl)' \ 93 | --eval '(quit)' 94 | 95 | gen-dependencies-list: 96 | ./find-dependencies.lisp > docs/dependencies.md 97 | 98 | serve-docs: 99 | docsify serve docs/ 100 | 101 | clean: 102 | rm ciel 103 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 |

CIEL

4 |

Common Lisp, batteries included.

5 |

6 | 7 |

8 | Home page | 9 | Issues | 10 | Discussions | 11 | Support us! | 12 | Buy me a coffee! 13 |

14 | 15 |

16 | 17 | # CIEL Is an Extended Lisp 18 | 19 | STATUS: ~~highly~~ WIP, the API WILL change, but it is usable. 20 | 21 | I am dogfooding it in public and private projects. 22 | 23 | 24 | ## What is this ? 25 | 26 | CIEL is a ready-to-use collection of libraries. 27 | 28 | It's Common Lisp, batteries included. 29 | 30 | It comes in 3 forms: 31 | 32 | - a binary, to run CIEL **scripts**: fast start-up times, standalone image, built-in utilities. 33 | - a simple full-featured **REPL** for the terminal. 34 | - a **Lisp library** and a **core image**. 35 | 36 | Questions, doubts? See the [FAQ](docs/FAQ.md). 37 | 38 | NEW: we now have a Docker file. 39 | 40 | ```lisp 41 | #!/usr/bin/env ciel 42 | 43 | (-> "https://fakestoreapi.com/products?limit=5" 44 | http:get 45 | json:read-json 46 | (elt 0) 47 | (access "title")) 48 | ``` 49 | 50 | ```bash 51 | $ chmodx +x getproduct.lisp 52 | $ time ./getproduct.lisp 53 | "Fjallraven - Foldsack No…ckpack, Fits 15 Laptops" 54 | ./getproduct.lisp 0.10s user 0.02s system 24% cpu 0.466 total 55 | ``` 56 | 57 | 58 | ## Rationale 59 | 60 | One of our goals is to make Common Lisp useful out of the box for 61 | mundane tasks -by today standards. As such, we ship libraries to handle 62 | **JSON** or **CSV**, as well as others to ease string 63 | manipulation, to do pattern matching, to bring regular expressions, for 64 | threads and jobs scheduling, for **HTTP** and URI handling, 65 | and so on. You can of course do all this 66 | without CIEL, but then you have to install the library manager first and 67 | load these libraries into your Lisp image every time you start it. Now, 68 | you have them at your fingertips whenever you start CIEL. 69 | 70 | We also aim to soften the irritating parts of standard Common Lisp. A 71 | famous one, puzzling for beginners and non-optimal for seasoned lispers, 72 | is the creation of hash-tables. We include the `dict` function from the 73 | Serapeum library (which we enhanced further with a pull request): 74 | 75 | CIEL-USER> (dict :a 1 :b 2 :c 3) 76 | 77 | which prints: 78 | 79 | ``` txt 80 | (dict 81 | :A 1 82 | :B 2 83 | :C 3 84 | ) 85 | ``` 86 | 87 | In standard Common Lisp, the equivalent is more convoluted: 88 | 89 | ``` commonlisp 90 | (let ((ht (make-hash-table :test 'equal))) 91 | (setf (gethash :a ht) 1) 92 | (setf (gethash :b ht) 2) 93 | (setf (gethash :c ht) 3) 94 | ht) 95 | ;; # 96 | ;; (and we don't get a readable representation, so our example is not even equivalent) 97 | ``` 98 | 99 | Moreover, we bring: 100 | 101 | - a **full featured REPL on the terminal** and 102 | - **scripting capabilities**, see more below. 103 | 104 | See *the documentation*. 105 | 106 | 107 | **Table of Contents** 108 | 109 | - [CIEL Is an Extended Lisp](#ciel-is-an-extended-lisp) 110 | - [What is this ?](#what-is-this-) 111 | - [Rationale](#rationale) 112 | - [Install](#install) 113 | - [Download a binary. For scripting and the custom REPL.](#download-a-binary-for-scripting-and-the-custom-repl) 114 | - [Build](#build) 115 | - [Dependencies](#dependencies) 116 | - [System dependencies](#system-dependencies) 117 | - [ASDF >= 3.3.4 (local-nicknames)](#asdf--334-local-nicknames) 118 | - [Install Quicklisp](#install-quicklisp) 119 | - [Install our Lisp dependencies [depends on your Quicklisp version]](#install-our-lisp-dependencies-depends-on-your-quicklisp-version) 120 | - [How to load CIEL with Quicklisp](#how-to-load-ciel-with-quicklisp) 121 | - [How to build a CIEL binary and a core image](#how-to-build-a-ciel-binary-and-a-core-image) 122 | - [Docker](#docker) 123 | - [Usage](#usage) 124 | - [Scripting](#scripting) 125 | - [Terminal REPL](#terminal-repl) 126 | - [CIEL as a library: "use" :ciel in your Lisp systems](#ciel-as-a-library-use-ciel-in-your-lisp-systems) 127 | - [Core image: configure your editor](#core-image-configure-your-editor) 128 | - [Libraries](#libraries) 129 | - [Language extensions](#language-extensions) 130 | - [Final words](#final-words) 131 | - [Misc: how to generate the documentation](#misc-how-to-generate-the-documentation) 132 | - [Contributors](#contributors) 133 | - [Lisp?!](#lisp) 134 | 135 | 136 | 137 | 138 | # Install 139 | 140 | ## Download a binary. For scripting and the custom REPL. 141 | 142 | Getting a binary allows you to run scripts, to play around in its 143 | terminal readline REPL. A binary doesn't allow you to use CIEL in your 144 | existing Common Lisp editor (which still offers the most interactive 145 | and fast development experience). 146 | 147 | To download a CIEL binary: 148 | 149 | - check our releases on https://github.com/ciel-lang/CIEL/releases/ 150 | - we provide a binary from a CI for some systems: go to 151 | , download the latest 152 | artifacts, unzip the `ciel-v0-{platform}.zip` archive and run `ciel-v0-{platform}/ciel`. 153 | 154 | CIEL is currently built for the following platforms: 155 | 156 | | Platform | System Version (release date) | 157 | |----------|-------------------------------| 158 | | debian | Debian Buster (2019) | 159 | | void | Void Linux glibc (2023-05), using [cinerion's Docker image](https://github.com/cinerion/sbcl-voidlinux-docker) | 160 | 161 | 162 | Start it with `./ciel`. 163 | 164 | With no arguments, you enter CIEL's terminal REPL. 165 | 166 | You can give a CIEL script as first argument, or call a built-in one. See the scripting section. 167 | 168 | # Build 169 | 170 | To build CIEL, both the binary and the core image, you need a couple 171 | system dependencies and you have to check a couple things on the side 172 | of lisp before proceeding. 173 | 174 | ## Dependencies 175 | 176 | ### System dependencies 177 | 178 | You will probably need the following system dependencies (names for a 179 | Debian Bullseye system): 180 | 181 | zlib1g-dev # from deploy for SBCL < 2.2.6 182 | 183 | If your SBCL version is >= 2.2.6 you might want to use the more 184 | performant `libzstd-dev` library instead of `zlib1g-dev`. 185 | 186 | libzstd-dev # from deploy for SBCL >= 2.2.6 187 | 188 | On Linux: 189 | 190 | inotify-tools 191 | 192 | On MacOS: 193 | 194 | fsevent 195 | 196 | You can run: `make debian-deps` or `make macos-deps`. 197 | 198 | 199 | ### ASDF >= 3.3.4 (local-nicknames) 200 | 201 | ASDF is the de-facto system definition facility of Common Lisp, that 202 | lets you define your system's metadata (author, dependencies, sources, 203 | modules…). 204 | 205 | Please ensure that you have ASDF >= 3.3.4. It is for instance not the case with SBCL 2.2.9. 206 | 207 | Ask the version with our script: 208 | 209 | $ make check-asdf-version 210 | 211 | or yourself with`(asdf:asdf-version)` on a Lisp REPL, or with 212 | this one-liner from a terminal: 213 | 214 | $ sbcl --eval '(and (print (asdf:asdf-version)) (quit))' 215 | 216 | Here's a one-liner to update ASDF: 217 | 218 | $ mkdir ~/common-lisp/ 219 | $ ( cd ~/common-lisp/ && wget https://asdf.common-lisp.dev/archives/asdf-3.3.5.tar.gz && tar -xvf asdf-3.3.5.tar.gz && mv asdf-3.3.5 asdf ) 220 | 221 | 222 | ### Install Quicklisp 223 | 224 | To build CIEL on your machine, you need the [Quicklisp library 225 | manager](https://quicklisp.org/beta/). Quicklisp downloads and 226 | installs a library and its dependencies on your machine. It's very 227 | slick, we can install everything from the REPL without restarting our 228 | Lisp process. It follows a "distrubution" approach, think Debian 229 | releases, where libraries are tested to load. 230 | 231 | It isn't the only library manager nowadays. See [https://github.com/CodyReichert/awesome-cl#library-manager](https://github.com/CodyReichert/awesome-cl#library-manager). 232 | 233 | Install it: 234 | 235 | ```sh 236 | curl -O https://beta.quicklisp.org/quicklisp.lisp 237 | sbcl --load quicklisp.lisp --eval "(quicklisp-quickstart:install)" --quit 238 | sbcl --load ~/quicklisp/setup.lisp --eval "(ql:add-to-init-file)" --quit 239 | ``` 240 | 241 | It creates a `~/quicklisp/` directory. Read its installation instructions to know more. 242 | 243 | ### Install our Lisp dependencies [MANDATORY] 244 | 245 | One library that we use is not included in Quicklisp (as of 246 | <2025-02-03>), [termp](https://github.com/vindarel/termp). It is a 247 | small and trivial library, you can clone it into your 248 | ~/quicklisp/local-projects: 249 | 250 | git clone https://github.com/vindarel/termp/ ~/quicklisp/local-projects/termp 251 | 252 | For a number of other libraries we need the Quicklisp version of August, 2024, or later. 253 | 254 | For those, you should either: 255 | * ensure that your Quicklisp version is recent enough (with `(ql:dist-version "quicklisp")`) and maybe update it (with `(ql:update-dist "quicklisp")`) 256 | * clone our dependencies locally with the command below. 257 | 258 | If you need it, clone all the required dependencies into your `~/quicklisp/local-projects/` with this command: 259 | 260 | make ql-deps 261 | 262 | NB: other tools exist for this (Qlot, ocicl…), we are just not using them yet. 263 | 264 | 265 | ## How to load CIEL with Quicklisp 266 | 267 | You need the dependencies above: Quicklisp, a good ASDF version, our up-to-date Lisp dependencies. 268 | 269 | This shows you how to load CIEL and all its goodies, in order to use it in your current editor. 270 | 271 | CIEL is not on Quicklisp yet, but it is on [Ultralisp](https://ultralisp.org). 272 | 273 | So, either clone this repository: 274 | 275 | git clone https://github.com/ciel-lang/CIEL ~/quicklisp/local-projects/CIEL 276 | 277 | or install the Ultralisp distribution and pull the library from there: 278 | 279 | ~~~lisp 280 | (ql-dist:install-dist "http://dist.ultralisp.org/" :prompt nil) 281 | ~~~ 282 | 283 | Now, in both cases, you can load the `ciel.asd` file (with `asdf:load-asd` 284 | or `C-c C-k` in Slime) and quickload "ciel": 285 | 286 | ```lisp 287 | CL-USER> (ql:quickload "ciel") 288 | ``` 289 | 290 | be sure to enter the `ciel-user` package: 291 | 292 | ```lisp 293 | (in-package :ciel-user) 294 | ``` 295 | you now have access to all CIEL's packages and functions. 296 | 297 | 298 | ## How to build a CIEL binary and a core image 299 | 300 | You need the dependencies above: Quicklisp, a good ASDF version, our up-to-date Lisp dependencies. 301 | 302 | To build CIEL's binary, use: 303 | 304 | $ make build 305 | 306 | This creates a `ciel` binary in the current directory. 307 | 308 | To create a Lisp image: 309 | 310 | $ make image 311 | # or 312 | $ sbcl --load build-image.lisp 313 | 314 | This creates the `ciel-core` Lisp image. 315 | 316 | Unlike a binary, we can not distribute core images. It is dependent on the machine it was built on. 317 | 318 | The way we use a core image is to load it at startup like this: 319 | 320 | sbcl --core ciel-core --eval '(in-package :ciel-user)' 321 | 322 | It loads fast and you have all CIEL libraries and goodies at your disposal. 323 | 324 | Then you have to configure your editor, like Slime, to have the choice of the Lisp image to 325 | start. See below. 326 | 327 | 328 | ## Docker 329 | 330 | We have a Dockerfile. 331 | 332 | Build your CIEL image: 333 | 334 | docker build -t ciel . 335 | 336 | The executable is built in `/usr/local/bin/ciel` of the Docker image. 337 | 338 | Get a CIEL REPL: 339 | 340 | docker run --rm -it ciel /usr/local/bin/ciel 341 | 342 | Run a script on your filesystem: 343 | 344 | docker run --rm -it ciel /usr/local/bin/ciel path/to/your/lisp/script.lisp 345 | 346 | Run a built-in script: 347 | 348 | docker run --rm -it ciel /usr/local/bin/ciel -s simpleHTTPserver 349 | 350 | So, save you some typing with a shell alias: 351 | 352 | alias ciel="sudo docker run --rm -it ciel /usr/local/bin/ciel" 353 | 354 | 355 | # Usage 356 | 357 | ## Scripting 358 | 359 | > [!NOTE] 360 | > this is brand new! Expect limitations and changes. 361 | 362 | Get the `ciel` binary and call it with your .lisp script: 363 | 364 | $ ciel script.lisp 365 | 366 | Use the `#!/usr/bin/env ciel` shebang line to directly call your files: 367 | 368 | $ ./script 369 | 370 | Call built-in scripts: 371 | 372 | $ ciel -s simpleHTTPserver 9000 373 | 374 | See available built-in scripts with `--scripts`. 375 | 376 | See [the scripts documentation](https://ciel-lang.github.io/CIEL/#/scripting). 377 | 378 | ## Terminal REPL 379 | 380 | CIEL ships a terminal REPL for the terminal which is more user friendly than the default SBCL one: 381 | 382 | - it has readline capabilities, meaning that the arrow keys work by 383 | default (woohoo!) and there is a persistent history, like in any 384 | shell. 385 | - it has **multiline input**. 386 | - it has **TAB completion**. 387 | - including for files (after a bracket) and binaries in the PATH. 388 | - it handles errors gracefully: you are not dropped into the debugger 389 | and its sub-REPL, you simply see the error message. 390 | - it has optional **syntax highlighting**. 391 | - it has a **shell pass-through**: try `!ls`. 392 | - it runs **interactive commands**: try `!htop`, `!vim test.lisp`, `!emacs -nw test.lisp` or `!env FOO=BAR sudo -i top`. 393 | - it has **documentation lookup** shorthands: use `:doc symbol` or `?` 394 | after a symbol to get its documentation: `ciel-user> (dict ?`. 395 | - it has **developer friendly** macros: use `(printv code)` for an 396 | annotated trace output. 397 | - it has an optional **lisp critic** that scans the code you enter at 398 | the REPL for instances of bad practices. 399 | - and it defines some more helper commands. 400 | - it works on Slime (to a certain extent) 401 | 402 | The CIEL terminal REPL loads the `~/.cielrc` init file at start-up if present. Don't load it with `--no-userinit`. 403 | 404 | See more in [*the documentation*](https://ciel-lang.github.io/CIEL/#/repl). 405 | 406 | > [!NOTE] 407 | > Our terminal readline REPL does NOT replace a good Common Lisp editor. You have more choices than Emacs. Check them out! https://lispcookbook.github.io/cl-cookbook/editor-support.html 408 | 409 | 410 | Run `ciel` with no arguments: 411 | 412 | ```bash 413 | $ ciel 414 | 415 | CIEL's REPL version 0.1.5 416 | Read more on packages with readme or summary. For example: (summary :str) 417 | Special commands: 418 | %help => Prints this general help message 419 | %doc => Print the available documentation for this symbol. 420 | %? => Gets help on a symbol : :? str 421 | %w => Writes the current session to a file 422 | %d => Dumps the disassembly of a symbol 423 | %t => Prints the type of a expression 424 | %q => Ends the session. 425 | %lisp-critic => Enable or disable the lisp critic. He critizes the code you type before compiling it. 426 | %edit => Edit a file with EDITOR and evaluate it. 427 | Press CTRL-D or type :q to exit 428 | 429 | ciel-user> 430 | ``` 431 | 432 | It is freely based on [sbcli](https://github.com/hellerve/sbcli). 433 | 434 | 435 | ## CIEL as a library: "use" :ciel in your Lisp systems 436 | 437 | You can install and `quickload` CIEL like any other Common Lisp library. 438 | 439 | To use it in your project, create a package and "use" `ciel` in addition 440 | of `cl`: 441 | 442 | ```lisp 443 | (defpackage yourpackage 444 | (:use :cl :ciel)) 445 | ``` 446 | 447 | You can also use `generic-ciel`, based on 448 | [generic-cl](https://github.com/alex-gutev/generic-cl/) (warn: 449 | generic-ciel is less tested at the moment). 450 | 451 | ~~~lisp 452 | (defpackage yourpackage 453 | (:use :cl :generic-ciel)) 454 | ~~~ 455 | 456 | generic-cl allows us to define our `+` or `equalp` methods for our own 457 | objects (and more). 458 | 459 | ## Core image: configure your editor 460 | 461 | The advantage of a core image is that it loads instantly, faster than 462 | a `(ql:quickload "ciel")`. We'll ask our editor to start SBCL with our 463 | CIEL core image. 464 | 465 | We'll configure SLIME for [multiple Lisps](https://common-lisp.net/project/slime/doc/html/Multiple-Lisps.html#Multiple-Lisps). 466 | 467 | You need to add this to your Emacs init file: 468 | 469 | ```lisp 470 | (setq slime-lisp-implementations 471 | `((sbcl ("sbcl" "--dynamic-space-size" "2000")) ;; default. Adapt if needed. 472 | (ciel-sbcl ("sbcl" "--core" "/path/to/ciel/ciel-core" "--eval" "(in-package :ciel-user)")))) 473 | (setq slime-default-lisp 'ciel-sbcl) 474 | ``` 475 | 476 | and start a Lisp process with `M-x slime`. 477 | 478 | If you didn't set `ciel-sbcl` as the default, then start the Lisp 479 | process with `M-- M-x slime` (alt-minus prefix), and choose 480 | `ciel-sbcl`. You can start more than one Lisp process from SLIME. 481 | 482 | The Lisp process should start instantly, as fast as the default SBCL, 483 | you won't wait for the quicklisp libraries to load. 484 | 485 | 486 | # Libraries 487 | 488 | We import, use and document libraries to fill various use cases: generic 489 | access to data structures, functional data structures, string 490 | manipulation, JSON, database access, web, URI handling, iteration 491 | helpers, type checking helpers, syntax extensions, developer utilities, 492 | etc. 493 | 494 | See the documentation. 495 | 496 | To see the full list of dependencies, see the `ciel.asd` project 497 | definition or this [dependencies list](docs/dependencies.md). 498 | 499 | # Language extensions 500 | 501 | We provide arrow macros, easy type declaratons in the function lambda 502 | list, macros for exhaustiveness type checking, pattern matching, etc. 503 | 504 | See [the documentation](https://ciel-lang.github.io/CIEL/#/language-extensions). 505 | 506 | # Final words 507 | 508 | That was your life in CL: 509 | 510 |

511 | 512 | and now: 513 | 514 |

515 | 516 | # Misc: how to generate the documentation 517 | 518 | See `src/ciel.lisp` and run `(generate-dependencies-page-reference)`. 519 | 520 | # Contributors 521 | 522 | Special big thanks to @cinerion, [@themarcelor](https://github.com/themarcelor) and everyone who helped (@agam, @patrixl, @bo-tato…). 523 | 524 | 525 | # Lisp?! 526 | 527 | - [awesome-cl](https://github.com/CodyReichert/awesome-cl) 528 | - [the Common Lisp Cookbook](https://lispcookbook.github.io/cl-cookbook/) 529 | - [editor support](https://lispcookbook.github.io/cl-cookbook/editor-support.html) (Emacs, Vim, VSCode, Atom, Pulsar, Jetbrains, Sublime, Jupyter notebooks…) 530 | - [Lisp companies](https://github.com/azzamsa/awesome-lisp-companies/) 531 | - blog posts: 532 | - [these years in Lisp: 2022 in review](https://lisp-journey.gitlab.io/blog/these-years-in-common-lisp-2022-in-review/) 533 | - [Python VS Common Lisp, workflow and ecosystem](https://lisp-journey.gitlab.io/pythonvslisp/) 534 | - [A road to Common Lisp](https://stevelosh.com/blog/2018/08/a-road-to-common-lisp/) 535 | - 🎥 [Youtube showcases](https://www.youtube.com/@vindarel): 536 | - [Debugging Lisp: fix and resume a program from any point in the stack](https://www.youtube.com/watch?v=jBBS4FeY7XM) 537 | - [How to call a REST API in Common Lisp: HTTP requests, JSON parsing, CLI arguments, binaries](https://www.youtube.com/watch?v=TAtwcBh1QLg) 538 | - 🎥 my [Common Lisp course in videos: from novice to efficient programmer](https://www.udemy.com/course/common-lisp-programming/?referralCode=2F3D698BBC4326F94358), on the Udemy platform. 539 | -------------------------------------------------------------------------------- /build-config.lisp: -------------------------------------------------------------------------------- 1 | ;;; 2 | ;;; Extra config to build the binary, 3 | ;;; stuff that doesn't need to be in the .asd 4 | ;;; (and would be harmful to be there). 5 | ;;; 6 | 7 | ;; We want to configure cl+ssl for the Deploy binary, 8 | ;; and we need to load cl+ssl before we can load this config. 9 | ;; If it was in the .asd, we would have to load cl+ssl before being able 10 | ;; to load the .asd file, which is annoying 11 | ;; (it impeds loading CIEL in the REPL with a usual load-asd (C-c C-k) 12 | ;; and complicates Ultralisp or Quicklisp distribution). 13 | ;; 14 | ;; So, we need cl+ssl to build the binary with asdf:make 15 | ;; See also the Makefile that quickloads cl+ssl already (maybe this below isn't 16 | ;; required any more?) 17 | (unless (find-package :cl+ssl) 18 | (warn "Loading build-config.lisp: we don't find the package CL+SSL. You need to install it before loading this config file and building CIEL's binary. Let's install it with Quicklisp now.~&") 19 | (ql:quickload "cl+ssl")) 20 | (require "cl+ssl") 21 | 22 | ;; Don't ship libssl, rely on the target OS'. 23 | #+linux (deploy:define-library cl+ssl::libssl :dont-deploy T) 24 | #+linux (deploy:define-library cl+ssl::libcrypto :dont-deploy T) 25 | 26 | ;; Use compression: from 114M, 0.02s startup time to 27M and 0.42s (SBCL 2.0.10). 27 | #+sb-core-compression 28 | (defmethod asdf:perform ((o asdf:image-op) (c asdf:system)) 29 | (uiop:dump-image (asdf:output-file o c) :executable t :compression t)) 30 | 31 | ;; Even with the binary, ASDF wants to update itself and crashes 32 | ;; if it doesn't find an ASDF directory, like on a user's system. 33 | ;; Thanks again to Shinmera. 34 | (deploy:define-hook (:deploy asdf) (directory) 35 | (declare (ignorable directory)) 36 | #+asdf (asdf:clear-source-registry) 37 | #+asdf (defun asdf:upgrade-asdf () nil)) 38 | -------------------------------------------------------------------------------- /build-image.lisp: -------------------------------------------------------------------------------- 1 | 2 | (in-package :cl-user) 3 | 4 | (ql:quickload "deploy") ;; not used to build the image, but used in the .asd. 5 | (ql:quickload "cl+ssl") ;; only because of Deploy's parameters. 6 | ;; (asdf:load-asd "./ciel.asd") 7 | ;; Bug on CI, needs an absolute pathname. 8 | (let ((pathname (merge-pathnames "ciel.asd" (uiop:getcwd)))) 9 | (uiop:format! t "~&--- loading this asd absolute pathname: ~S~&" pathname) 10 | (asdf:load-asd pathname)) 11 | 12 | (ql:quickload "swank") 13 | (ql:quickload "ciel") 14 | 15 | (in-package :ciel-user) 16 | 17 | ;; XXX: we would like to read our ~/.cielrc init file when resuming the core 18 | ;; in Slime. 19 | ;; Currently we load it only when starting the terminal REPL. 20 | ;; See the :toplevel option. 21 | 22 | (sb-ext:save-lisp-and-die "ciel-core") 23 | -------------------------------------------------------------------------------- /check-asdf-version.lisp: -------------------------------------------------------------------------------- 1 | 2 | (require 'asdf) 3 | 4 | (uiop:format! t "ASDF version: ~a~&" (asdf:asdf-version)) 5 | (let ((version (asdf:asdf-version))) 6 | (cond 7 | ((uiop:version<= version "3.3.4") 8 | (uiop:quit 1)) 9 | (t 10 | (uiop:quit 0)))) 11 | -------------------------------------------------------------------------------- /ciel.asd: -------------------------------------------------------------------------------- 1 | #| 2 | This file is a part of ciel project. 3 | |# 4 | 5 | (require "asdf") ;; for CI 6 | 7 | (asdf:defsystem "ciel" 8 | :description "CIEL Is an Extended Lisp (Common Lisp, batteries included)." 9 | :version "0.2.1" 10 | :author "vindarel" 11 | :license "MIT" 12 | :homepage "https://github.com/ciel-lang/CIEL/" 13 | :source-control (:git "https://github.com/ciel-lang/CIEL/") 14 | :bug-tracker "https://github.com/ciel-lang/CIEL/issues/" 15 | 16 | :depends-on ( 17 | :cl-reexport ;; for us 18 | :cl-ansi-text 19 | 20 | :access 21 | :alexandria 22 | :arrow-macros 23 | 24 | ;; CSV 25 | :cl-csv 26 | :cl-csv-data-table 27 | :data-table 28 | 29 | ;; Previously, we had dependencies that depended on Osicat (fof, moira), 30 | ;; hence complicating deployment of binaries. 31 | ;; Check with (ql:who-depends-on "osicat") and ditch Osicat. 32 | ;; 33 | :file-finder ;; file-object finder 34 | 35 | ;; threads 36 | :bordeaux-threads 37 | :trivial-monitored-thread 38 | :lparallel 39 | :moira/light ;; monitor background threads 40 | :cl-cron 41 | 42 | :closer-mop 43 | :cl-ansi-text 44 | :cl-csv 45 | :shasht ;; json 46 | :cl-json-pointer/synonyms 47 | :dissect 48 | :fset 49 | :file-notify ;; needs inotify (linux) or fsevent (macos) 50 | :generic-cl 51 | 52 | ;; web 53 | :dexador 54 | :hunchentoot 55 | :easy-routes ;; better route definition for Hunchentoot. 56 | :quri 57 | :lquery 58 | :spinneret ;; lispy templates. Used in simpleHTTPserver.lisp 59 | 60 | ;; other networking: 61 | :cl-ftp ;; depends on only: split-sequence and usocket. 62 | 63 | ;; GUI 64 | ;; We remove nodgui as of <2024-08-30> 65 | ;; because it was too heavy in dependencies, see 66 | ;; https://github.com/ciel-lang/CIEL/issues/56 67 | ;; We'll test again with its lightweight nodgui-lite system. 68 | ;; :nodgui ;; ltk fork with built-in themes and more widgets. 69 | ;; to test: 70 | ;; :nodgui-lite 71 | 72 | ;; CLI 73 | :clingon ;; args parsing 74 | 75 | :local-time 76 | :modf 77 | 78 | ;; number parsing 79 | :parse-float 80 | :parse-number 81 | 82 | ;; database 83 | :dbi ; connects and executes queries. 84 | ;; dbi users must reference the driver's dependency 85 | ;; when building a binary. 86 | ;; If not, dbi wants to install a system on the fly, 87 | ;; calls to ASDF, which fails with a useless message. 88 | ;; 89 | ;; Can we suppose sqlite3 is ubiquitous? 90 | ;; This would require libsqlite3 (libsqlite3-dev on Debian). 91 | ;; :dbd-sqlite3 92 | ;; With those: 93 | ;; :dbd-mysql ;; requires libmysqlclient 94 | ;; :dbd-postgres 95 | 96 | :sxql ;; SQL generator from lispy syntax. 97 | ;; I recently removed Mito. Why? lol. 98 | 99 | ;; numerical 100 | :vgplot 101 | 102 | ;; regexp 103 | :cl-ppcre 104 | 105 | ;; string manipulation 106 | :str 107 | 108 | ;; security 109 | :secret-values 110 | 111 | ;; other utilities 112 | :progressons ;; no deps. Simple progress bar. Not in Quicklisp as of <2024-08-30>. 113 | :termp ;; no deps. Are we in a dumb terminal like Slime's REPL? 114 | 115 | ;;; 116 | ;;; Language extensions. 117 | ;;; 118 | ;; triple quotes 119 | :pythonic-string-reader 120 | 121 | ;; pattern matching 122 | :trivia 123 | :trivial-arguments 124 | :trivial-package-local-nicknames 125 | :trivial-types 126 | 127 | ;; extended let 128 | :metabang-bind 129 | 130 | ;; type declarations 131 | :defstar 132 | 133 | ;; iteration 134 | :for 135 | :trivial-do 136 | 137 | :cmd 138 | :serapeum 139 | :shlex 140 | 141 | :function-cache ;; memoization 142 | 143 | ;; tests 144 | :fiveam 145 | 146 | :which 147 | 148 | ;;; 149 | ;;; Debugging, developer utilities. 150 | ;;; 151 | :log4cl 152 | :printv 153 | :repl-utilities ;; see readme, summary, doc, package-apropos, trace-package etc 154 | 155 | ;;; 156 | ;;; User helpers. 157 | ;;; ;TODO: we don't want these dependencies when we build a binary. 158 | ;;; 159 | :named-readtables 160 | :quicksearch ;; search on GitHub, Cliki, Quickdocs. 161 | ) 162 | :components ((:module "src" 163 | :components 164 | ((:file "packages") 165 | (:file "json-pointer-minus") 166 | (:file "ciel") 167 | (:file "csv") 168 | (:file "gui"))) 169 | (:file "utils") 170 | (:module "src/more-docstrings" 171 | :components 172 | ((:file "docstrings")))) 173 | ) 174 | 175 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 176 | ;;; Sub-system for the terminal REPL. 177 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 178 | (asdf:defsystem "ciel/repl" 179 | :description "readline REPL for CIEL with quality of life improvements." 180 | :depends-on (;; :ciel ;; let's avoid, it could run side effects twice (like a defparameter set then reset). 181 | ;; deps 182 | :cl-readline 183 | :lisp-critic ;; it would be nice to integrate it with Slime. 184 | :magic-ed) 185 | :components ((:file "repl") 186 | (:file "utils") 187 | (:file "scripting") 188 | (:file "repl-utils") 189 | 190 | ;; I define them here, for good practice (for me), 191 | ;; but I don't use them. 192 | ;; static-file is important, otherwise the scripts would be run. 193 | (:module "src/scripts" 194 | :components 195 | ((:static-file "quicksearch") 196 | (:static-file "simpleHTTPserver"))) 197 | ) 198 | 199 | :build-operation "program-op" 200 | :build-operation "program-op" 201 | :build-pathname "ciel" 202 | :entry-point "ciel::main") 203 | 204 | ;;; This defines ciel.asd. It is enough to quickload CIEL. 205 | ;;; But to build a binary, 206 | ;;; see build-config.lisp for extra config. 207 | 208 | ;; build a smaller executable with SBCL's core compression: 209 | ;; from 119MB to 28MB, however startup time increases from 0.02 to 0.35s (noticeable). 210 | #+sb-core-compression 211 | (defmethod asdf:perform ((o asdf:image-op) (c asdf:system)) 212 | (uiop:dump-image (asdf:output-file o c) :executable t :compression t)) 213 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciel-lang/CIEL/0538a9e5fdaf290ef39747260748743f35651e35/docs/.nojekyll -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | ciel-lang.org -------------------------------------------------------------------------------- /docs/FAQ.md: -------------------------------------------------------------------------------- 1 | About CIEL 2 | ========== 3 | 4 | Is CIEL yet another language re-design? 5 | --------------------------------------- 6 | 7 | Absolutely not. CIEL is plain Common Lisp. We don't redefine the semantics of the language. CIEL is a collection of useful libraries, shipped as one Quicklisp meta-library, a core image and an executable. 8 | 9 | Is CIEL a standard library? 10 | --------------------------- 11 | 12 | No, we can't say that. We ship useful libraries written by a variety of people, including ourselves, and we make them available to you so you don't have to spend time looking for them, choosing them, installing them and importing them. We use the same libraries that every other lisper can find on Quicklisp. We provide a core image and a ready-to-use REPL to make on-boarding even easier. 13 | 14 | If I use CIEL, do I learn Common Lisp? 15 | -------------------------------------- 16 | 17 | Yes you do. And in addition you'll be acquainted to useful and often popular third-party libraries and utilities. They are all referenced on our website. 18 | 19 | How do I switch from CIEL to plain Common Lisp? 20 | ----------------------------------------------- 21 | 22 | You will have to know what external libraries you are using. Mainly, by reading CIEL's documentation, or by using your editor's "go to definition" feature (`M-.` in Slime) to find out. You'll have to use the `cl` or `cl-user` package instead of `ciel-user`, and you'll have to declare your dependencies yourself in your project's `.asd` file. 23 | 24 | Eventually, there could be a script that does that for you. 25 | 26 | Is CIEL as fast as Common Lisp? 27 | ------------------------------- 28 | 29 | In general, yes. Only some functions are more generic, thus slower, than default Common Lisp. For example, `access`. That is more the case if you use `generic-ciel`. 30 | 31 | Is CIEL stable? 32 | --------------- 33 | 34 | No. At least not in the Common Lisp sense of stability, which means "very stable". CIEL is as stable as the set of the libraries it includes. We have solutions to improve stability, so this is an open question (using our own Quicklisp distribution, redifining a symbol in case of an upstream change for backwards compatibility,…). 35 | 36 | Generally though, the ecosystem is quite conservative. We saw deprecation warnings staying for 12 years. 37 | 38 | Who is CIEL for? 39 | ---------------- 40 | 41 | CIEL is for everybody who wants to discover Common Lisp, or for more experienced lispers who want to have a batteries-included Common Lisp distribution at hand. 42 | 43 | I am a seasoned lisper, why should I care? 44 | ------------------------------------------ 45 | 46 | You must regularly hear that "getting started with Common Lisp is hard", "Common Lisp is full of quirks", "finding what one needs is difficult", etc. CIEL is an attempt to ease on-boarding, and getting rid off these (legitimate) complaints. 47 | 48 | You can test and discover new libraries. 49 | 50 | You can show CIEL to your non-lispers friends and colleagues, without saying embarrassing things like: "Just install rlwrap". "To join strings, use format's ~{ @ : }". "Yeah, there's parse-integer but not parse-float, just install it". "To see what's in a hash-table, I'll give you a snippet". etc. 51 | 52 | CIEL is bloated. How can I build a lighter one? 53 | ----------------------------------------------- 54 | 55 | First and foremost, if you think there is room for a lighter CIEL 56 | package, come discuss in the Github issues. We can maybe create and 57 | independent package with lighter dependencies. 58 | 59 | Otherwise, you can have a look to this [core-dumper](https://gitlab.com/ambrevar/lisp-repl-core-dumper) tool. 60 | 61 | 62 | What is CIEL for? 63 | ----------------- 64 | 65 | Please see the project's homepage, and write to us if it is not clear enough! 66 | 67 | ## About CIEL scripting 68 | 69 | ### Is CIEL like Babashka for Clojure? 70 | 71 | Babashka is a popular Clojure tool that is: 72 | 73 | - a fast-starting scripting environment 74 | - a standalone binary 75 | - a collection of useful built-in libraries 76 | 77 | So, it looks like it is. 78 | 79 | Babashka was made possible thanks to the GraalVM Native Image, a 80 | technical breakthrough on the JVM world. Without it, they wouldn't 81 | have a fast-starting scripting environment. Common Lispers on the 82 | contrary always could build standalone binaries, with Lisp sources 83 | compiled to machine code. So these "scripting" capabilities are not a 84 | surprise. CIEL scripting only makes it very easy to run and share 85 | Common Lisp scripts (with batteries included). 86 | 87 | ### Does CIEL scripting replace Roswell? 88 | 89 | Roswell does a lot more than scripting, especially it allows to easily 90 | install various Common Lisp implementations. 91 | 92 | It makes it easy to share programs, we just have to run 93 | `ros install github-handle/software-name`. 94 | 95 | However we find easier and faster to install and run a CIEL script, 96 | since CIEL avoids compilation times, thanks to it including various 97 | libraries out of the box. 98 | 99 | At the time of writing, Roswell does something (or many things) more. It allows to 100 | [build images and executables](https://github.com/roswell/roswell/wiki/Building-images-and-executables), 101 | and it even provides a few interesting options, like options to reduce the binary size. 102 | 103 | ### What about cl-launch? 104 | 105 | [cl-launch](https://www.cliki.net/cl-launch) is supposed to help for 106 | scripting. Because of its bad documentation, I have difficulties 107 | seeing what it does and how to use it. It can maybe be helpful, but it 108 | won't give you batteries included like CIEL does. 109 | 110 | ### Is that all the scripting options available for Common Lisp? 111 | 112 | Of course not. For one, implementations like SBCL have the `--script` and `--load` flags. 113 | 114 | You can use a shebang line with them too. Here's one: 115 | 116 | ``` 117 | #| 118 | exec sbcl --load "$0" --eval "(main)" --quit --end-toplevel-options "${@:1}" 119 | |# 120 | .... 121 | (defun main () 122 | ...) 123 | ``` 124 | 125 | You can make the script executable with `chmod +x` and run it. 126 | 127 | Here's how it works. Lines starting by a `#` are shell comments, so 128 | the first line is ignored. Then `sbcl` is run and the Lisp process 129 | takes over. The remaining lines are not read by the shell. `sbcl` 130 | loads this same file (`$0`) and calls the `(main)` function. In Lisp, 131 | lines in-between `#| … |#` are comments, so the `exec` line is 132 | ignored. Command line options are passed on to the Lisp 133 | process. Thanks to the main function, we can also load this file 134 | interactively on our editor, we don't have top-level code that will be 135 | run and have side effects. 136 | 137 | This works fine, but everytime you'll want to use third-party 138 | libraries, you'll notice the time it takes to load them. In CIEL, many 139 | are built-in, so your script starts up (very) fast. 140 | 141 | Also, our shebang line is easier to type: 142 | 143 | ``` 144 | #!/usr/bin/env ciel 145 | ``` 146 | 147 | See other solutions and attempts for scripting in CL on [awesome-cl#scripting](https://github.com/CodyReichert/awesome-cl#scripting). 148 | 149 | ### But writing Lisp code on the terminal is not fun :( 150 | 151 | I find it fun, but don't write big one-liners to feed to `--eval` ;) 152 | You can write your CIEL scripts using your favourite editor setup. 153 | 154 | Also, Common Lisp strings only accept double quotes, so use single quotes for the outter eval expression, and double quotes inside. 155 | 156 | Anyways, CIEL scripting doesn't replace a good editor setup, where you 157 | can have all the praised [image-based interactivity](https://www.youtube.com/watch?v=jBBS4FeY7XM) 158 | a good Lisp provides. 159 | 160 | Also, once you have your Common Lisp development environment in place, 161 | you can build your own standalone binaries, with or without relying on 162 | the `:ciel` library. 163 | 164 | 165 | 166 | 167 | About Common Lisp 168 | ================= 169 | 170 | What is Common Lisp good for, really? 171 | ------------------------------------- 172 | 173 | We have a famous quote for this question: 174 | 175 | > Please don't assume Lisp is only useful for Animation and Graphics, AI, Bioinformatics, B2B and Ecommerce, Data Mining, EDA/Semiconductor applications, Expert Systems, Finance, Intelligent Agents, Knowledge Management, Mechanical CAD, Modeling and Simulation, Natural Language, Optimization, Research, Risk Analysis, Scheduling, Telecom, and Web Authoring just because these are the only things they happened to list. 176 | > 177 | > Kent Pitman 178 | 179 | Will I get hit by the Lisp curse? 180 | --------------------------------- 181 | 182 | The only, very serious Lisp curse we know, is that once you taste Lisp, all other languages become insipid. CIEL brings you higher in the sky, at a higher risk. Sorry! 183 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # CIEL 2 | 3 | CIEL is a ready-to-use collection of Lisp libraries. 4 | 5 | It's Common Lisp, batteries included. 6 | 7 | It comes in 3 forms: 8 | 9 | - a binary, to run CIEL **scripts**: fast start-up times, standalone binary, built-in utilities. 10 | - a simple full-featured **REPL** for the terminal. 11 | - a **Lisp library**. 12 | 13 | Questions, doubts? See the [FAQ](FAQ.md). 14 | 15 | Status: it's a work in progress. I deployed it for client projects. 16 | 17 | ```lisp 18 | #!/usr/bin/env ciel 19 | 20 | (-> "https://fakestoreapi.com/products?limit=5" 21 | http:get 22 | json:read-json 23 | (elt 0) 24 | (access "title")) 25 | ``` 26 | 27 | ```bash 28 | $ chmodx +x getproduct.lisp 29 | $ time ./getproduct.lisp 30 | "Fjallraven - Foldsack No…ckpack, Fits 15 Laptops" 31 | ./getproduct.lisp 0.10s user 0.02s system 24% cpu 0.466 total 32 | ``` 33 | 34 | 35 | 36 | ## Rationale 37 | 38 | One of our goals is to make Common Lisp useful out of the box for 39 | mundane tasks -by today's standards. 40 | 41 | Consequently, **we ship libraries** to 42 | handle JSON and CSV, as well as others to ease string manipulation, 43 | to have regular expressions out of the box, for threads and 44 | jobs scheduling, for HTTP and URI handling, to create simple GUIs with 45 | nodgui (Tk-based, nice theme), and so on. You can of course do all this without CIEL, but 46 | then you'd have to install the library manager (Quicklisp) first and load these libraries 47 | into your Lisp image every time you start it. Now, you have them at 48 | your fingertips whenever you start CIEL. Some of the libraries we bring in are for extending the language 49 | syntax a bit. For example, we include the `Trivia` library for 50 | pattern matching. 51 | 52 | We also aim to **soften the irritating parts of standard Common Lisp**. 53 | A famous one, puzzling for beginners and non-optimal for seasoned 54 | lispers, is the creation of hash-tables. We include the `dict` function 55 | from the Serapeum library (which we enhanced further with a pull request): 56 | 57 | 58 | ~~~lisp 59 | CIEL-USER> (dict :a 1 :b 2 :c 3) 60 | ~~~ 61 | 62 | which prints: 63 | 64 | ~~~lisp 65 | (dict 66 | :A 1 67 | :B 2 68 | :C 3 69 | ) 70 | ~~~ 71 | 72 | In standard Common Lisp, the equivalent is more convoluted: 73 | 74 | ~~~lisp 75 | (let ((ht (make-hash-table :test 'equal))) 76 | (setf (gethash :a ht) 1) 77 | (setf (gethash :b ht) 2) 78 | (setf (gethash :c ht) 3) 79 | ht) 80 | ;; # 81 | ;; (and we don't get a readable representation, so our example is not even equivalent) 82 | ~~~ 83 | 84 | We **add missing functions**. For example, after you used `parse-integer`, you are probably looking for a `parse-float` function… but you can't find it, because it isn't defined by the standard. You must install a third-party library. Now you have it. 85 | 86 | We **enhance the docstrings** of built-in functions and macros with more 87 | explanations and examples, so you don't have to reach to external 88 | documentation just yet in order to understand and try out things like 89 | `mapcar` or `loop` (look 'ma, LOOP has no docstring by default). 90 | 91 | Moreover, we bring a **user friendly REPL on the terminal**, 92 | with bells and whistles useful to the developer and people living in a 93 | terminal window. For example, our [repl for the terminal](repl.md) has readline support, multi-line editing, optional syntax highlighting, a shell passthrough, and more goodies. 94 | 95 | We bring **scripting capabilities**. Just run `ciel myscript.lisp`, and use all the high-level CIEL libraries and Common Lisp features in your script. It starts up fast. 96 | 97 | 98 | *We are only started. You can sponsor us [on GitHub sponsors](https://github.com/sponsors/vindarel/), thank you!* 99 | 100 | [![ko-fi](https://www.ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/K3K828W0V) 101 | 102 | *If you need to learn Common Lisp the efficient way, I have [a course on videos](https://www.udemy.com/course/common-lisp-programming/?referralCode=2F3D698BBC4326F94358), with many real-world practical stuff in and still growing. If you are a student, drop me a line for a coupon. Thank you for your support!* 103 | 104 | 105 | # Install 106 | 107 | Let's get started. See the [installation instructions](install.md). 108 | 109 | # Scripting & libraries 110 | 111 | Then, have a look at CIEL's [scripting capabilies](scripting.md), and on the [libraries](libraries.md) we ship. 112 | 113 | 114 | # Final words 115 | 116 | That was your life in CL: 117 | 118 |

119 | and now: 120 | 121 |

122 | -------------------------------------------------------------------------------- /docs/_coverpage.md: -------------------------------------------------------------------------------- 1 | 2 | # CIEL 0.0-dev 3 | 4 | > CIEL Is an Extended Lisp 5 | 6 | - 100% Common Lisp 7 | - batteries included 8 | 9 | [GitHub](https://github.com/ciel-lang/CIEL) 10 | [Show me](#ciel) 11 | 12 | 13 | 14 | ![color](#f0f0f0) -------------------------------------------------------------------------------- /docs/_navbar.md: -------------------------------------------------------------------------------- 1 | 2 | * More 3 | * [dependencies](dependencies.md) 4 | * [Symbols from Serapeum](serapeum.md) 5 | * [Symbols from Alexandria](alexandria.md) 6 | * [Symbols from Trivial-types](trivial-types.md) 7 | -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | 2 | * [CIEL](/) 3 | * [Install](install.md) 4 | * [Scripting](scripting.md) 5 | * [REPL and shell integration](repl.md) 6 | * [Libraries](libraries.md) 7 | * [Language extensions](language-extensions.md) 8 | * [See also](see-also.md) 9 | * [FAQ](FAQ.md) 10 | -------------------------------------------------------------------------------- /docs/after-plus.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciel-lang/CIEL/0538a9e5fdaf290ef39747260748743f35651e35/docs/after-plus.jpeg -------------------------------------------------------------------------------- /docs/alexandria-control-flow.md: -------------------------------------------------------------------------------- 1 | # Symbols imported from ALEXANDRIA for control flow. 2 | 3 | 4 | ## IF-LET 5 | 6 | ARGLIST: `(bindings &body (then-form &optional else-form))` 7 | 8 | FUNCTION: Creates new variable bindings, and conditionally executes either 9 | THEN-FORM or ELSE-FORM. ELSE-FORM defaults to NIL. 10 | 11 | BINDINGS must be either single binding of the form: 12 | 13 | (variable initial-form) 14 | 15 | or a list of bindings of the form: 16 | 17 | ((variable-1 initial-form-1) 18 | (variable-2 initial-form-2) 19 | ... 20 | (variable-n initial-form-n)) 21 | 22 | All initial-forms are executed sequentially in the specified order. Then all 23 | the variables are bound to the corresponding values. 24 | 25 | If all variables were bound to true values, the THEN-FORM is executed with the 26 | bindings in effect, otherwise the ELSE-FORM is executed with the bindings in 27 | effect. 28 | 29 | ## WHEN-LET 30 | 31 | ARGLIST: `(bindings &body forms)` 32 | 33 | FUNCTION: Creates new variable bindings, and conditionally executes FORMS. 34 | 35 | BINDINGS must be either single binding of the form: 36 | 37 | (variable initial-form) 38 | 39 | or a list of bindings of the form: 40 | 41 | ((variable-1 initial-form-1) 42 | (variable-2 initial-form-2) 43 | ... 44 | (variable-n initial-form-n)) 45 | 46 | All initial-forms are executed sequentially in the specified order. Then all 47 | the variables are bound to the corresponding values. 48 | 49 | If all variables were bound to true values, then FORMS are executed as an 50 | implicit PROGN. 51 | 52 | ## WHEN-LET* 53 | 54 | ARGLIST: `(bindings &body body)` 55 | 56 | FUNCTION: Creates new variable bindings, and conditionally executes BODY. 57 | 58 | BINDINGS must be either single binding of the form: 59 | 60 | (variable initial-form) 61 | 62 | or a list of bindings of the form: 63 | 64 | ((variable-1 initial-form-1) 65 | (variable-2 initial-form-2) 66 | ... 67 | (variable-n initial-form-n)) 68 | 69 | Each INITIAL-FORM is executed in turn, and the variable bound to the 70 | corresponding value. INITIAL-FORM expressions can refer to variables 71 | previously bound by the WHEN-LET*. 72 | 73 | Execution of WHEN-LET* stops immediately if any INITIAL-FORM evaluates to NIL. 74 | If all INITIAL-FORMs evaluate to true, then BODY is executed as an implicit 75 | PROGN. 76 | -------------------------------------------------------------------------------- /docs/alexandria.md: -------------------------------------------------------------------------------- 1 | # Symbols imported from ALEXANDRIA for control flow. 2 | 3 | 4 | ## IF-LET 5 | 6 | ARGLIST: `(bindings &body (then-form &optional else-form))` 7 | 8 | FUNCTION: Creates new variable bindings, and conditionally executes either 9 | THEN-FORM or ELSE-FORM. ELSE-FORM defaults to NIL. 10 | 11 | BINDINGS must be either single binding of the form: 12 | 13 | (variable initial-form) 14 | 15 | or a list of bindings of the form: 16 | 17 | ((variable-1 initial-form-1) 18 | (variable-2 initial-form-2) 19 | ... 20 | (variable-n initial-form-n)) 21 | 22 | All initial-forms are executed sequentially in the specified order. Then all 23 | the variables are bound to the corresponding values. 24 | 25 | If all variables were bound to true values, the THEN-FORM is executed with the 26 | bindings in effect, otherwise the ELSE-FORM is executed with the bindings in 27 | effect. 28 | 29 | ## WHEN-LET 30 | 31 | ARGLIST: `(bindings &body forms)` 32 | 33 | FUNCTION: Creates new variable bindings, and conditionally executes FORMS. 34 | 35 | BINDINGS must be either single binding of the form: 36 | 37 | (variable initial-form) 38 | 39 | or a list of bindings of the form: 40 | 41 | ((variable-1 initial-form-1) 42 | (variable-2 initial-form-2) 43 | ... 44 | (variable-n initial-form-n)) 45 | 46 | All initial-forms are executed sequentially in the specified order. Then all 47 | the variables are bound to the corresponding values. 48 | 49 | If all variables were bound to true values, then FORMS are executed as an 50 | implicit PROGN. 51 | 52 | ## WHEN-LET* 53 | 54 | ARGLIST: `(bindings &body body)` 55 | 56 | FUNCTION: Creates new variable bindings, and conditionally executes BODY. 57 | 58 | BINDINGS must be either single binding of the form: 59 | 60 | (variable initial-form) 61 | 62 | or a list of bindings of the form: 63 | 64 | ((variable-1 initial-form-1) 65 | (variable-2 initial-form-2) 66 | ... 67 | (variable-n initial-form-n)) 68 | 69 | Each INITIAL-FORM is executed in turn, and the variable bound to the 70 | corresponding value. INITIAL-FORM expressions can refer to variables 71 | previously bound by the WHEN-LET*. 72 | 73 | Execution of WHEN-LET* stops immediately if any INITIAL-FORM evaluates to NIL. 74 | If all INITIAL-FORMs evaluate to true, then BODY is executed as an implicit 75 | PROGN. 76 | -------------------------------------------------------------------------------- /docs/assets/vgplot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciel-lang/CIEL/0538a9e5fdaf290ef39747260748743f35651e35/docs/assets/vgplot.png -------------------------------------------------------------------------------- /docs/before.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciel-lang/CIEL/0538a9e5fdaf290ef39747260748743f35651e35/docs/before.jpeg -------------------------------------------------------------------------------- /docs/dependencies.md: -------------------------------------------------------------------------------- 1 | 2 | - access: A library providing functions that unify data-structure access for Common Lisp: access and (setf access) 3 | - alexandria: Alexandria is a collection of portable public domain utilities. 4 | - arrow-macros: arrow-macros provides clojure-like arrow macros and diamond wands. 5 | - bordeaux-threads: Bordeaux Threads makes writing portable multi-threaded apps simple. 6 | - cl-ansi-text: ANSI control string characters, focused on color 7 | - cl-ansi-text: ANSI control string characters, focused on color 8 | - cl-cron: A simple tool that provides cron like facilities directly inside of common lisp. For this to work properly note that your lisp implementation should have support for threads 9 | - cl-csv: Facilities for reading and writing CSV format files 10 | - cl-ftp: send or receive files from FTP. 11 | - cl-ppcre: Perl-compatible regular expression library 12 | - cl-reexport: Reexport external symbols in other packages. 13 | mechanisms for running and composing Unix shell commands and 14 | constructs from Common Lisp. 15 | 16 | Essentially, it provides a '!' syntax that you can use to ... 17 | - clingon: Command-line options parser system for Common Lisp 18 | - closer-mop: Closer to MOP is a compatibility layer that rectifies many of the absent or incorrect CLOS MOP features across a broad range of Common Lisp implementations. 19 | - cmd: A utility for running external programs 20 | - dbi: Database independent interface for Common Lisp 21 | - defstar: defstar: macros allowing easy inline type declarations for 22 | variables and and function return values. 23 | - dexador: Yet another HTTP client for Common Lisp 24 | - dissect: A lib for introspecting the call stack and active restarts. 25 | - easy-routes: Yet another routes handling utility on top of Hunchentoot 26 | - fiveam: A simple regression testing framework 27 | 28 | 29 | - for: An extensible iteration macro library. 30 | - fset: A functional set-theoretic collections library. 31 | See: https://gitlab.common-lisp.net/fset/fset/-/wikis/home 32 | - generic-cl: Standard Common Lisp functions implemented using generic functions. 33 | - hunchentoot: Hunchentoot is a HTTP server based on USOCKET and 34 | BORDEAUX-THREADS. It supports HTTP 1.1, serves static files, has a 35 | simple framework for user-defined handlers and can be extended 36 | through su... 37 | - local-time: A library for manipulating dates and times, based on a paper by Erik Naggum 38 | - log4cl: NIL 39 | - lparallel: Parallelism for Common Lisp 40 | - lquery: A library to allow jQuery-like HTML/DOM manipulation. 41 | - nodgui: Tck-Tk graphical user interfaces. nodgui is a fork of Ltk with a built-in theme and more widgets. 42 | - metabang-bind: Bind is a macro that generalizes multiple-value-bind, let, let*, destructuring-bind, structure and slot accessors, and a whole lot more. 43 | - modf: A SETF like macro for functional programming 44 | 45 | 46 | - named-readtables: Library that creates a namespace for readtables akin 47 | to the namespace of packages. 48 | - parse-float: Parse floating point values in strings. 49 | - parse-number: Number parsing library 50 | - printv: printv: a batteries-included tracing and debug-logging macro 51 | - pythonic-string-reader: A simple and unintrusive read table modification that allows for 52 | simple string literal definition that doesn't require escaping characters. 53 | - quicksearch: Quicksearch searches CL library, and outputs results at REPL. 54 | - quri: Yet another URI library for Common Lisp 55 | - repl-utilities: Ease common tasks at the REPL. 56 | - serapeum: Utilities beyond Alexandria. 57 | - shasht: JSON reading and writing for the Kzinti. 58 | - shlex: Lexical analyzer for simple shell-like syntax. 59 | - spinneret: Common Lisp HTML5 generator. 60 | - secret-values: reduce the risk of accidentally revealing secret values such as passwords. 61 | - str: Modern, consistent and terse Common Lisp string manipulation library. 62 | - sxql: A SQL generator 63 | - trivia: NON-optimized pattern matcher compatible with OPTIMA, with extensible optimizer interface and clean codebase 64 | - trivial-arguments: A simple library to retrieve the lambda-list of a function. 65 | - trivial-do: Looping extensions that follow the style of the core DO functions. 66 | - trivial-monitored-thread: Trivial Monitored Thread offers a very simple (aka trivial) way of spawning threads and being informed when one any of them crash and die. 67 | - trivial-package-local-nicknames: Portability library for package-local nicknames 68 | - trivial-types: Trivial type definitions 69 | - vgplot: Interface to gnuplot 70 | - which: The which UNIX command in Common Lisp. 71 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CIEL Is an Extended Lisp 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 |
20 |

21 | 22 | CIEL 0.0-dev 23 | 24 | 25 |

26 |
27 | 28 |

CIEL Is an Extended Lisp

29 |
    30 |
  • 100% Common Lisp
  • batteries included
  • 31 |
32 |

33 | GitHub 34 | Show me

35 |
36 | 37 |
#!/usr/bin/env ciel
 38 | 
 39 | (print
 40 |   (json:read-json
 41 |     (http:get "https://fakestoreapi.com/products?limit=5")))
42 | 43 |

44 |

$ ./myscript.lisp
45 |

46 | 47 |

48 |

You need JavaScript to see the rest of the doc :( Just look at the GitHub repository. Cheers, EWW user maybe?
49 |

50 | 51 |
52 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /docs/install.md: -------------------------------------------------------------------------------- 1 | 2 | # Install 3 | 4 | CIEL can be used in two different roles: 5 | 6 | - As a library that you can load into any Common Lisp implementation. 7 | - As a binary based on `sbcl`, which is a command-line tool for use in the terminal or in shell scripts. It provides a more feature-rich REPL than standard `sbcl`, and a much faster startup time than starting `sbcl` and then loading the CIEL library. 8 | 9 | If you use a Lisp development environment, such as Emacs with Slime, you should opt for the library rather than the binary. To get the same fast startup time, you can use a prebuilt core image, as we will explain below. 10 | 11 | In the following, we will explain how to install the library, the binary, and the prebuilt core image, for various common SBCL setups. 12 | 13 | ## Download a prebuilt binary. 14 | 15 | To download a CIEL binary: 16 | 17 | - check our releases on https://github.com/ciel-lang/CIEL/releases/ 18 | - we provide a binary from a CI for some systems: go to 19 | , download the latest 20 | artifacts, unzip the `ciel-v0-{platform}.zip` archive and run `ciel-v0-{platform}/ciel`. 21 | - if you use the [Guix](https://guix.gnu.org/) package manager, install package `sbcl-ciel-repl`. 22 | 23 | CIEL is currently built for the following platforms: 24 | 25 | | Platform | System Version (release date) | 26 | |----------|-------------------------------| 27 | | debian | Debian Buster (2019) | 28 | | void | Void Linux glibc (2023-05), using [cinerion's Docker image](https://github.com/cinerion/sbcl-voidlinux-docker) | 29 | 30 | Start it with `./ciel` (adapt the path if you put the binary somewhere else). 31 | 32 | With no arguments, you enter CIEL's terminal REPL. 33 | 34 | You can give a CIEL script as first argument, or call a built-in one. See the scripting section. 35 | 36 | ## Run the binary in a Docker container 37 | 38 | We have a Dockerfile. 39 | 40 | Build your CIEL image: 41 | 42 | docker build -t ciel . 43 | 44 | The executable is built in `/usr/local/bin/ciel` of the Docker image. 45 | 46 | Get a CIEL REPL: 47 | 48 | docker run --rm -it ciel /usr/local/bin/ciel 49 | 50 | Run a script on your filesystem: 51 | 52 | docker run --rm -it ciel /usr/local/bin/ciel path/to/your/lisp/script.lisp 53 | 54 | Run a built-in script: 55 | 56 | docker run --rm -it ciel /usr/local/bin/ciel -s simpleHTTPserver 57 | 58 | So, save you some typing with a shell alias: 59 | 60 | alias ciel="sudo docker run --rm -it ciel /usr/local/bin/ciel" 61 | 62 | ## Install CIEL as a library 63 | 64 | You can install and CIEL like any other Common Lisp library, but you must make sure to also get all of its dependencies, a task that is best left to a package manager. 65 | 66 | ### Quicklisp 67 | 68 | CIEL is not on Quicklisp yet, but it is on [Ultralisp](https://ultralisp.org). 69 | 70 | So, either clone this repository: 71 | 72 | git clone https://github.com/ciel-lang/CIEL ~/quicklisp/local-projects/CIEL 73 | 74 | or install the Ultralisp distribution and pull the library from there: 75 | 76 | ```lisp 77 | (ql-dist:install-dist "http://dist.ultralisp.org/" :prompt nil) 78 | ``` 79 | 80 | #### Install our Lisp dependencies [MANDATORY] 81 | 82 | Even if you have a Lisp setup with Quicklisp installed, the current 83 | distribution of Quicklisp is quite old (as of August, 2024) and you 84 | need to pull recent dependencies. 85 | 86 | We'll clone the required ones into your `~/quicklisp/local-projects/`. 87 | 88 | make ql-deps 89 | 90 | Other tools exist for this (Qlot, ocicl…), we are just not using them yet. 91 | 92 | #### Loading CIEL with Quicklisp 93 | 94 | Now, in both cases, you can load the `ciel.asd` file (with `asdf:load-asd` 95 | or `C-c C-k` in Slime) or quickload "ciel": 96 | 97 | ```lisp 98 | (ql:quickload "ciel") 99 | ``` 100 | 101 | be sure to enter the `ciel-user` package: 102 | 103 | ```lisp 104 | (in-package :ciel-user) 105 | ``` 106 | you now have access to all CIEL's packages and functions. 107 | 108 | ### Guix 109 | 110 | CIEL is available via the [Guix](https://guix.gnu.org/) package manager, as a source code package (`cl-ciel`) or precompiled for SBCL (`sbcl-ciel`) and ECL (`ecl-ciel`). You have to add Lisp itself (package `sbcl` or `ecl`), and any other Lisp library you may want to use. 111 | 112 | In Lisp, do 113 | ```lisp 114 | (require "asdf") 115 | (asdf:load-system :ciel) 116 | (in-package :ciel-user) 117 | ``` 118 | 119 | Alternatively, or in addition, you can install `sbcl-ciel:image`, which contains a prebuilt core image under `bin/ciel.image`. It is executable, so you can run it in place of `sbcl`, or you can load it from the `sbcl` command line: 120 | 121 | ``` 122 | sbcl --core $(GUIX_PROFILE)/bin/ciel.image 123 | ``` 124 | 125 | In either case, you get a Lisp environment with CIEL preloaded, so all you have to do is 126 | 127 | ```lisp 128 | (in-package :ciel-user) 129 | ``` 130 | 131 | 132 | ## Using CIEL as a library in your Lisp code 133 | 134 | To use it in your project, create a package and "use" `ciel` in addition 135 | to `cl`: 136 | 137 | ```lisp 138 | (defpackage yourpackage 139 | (:use :cl :ciel)) 140 | ``` 141 | 142 | Alternatively, you can use `generic-ciel`, based on 143 | [generic-cl](https://github.com/alex-gutev/generic-cl/) (warn: 144 | generic-ciel is less tested at the moment). 145 | 146 | ```lisp 147 | (defpackage yourpackage 148 | (:use :cl :generic-ciel)) 149 | ``` 150 | 151 | `generic-cl` allows you to define `+` or `equalp` methods for your own 152 | objects (and more). 153 | 154 | # Building CIEL binaries and core images 155 | 156 | To build CIEL, both the binary and the core image, you need a couple 157 | system dependencies and you have to check a couple things on the side 158 | of Lisp before proceeding. 159 | 160 | ## Dependencies 161 | 162 | ### System dependencies 163 | 164 | You will probably need the following system dependencies (names for a 165 | Debian Bullseye system): 166 | 167 | zlib1g-dev # from deploy for SBCL < 2.2.6 168 | 169 | If your SBCL version is >= 2.2.6 you might want to use the more 170 | performant `libzstd-dev` library instead of `zlib1g-dev`. 171 | 172 | libzstd-dev # from deploy for SBCL >= 2.2.6 173 | 174 | On Linux: 175 | 176 | inotify-tools 177 | 178 | On MacOS: 179 | 180 | fsevent 181 | 182 | You can run: `make debian-deps` or `make macos-deps`. 183 | 184 | 185 | ### ASDF >= 3.3.4 (local-nicknames) 186 | 187 | ASDF is the de-facto system definition facility of Common Lisp, that 188 | lets you define your system's metadata (author, dependencies, sources, 189 | modules…). 190 | 191 | Please ensure that you have ASDF >= 3.3.4. 192 | 193 | It is for instance not the case with SBCL 2.2.9. 194 | 195 | Ask the version with `(asdf:asdf-version)` on a Lisp REPL, or with 196 | this one-liner from a terminal: 197 | 198 | $ sbcl --eval '(and (print (asdf:asdf-version)) (quit))' 199 | 200 | Here's a one-liner to update ASDF: 201 | 202 | $ mkdir ~/common-lisp/ 203 | $ ( cd ~/common-lisp/ && wget https://asdf.common-lisp.dev/archives/asdf-3.3.5.tar.gz && tar -xvf asdf-3.3.5.tar.gz && mv asdf-3.3.5 asdf ) 204 | 205 | 206 | ### Install Quicklisp 207 | 208 | To build CIEL on your machine, you need the [Quicklisp library 209 | manager](https://quicklisp.org/beta/). Quicklisp downloads and 210 | installs a library and its dependencies on your machine. It's very 211 | slick, we can install everything from the REPL without restarting our 212 | Lisp process. It follows a "distrubution" approach, think Debian 213 | releases, where libraries are tested to load. 214 | 215 | It isn't the only library manager nowadays. See [https://github.com/CodyReichert/awesome-cl#library-manager](https://github.com/CodyReichert/awesome-cl#library-manager). 216 | 217 | Install it: 218 | 219 | ```sh 220 | curl -O https://beta.quicklisp.org/quicklisp.lisp 221 | sbcl --load quicklisp.lisp --eval "(quicklisp-quickstart:install)" --quit 222 | sbcl --load ~/quicklisp/setup.lisp --eval "(ql:add-to-init-file)" --quit 223 | ``` 224 | 225 | It creates a `~/quicklisp/` directory. Read its installation instructions to know more. 226 | 227 | See the section above for loading CIEL via Quicklisp for how to make sure you have all the required dependencies. 228 | 229 | ### Run the build procedure 230 | 231 | You need the dependencies above: Quicklisp, a good ASDF version, our up-to-date Lisp dependencies. 232 | 233 | To build CIEL's binary, use: 234 | 235 | $ make build 236 | 237 | This creates a `ciel` binary in the current directory. 238 | 239 | To create a Lisp image: 240 | 241 | $ make image 242 | # or 243 | $ sbcl --load build-image.lisp 244 | 245 | This creates the `ciel-core` Lisp image. 246 | 247 | Unlike binaries, we cannot distribute core images. They depend on the machine they was were built on. 248 | 249 | ### Using the core image 250 | 251 | The way we use a core image is to load it at startup like this: 252 | 253 | sbcl --core ciel-core --eval '(in-package :ciel-user)' 254 | 255 | It loads fast and you have all CIEL libraries and goodies at your disposal. 256 | 257 | Then you have to configure your editor, like Slime, to have the choice of the Lisp image to 258 | start. See below. 259 | 260 | ### Core image: configure your editor 261 | 262 | The advantage of a core image is that it loads instantly, faster than 263 | a `(ql:quickload "ciel")`. We'll ask our editor to start SBCL with our 264 | CIEL core image. 265 | 266 | We'll configure SLIME for [multiple Lisps](https://common-lisp.net/project/slime/doc/html/Multiple-Lisps.html#Multiple-Lisps). 267 | 268 | You need to add this to your Emacs init file: 269 | 270 | ```lisp 271 | (setq slime-lisp-implementations 272 | `((sbcl ("sbcl" "--dynamic-space-size" "2000")) ;; default. Adapt if needed. 273 | (ciel-sbcl ("sbcl" "--core" "/path/to/ciel/ciel-core" "--eval" "(in-package :ciel-user)")))) 274 | (setq slime-default-lisp 'ciel-sbcl) 275 | ``` 276 | 277 | and start a Lisp process with `M-x slime`. 278 | 279 | If you didn't set `ciel-sbcl` as the default, then start the Lisp 280 | process with `M-- M-x slime` (alt-minus prefix), and choose 281 | `ciel-sbcl`. You can start more than one Lisp process from SLIME. 282 | 283 | The Lisp process should start instantly, as fast as the default SBCL, 284 | you won't wait for the quicklisp libraries to load. 285 | -------------------------------------------------------------------------------- /docs/language-extensions.md: -------------------------------------------------------------------------------- 1 | ## Data structures 2 | 3 | ### Generic and nested access to datastructures (access) 4 | 5 | From [Access](https://github.com/AccelerationNet/access/), we import `access` and `accesses` (plural). 6 | 7 | It's always 8 | 9 | ```lisp 10 | (access my-structure :elt) 11 | ``` 12 | 13 | for an alist, a hash-table, a struct, an object… Use `accesses` for nested access (specially useful with JSON). See also `json-pointer`. 14 | 15 | ### Hash-table utilities (Alexandria and Serapeum) 16 | 17 | We import functions from [Alexandria](https://alexandria.common-lisp.dev/draft/alexandria.html#Hash-Tables) and [Serapeum](https://github.com/ruricolist/serapeum/blob/master/REFERENCE.md#hash-tables). 18 | 19 | To see their full list with their documentation, see [alexandria](alexandria.md) [serapeum](serapeum.md). 20 | 21 | ```txt 22 | ;; alexandria 23 | hash-table-keys 24 | hash-table-values 25 | ensure-gethash 26 | ``` 27 | 28 | ``` txt 29 | ;; serapeum 30 | :dict 31 | :do-hash-table ;; see also trivial-do 32 | :dict* 33 | :dictq ;; quoted 34 | :href ;; for nested lookup. 35 | :href-default 36 | :pophash 37 | :swaphash 38 | :hash-fold 39 | :maphash-return 40 | :merge-tables 41 | :flip-hash-table 42 | :set-hash-table 43 | :hash-table-set 44 | :hash-table-predicate 45 | :hash-table-function 46 | :make-hash-table-function 47 | :delete-from-hash-table 48 | :pairhash 49 | ``` 50 | 51 | Here's how we can create a hash-table with keys and values: 52 | 53 | ``` lisp 54 | ;; create a hash-table: 55 | (dict :a 1 :b 2 :c 3) 56 | ;; => 57 | (dict 58 | :A 1 59 | :B 2 60 | :C 3 61 | ) 62 | ``` 63 | 64 | In default Common Lisp, you would do: 65 | 66 | ```lisp 67 | (let ((ht (make-hash-table :test 'equal))) 68 | (setf (gethash :a ht) 1) 69 | (setf (gethash :b ht) 2) 70 | (setf (gethash :c ht) 3) 71 | ht) 72 | ;; # 73 | ``` 74 | 75 | As seen above, hash-tables are pretty-printed by default. 76 | 77 | You can toggle the representation with `toggle-pretty-print-hash-table`, or by setting 78 | 79 | ```lisp 80 | (setf *pretty-print-hash-tables* nil) 81 | ``` 82 | 83 | in your configuration file. 84 | 85 | ### Sequences utilities (Alexandria, Serapeum) 86 | 87 | From [Serapeum](https://github.com/ruricolist/serapeum/blob/master/REFERENCE.md#sequences) we import: 88 | 89 | ``` txt 90 | :assort 91 | :batches 92 | :filter 93 | :runs 94 | :partition 95 | :partitions 96 | :split-sequence 97 | ``` 98 | 99 | And from [Alexandria](https://common-lisp.net/project/alexandria/draft/alexandria.html): 100 | 101 | ``` text 102 | :iota 103 | :flatten 104 | :shuffle 105 | :random-elt 106 | :length= 107 | :last-elt 108 | :emptyp 109 | ``` 110 | 111 | From `alexandria-2` we import: 112 | 113 | ```text 114 | :subseq* (the end argument can be larger than the sequence's length) 115 | ``` 116 | 117 | and some more. 118 | 119 | ### String manipulation (str) 120 | 121 | Available with the `str` prefix. 122 | 123 | It provides functions such as: `trim`, `join`, `concat`, `split`, `repeat`, `pad`, `substring`, `replace-all`, `emptyp`, `blankp`, `alphanump`, `upcase`, `upcasep`, `remove-punctuation`, `from-file`, `to-file`… 124 | 125 | See and https://lispcookbook.github.io/cl-cookbook/strings.html 126 | 127 | ## Arrow macros 128 | 129 | We provide the Clojure-like arrow macros and "diamond wands" from the [arrow-macros](https://github.com/hipeta/arrow-macros) library. 130 | 131 | 132 | #### **CIEL** 133 | 134 | ```lisp 135 | ;; -> inserts the previous value as its first argument: 136 | (-> " hello macros " 137 | str:upcase 138 | str:words) ; => ("HELLO" "MACROS") 139 | 140 | ;; ->> inserts it as its second argument: 141 | (->> " hello macros " 142 | str:upcase 143 | str:words 144 | (mapcar #'length)) ; => (5 6) 145 | 146 | 147 | ;; use as-> to be flexible on the position of the argument: 148 | (as-> 4 x 149 | (1+ x) 150 | (+ x x)) ; => 10 151 | ``` 152 | 153 | #### **CL** 154 | 155 | ```lisp 156 | ;; In pure CL, just wrap function calls… 157 | (mapcar #'length (str:words (str:upcase " hello world "))) 158 | 159 | ;; … or use let* and intermediate variables: 160 | (let* ((var "hello macros") 161 | (upcased (str:upcase var)) 162 | (words (str:words upcased))) 163 | words) 164 | ``` 165 | 166 | 167 | 168 | And there is more. All the available macros are: 169 | 170 | ``` txt 171 | :-> 172 | :->> 173 | :some-> 174 | :some->> 175 | :as-> 176 | :cond-> 177 | :cond->> 178 | :-<> 179 | :-<>> 180 | :some-<> 181 | :some-<>> 182 | ``` 183 | 184 | ## Functions 185 | 186 | ### Partial application 187 | 188 | We import Serapeum's `partial` and Alexandria's `rcurry`. They allow 189 | partial application of functions. 190 | 191 | `partial` is similar to `alexandria:curry` but is always inlined. 192 | 193 | We import Serapeum's `juxt`. 194 | 195 | You can check [Serapeum's function helpers](https://github.com/ruricolist/serapeum/blob/master/REFERENCE.md#functions), 196 | the non-imported ones are only a `serapeum` package prefix away in CIEL if you need 197 | them. Other noticeable functions: `distinct`, `once`, `throttle`, `trampoline`, `do-nothing`… 198 | 199 | ### Memoization 200 | 201 | We import `defcached` from 202 | [function-cache](https://github.com/AccelerationNet/function-cache), a 203 | library that provides extensible caching/memoization. 204 | 205 | For example: 206 | 207 | ```lisp 208 | (defcached (foo :timeout 10) (arg) 209 | (sleep 3) 210 | arg) 211 | ``` 212 | 213 | Run `(foo 1)`, it sleeps 3 seconds and returns 1. Run `(foo 1)` again 214 | and it returns 1 immediately. Its result (for this argument) is cached 215 | for 10 seconds. 216 | 217 | 218 | 219 | ## Bind, more destructuring in `let` (metabang-bind) 220 | 221 | We import the `bind` macro from [metabang-bind](https://common-lisp.net/project/metabang-bind/user-guide.html) ([GitHub](https://github.com/gwkkwg/metabang-bind)). 222 | 223 | The idiomatic way to declare local variables is `let` (and `let*`), 224 | the way to declare local functions is `flet` (and `labels`). Use them 225 | if it is fine for you. 226 | 227 | However, if you ever noticed you write convoluted `let` forms, adding 228 | list destructuring, multiple values or slot access into the mix, and 229 | if you use a `flet` *and then* a `let`, then read on. 230 | 231 | `bind` integrates more variable binding and list destructuring idioms. It has two goals. Quoting: 232 | 233 | > 1. reduce the number of nesting levels 234 | 235 | > 2. make it easier to understand all of the different forms of destructuring and variable binding by unifying the multiple forms of syntax and reducing special cases. 236 | 237 | ### Bind in CIEL 238 | 239 | We import the `bind` macro. However, the package has more external 240 | symbols that we don't import, such as its error type (`bind-error`) and 241 | its extension mechanism. 242 | 243 | > Note: if you like object destructuring in general, you'll like [pattern matching](/language-extensions?id=pattern-matching). 244 | 245 | 246 | ### Bind is a replacement for `let` and `let*`. 247 | 248 | You can use `bind` in lieue of `let*`. 249 | 250 | So, its simpler form is: 251 | 252 | ~~~lisp 253 | (bind (a) 254 | (do-something a)) 255 | ~~~ 256 | 257 | `a` is initialized to `nil`. 258 | 259 | To give it a default value, use a pair, as in `(a 1)` below: 260 | 261 | ~~~lisp 262 | (bind ((a 1) 263 | (b 2)) 264 | ...) 265 | ~~~ 266 | 267 | Below, we'll use indicators for `a`, the binding on the left-hand 268 | side, so we can have a `bind` form that starts with three 269 | parenthesis. But it's OK, you know how to read them. 270 | 271 | ~~~lisp 272 | (bind (((:values a b c) (a-function))) 273 | ...) 274 | ~~~ 275 | 276 | 277 | ### Bind with multiple-values: `(:values ...)` 278 | 279 | Use `(:values ...)` in the left-hand side of the binding: 280 | 281 | ~~~lisp 282 | (bind (((:values a b) (truncate 4.5)) 283 | ... 284 | ~~~ 285 | 286 | In pure CL, you'd use `multiple-value-bind` (aka mvb for completion). 287 | 288 | 289 | ### Ignore values: the `_` placeholder 290 | 291 | As in: 292 | 293 | ~~~lisp 294 | (bind (((_ value-1 value-2) (some-function-returning-3-values))) 295 | ...) 296 | ~~~ 297 | 298 | ### Destructuring lists 299 | 300 | Use a list in the left-hand side: 301 | 302 | ~~~lisp 303 | (defun return-list (a b) (list a b)) 304 | 305 | (bind (((a b) (return-list 3 4))) 306 | (list a b)) 307 | ;; => (3 4) 308 | ~~~ 309 | 310 | You can use usual lambda parameters for more destructuring: 311 | 312 | ~~~lisp 313 | (bind ((a 2) 314 | ((b &rest args &key (c 2) &allow-other-keys) '(:a :c 5 :d 10 :e 54)) 315 | ((:values d e) (truncate 4.5))) 316 | (list a b c d e args)) 317 | ~~~ 318 | 319 | ### Bind with plists, arrays, classes, structures, regular expressions, flet and labels 320 | 321 | It's all well explained [in the documentation](https://common-lisp.net/project/metabang-bind/user-guide.html)! 322 | 323 | 324 | Conditions 325 | ---------- 326 | 327 | See 328 | 329 | From Serapeum, we import [`ignoring`](https://github.com/ruricolist/serapeum/blob/master/REFERENCE.md#ignoring-type-body-body). 330 | 331 | An improved version of `ignore-errors`. The behavior is the same: if an error occurs in the body, the form returns two values, nil and the condition itself. 332 | 333 | `ignoring` forces you to specify the kind of error you want to ignore: 334 | 335 | ```lisp 336 | (ignoring parse-error 337 | ...) 338 | ``` 339 | 340 | ## Iteration 341 | 342 | See for examples, including about the good old `loop`. 343 | 344 | We import macros from [trivial-do](https://github.com/yitzchak/trivial-do/), that provides `dolist`-like macro to iterate over more structures: 345 | 346 | - `dohash`: dohash iterates over the elements of an hash table and binds key-var to the key, value-var to the associated value and then evaluates body as a tagbody that can include declarations. Finally the result-form is returned after the iteration completes. 347 | 348 | - `doplist`: doplist iterates over the elements of an plist and binds key-var to the key, value-var to the associated value and then evaluates body as a tagbody that can include declarations. Finally the result-form is returned after the iteration completes. 349 | 350 | - `doalist`: doalist iterates over the elements of an alist and binds key-var to the car of each element, value-var to the cdr of each element and then evaluates body as a tagbody that can include declarations. Finally the result-form is returned after the iteration completes. 351 | 352 | - `doseq*`: doseq\* iterates over the elements of an sequence and binds position-var to the index of each element, value-var to each element and then evaluates body as a tagbody that can include declarations. Finally the result-form is returned after the iteration completes. 353 | 354 | - `doseq`: doseq iterates over the elements of an sequence and binds value-var to successive values and then evaluates body as a tagbody that can include declarations. Finally the result-form is returned after the iteration completes. 355 | 356 | - `dolist*`: dolist\* iterates over the elements of an list and binds position-var to the index of each element, value-var to each element and then evaluates body as a tagbody that can include declarations. Finally the result-form is returned after the iteration completes. 357 | 358 | We ship [`for`](https://github.com/Shinmera/for) so you can try it, but we don't import its symbols. `for`'s `over` keyword allows to loop across all data structures (lists, hash-tables…). 359 | 360 | 361 | ## Lambda shortcut 362 | 363 | `^` is a synonym macro for `lambda`. 364 | 365 | ```lisp 366 | (^ (x) (+ x 10)) 367 | => 368 | (lambda (x) (+ x 10)) 369 | ``` 370 | 371 | ## Pythonic triple quotes docstring 372 | 373 | We can enable the syntax to use triple quotes for docstrings, and double quotes within them: 374 | 375 | 376 | 377 | #### **CIEL** 378 | ```lisp 379 | (ciel:enable-pythonic-string-syntax) 380 | 381 | (defun foo () 382 | """foo "bar".""" 383 | t) 384 | ``` 385 | 386 | #### **CL** 387 | 388 | ~~~lisp 389 | ;; Normally single quotes must be escaped. 390 | (defun foo () 391 | "foo \"bar\"." 392 | t) 393 | 394 | ;; use: 395 | (pythonic-string-reader:enable-pythonic-string-syntax) 396 | ~~~ 397 | 398 | 399 | To disable this syntax, do: 400 | 401 | ~~~lisp 402 | (ciel:disable-pythonic-string-syntax) 403 | ~~~ 404 | 405 | We use [pythonic-string-reader](https://github.com/smithzvk/pythonic-string-reader). 406 | 407 | !> This syntax conflicts with libraries that use cl-syntax to use triple quotes 408 | too, even only internally. It happens with the Jonathan library. 409 | 410 | 411 | ## Packages 412 | 413 | `defpackage` is nice and well, until you notice some shortcomings. That's why we import UIOP's `define-package`. You'll get: 414 | 415 | - less warnings when you remove an exported symbol 416 | - a `:reexport` option (as well as `:use-reexport` and `:mix-reeport`) 417 | - `:recycle` and `:mix` options. 418 | 419 | It is a drop-in replacement. 420 | 421 | Here's [uiop:define-package documentation](https://asdf.common-lisp.dev/uiop.html#UIOP_002fPACKAGE). 422 | 423 | ### Packages local nicknames 424 | 425 | CIEL defines local nicknames for other libraries. 426 | 427 | For example, `csv` is a shorter nickname for `cl-csv`. `time` is a 428 | shorter nickname for `local-time`. 429 | 430 | They are available when you are "inside" the CIEL-USER package (when you do `(in-package :ciel-user)`). 431 | 432 | If you define a new package that "uses" CIEL, you might want to also 433 | get this set of nicknames. Here's the full list: 434 | 435 | ~~~lisp 436 | (uiop:define-package myproject 437 | (:use :cl :ciel) 438 | (:local-nicknames (:/os :uiop/os) 439 | (:os :uiop/os) 440 | (:filesystem :uiop/filesystem) 441 | (:finder :file-finder) 442 | (:notify :org.shirakumo.file-notify) 443 | (:alex :alexandria) 444 | (:csv :cl-csv) 445 | (:http :dexador) 446 | (:json :shasht) 447 | (:json-pointer :cl-json-pointer/synonyms) 448 | (:time :local-time) 449 | (:routes :easy-routes)) 450 | (:documentation "My package, using CIEL and defining the same local nicknames.")) 451 | ~~~ 452 | 453 | 454 | 455 | Pattern matching 456 | ---------------- 457 | 458 | We use [Trivia](https://github.com/guicho271828/trivia/) (see 459 | [its wiki](https://github.com/guicho271828/trivia/wiki/What-is-pattern-matching%3F-Benefits%3F)), from which we import `match`. 460 | 461 | You can start typing "match", followed by the object to match against, and the clauses, which are similar to a `cond`. Here's an example to match a list: 462 | 463 | ~~~lisp 464 | (match '(1 2) 465 | ((list x y) ;; <-- pattern 466 | (print x) 467 | (print y)) 468 | (_ ;; <-- failover clause 469 | :else)) 470 | ;; 1 471 | ;; 2 472 | ~~~ 473 | 474 | On the above example, `(list x y)` is the pattern. It binds `x` to 1 and `y` to 2. Pay attention that the `list` pattern is "strict": it has two subpatterns (x and y) and it will thus match against an object of length 2. 475 | 476 | If you wanted `y` to match the rest of the list, use `list*`: 477 | 478 | ~~~lisp 479 | (match '(1 2 3) 480 | ((list* x y) 481 | (print x) 482 | (print y)) 483 | (_ :else)) 484 | ;; 1 485 | ;; (2 3) 486 | ~~~ 487 | 488 | This could also be achieved with the `cons` pattern: 489 | 490 | ~~~lisp 491 | (match '(1 2 3) 492 | ((cons x y) 493 | (print x) 494 | (print y)) 495 | (_ :else)) 496 | ;; 1 497 | ;; (2 3) 498 | ~~~ 499 | 500 | You can of course use `_` placeholders: 501 | 502 | ~~~lisp 503 | (match '(1 2 3) 504 | ((list* x _) 505 | (print x)) 506 | (_ :else)) 507 | ;; 1 508 | ~~~ 509 | 510 | As we saw with `list` and `cons`, Trivia has patterns to match against types (vectors, alists, plists, arrays), including classes and structures. 511 | 512 | You can use [numeric patterns](https://github.com/guicho271828/trivia/wiki/Numeric-Patterns) (`=`, `<` and friends, that behave as you expect): 513 | 514 | ~~~lisp 515 | (let ((x 5)) 516 | (match x 517 | ((< 10) 518 | :lower))) 519 | ;; :LOWER 520 | ~~~ 521 | 522 | Then, you can combine them with [logic based patterns and guards](https://github.com/guicho271828/trivia/wiki/Logic-Based-Patterns). For example: 523 | 524 | ~~~lisp 525 | (match x 526 | ((or (list 1 a) 527 | (cons a 3)) 528 | a)) 529 | ~~~ 530 | 531 | guards allow to check the matches against a predicate. For example: 532 | 533 | ~~~lisp 534 | (match (list 2 5) 535 | ((guard (list x y) ; subpattern1 536 | (= 10 (* x y))) ; test-form 537 | t)) 538 | ~~~ 539 | 540 | Above we use the `list` pattern, and we verify a predicate. 541 | 542 | Trivia has more tricks in its sleeve. See the [special patterns](https://github.com/guicho271828/trivia/wiki/Special-Patterns) (access and change objects), the [ppcre contrib](https://github.com/guicho271828/trivia/wiki/Contrib-packages), etc. 543 | 544 | You migth also be interested in exhaustiveness type checking explained just below. 545 | 546 | 547 | ## Regular expressions 548 | 549 | Use `ppcre`. 550 | 551 | See and 552 | 553 | 554 | Type declarations 555 | ----------------- 556 | 557 | Use the `-->` macro to gradually add type declarations. 558 | 559 | Alternatively, use `defun*`, `defgeneric*`, `defmethod*`, `defparameter*` and `defvar*` to add type declarations directly in the lambda list. 560 | 561 | These notations are not strictly equivalent though. 562 | 563 | `-->` comes from [Serapeum](https://github.com/ruricolist/serapeum/blob/master/REFERENCE.md#types). It is a shortcut for `(declaim (ftype my-function (… input types …) … return type …))` 564 | 565 | 566 | 567 | #### **CIEL** 568 | 569 | ```lisp 570 | (--> mod-fixnum+ (fixnum fixnum) fixnum) 571 | (defun mod-fixnum+ (x y) ...) 572 | 573 | ;; --> comes straight from serapeum:-> 574 | ``` 575 | 576 | #### **CL** 577 | 578 | ```lisp 579 | (declaim (ftype (function (fixnum fixnum) fixnum) mod-fixnum+)) 580 | (defun mod-fixnum+ (x y) ...) 581 | ``` 582 | 583 | 584 | Now `defun*` and friends allow to add type declarations directly in the lambda list. They add the `(declaim (ftype` as above, and additionnaly `declare` types inside the function body: 585 | 586 | 587 | 588 | #### **CIEL** 589 | 590 | ```lisp 591 | (defun* foo ((a integer)) 592 | (:returns integer) 593 | (* 10 a)) 594 | ``` 595 | 596 | #### **CL** 597 | 598 | ```lisp 599 | ;; In pure CL, type the functions at its boundaries with ftype. 600 | ;; It is a bit verbose, but it has the advantage, being not tied to defun, 601 | ;; that we can easily refine types during development. 602 | (declaim (ftype (function (integer) integer) foo)) 603 | ;; ^^ inputs ^^ output [optional] ^^ function name 604 | 605 | ;; defstar adds the internal "declare" and "the…". 606 | ;; "the" is a promise made to the compiler, that will optimize things out. 607 | (defun foo (a) 608 | (declare (type integer a)) 609 | (the integer (* 10 a))) 610 | 611 | ``` 612 | 613 | 614 | A type declaration for a parameter: 615 | 616 | 617 | 618 | #### **CIEL** 619 | 620 | ```lisp 621 | (defparameter* (*file-position* (integer 0)) 0) 622 | ``` 623 | 624 | #### **CL** 625 | 626 | ```lisp 627 | 628 | ;; Normal defparameter: 629 | (defparameter *file-position* 0) 630 | 631 | ;; Assigning a bad value works: 632 | (setf *file-position* "8") 633 | ;; "8" 634 | 635 | ;; We add a type declaration: 636 | (declaim (type (integer 0) *file-position*)) 637 | 638 | ;; and now: 639 | (setf *file-position* "8") 640 | ;; 641 | ;; Value of #1="8" in (THE INTEGER "8") is #1#, not a INTEGER. 642 | ;; [Condition of type SIMPLE-TYPE-ERROR] 643 | ;; 644 | ;; we get a type error. 645 | ``` 646 | 647 | 648 | We can use any type specifier: 649 | 650 | ~~~lisp 651 | (deftype natural () '(real 0)) 652 | (defun* sum ((a natural) (b natural)) 653 | (:returns natural) 654 | (+ a b)) 655 | ~~~ 656 | 657 | Now, we get type errors at compile time: 658 | 659 | ~~~lisp 660 | (foo "3") 661 | ;; => 662 | The value 663 | "3" 664 | is not of type 665 | INTEGER 666 | when binding A 667 | [Condition of type TYPE-ERROR] 668 | 669 | Restarts: […] 670 | 671 | Backtrace: 672 | 0: (FOO "3") [external] 673 | 1: (SB-INT:SIMPLE-EVAL-IN-LEXENV (FOO "a") #) 674 | 2: (EVAL (FOO "3")) 675 | ~~~ 676 | 677 | and we get compile-time warnings on type mismatches (but to be honest on simple cases like this SBCL is already quite good): 678 | 679 | ~~~lisp 680 | (defun* bad-foo ((a integer)) 681 | (:returns integer) 682 | (format t "~a" (* 10 a))) 683 | ; 684 | ; in: DEFUN* BAD-FOO 685 | ; (THE INTEGER (FORMAT T "~a" (* 10 CIEL::A))) 686 | ; 687 | ; caught WARNING: 688 | ; Constant NIL conflicts with its asserted type INTEGER. 689 | ; See also: 690 | ; The SBCL Manual, Node "Handling of Types" 691 | ; 692 | ; compilation unit finished 693 | ; caught 1 WARNING condition 694 | BAD-FOO 695 | ~~~ 696 | 697 | We could add extra protection and a `check-type`, evaluated at runtime. 698 | Defstar can add them automatically if `defstar:*check-argument-types-explicitly?*` is non-nil. 699 | 700 | In theory, such declarations don't guarantee that Lisp will do type checking but in practice the implementations, and in particular SBCL, perform type checking. 701 | 702 | We use the [defstar](https://github.com/lisp-maintainers/defstar) library. Its README has many more examples and even more features (adding assertions, `:pre` and `:post` clauses). 703 | 704 | > Note: we are not talking thorough ML-like type checking here (maybe the [Coalton](https://github.com/stylewarning/coalton) library will bring it to Common Lisp). But in practice, the compiler warnings and errors are helpful during development, "good enough", and SBCL keeps improving in that regard. 705 | 706 | > Note: there is no "undeclaim" form :] You can unintern a symbol and re-define it. 707 | 708 | See also: 709 | 710 | - [declarations](http://clhs.lisp.se/Body/03_c.htm) in the Common Lisp Hyper Spec. 711 | - https://lispcookbook.github.io/cl-cookbook/type.html 712 | - the article [Static type checking in SBCL](https://medium.com/@MartinCracauer/static-type-checking-in-the-programmable-programming-language-lisp-79bb79eb068a), by Martin Cracauer 713 | - the article [Typed List, a Primer](https://alhassy.github.io/TypedLisp) - let's explore Lisp's fine-grained type hierarchy! with a shallow comparison to Haskell. 714 | 715 | 716 | Type checking: exhaustiveness type checking 717 | ------------------------------------------- 718 | 719 | Write a "case" and get a compile-time warning if you don't cover all cases. 720 | 721 | From Serapeum, we import: 722 | 723 | ```lisp 724 | :etypecase-of 725 | :ctypecase-of 726 | :typecase-of 727 | :case-of 728 | :ccase-of 729 | ``` 730 | 731 | `etypecase-of` allows to do [compile-time exhaustiveness type checking](https://github.com/ruricolist/serapeum#compile-time-exhaustiveness-checking%0A). 732 | 733 | ### Example with enums 734 | 735 | We may call a type defined using member an enumeration. Take an enumeration like this: 736 | 737 | ```lisp 738 | (deftype switch-state () 739 | '(member :on :off :stuck :broken)) 740 | ``` 741 | 742 | Now we can use `ecase-of` to take all the states of the switch into account. 743 | 744 | ```lisp 745 | (defun flick (switch) 746 | (ecase-of switch-state (state switch) 747 | (:on (switch-off switch)) 748 | (:off (switch-on switch)))) 749 | => Warning 750 | ``` 751 | 752 | ```lisp 753 | (defun flick (switch) 754 | (ecase-of switch-state (state switch) 755 | (:on (switch-off switch)) 756 | (:off (switch-on switch)) 757 | ((:stuck :broken) (error "Sorry, can't flick ~a" switch)))) 758 | => No warning 759 | ``` 760 | 761 | ### Example with union types 762 | 763 | ```lisp 764 | (defun negative-integer? (n) 765 | (etypecase-of t n 766 | ((not integer) nil) 767 | ((integer * -1) t) 768 | ((integer 1 *) nil))) 769 | => Warning 770 | 771 | (defun negative-integer? (n) 772 | (etypecase-of t n 773 | ((not integer) nil) 774 | ((integer * -1) t) 775 | ((integer 1 *) nil) 776 | ((integer 0) nil))) 777 | => No warning 778 | ``` 779 | 780 | See [Serapeum's reference](https://github.com/ruricolist/serapeum/blob/master/REFERENCE.md#control-flow). 781 | 782 | ## trivial-types: more type definitions 783 | 784 | From [trivial-types](https://github.com/m2ym/trivial-types), we import 785 | 786 | - `association-list-p` 787 | - `type-expand` 788 | - `string-designator` 789 | - `property-list` 790 | - `tuple` 791 | - `association-list` 792 | - `character-designator` 793 | - `property-list-p` 794 | - `file-associated-stream-p` 795 | - `type-specifier-p` 796 | - `list-designator` 797 | - `package-designator` 798 | - `tuplep` 799 | - `non-nil` 800 | - `file-associated-stream` 801 | - `stream-designator` 802 | - `function-designator` 803 | - `file-position-designator` 804 | - `pathname-designator` 805 | -------------------------------------------------------------------------------- /docs/repl.md: -------------------------------------------------------------------------------- 1 | # CIEL's custom REPL 2 | 3 | CIEL's REPL is more user friendly than the default SBCL one. In particular: 4 | 5 | - it has readline capabilities, meaning that the arrow keys work by default (wouhou!) and there is a persistent history, like in any shell. 6 | - it has **multiline input**. 7 | - it has **TAB completion**. 8 | - including for files (after a bracket) and binaries in the PATH. 9 | - it handles errors gracefully: you are not dropped into the debugger and its sub-REPL, you simply see the error message. 10 | - it has optional **syntax highlighting**. 11 | - it has a **shell pass-through**: try `!ls` (also available in Slime) 12 | - it runs **interactive commands**: try `!htop`, `!vim test.lisp`, `!emacs -nw test.lisp` or `!env FOO=BAR sudo -i powertop`. 13 | - it has a quick **edit and load file** command: calling `%edit file.lisp` will open the file with the editor of the EDITOR environment variable. When you close it, the file is loaded and evaluated. 14 | - it has an optional **lisp critic** that scans the code you enter at 15 | the REPL for instances of bad practices. 16 | 17 | - it defines more **helper commands**: 18 | 19 | ``` txt 20 | %help => Prints this general help message 21 | %doc => Prints the available documentation for this symbol 22 | %? => Gets help on a symbol : :? str 23 | %w => Writes the current session to a file 24 | %d => Dumps the disassembly of a symbol 25 | %t => Prints the type of an expression 26 | %q => Ends the session. 27 | ``` 28 | 29 | Our REPL is adapted from [sbcli](https://github.com/hellerve/sbcli). See also [cl-repl](https://github.com/koji-kojiro/cl-repl/), that has an interactive debugger. 30 | 31 | > Note: a shell interface doesn't replace a good development environment. See this [list of editors for Common Lisp](https://lispcookbook.github.io/cl-cookbook/editor-support.html): Emacs, Vim, Atom, VSCode, Intellij, SublimeText, Jupyter Notebooks and more. 32 | 33 | ## Quick documentation lookup 34 | 35 | The documentation for a symbol is available with `%doc` and also by 36 | appending a "?" after a function name: 37 | 38 | ``` 39 | ciel-user> %doc dict 40 | ;; or: 41 | ciel-user> (dict ? 42 | ``` 43 | 44 | ## Shell pass-through with "!" 45 | 46 | Use `!` to send a shell command. All shell commands are run interactively, so you can run `htop`, `sudo`, `emacs -nw` etc. 47 | 48 | ``` 49 | !ls 50 | !sudo emacs -nw /etc/ 51 | ``` 52 | 53 | We provide TAB completion for shell commands that are in your PATH. 54 | 55 | See [Lish](https://github.com/nibbula/lish/) and [SHCL](https://github.com/bradleyjensen/shcl) for more unholy union of (posix) shells and Common Lisp. 56 | 57 | 58 | ## Syntax highlighting 59 | 60 | Syntax highlighting is off by default. To enable it, install [pygments](https://pygments.org/) and add this in your `~/.cielrc`: 61 | 62 | ```lisp 63 | (setf sbcli:*syntax-highlighting* t) 64 | 65 | ;; and, optionally: 66 | ;; (setf sbcli::*pygmentize* "/path/to/pygmentize") 67 | ;; (setf sbcli::*pygmentize-options* (list "-s" "-l" "lisp")) 68 | ``` 69 | 70 | You can also switch it on and off from the REPL: 71 | 72 | ```lisp 73 | (setf sbcli:*syntax-highlighting* t) 74 | ``` 75 | 76 | ## Friendly lisp-critic 77 | 78 | The `%lisp-critic` helper command toggles on and off the 79 | [lisp-critic](https://github.com/g000001/lisp-critic). The Lisp Critic 80 | scans your code for instances of bad Lisp programming practice. For 81 | example, when it sees the following function: 82 | 83 | 84 | ~~~lisp 85 | (critique 86 | (defun count-a (lst) 87 | (setq n 0) 88 | (dolist (x lst) 89 | (if (equal x 'a) 90 | (setq n (+ n 1)))) 91 | n)) 92 | ~~~ 93 | 94 | the lisp-critic gives you these advices: 95 | 96 | ``` 97 | ---------------------------------------------------------------------- 98 | 99 | SETS-GLOBALS: GLOBALS!! Don't use global variables, i.e., N 100 | ---------------------------------------------------------------------- 101 | 102 | DOLIST-SETF: Don't use SETQ inside DOLIST to accumulate values for N. 103 | Use DO. Make N a DO variable and don't use SETQ etc at all. 104 | ---------------------------------------------------------------------- 105 | 106 | USE-EQL: Unless something special is going on, use EQL, not EQUAL. 107 | ---------------------------------------------------------------------- 108 | 109 | X-PLUS-1: Don't use (+ N 1), use (1+ N) for its value or (INCF N) to 110 | change N, whichever is appropriate here. 111 | ---------------------------------------------------------------------- 112 | ; in: DEFUN COUNT-A 113 | ; (SETQ CIEL-USER::N 0) 114 | ; 115 | ; caught WARNING: 116 | ; undefined variable: N 117 | ; 118 | ; compilation unit finished 119 | ; Undefined variable: 120 | ; N 121 | ; caught 1 WARNING condition 122 | => COUNT-A 123 | ``` 124 | 125 | ## Quick edit & load a file 126 | 127 | Use `%edit file.lisp`. 128 | 129 | This will open the file with the editor of the EDITOR environment variable. When you 130 | close it, the file is loaded and evaluated. If you defined functions, you can try them in the REPL. 131 | 132 | It is a quick way to write lisp code and have fast feedback. It's nice 133 | to use to tinker with code, to write small throw-away 134 | programs. However, this doesn't replace a true editor setup! 135 | 136 | We use [magic-ed](https://github.com/sanel/magic-ed). You can also call it manually with `(magic-ed "file.lisp")`, and give it a couple arguments: 137 | 138 | - `:eval nil`: don't evaluate the file when you close it. 139 | - `:output :string`: output the file content as a string. 140 | -------------------------------------------------------------------------------- /docs/scripting.md: -------------------------------------------------------------------------------- 1 | # Scripting 2 | 3 | CIEL provides a fast-starting scripting solution for Common Lisp. 4 | 5 | It is based on a standalone binary (created with the fast SBCL 6 | implementation) and it ships useful built-in utilities, for real-world 7 | needs: HTTP, JSON, CSV handling, plotting, and more. You just have to 8 | get the binary and run your script. Use a shebang line if you wish. 9 | 10 | It's a fast and easy solution to write Lisp code for your day-to-day tasks. 11 | 12 | > Note: this is brand new! Expect limitations and changes. 13 | 14 | Get the `ciel` binary (it's under 30MB) and call it with your .lisp script: 15 | 16 | ``` 17 | $ ciel script.lisp 18 | ``` 19 | 20 | (or just `./script.lisp` with a shebang line, see below) 21 | 22 | Call built-in scripts: 23 | 24 | ``` 25 | $ ciel -s simpleHTTPserver 9000 26 | ``` 27 | 28 | > Note: script names are case insensitive. 29 | 30 | ### Example script 31 | 32 | ```lisp 33 | #!/usr/bin/env ciel 34 | ;; optional shebang line, only for the short ./script call) 35 | 36 | ;; Start your script with this to access all CIEL goodies. 37 | ;; It is now also optional. 38 | (in-package :ciel-user) 39 | 40 | (defun hello (name) 41 | "Say hello." 42 | ;; format! prints on standard output and flushes the streams. 43 | (format! t "Hello ~a!~&" name)) 44 | 45 | ;; Access CLI args: 46 | (hello (second *script-args*)) 47 | 48 | ;; We have access to the DICT notation for hash-tables: 49 | (print "testing dict:") 50 | (print (dict :a 1 :b 2)) 51 | 52 | ;; We can run shell commands: 53 | (cmd:cmd "ls") 54 | 55 | ;; Access environment variables: 56 | (hello (os:getenv "USER")) ;; os is a nickname for uiop/os 57 | 58 | (format! t "Let's define an alias to run shell commands with '!'. This gives: ") 59 | (defalias ! #'cmd:cmd) 60 | (! "pwd") 61 | 62 | ;; In cas of an error, we can ask for a CIEL toplevel REPL: 63 | (handler-case 64 | (error "oh no") 65 | (error (c) 66 | (format! t "An error occured: ~a" c) 67 | (format! t "Here's a CIEL top level REPL: ") 68 | (sbcli::repl :noinform t))) 69 | ``` 70 | 71 | Output: 72 | 73 | 74 | ``` 75 | $ ciel script.lisp you 76 | => 77 | 78 | Hello you! 79 | "testing dict:" 80 | 81 | (dict 82 | :A 1 83 | :B 2 84 | ) 85 | cmd? ABOUT.org ciel ciel-core 86 | bin docs src 87 | […] 88 | Hello vindarel! 89 | Let's define an alias to run shell commands with '!'. This gives: 90 | /home/vindarel/projets/ciel 91 | ciel-user> 92 | ``` 93 | 94 | ### Run (interactive) shell commands 95 | 96 | Use [cmd](https://github.com/ruricolist/cmd): 97 | 98 | ~~~lisp 99 | (cmd:cmd "shell command") 100 | ~~~ 101 | 102 | For interactive commands, do: 103 | 104 | ~~~lisp 105 | (cmd:cmd "sudo htop" :input :interactive :output :interactive) 106 | ;; aka (cmd:cmd "..." :<> :interactive) 107 | ;; aka (uiop:run-program '("sudo" "htop") :output :interactive :input :interactive) 108 | ~~~ 109 | 110 | this works for `sudo`, `htop`, `less`, `vim`, `ncdu`, `fzf`, `gum` etc. 111 | 112 | 113 | ## Command line arguments 114 | 115 | Access them with `ciel-user:*script-args*`. It is a list of strings that 116 | contains your script name as first argument. 117 | 118 | This list of arguments is modified by us, so that it only contains 119 | arguments for your script, and so that it is the same list wether you 120 | call the script with `-s` or with the shebang line. You can always 121 | check the full original list with `(uiop:command-line-arguments)`. 122 | 123 | You can use CL built-ins to look what's into this list, such as `(member "-h" *script-args* :test #'equal)`. 124 | 125 | You can also use a proper command-line options parser, which is shipped with CIEL: [Clingon](https://github.com/dnaeon/clingon). This top-notch library supports: 126 | 127 | - Short and long option names 128 | - Automatic generation of help/usage information for commands and sub-commands 129 | - Support for various kinds of options like *string*, *integer*, *boolean*, *switches*, *enums*, *list*, *counter*, *filepath*, etc. 130 | - Out of the box support for `--version` and `--help` flags 131 | - Subcommands 132 | - Support for pre-hook and post-hook actions for commands, which allows invoking functions before and after the respective handler of the command is executed 133 | - Support for Bash and Zsh shell completions 134 | - and more. 135 | 136 | See below for an example on how to use Clingon. For more, see its README and [the Cookbook: scripting page](https://lispcookbook.github.io/cl-cookbook/scripting.html#parsing-command-line-arguments). 137 | 138 | ### Parse command-line arguments with Clingon 139 | 140 | Here's a quick example. 141 | 142 | First, we define our options: 143 | 144 | ~~~lisp 145 | (defparameter *cli/options* 146 | (list 147 | (clingon:make-option 148 | :flag ;; <-- option kind: a flag. Doesn't expect a parameter. 149 | :description "show help" 150 | :short-name #\h 151 | ;; :long-name "help" ;; <-- already handled by Clingon for CIEL. 152 | :key :help)) 153 | "Our script's options.") 154 | ~~~ 155 | 156 | Our option kinds include: `:counter`, `:string`, `:integer`, `:boolean`… and more. 157 | 158 | Then, we define a top-level command: 159 | 160 | ~~~lisp 161 | (defparameter *cli/command* 162 | (clingon:make-command 163 | :name "command-example" 164 | :description "only has a -h option, and it accepts free arguments." 165 | :version "0.1.0" 166 | :authors '("John Doe 277 | 278 | ## Main function and interactive development 279 | 280 | TLDR: use the `#+ciel` feature flag as in: 281 | 282 | ~~~lisp 283 | (in-package :ciel-user) 284 | 285 | (defun main () 286 | …) 287 | 288 | #+ciel 289 | (main) 290 | ~~~ 291 | 292 | Writing scripts is nice, but it is even better when doing so in a 293 | fully interactive Lisp environment, such as in Emacs and Slime (which 294 | is not the only good one anymore ;) ). We then must have a way to have 295 | a piece of code executed when we run the script (the call to the 296 | "main" function doing the side effects), but *not* executed when we 297 | `load` the file or when we compile and load the whole buffer during 298 | development (`C-c C-k`) (note that we can always compile functions 299 | individually with `C-c C-c`). 300 | 301 | In Python, the pattern is `__name__ == "__main__"`. In CIEL, we use 302 | Common Lisp's feature flags: the variable `*features*` (inside the 303 | `ciel-user` package) is a list containing symbols that represent 304 | features currently enabled in the Lisp image. For example, here's an 305 | extract: 306 | 307 | ~~~lisp 308 | CIEL-USER> *features* 309 | (… 310 | :CL-PPCRE-UNICODE :THREAD-SUPPORT :SWANK :QUICKLISP :ASDF3.3 311 | :ASDF :OS-UNIX :ASDF-UNICODE :X86-64 :64-BIT 312 | :COMMON-LISP :ELF :IEEE-FLOATING-POINT :LINUX :LITTLE-ENDIAN 313 | :PACKAGE-LOCAL-NICKNAMES :SB-LDB :SB-PACKAGE-LOCKS :SB-THREAD :SB-UNICODE 314 | :SBCL :UNIX) 315 | ~~~ 316 | 317 | Before running your script, we add the `:CIEL` symbol to this 318 | list. The `#+foo` reader macro is the way to check if the feature 319 | "foo" is enabled. You can also use `#-foo` to check its absence. To 320 | always disable a piece of code, the pattern is `#+(or)`, that always 321 | evaluates to nil. 322 | 323 | Make sure you are "in" the `ciel-user` package when writing this `#+ciel` check. 324 | 325 | 326 | ## Eval and one-liners 327 | 328 | Use `--eval` or `-e` to eval some lisp code. 329 | 330 | Example: 331 | 332 | ```sh 333 | $ ciel -e "(uiop:file-exists-p \"README.org\")" 334 | /home/vindarel/projets/ciel/README.org 335 | 336 | $ ciel -e "(-> \"README.org\" (uiop:file-exists-p))" 337 | /home/vindarel/projets/ciel/README.org 338 | 339 | $ ciel -e "(-> (http:get \"https://fakestoreapi.com/products/1\") (json:read-json))" 340 | 341 | (dict 342 | "id" 1 343 | "title" "Fjallraven - Foldsack No. 1 Backpack, Fits 15 Laptops" 344 | "price" 109.95 345 | "description" "Your perfect pack for everyday use and walks in the forest. Stash your laptop (up to 15 inches) in the padded sleeve, your everyday" 346 | "category" "men's clothing" 347 | "image" "https://fakestoreapi.com/img/81fPKd-2AYL._AC_SL1500_.jpg" 348 | "rating" 349 | (dict 350 | "rate" 3.9 351 | "count" 120 352 | ) 353 | ) 354 | ``` 355 | 356 | ## Built-in scripts 357 | 358 | Call built-in scripts with `--script ` or `-s`. 359 | 360 | Call `ciel --scripts` to list the available ones. 361 | 362 | Those are for demo purposes and are subject to evolve. Ideas and contributions welcome. 363 | 364 | ### Simple HTTP server 365 | 366 | ``` 367 | $ ciel -s simpleHTTPserver 9000 368 | ``` 369 | 370 | open `http://localhost:9000` and see the list of files. 371 | 372 | See `src/scripts/simpleHTTPserver.lisp` in the CIEL repository. 373 | 374 | You can preview HTML files and have static assets under a `static/` directory. 375 | 376 | Given you have an `index.html` file: 377 | 378 | ```html 379 | 380 | 381 | Hello! 382 | 383 | 384 |

Hello CIEL!

385 |

386 | We just served our own files. 387 |

388 | 389 | 390 | ``` 391 | 392 | The script will serve static assets under a `static/` directory. 393 | 394 | Now load a .js file as usual in your template: 395 | 396 | 397 | 398 | which can be: 399 | 400 | ~~~javascript 401 | // ciel.js 402 | alert("hello CIEL!"); 403 | ~~~ 404 | 405 | Example output: 406 | 407 | ``` 408 | $ ciel -s simpleHTTPserver 4242 409 | Serving files on port 4242… 410 | 411 | ⤷ http://127.0.0.1:4242 412 | 413 | [click on the index.html file] 414 | 415 | 127.0.0.1 - [2022-12-14 12:06:00] "GET / HTTP/1.1" 200 200 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:103.0) Gecko/20100101 Firefox/103.0" 416 | ``` 417 | 418 | ### Quicksearch 419 | 420 | Search for Lisp libraries on Quicklisp, Cliki and Github. 421 | 422 | see `src/scripts/quicksearch.lisp`. 423 | 424 | ```lisp 425 | $ ciel -s quicksearch color 426 | 427 | SEARCH-RESULTS: "color" 428 | ======================= 429 | 430 | Quicklisp 431 | --------- 432 | cl-colors 433 | /home/vince/quicklisp/dists/quicklisp/software/cl-colors-20180328-git/ 434 | http://beta.quicklisp.org/archive/cl-colors/2018-03-28/cl-colors-20180328-git.tgz 435 | http://quickdocs.org/cl-colors/ 436 | […] 437 | Cliki 438 | ----- 439 | colorize 440 | http://www.cliki.net/colorize 441 | Colorize is an Application for colorizing chunks of Common Lisp, Scheme, 442 | Elisp, C, C++, or Java code 443 | […] 444 | GitHub 445 | ------ 446 | colorize 447 | https://github.com/kingcons/colorize 448 | A Syntax Highlighting library 449 | cl-colors 450 | https://github.com/tpapp/cl-colors 451 | Simple color library for Common Lisp 452 | […] 453 | ``` 454 | 455 | ### API Pointer 456 | 457 | Call a JSON API and access nested data with a JSON pointer: 458 | 459 | ciel -s apipointer URL "/json/pointer" 460 | 461 | Example: 462 | 463 | $ ciel -s apipointer https://fakestoreapi.com/products\?limit\=3 "/0/rating/rate" 464 | 3.9 465 | 466 | We welcome more capable, expanded scripts! 467 | 468 | --- 469 | 470 | Now, let us iron out the details ;) 471 | 472 | ### Simple web app with routes 473 | 474 | See [`scr/scripts/webapp.lisp`](https://github.com/ciel-lang/CIEL/blob/master/src/scripts/webapp.lisp) for inspiration. 475 | 476 | This creates one route on `/` with an optional `name` parameter. Go to `localhost:4567/?name=you` and see. 477 | 478 | ```lisp 479 | #!/usr/bin/env ciel 480 | ;;; 481 | ;;; Run with: 482 | ;;; $ ./webapp.lisp 483 | ;;; 484 | 485 | (in-package :ciel-user) 486 | 487 | (routes:defroute route-root "/" (&get name) 488 | (format nil "Hello ~a!" (or name (os:getenv "USER") "lisper"))) 489 | 490 | (defvar *server* nil) 491 | 492 | (defun start-webapp () 493 | (setf *server* (make-instance 'routes:easy-routes-acceptor :port 4567)) 494 | (hunchentoot:start *server*)) 495 | 496 | (defun stop-webapp () 497 | (hunchentoot:stop *server*)) 498 | 499 | #+ciel 500 | (progn 501 | (start-webapp) 502 | (format t "~&App started on localhost:4567…~&") 503 | (sleep most-positive-fixnum)) 504 | ``` 505 | 506 | At this point you'll certainly want to live-reload your changes. 507 | 508 | ### Auto-reload 509 | 510 | In this snippet: 511 | [`webapp-notify.lisp`](https://github.com/ciel-lang/CIEL/blob/master/src/scripts/webapp-notify.lisp), 512 | we use the [file-notify](https://github.com/shinmera/file-notify) 513 | library (shipped in CIEL) to watch write changes to our lisp file, and load 514 | it again. 515 | 516 | This allows you to have a dumb "live reload" workflow with a simple editor and a terminal. 517 | 518 | > WARNING: This does NOT take advantage of Common Lisp's image-based-development features at all. Install yourself a Common Lisp IDE to enjoy the interactive debugger, compiling one function at a time, trying things out in the REPL, autocompletion, code navigation… 519 | 520 | > INFO: you need `inotify` on Linux and `fsevent` on MacOS. 521 | 522 | ~~~lisp 523 | (defun simple-auto-reload () 524 | (notify:watch "webapp.lisp") 525 | (notify:with-events (file change :timeout T) 526 | ;; Print the available list of events: 527 | ;; (print (list file change)) 528 | (when (equal change :close-write) 529 | (format! t "~%~%Reloading ~a…~&" file) 530 | (handler-case 531 | (ciel::load-without-shebang "webapp.lisp") 532 | (reader-error () 533 | ;; Catch some READ errors, such as parenthesis not closed, etc. 534 | (format! t "~%~%read error, waiting for change…~&")))))) 535 | 536 | #+ciel 537 | (unless *server* 538 | (start-webapp) 539 | (format t "~&App started on localhost:4567…~&") 540 | (simple-auto-reload) 541 | (sleep most-positive-fixnum)) 542 | ~~~ 543 | 544 | ## Misc 545 | 546 | ### Load your scripts in the REPL 547 | 548 | Calling your scripts from the shell is pretty cool, what if you could 549 | *also* have them available at your fingertips in a Lisp REPL? 550 | 551 | TLDR; 552 | 553 | ```lisp 554 | ;; in ~/.cielrc 555 | (ciel::load-without-shebang "~/path/to/yourscript.lisp") 556 | ``` 557 | 558 | As the name suggests, this `load` function works even if your file starts with a shebang line (which is not valid Lisp code, so the default `LOAD` function would fail). 559 | 560 | Y'know, sometimes you live longer in a Lisp REPL than in a shell 561 | without noticing. Or simply, manipulating real objects in a text 562 | buffer can be more practical than copy-pasting text in a rigid 563 | terminal (even though Emacs' 564 | [vterm](https://github.com/akermu/emacs-libvterm) is an excellent improvement too). 565 | 566 | > INFO: the `~/.cielrc` file is loaded at start-up of the terminal REPL (called with `ciel`), not yet when you start the core image in your IDE. 567 | 568 | ### Searching files 569 | 570 | We use the File Object Finder library 571 | ([`fof`](https://gitlab.com/ambrevar/fof), a new library meant to 572 | supersede `find`, `ls`, `stat`, `chown`, `chmod`, `du`, `touch` and 573 | the Osicat library) to search for files recursively: 574 | 575 | ~~~lisp 576 | (defun find-on-directory (root params) 577 | (fof:finder* 578 | :root root 579 | :predicates (apply #'fof/p:path~ (ensure-list params)))) 580 | 581 | (find-on-directory "~/Music/" "mp3") 582 | ~~~ 583 | 584 | and this returns a list of `fof:file` objects. Get their real name as 585 | a string with `fof:path`. 586 | 587 | Of course, you can also outsource the work to Unix commands, with 588 | `cmd:cmd` (prints to standard output) or `cmd:$cmd` (returns a 589 | string): 590 | 591 | ~~~lisp 592 | (-> (cmd:$cmd "find . -iname \"*mp3\"") 593 | str:lines) 594 | 595 | ;; With find alternative fd: 596 | ;; https://github.com/sharkdp/fd 597 | ;; apt install fd-find 598 | (-> (cmd:$cmd "fdfind mp3") 599 | str:lines) 600 | 601 | ;; Play music: 602 | (cmd:cmd "fdfind mp3 -X mpv") 603 | ~~~ 604 | -------------------------------------------------------------------------------- /docs/see-also.md: -------------------------------------------------------------------------------- 1 | See also 2 | ======== 3 | 4 | In addition to all the libraries mentioned, these projects share some 5 | of CIEL's goals and are of particular interest: 6 | 7 | - [CLNG](https://github.com/commander-trashdin/clng/issues) (Common Lisp Next Generation): a place to discuss low-level and high-level new features for Common Lisp implementations. 8 | -------------------------------------------------------------------------------- /docs/serapeum.md: -------------------------------------------------------------------------------- 1 | # Symbols imported from SERAPEUM for sequences and hashtables 2 | 3 | 4 | ## ASSORT 5 | 6 | ARGLIST: `(seq &key (key #'identity) (test #'eql) (start 0) end hash &aux 7 | (orig-test test))` 8 | 9 | FUNCTION: Return SEQ assorted by KEY. 10 | 11 | (assort (iota 10) 12 | :key (lambda (n) (mod n 3))) 13 | => '((0 3 6 9) (1 4 7) (2 5 8)) 14 | 15 | Groups are ordered as encountered. This property means you could, in 16 | principle, use `assort' to implement `remove-duplicates' by taking the 17 | first element of each group: 18 | 19 | (mapcar #'first (assort list)) 20 | ≡ (remove-duplicates list :from-end t) 21 | 22 | However, if TEST is ambiguous (a partial order), and an element could 23 | qualify as a member of more than one group, then it is not guaranteed 24 | that it will end up in the leftmost group that it could be a member 25 | of. 26 | 27 | (assort '(1 2 1 2 1 2) :test #'<=) 28 | => '((1 1) (2 2 1 2)) 29 | 30 | The default algorithm used by `assort' is, in the worst case, O(n) in 31 | the number of groups. If HASH is specified, then a hash table is used 32 | instead. However TEST must be acceptable as the `:test' argument to 33 | `make-hash-table'. 34 | 35 | ## BATCHES 36 | 37 | ARGLIST: `(seq n &key (start 0) end even)` 38 | 39 | FUNCTION: Return SEQ in batches of N elements. 40 | 41 | (batches (iota 11) 2) 42 | => ((0 1) (2 3) (4 5) (6 7) (8 9) (10)) 43 | 44 | If EVEN is non-nil, then SEQ must be evenly divisible into batches of 45 | size N, with no leftovers. 46 | 47 | ## IOTA 48 | 49 | ARGLIST: `(n &key (start 0) (step 1))` 50 | 51 | FUNCTION: Return a list of n numbers, starting from START (with numeric contagion 52 | from STEP applied), each consequtive number being the sum of the previous one 53 | and STEP. START defaults to 0 and STEP to 1. 54 | 55 | Examples: 56 | 57 | (iota 4) => (0 1 2 3) 58 | (iota 3 :start 1 :step 1.0) => (1.0 2.0 3.0) 59 | (iota 3 :start -1 :step -1/2) => (-1 -3/2 -2) 60 | 61 | ## RUNS 62 | 63 | ARGLIST: `(seq &key (start 0) end (key #'identity) (test #'eql) 64 | (count most-positive-fixnum))` 65 | 66 | FUNCTION: Return a list of runs of similar elements in SEQ. 67 | The arguments START, END, and KEY are as for `reduce'. 68 | 69 | (runs '(head tail head head tail)) 70 | => '((head) (tail) (head head) (tail)) 71 | 72 | The function TEST is called with the first element of the run as its 73 | first argument. 74 | 75 | (runs '(1 2 3 1 2 3) :test #'<) 76 | => ((1 2 3) (1 2 3)) 77 | 78 | The COUNT argument limits how many runs are returned. 79 | 80 | (runs '(head tail tail head head tail) :count 2) 81 | => '((head) (tail tail)) 82 | 83 | ## PARTITION 84 | 85 | ARGLIST: `(pred seq &key (start 0) end (key #'identity))` 86 | 87 | FUNCTION: Partition elements of SEQ into those for which PRED returns true 88 | and false. 89 | 90 | Return two values, one with each sequence. 91 | 92 | Exactly equivalent to: 93 | (values (remove-if-not predicate seq) (remove-if predicate seq)) 94 | except it visits each element only once. 95 | 96 | Note that `partition` is not just `assort` with an up-or-down 97 | predicate. `assort` returns its groupings in the order they occur in 98 | the sequence; `partition` always returns the “true” elements first. 99 | 100 | (assort '(1 2 3) :key #'evenp) => ((1 3) (2)) 101 | (partition #'evenp '(1 2 3)) => (2), (1 3) 102 | 103 | ## PARTITIONS 104 | 105 | ARGLIST: `(preds seq &key (start 0) end (key #'identity))` 106 | 107 | FUNCTION: Generalized version of PARTITION. 108 | 109 | PREDS is a list of predicates. For each predicate, `partitions' 110 | returns a filtered copy of SEQ. As a second value, it returns an extra 111 | sequence of the items that do not match any predicate. 112 | 113 | Items are assigned to the first predicate they match. 114 | 115 | ## SPLIT-SEQUENCE 116 | 117 | ARGLIST: `(delimiter sequence &key (start 0) (end nil) (from-end nil) (count nil) 118 | (remove-empty-subseqs nil) (test #'eql test-p) (test-not nil test-not-p) 119 | (key #'identity))` 120 | 121 | FUNCTION: Return a list of subsequences in seq delimited by delimiter. 122 | If :remove-empty-subseqs is NIL, empty subsequences will be included 123 | in the result; otherwise they will be discarded. All other keywords 124 | work analogously to those for CL:SUBSTITUTE. In particular, the 125 | behaviour of :from-end is possibly different from other versions of 126 | this function; :from-end values of NIL and T are equivalent unless 127 | :count is supplied. :count limits the number of subseqs in the main 128 | resulting list. The second return value is an index suitable as an 129 | argument to CL:SUBSEQ into the sequence indicating where processing 130 | stopped. 131 | 132 | ## COUNT-CPUS 133 | 134 | ARGLIST: `(&key (default 2) online)` 135 | 136 | FUNCTION: Try very hard to return a meaningful count of CPUs. 137 | If ONLINE is non-nil, try to return only the active CPUs. 138 | 139 | The second value is T if the number of processors could be queried, 140 | `nil' otherwise. 141 | 142 | ## DICT 143 | 144 | ARGLIST: `(&rest keys-and-values)` 145 | 146 | FUNCTION: A concise constructor for hash tables. 147 | 148 | (gethash :c (dict :a 1 :b 2 :c 3)) => 3, T 149 | 150 | By default, return an 'equal hash table containing each successive 151 | pair of keys and values from KEYS-AND-VALUES. 152 | 153 | If the number of KEYS-AND-VALUES is odd, then the first argument is 154 | understood as the test. 155 | 156 | (gethash "string" (dict "string" t)) => t 157 | (gethash "string" (dict 'eq "string" t)) => nil 158 | 159 | Note that `dict' can also be used for destructuring (with Trivia). 160 | 161 | (match (dict :x 1) 162 | ((dict :x x) x)) 163 | => 1 164 | 165 | ## DO-HASH-TABLE 166 | 167 | ARGLIST: `((key value table &optional return) &body body)` 168 | 169 | FUNCTION: Iterate over hash table TABLE, in no particular order. 170 | 171 | At each iteration, a key from TABLE is bound to KEY, and the value of 172 | that key in TABLE is bound to VALUE. 173 | 174 | ## DICT* 175 | 176 | ARGLIST: `(dict &rest args)` 177 | 178 | FUNCTION: Merge new bindings into DICT. 179 | Roughly equivalent to `(merge-tables DICT (dict args...))'. 180 | 181 | ## DICTQ 182 | 183 | ARGLIST: `(&rest keys-and-values)` 184 | 185 | FUNCTION: A literal hash table. 186 | Like `dict', but the keys and values are implicitly quoted, and the 187 | hash table is inlined as a literal object. 188 | 189 | ## POPHASH 190 | 191 | ARGLIST: `(key hash-table)` 192 | 193 | FUNCTION: Lookup KEY in HASH-TABLE, return its value, and remove it. 194 | 195 | This is only a shorthand. It is not in itself thread-safe. 196 | 197 | From Zetalisp. 198 | 199 | ## SWAPHASH 200 | 201 | ARGLIST: `(key value hash-table)` 202 | 203 | FUNCTION: Set KEY and VALUE in HASH-TABLE, returning the old values of KEY. 204 | 205 | This is only a shorthand. It is not in itself thread-safe. 206 | 207 | From Zetalisp. 208 | 209 | ## HASH-FOLD 210 | 211 | ARGLIST: `(fn init hash-table)` 212 | 213 | FUNCTION: Reduce TABLE by calling FN with three values: a key from the hash 214 | table, its value, and the return value of the last call to FN. On the 215 | first call, INIT is supplied in place of the previous value. 216 | 217 | From Guile. 218 | 219 | ## MAPHASH-RETURN 220 | 221 | ARGLIST: `(fn hash-table)` 222 | 223 | FUNCTION: Like MAPHASH, but collect and return the values from FN. 224 | From Zetalisp. 225 | 226 | ## MERGE-TABLES 227 | 228 | ARGLIST: `(&rest tables)` 229 | 230 | FUNCTION: Merge TABLES, working from left to right. 231 | The resulting hash table has the same parameters as the first table. 232 | 233 | If no tables are given, an new, empty hash table is returned. 234 | 235 | If a single table is given, a copy of it is returned. 236 | 237 | If the same key is present in two tables, the value from the rightmost 238 | table is used. 239 | 240 | All of the tables being merged must have the same value for 241 | `hash-table-test'. 242 | 243 | Clojure's `merge'. 244 | 245 | ## FLIP-HASH-TABLE 246 | 247 | ARGLIST: `(table &key (test (constantly t)) (key #'identity))` 248 | 249 | FUNCTION: Return a table like TABLE, but with keys and values flipped. 250 | 251 | (gethash :y (flip-hash-table (dict :x :y))) 252 | => :x, t 253 | 254 | TEST allows you to filter which keys to set. 255 | 256 | (def number-names (dictq 1 one 2 two 3 three)) 257 | 258 | (def name-numbers (flip-hash-table number-names)) 259 | (def name-odd-numbers (flip-hash-table number-names :filter #'oddp)) 260 | 261 | (gethash 'two name-numbers) => 2, t 262 | (gethash 'two name-odd-numbers) => nil, nil 263 | 264 | KEY allows you to transform the keys in the old hash table. 265 | 266 | (def negative-number-names (flip-hash-table number-names :key #'-)) 267 | (gethash 'one negative-number-names) => -1, nil 268 | 269 | KEY defaults to `identity'. 270 | 271 | ## SET-HASH-TABLE 272 | 273 | ARGLIST: `(set &rest hash-table-args &key (test #'eql) (key #'identity) (strict t) 274 | &allow-other-keys)` 275 | 276 | FUNCTION: Return SET, a list considered as a set, as a hash table. 277 | This is the equivalent of Alexandria's `alist-hash-table' and 278 | `plist-hash-table' for a list that denotes a set. 279 | 280 | STRICT determines whether to check that the list actually is a set. 281 | 282 | The resulting hash table has the elements of SET for both its keys and 283 | values. That is, each element of SET is stored as if by 284 | (setf (gethash (key element) table) element) 285 | 286 | ## HASH-TABLE-PREDICATE 287 | 288 | ARGLIST: `(hash-table)` 289 | 290 | FUNCTION: Return a predicate for membership in HASH-TABLE. 291 | The predicate returns the same two values as `gethash', but in the 292 | opposite order. 293 | 294 | ## HASH-TABLE-FUNCTION 295 | 296 | ARGLIST: `(hash-table &key read-only strict (key-type 't) (value-type 't) strict-types)` 297 | 298 | FUNCTION: Return a function for accessing HASH-TABLE. 299 | 300 | Calling the function with a single argument is equivalent to `gethash' 301 | against a copy of HASH-TABLE at the time HASH-TABLE-FUNCTION was 302 | called. 303 | 304 | (def x (make-hash-table)) 305 | 306 | (funcall (hash-table-function x) y) 307 | ≡ (gethash y x) 308 | 309 | If READ-ONLY is nil, then calling the function with two arguments is 310 | equivalent to `(setf (gethash ...))' against HASH-TABLE. 311 | 312 | If STRICT is non-nil, then the function signals an error if it is 313 | called with a key that is not present in HASH-TABLE. This applies to 314 | setting keys, as well as looking them up. 315 | 316 | The function is able to restrict what types are permitted as keys and 317 | values. If KEY-TYPE is specified, an error will be signaled if an 318 | attempt is made to get or set a key that does not satisfy KEY-TYPE. If 319 | VALUE-TYPE is specified, an error will be signaled if an attempt is 320 | made to set a value that does not satisfy VALUE-TYPE. However, the 321 | hash table provided is *not* checked to ensure that the existing 322 | pairings KEY-TYPE and VALUE-TYPE -- not unless STRICT-TYPES is also 323 | specified. 324 | 325 | ## MAKE-HASH-TABLE-FUNCTION 326 | 327 | ARGLIST: `(&rest args &key &allow-other-keys)` 328 | 329 | FUNCTION: Call `hash-table-function' on a fresh hash table. 330 | ARGS can be args to `hash-table-function' or args to 331 | `make-hash-table', as they are disjoint. 332 | 333 | ## DELETE-FROM-HASH-TABLE 334 | 335 | ARGLIST: `(table &rest keys)` 336 | 337 | FUNCTION: Return TABLE with KEYS removed (as with `remhash'). 338 | Cf. `delete-from-plist' in Alexandria. 339 | 340 | ## PAIRHASH 341 | 342 | ARGLIST: `(keys data &optional hash-table)` 343 | 344 | FUNCTION: Like `pairlis', but for a hash table. 345 | 346 | Unlike `pairlis', KEYS and DATA are only required to be sequences (of 347 | the same length), not lists. 348 | 349 | By default, the hash table returned uses `eql' as its tests. If you 350 | want a different test, make the table yourself and pass it as the 351 | HASH-TABLE argument. 352 | -------------------------------------------------------------------------------- /docs/trivial-types.md: -------------------------------------------------------------------------------- 1 | # Symbols imported from TRIVIAL-TYPES 2 | 3 | ## ASSOCIATION-LIST-P 4 | 5 | 6 | ARGLIST: `(var)` 7 | 8 | FUNCTION: Returns true if OBJECT is an association list. 9 | 10 | Examples: 11 | 12 | (association-list-p 1) => NIL 13 | (association-list-p '(1 2 3)) => NIL 14 | (association-list-p nil) => T 15 | (association-list-p '((foo))) => T 16 | (association-list-p '((:a . 1) (:b . 2))) => T 17 | ## TYPE-EXPAND 18 | 19 | 20 | ARGLIST: `(type-specifier &optional env)` 21 | 22 | FUNCTION: Expand TYPE-SPECIFIER in the lexical environment ENV. 23 | ## STRING-DESIGNATOR 24 | 25 | ## PROPERTY-LIST 26 | 27 | 28 | TYPE: Equivalent to `(and list (satisfies 29 | property-list-p))`. VALUE-TYPE is just ignored. 30 | 31 | Examples: 32 | 33 | (typep '(:a 1 :b 2) '(property-list integer)) => T 34 | (typep '(:a 1 :b 2) '(property-list string)) => T 35 | ## TUPLE 36 | 37 | 38 | ARGLIST: `(&rest args)` 39 | 40 | FUNCTION: Exactly same as LIST. 41 | 42 | TYPE: Equivalent to `(and list (cons ARG1 (cons ARG2 (cons ARG3 ...))))` 43 | where ARGn is each element of ELEMENT-TYPES. 44 | 45 | Examples: 46 | 47 | (typep 1 'tuple) => NIL 48 | (typep '(1 . 2) 'tuple) => NIL 49 | (typep '(1 2 3) 'tuple) => NIL 50 | (typep '(1 2 3) '(tuple integer integer)) => NIL 51 | (typep '(1 2 3) '(tuple string integer integer)) => NIL 52 | (typep nil 'tuple) => T 53 | (typep nil '(tuple)) => T 54 | (typep '(1 2 3) '(tuple integer integer integer)) => T 55 | ## ASSOCIATION-LIST 56 | 57 | 58 | TYPE: Equivalent to `(proper-list (cons KEY-TYPE VALUE-TYPE))`. KEY-TYPE 59 | and VALUE-TYPE are just ignored. 60 | 61 | Examples: 62 | 63 | (typep '((:a . 1) (:b . 2)) '(association-list integer)) => T 64 | (typep '((:a . 1) (:b . 2)) '(association-list string)) => T 65 | ## CHARACTER-DESIGNATOR 66 | 67 | ## PROPERTY-LIST-P 68 | 69 | 70 | ARGLIST: `(object)` 71 | 72 | FUNCTION: Returns true if OBJECT is a property list. 73 | 74 | Examples: 75 | 76 | (property-list-p 1) => NIL 77 | (property-list-p '(1 2 3)) => NIL 78 | (property-list-p '(foo)) => NIL 79 | (property-list-p nil) => T 80 | (property-list-p '(foo 1)) => T 81 | (property-list-p '(:a 1 :b 2)) => T 82 | ## FILE-ASSOCIATED-STREAM-P 83 | 84 | 85 | ARGLIST: `(stream)` 86 | 87 | FUNCTION: Returns true if STREAM is a stream associated to a file. 88 | ## TYPE-SPECIFIER-P 89 | 90 | 91 | ARGLIST: `(type-specifier)` 92 | 93 | FUNCTION: Returns true if TYPE-SPECIFIER is a valid type specfiier. 94 | ## LIST-DESIGNATOR 95 | 96 | ## PACKAGE-DESIGNATOR 97 | 98 | ## TUPLEP 99 | 100 | 101 | ARGLIST: `(object)` 102 | 103 | FUNCTION: Returns true if OBJECT is a tuple, meaning a proper list. 104 | 105 | Examples: 106 | 107 | (tuplep 1) => NIL 108 | (tuplep '(1 . 2)) => NIL 109 | (tuplep nil) => T 110 | (tuplep '(1 2 3)) => T 111 | ## NON-NIL 112 | 113 | 114 | TYPE: Equivalent to `(and (not null) TYPE)` if TYPE is given, 115 | otherwise `(not null)`. 116 | 117 | Examples: 118 | 119 | (typep nil '(non-nil symbol)) => NIL 120 | ## FILE-ASSOCIATED-STREAM 121 | 122 | 123 | TYPE: Equivalent to `(and stream (satisfies file-associated-stream-p))`. 124 | ## STREAM-DESIGNATOR 125 | 126 | ## FUNCTION-DESIGNATOR 127 | 128 | ## FILE-POSITION-DESIGNATOR 129 | 130 | ## PATHNAME-DESIGNATOR 131 | 132 | -------------------------------------------------------------------------------- /find-dependencies.lisp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sbcl --script 2 | 3 | #| 4 | Show CIEL's dependencies. 5 | 6 | Run as a script. 7 | 8 | Redirect the script output to docs/dependencies.md (see Makefile). 9 | |# 10 | 11 | (require 'asdf) 12 | (print "loading quicklisp…") 13 | (load "~/quicklisp/setup") 14 | 15 | (require "cl+ssl") 16 | (load "ciel.asd") 17 | (require 'swank) ;; but why? 18 | (ql:quickload '("ciel" "str") :silent t) 19 | 20 | (defun system-dependencies (system/str) 21 | "Return a list of system names, as strings." 22 | (sort 23 | (asdf:system-depends-on (asdf/find-system:find-system system/str)) 24 | #'string< 25 | :key #'asdf/system:primary-system-name)) 26 | 27 | ;; where's a project URL? 28 | 29 | (defun print-dependencies (deps/str) 30 | ;XXX: doesn't find dependencies from package-inferred-systems (like fof). 31 | (let ((systems (mapcar #'asdf/find-system:find-system 32 | (system-dependencies deps/str)))) 33 | (loop for system in systems 34 | do (format t "- ~a: ~a~&" (asdf:primary-system-name system) 35 | (str:shorten 200 (asdf:system-description system)))))) 36 | 37 | (format t "~&") 38 | (print-dependencies "ciel") 39 | -------------------------------------------------------------------------------- /repl-utils.lisp: -------------------------------------------------------------------------------- 1 | (in-package :sbcli) 2 | 3 | (defun last-nested-expr (s/sexp) 4 | "From an input with nested parens (or none), return the most nested 5 | function call (or the first thing at the prompt). 6 | 7 | (hello (foo (bar:qux *zz* ? 8 | => 9 | bar:qux 10 | " 11 | (let* ((input (str:trim s/sexp)) 12 | (last-parens-token (first (last (str:split #\( input))))) 13 | (first (str:words last-parens-token)))) 14 | 15 | #+or(nil) 16 | (progn 17 | (assert (string= "baz:qux" 18 | (last-nested-expr "(hello (foo bar (baz:qux zz ?"))) 19 | (assert (string= "baz:qux" 20 | (last-nested-expr "(baz:qux zz ?"))) 21 | (assert (string= "qux" 22 | (last-nested-expr "(baz (qux ?"))) 23 | (assert (string= "sym" 24 | (last-nested-expr "sym ?")))) 25 | 26 | ;;;; 27 | ;;;; Syntax highlighting if pygments is installed. 28 | ;;;; 29 | (defun maybe-highlight (str) 30 | (if *syntax-highlighting* 31 | (let ((pygmentize (or *pygmentize* 32 | (which:which "pygmentize")))) 33 | (when pygmentize 34 | (with-input-from-string (s str) 35 | (let ((proc (uiop:launch-program (alexandria:flatten 36 | (list pygmentize *pygmentize-options*)) 37 | :input s 38 | :output :stream))) 39 | (read-line (uiop:process-info-output proc) nil ""))))) 40 | str)) 41 | 42 | (defun syntax-hl () 43 | ;; XXX: when enabled, this f* up the whitespace output of the first output line. 44 | ;; To try, print this: 45 | ;; (format t "~s" '(hello some test arithmetic)) 46 | ;; it will be shown on multiple lines with lots of whitespace. 47 | (rl:redisplay) 48 | (let ((res (maybe-highlight rl:*line-buffer*))) 49 | (format t "~c[2K~c~a~a~c[~aD" #\esc #\return rl:*display-prompt* res #\esc (- rl:+end+ rl:*point*)) 50 | (when (= rl:+end+ rl:*point*) 51 | (format t "~c[1C" #\esc)) 52 | (finish-output))) 53 | -------------------------------------------------------------------------------- /scripting.lisp: -------------------------------------------------------------------------------- 1 | 2 | (in-package :ciel) 3 | 4 | (defparameter *ciel-version* #.(asdf:component-version (asdf:find-system :ciel)) 5 | "CIEL's version, read from the .asd.") 6 | 7 | (defparameter *scripts* (dict 'equalp) 8 | "Available scripts. 9 | Hash-table: file name (sans extension) -> file content (string). 10 | The name is case-insensitive (it's easier for typing things in the terminal).") 11 | 12 | ;; eval 13 | (defun wrap-user-code (s) 14 | "Wrap this user code to handle common conditions, such as a C-c C-c to quit gracefully." 15 | ;; But is it enough when we run a shell command? 16 | `(handler-case 17 | ,s ;; --eval takes one form only so no need of ,@ 18 | (sb-sys:interactive-interrupt (c) 19 | (declare (ignore c)) 20 | (format! *error-output* "Bye!~%")) 21 | (error (c) 22 | (format! *error-output* "~a" c)))) 23 | 24 | 25 | (defun register-builtin-scripts () 26 | "Find available scripts in src/scripts, register them in *SCRIPTS*. 27 | Call this before creating the CIEL binary." 28 | ;; We save the file's content as a string. 29 | ;; We will run them with LOAD (and an input stream from the string). 30 | ;; 31 | ;; Example: 32 | ;; 33 | ;; (load (make-string-input-stream (str:from-file "src/scripts/simpleHTTPserver.lisp"))) 34 | (loop for file in (uiop:directory-files "src/scripts/") 35 | if (equal "lisp" (pathname-type file)) 36 | do (format t "~t scripts: registering ~a~&" (pathname-name file)) 37 | (setf (gethash (pathname-name file) *scripts*) 38 | (str:from-file file)))) 39 | 40 | (defun run-script (name) 41 | "If NAME is registered in *SCRIPTS*, run this script." 42 | (bind (((:values content exists) (gethash name *scripts*))) 43 | (cond 44 | ((and exists (str:blankp content) 45 | (format *error-output* "uh the script ~s has no content?~&" name))) 46 | ((not exists) 47 | (format *error-output* "The script ~s was not found.~&" name)) 48 | (t 49 | ;; Run it! 50 | ;; We first add a symbol in the feature list, so a script nows when it is being executed. 51 | (push :ciel ciel-user::*features*) 52 | ;; We ignore the shebang line, if there is one. 53 | ;; We can call scripts either with ciel -s or with ./script 54 | (load (maybe-ignore-shebang 55 | (make-string-input-stream content))))))) 56 | 57 | (defun top-level/command () 58 | "Creates and returns the top-level command" 59 | (clingon:make-command 60 | :name "ciel" 61 | :description "CIEL Is an Extended Lisp. It's Common Lisp, batteries included." 62 | :version *ciel-version* 63 | :license "todo" 64 | :authors '("vindarel ") 65 | :usage (format nil "accepts optional command-line arguments.~% 66 | ~t~tWith no arguments, run the CIEL readline REPL.~% 67 | ~t~tWith a file as argument, run it as a script.~% 68 | ~t~tWith --eval / -e
, eval a Lisp form.~% 69 | ~t~tWith --script / -s