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 | [](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 | 
--------------------------------------------------------------------------------
/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 |
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