├── docs ├── .nojekyll ├── CNAME ├── before.jpeg ├── after-plus.jpeg ├── assets │ └── vgplot.png ├── _navbar.md ├── _sidebar.md ├── _coverpage.md ├── see-also.md ├── alexandria.md ├── alexandria-control-flow.md ├── trivial-types.md ├── index.html ├── dependencies.md ├── README.md ├── repl.md ├── FAQ.md ├── install.md ├── serapeum.md ├── scripting.md └── language-extensions.md ├── .gitignore ├── src ├── docker_utils │ ├── sbcl_ros_wrapper │ └── sbclrc ├── gui.lisp ├── csv.lisp ├── scripts │ ├── README.md │ ├── webapp.lisp │ ├── quicksearch.lisp │ ├── finder.lisp │ ├── apipointer.lisp │ ├── webapp-notify.lisp │ ├── ffmpeg.lisp │ └── simpleHTTPserver.lisp ├── more-docstrings │ ├── more-docstrings.asd │ ├── README.md │ └── docstrings.lisp ├── more-common-names │ └── names.lisp ├── packages.lisp ├── json-pointer-minus.lisp └── ciel.lisp ├── .gitattributes ├── ABOUT.org ├── check-asdf-version.lisp ├── .github └── FUNDING.yml ├── Dockerfile ├── find-dependencies.lisp ├── utils.lisp ├── CONTRIBUTING.markdown ├── repl-utils.lisp ├── .gitlab-ci.yml ├── Makefile ├── ciel.asd ├── scripting.lisp └── README.md /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | ciel-lang.org -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.fasl 2 | ciel 3 | ciel-core 4 | /node_modules/ 5 | -------------------------------------------------------------------------------- /docs/before.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciel-lang/CIEL/HEAD/docs/before.jpeg -------------------------------------------------------------------------------- /src/docker_utils/sbcl_ros_wrapper: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exec ros +R -l ~/.sbclrc run -- "$@" 3 | -------------------------------------------------------------------------------- /docs/after-plus.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciel-lang/CIEL/HEAD/docs/after-plus.jpeg -------------------------------------------------------------------------------- /docs/assets/vgplot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciel-lang/CIEL/HEAD/docs/assets/vgplot.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | + docs/serapeum.md linguist-vendored 2 | + docs/dependencies.md linguist-vendored 3 | -------------------------------------------------------------------------------- /src/docker_utils/sbclrc: -------------------------------------------------------------------------------- 1 | (setf ql:*local-project-directories* '("/root/quicklisp/local-projects")); 2 | -------------------------------------------------------------------------------- /src/gui.lisp: -------------------------------------------------------------------------------- 1 | (in-package :ciel) 2 | 3 | ;; let's try nodgui-lite 4 | ;; (setf nodgui:*default-theme* "yaru") 5 | -------------------------------------------------------------------------------- /src/csv.lisp: -------------------------------------------------------------------------------- 1 | (in-package :ciel-csv) 2 | 3 | 4 | (cl-reexport:reexport-from :cl-csv) 5 | 6 | (cl-reexport:reexport-from :data-table) 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/scripts/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | - it is better to use `uiop:*command-line-arguments*` instead of `(uiop:command-line-arguments)`. The latter always get the original full list, but when calling `ciel -s` we need to pop the arguments list, to ditch the `-s`. 4 | -------------------------------------------------------------------------------- /ABOUT.org: -------------------------------------------------------------------------------- 1 | 2 | * vindarel 3 | 4 | I got hooked into Lisp circa 2017, after around ten years in working 5 | in Python and JS. I contribute to collaborative resources (the Common 6 | Lisp Coobook) and I am now running an open-source web application in 7 | production©. 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/_coverpage.md: -------------------------------------------------------------------------------- 1 | 2 | # CIEL 0.0-dev 3 | 4 | > CIEL Is an Extended Lisp 5 | 6 | - 100% Common Lisp 7 | - batteries included 8 | 9 | [GitHub](https://github.com/ciel-lang/CIEL) 10 | [Show me](#ciel) 11 | 12 | 13 | 14 | ![color](#f0f0f0) -------------------------------------------------------------------------------- /docs/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 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/en/github/administering-a-repository/displaying-a-sponsor-button-in-your-repository#displaying-a-sponsor-button-in-your-repository 2 | 3 | github: [vindarel,] # we can put 4 handles here. 4 | # we can only put 1 handle below: 5 | ko_fi: vindarel 6 | liberapay: vindarel 7 | # and again, up to 4 custom links: 8 | # custom: [link1, …, link4] 9 | -------------------------------------------------------------------------------- /src/scripts/webapp.lisp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ciel 2 | ;;; 3 | ;;; Run with: 4 | ;;; $ ./webapp.lisp 5 | ;;; 6 | 7 | (in-package :ciel-user) 8 | 9 | (routes:defroute route-root "/" (&get name) 10 | (format nil "Hello ~a!" (or name (os:getenv "USER") "lisper"))) 11 | 12 | (defvar *server* nil) 13 | 14 | (defun start-webapp () 15 | (setf *server* (make-instance 'routes:easy-routes-acceptor :port 4567)) 16 | (hunchentoot:start *server*)) 17 | 18 | (defun stop-webapp () 19 | (hunchentoot:stop *server*)) 20 | 21 | #+ciel 22 | (progn 23 | (start-webapp) 24 | (format t "~&App started on localhost:4567…~&") 25 | (sleep most-positive-fixnum)) 26 | -------------------------------------------------------------------------------- /src/more-docstrings/more-docstrings.asd: -------------------------------------------------------------------------------- 1 | (in-package :cl-user) 2 | 3 | ;; This .asd definition is not used in CIEL, where docstring.lisp is 4 | ;; included in the sources, so we just load the .lisp file. 5 | ;; 6 | ;; It is there to give it a chance to be used outside of CIEL. We'll see. 7 | (asdf:defsystem "more-docstrings" 8 | :version "0.1" 9 | :author "vindarel" 10 | :license "MIT" 11 | :homepage "https://github.com/ciel-lang/more-docstrings/" 12 | :depends-on ("str") 13 | :components ((:file "docstrings")) 14 | 15 | :description "more-docstrings augments the docstring of built-in functions, macros and variables to give more explanations and examples. It is part of the CIEL project.") 16 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM fukamachi/sbcl:2.3.8 AS build 2 | 3 | WORKDIR /home 4 | 5 | RUN apt-get update \ 6 | && apt-get install -y libmagic-dev libc6-dev gcc wget git make libreadline-dev 7 | 8 | COPY . . 9 | 10 | # install CIEL dependencies 11 | RUN mkdir -p ~/common-lisp \ 12 | && ( cd ~/common-lisp/ && wget https://asdf.common-lisp.dev/archives/asdf-3.3.5.tar.gz && tar -xvf asdf-3.3.5.tar.gz && mv asdf-3.3.5 asdf ) 13 | 14 | RUN mv src/docker_utils/sbclrc ~/.sbclrc \ 15 | && mv src/docker_utils/sbcl_ros_wrapper /usr/local/bin/sbcl \ 16 | && ln -sf ~/.roswell/lisp/quicklisp ~/quicklisp 17 | 18 | RUN make ql-deps \ 19 | && make build \ 20 | && cp ./ciel /usr/local/bin/ 21 | 22 | ENTRYPOINT ["/usr/local/bin/ciel"] 23 | -------------------------------------------------------------------------------- /src/scripts/quicksearch.lisp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ciel 2 | ;;; 3 | ;;; Search for Lisp libraries on Quicklisp, Cliki and Github. 4 | ;;; 5 | ;;; 6 | ;;; Run with: 7 | ;;; $ ciel -s quicksearch keyword 8 | ;;; 9 | ;;; or add a shebang line and make this script executable. 10 | ;;; 11 | 12 | (in-package :ciel-user) 13 | 14 | (unless (second uiop:*command-line-arguments*) 15 | (format! t "Quicksearch: search for Lisp libraries on Quicklisp, Cliki and Github.~&") 16 | (format! t "Usage: ciel -s quicksearch keyword~&") 17 | (uiop:quit)) 18 | 19 | ;; We use a "feature flag" kind of like a "file == __main__" check: 20 | ;; don't run this when developing on the REPL, but yes run it when calling it with CIEL. 21 | #+ciel 22 | (quicksearch:? (second uiop:*command-line-arguments*) :ud) ;; url and details. 23 | -------------------------------------------------------------------------------- /src/scripts/finder.lisp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ciel 2 | 3 | ;; 4 | ;; $ ciel finder.lisp foo bar | xargs mpv 5 | ;; 6 | ;; Searches for files matching "foo" and "bar" (in any order) in my music directories. 7 | ;; 8 | ;; o/ 9 | ;; 10 | 11 | (in-package :ciel-user) 12 | 13 | (defparameter *directories* '("~/Music/" "~/Downloads/")) 14 | 15 | (defun find-on-directory (root params) 16 | (finder:finder* 17 | :root root 18 | ;; "and" the params: 19 | :predicates (apply #'finder:every-path~ params) 20 | ;; does a "or": 21 | ;; :predicates (apply #'finder:path~ params) 22 | )) 23 | 24 | (defun find-files (&optional params) 25 | (unless params 26 | (format *error-output* "No search terms supplied.~&Usage: finder.lisp search terms.~&") 27 | (return-from find-files)) 28 | (let ((str:*ignore-case* t) 29 | (params (ensure-list params))) 30 | (flatten 31 | (loop for root in *directories* 32 | collect 33 | (find-on-directory root params))))) 34 | 35 | (defun pprint-for-shell (list) 36 | (mapcar (lambda (p) 37 | (format t "~s~&" (finder:path p))) 38 | list) 39 | (terpri)) 40 | 41 | #+ciel 42 | (pprint-for-shell (find-files (rest *script-args*))) 43 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /utils.lisp: -------------------------------------------------------------------------------- 1 | (in-package :ciel) 2 | 3 | 4 | ;;; Utilities that are useful enough to be available everywhere. 5 | 6 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 7 | ;;; These are used for the scripting capabilities. 8 | ;;; We can load a file with or without a shebang line. 9 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 10 | (defun maybe-ignore-shebang (in) 11 | "If this file starts with #!, delete the shebang line, 12 | so we can LOAD the file. 13 | Return: a stream (it is LOADable)." 14 | ;; thanks Roswell for the trick. 15 | (let ((first-line (read-line in))) 16 | (make-concatenated-stream 17 | ;; remove shebang: 18 | (make-string-input-stream 19 | (format nil "~a" 20 | (if (str:starts-with-p "#!" first-line) 21 | "" 22 | first-line))) 23 | ;; rest of the file: 24 | in))) 25 | 26 | (defun load-without-shebang (file) 27 | "LOAD this file, but exclude the first line if it is a shebang line." 28 | (with-open-file (file-stream file) 29 | (load 30 | (maybe-ignore-shebang file-stream)))) 31 | 32 | (defun has-shebang (file) 33 | "Return T if the first line of this file is a shell shebang line (starts with #!)." 34 | (with-open-file (s file) 35 | (str:starts-with-p "#!" (read-line s)))) 36 | -------------------------------------------------------------------------------- /src/more-docstrings/README.md: -------------------------------------------------------------------------------- 1 | 2 | Augment the docstring of built-in functions and macros with more explanations and examples. 3 | 4 | Let's see how useful it is for newcomers. 5 | 6 | This is part of [CIEL](https://github.com/ciel-lang/CIEL/) (CIEL Is an Extended Lisp). 7 | 8 | A few functions are done: 9 | 10 | - `loop`: no docstring by default. It becomes 11 | 12 | ``` 13 | The basic LOOP structure is 14 | 15 | (loop for x in (list x y z) 16 | do …) 17 | 18 | "do" is for side effects. 19 | 20 | Use "collect" to return results: 21 | 22 | (loop for x in (list 1 2 3) 23 | collect (* x 10)) 24 | 25 | To iterate over arrays, use "across" instead of "in". 26 | 27 | To iterate over hash-tables… try MAPHASH first :D 28 | 29 | For many examples, see the CL Cookbook: 30 | https://lispcookbook.github.io/cl-cookbook/iteration.html 31 | ``` 32 | 33 | - `mapcar`, `mapcan` 34 | - `maphash` 35 | - `sort` 36 | - `loop` 37 | - `defun`, `defmacro` 38 | - `defclass`, `defgeneric`, `print-object` 39 | - `defstruct` 40 | - `find` 41 | - `with-open-file` 42 | - `round` 43 | 44 | and variables: 45 | 46 | - `*default-pathname-defaults*` 47 | 48 | and that's it. To be continued. 49 | 50 | A gotcha though: 51 | 52 | - we are modifying the symbols in the :cl package, there is currently no way to "undo" these additions. 53 | 54 | ## Installation 55 | 56 | This library is on [Ultralisp](https://ultralisp.org/github). 57 | -------------------------------------------------------------------------------- /src/scripts/apipointer.lisp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ciel 2 | ;;; 3 | ;;; Call a JSON API, access elements with json-pointer. 4 | ;;; 5 | ;;; Example: 6 | ;;; 7 | ;;; $ ciel -s apipointer https://fakestoreapi.com/products?limit=5 "/0/rating/rate" 8 | ;;; or 9 | ;;; $ ./apipointer.lisp etc etc 10 | ;;; 11 | 12 | (in-package :ciel-user) 13 | 14 | ;; Currently, only -> is imported from arrow-macros. Use more? 15 | (import 'arrow-macros:-<>) 16 | (import 'arrow-macros:<>) 17 | 18 | ;;; For the example sake, we don't do error handling in (main), 19 | ;;; because when the script is run, CIEL adds a handler-case to handle 20 | ;;; any error and print a short message, sans the stacktrace. 21 | ;;; Proper error handling is left as an exercise to the reader. 22 | 23 | (defun main (url pointer) 24 | (-<> url 25 | dex:get 26 | json:read-json 27 | (json-pointer:get <> pointer) 28 | ;; for a terminal output: 29 | pprint)) 30 | 31 | (unless (second uiop:*command-line-arguments*) 32 | (format! t "APIPointer: request a JSON API, get nested elements with json-pointer.~&") 33 | (format! t "Usage: ciel apipointer.lisp URL [JSON-POINTERS]~&") 34 | (uiop:quit)) 35 | 36 | ;; Feature flag: 37 | ;; call our main function only when running the script, not when developing on the REPL. 38 | #+ciel 39 | (main (second uiop:*command-line-arguments*) (third uiop:*command-line-arguments*)) 40 | #-ciel 41 | (format t "Usage: (main url pointer)~&") 42 | -------------------------------------------------------------------------------- /CONTRIBUTING.markdown: -------------------------------------------------------------------------------- 1 | 2 | Thanks for contributing to CIEL. We hope it is useful to you and will 3 | be now more useful to everybody. 4 | 5 | Please follow these short guidelines. They'll help the maintainer(s) 6 | craft release notes and they make for a clearer commits log, *IMO*. Thank you! 7 | 8 | ## Commit messages 9 | 10 | Please say at the beginning what your commit is changing: is it about 11 | dependencies? The Makefile, the .asd? The terminal/readline interface? 12 | 13 | ` README:` for the README 14 | - `docs:` is for documentation 15 | - `deps:` for the Lisp dependencies (be more explicit for system-wide dependencies) 16 | - `CI:` 17 | - `asd:` 18 | - `terminal`: for the terminal REPL 19 | 20 | Example: 21 | 22 | > deps: libmagic-dev is no more required 23 | 24 | If your change is about a domain, you can say it up front too. For example: 25 | 26 | > database: mention the need of db drivers for binaries 27 | 28 | If your change is adding or removing something, you can say this action up front. 29 | 30 | If your change is wider or doesn't fit here, don't think harder, just contribute. Thanks. 31 | 32 | 33 | ### Minor commit messages 34 | 35 | I like to see the `(minor)` mention when the change is really trivial 36 | and not worth looking at. Likewise, we can grep-it out from the 37 | release notes. 38 | 39 | For example: 40 | 41 | > (minor) add site icon 42 | 43 | > (minor) make run typo 44 | 45 | ## Avoid small and useless commits, squash them 46 | 47 | Please avoid small commits that say "fix" "fix" and again 48 | "fix". Squash them into one with a good commit message (see above), 49 | thank you. 50 | -------------------------------------------------------------------------------- /src/more-common-names/names.lisp: -------------------------------------------------------------------------------- 1 | 2 | (uiop:define-package :more-common-names 3 | (:use :cl) 4 | (:export :nappend 5 | :nremove)) 6 | 7 | (in-package :more-common-names) 8 | 9 | ;; from cl-str 10 | (defun concat (&rest strings) 11 | "Join all the string arguments into one string." 12 | (apply #'concatenate 'string strings)) 13 | 14 | ;; from more-docstrings. 15 | (defvar *docstrings-cache* (make-hash-table) 16 | "Cache the original docstring of functions and macros we are augmenting. 17 | Mainly to ease our tests at the REPL.") 18 | 19 | (defun documentation-with-cache (symbol &optional (doc-type 'function)) 20 | (let ((cached (gethash symbol *docstrings-cache*))) 21 | (if cached 22 | cached 23 | 24 | (let ((doc (documentation symbol doc-type))) 25 | (setf (gethash symbol *docstrings-cache*) 26 | ;; don't store a NIL docstring. 27 | (or doc "")) 28 | doc)))) 29 | 30 | (defun docstring-append (symbol s &optional (doc-type 'function)) 31 | "Add S to the docstring of SYMBOL (to designate a function or a macro). 32 | DOC-TYPE is the required argument of DOCUMENTATION, by default 'function (for functions and macros), otherwise use 'variable." 33 | (let ((doc (documentation-with-cache symbol doc-type))) 34 | (setf (documentation symbol doc-type) 35 | (concat doc s)))) 36 | 37 | 38 | ;; nappend 39 | (setf (fdefinition 'nappend) #'nconc) 40 | 41 | (docstring-append 'nappend " 42 | 43 | An alias to the built-in NCONC." 44 | 'function) 45 | 46 | ;; nremove 47 | (setf (fdefinition 'nremove) #'delete) 48 | 49 | (docstring-append 'nremove " 50 | Return a sequence formed by destructively removing the specified ITEM from 51 | the given SEQUENCE. 52 | 53 | An alias to the built-in DELETE." 54 | 'function) 55 | -------------------------------------------------------------------------------- /src/scripts/webapp-notify.lisp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ciel 2 | ;;; 3 | ;;; Run with: 4 | ;;; $ ./webapp-notify.lisp 5 | ;;; 6 | ;;; Watch this file for write events and load & compile it again. 7 | ;;; This redefines our web routes, so we can develop our app in a simple interactive way. 8 | ;;; 9 | ;;; If you are doing that, you'll want to setup a proper dev environment to enjoy full Common Lisp image-based development. 10 | 11 | (in-package :ciel-user) 12 | 13 | (routes:defroute route-root "/" (&get name) 14 | (format nil "Hello ~a!" (or name (os:getenv "USER") "lisper"))) 15 | 16 | (routes:defroute route-hello "/hello" () 17 | (format nil "Hello :)")) 18 | 19 | ;; Try adding new routes. 20 | ;; (gotcha: give them unique names) 21 | 22 | (defvar *server* nil) 23 | 24 | (defun start-webapp () 25 | (unless *server* 26 | (setf *server* (make-instance 'routes:easy-routes-acceptor :port 4567)) 27 | (hunchentoot:start *server*))) 28 | 29 | (defun stop-webapp () 30 | (hunchentoot:stop *server*)) 31 | 32 | (defun dumb-auto-reload () 33 | "Watch this file for write events and load & compile it again. 34 | This redefines our web routes, so we can develop our app in a simple interactive way. 35 | 36 | If you are doing that, you'll want to setup a proper dev environment to enjoy full Common Lisp image-based development." 37 | (format! t "~&Watching webapp.lisp…") 38 | (notify:watch "webapp.lisp") 39 | (notify:with-events (file change :timeout T) 40 | ;; list of events: 41 | ;; (print (list file change)) 42 | (when (equal change :close-write) 43 | (format! t "~%~%Reloading ~a…~&" file) 44 | (handler-case 45 | (ciel::load-without-shebang "webapp.lisp") 46 | (reader-error () 47 | ;; READ errors, parenthesis not closed, etc. Wait for the developer. 48 | (format! t "~%~%read error, waiting for change…~&")))))) 49 | 50 | 51 | #+ciel 52 | (unless *server* 53 | (start-webapp) 54 | (format t "~&App started on localhost:4567…~&") 55 | (dumb-auto-reload) 56 | (sleep most-positive-fixnum)) 57 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | build debian: 2 | stage: build 3 | 4 | # Build on Debian and SBCL. 5 | # We want to build CIEL in a sufficiently old Debian version: 6 | # the glibc will be compatible with lots of Debian and Ubuntu systems, 7 | # however building on the latest (Bullseye) would lead to incompatible glibc errors 8 | # when running on newer systems. 9 | # Buster: released in July, 2019. 10 | # Bullseye: released in August, 2021, supported until July, 2024. 11 | image: clfoundation/sbcl:2.1.5-buster 12 | 13 | # We need to install some system dependencies, 14 | # to clone libraries not in Quicklisp, 15 | # and to update ASDF to >= 3.3.5 in order to use local-package-nicknames. 16 | before_script: 17 | - apt-get update -qy 18 | - apt-get install -y git-core tar 19 | 20 | # The image doesn't have Quicklisp installed by default. 21 | - QUICKLISP_ADD_TO_INIT_FILE=true /usr/local/bin/install-quicklisp 22 | 23 | # Upgrade ASDF (UIOP) to 3.3.5 because we want package-local-nicknames. 24 | - mkdir -p ~/common-lisp/asdf/ 25 | - ( cd ~/common-lisp/ && wget https://asdf.common-lisp.dev/archives/asdf-3.3.5.tar.gz && tar -xvf asdf-3.3.5.tar.gz && mv asdf-3.3.5 asdf ) 26 | - echo "Content of ~/common-lisp/asdf/:" && ls ~/common-lisp/asdf/ 27 | 28 | # Install system dependencies 29 | - make debian-deps 30 | # Clone upstream QL libraries. 31 | - make ql-deps 32 | script: 33 | # build a ciel binary on cwd: 34 | - make build 35 | 36 | artifacts: 37 | name: "ciel" 38 | paths: 39 | - ciel 40 | 41 | build void: 42 | stage: build 43 | # Use custom docker image since the official ones 44 | # can't be used in gitlab CI pipelines 45 | image: cinerion/ciel-sbcl-voidlinux 46 | 47 | # We need to install some system dependencies, 48 | # to clone libraries not in Quicklisp, 49 | # and to update ASDF to >= 3.3.5 in order to use local-package-nicknames. 50 | before_script: 51 | - xbps-install -S 52 | - xbps-install -uy xbps 53 | - xbps-install -uy 54 | 55 | # The image doesn't have Quicklisp installed by default. 56 | - QUICKLISP_ADD_TO_INIT_FILE=true /usr/local/bin/install-quicklisp 57 | 58 | # Upgrade ASDF (UIOP) to 3.3.5 because we want package-local-nicknames. 59 | - mkdir -p ~/common-lisp/asdf/ 60 | - ( cd ~/common-lisp/ && wget https://asdf.common-lisp.dev/archives/asdf-3.3.5.tar.gz && tar -xvf asdf-3.3.5.tar.gz && mv asdf-3.3.5 asdf ) 61 | - echo "Content of ~/common-lisp/asdf/:" && ls ~/common-lisp/asdf/ 62 | 63 | # Clone upstream QL libraries. 64 | - make ql-deps 65 | script: 66 | # build a ciel binary on cwd: 67 | - make build 68 | 69 | artifacts: 70 | name: "ciel" 71 | paths: 72 | - ciel 73 | -------------------------------------------------------------------------------- /src/packages.lisp: -------------------------------------------------------------------------------- 1 | (in-package :cl-user) 2 | 3 | (uiop:define-package ciel-csv 4 | (:use :cl)) 5 | 6 | (uiop:define-package ciel 7 | (:use :cl) 8 | ;; xxx: nicknames copied from ciel-user below. 9 | (:local-nicknames (:/os :uiop/os) 10 | (:os :uiop/os) 11 | (:filesystem :uiop/filesystem) 12 | (:finder :file-finder) 13 | (:notify :org.shirakumo.file-notify) 14 | (:alex :alexandria) 15 | (:csv :ciel-csv) 16 | (:http :dexador) 17 | (:json :shasht) 18 | (:json-pointer :cl-json-pointer/synonyms) 19 | (:time :local-time) 20 | (:routes :easy-routes)) 21 | (:export #:--> 22 | #:^ 23 | #:load-without-shebang)) 24 | 25 | (uiop:define-package ciel-user 26 | (:use :cl :ciel) 27 | (:local-nicknames (:/os :uiop/os) ; let's try this nickname. 28 | ;; Simply :os doesn't help at auto-discovery with SLIME's autocompletion. 29 | ;; But let's add it anyways for correctness, 30 | ;; it's handy for the shell and scripts. 31 | (:os :uiop/os) 32 | ;; This other uiop module is always useful: 33 | (:filesystem :uiop/filesystem) 34 | (:finder :file-finder) 35 | (:notify :org.shirakumo.file-notify) 36 | (:alex :alexandria) 37 | (:csv :ciel-csv) 38 | (:http :dexador) 39 | (:json :shasht) 40 | (:json-pointer :cl-json-pointer/synonyms) 41 | (:time :local-time) 42 | (:routes :easy-routes)) 43 | (:export 44 | #:*script-args*)) 45 | 46 | (uiop:define-package ciel-5am-user 47 | (:use :cl :ciel 48 | ;; one addition from ciel-user: 49 | :5am) 50 | (:local-nicknames (:/os :uiop/os) ; let's try this nickname. 51 | ;; Simply :os doesn't help at auto-discovery with SLIME's autocompletion. 52 | ;; But let's add it anyways for correctness, 53 | ;; it's handy for the shell and scripts. 54 | (:os :uiop/os) 55 | ;; This other uiop module is always useful: 56 | (:filesystem :uiop/filesystem) 57 | (:finder :file-finder) 58 | (:notify :org.shirakumo.file-notify) 59 | 60 | (:alex :alexandria) 61 | (:csv :cl-csv) 62 | (:http :dexador) 63 | (:json :shasht) 64 | (:json-pointer :cl-json-pointer/synonyms) 65 | (:time :local-time) 66 | (:routes :easy-routes)) 67 | (:documentation "Same package as ciel-user, with the added symbols of fiveam, in order to define ad run unit tests.")) 68 | 69 | 70 | ;TODO: a conflict between Serapeum and generic-cl 71 | (uiop:define-package generic-ciel 72 | (:use :generic-cl 73 | :ciel) 74 | ;XXX: local nicknames are duplicated in each package declaration. 75 | (:local-nicknames (:csv :cl-csv) 76 | (:http :dexador))) 77 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | LISP ?= sbcl 2 | 3 | all: build 4 | 5 | # can use bespoke dir like 'QLDIR=~/nostandard/local-projects make' 6 | QLDIR ?= $(HOME)/quicklisp/local-projects 7 | 8 | # make will exit early if git clone errors b/c dir already exists 9 | define git-clone-pull = 10 | if test -d $(QLDIR)/$(notdir $1); then cd $(QLDIR)/$(notdir $1) && git pull; else git clone $1 $(QLDIR)/$(notdir $1); fi 11 | endef 12 | 13 | MISC_EXTENSIONS_HEALTHY_COMMIT=7af1c37c725fa32cf74c69a4bdafa00d1b79a1ca 14 | 15 | $(QLDIR)/misc-extensions: 16 | mkdir -p $(QLDIR) 17 | git clone https://gitlab.common-lisp.net/misc-extensions/misc-extensions.git $(QLDIR)/misc-extensions 18 | cd $(QLDIR)/misc-extensions && git checkout $(MISC_EXTENSIONS_HEALTHY_COMMIT) 19 | 20 | $(QLDIR)/asdf: 21 | # 2024-08: building with older asdf fails 22 | # unrecognized define-package keyword :LOCAL-NICKNAMES 23 | # https://github.com/ciel-lang/CIEL/issues/58 24 | mkdir -p $(QLDIR) 25 | cd $(QLDIR) && \ 26 | curl -sL https://asdf.common-lisp.dev/archives/asdf-3.3.5.tar.gz | \ 27 | tar -xvzf - && \ 28 | mv asdf-3.3.5 asdf 29 | 30 | asdf: $(QLDIR)/asdf 31 | @echo "New ASDF version installed to " $(QLDIR) 32 | 33 | check-asdf-version: 34 | sbcl --script check-asdf-version.lisp || echo "Your ASDF version is too old. You can update it with 'make asdf'. It will be downloaded to " $(QLDIR) ". You can set QLDIR." 35 | 36 | # Install some Quicklisp dependencies. 37 | ql-deps: check-asdf-version 38 | # termp, little utility, it is NOT in Quicklisp as of <2025-02-02>. 39 | # I asked for inclusion in Quicklisp. 40 | $(call git-clone-pull,https://github.com/vindarel/termp) 41 | 42 | # 2023-11: The symbol SB-INT:TRULY-DYNAMIC-EXTENT is absent since at least 43 | # SBCL v2.3.10, which was required in older versions of cl-environments 44 | # and cl-form-types. 45 | # See issue https://github.com/ciel-lang/CIEL/issues/38 46 | # This has been fixed upstream, it is in Quicklisp 2024-08 47 | $(call git-clone-pull,https://github.com/alex-gutev/cl-environments) 48 | $(call git-clone-pull,https://github.com/alex-gutev/cl-form-types) 49 | 50 | # 2024-08: Moira needs moira/light, added <2023-11-23 Thu>, 51 | # it is in Quicklisp 2024-08 52 | # moira/light doesn't depend on Osicat. 53 | $(call git-clone-pull,https://github.com/ruricolist/moira) 54 | 55 | # 2024-08: simple progress bar, it is in Quicklisp 2024-08. 56 | $(call git-clone-pull,https://github.com/vindarel/progressons) 57 | 58 | # In Quicklisp 2024-08. 59 | $(call git-clone-pull,https://github.com/lisp-maintainers/file-finder) 60 | 61 | # <2024-08-30> error with SBCL: Lock on package SB-DI violated… 62 | # fixed https://github.com/Shinmera/dissect/issues/18 on March, 2024, in Quicklisp 2024-08. 63 | $(call git-clone-pull,https://github.com/Shinmera/dissect) 64 | 65 | # fix fset on latest SBCL 66 | # "Lock on package SB-EXT violated when interning ONCE-ONLY while in package FSET" 67 | # see https://github.com/slburson/fset/pull/46 68 | $(MAKE) $(QLDIR)/misc-extensions 69 | $(call git-clone-pull,https://github.com/slburson/fset) 70 | 71 | # Install some system dependencies. 72 | debian-deps: 73 | apt-get install -y libinotifytools0 74 | 75 | macos-deps: 76 | echo "please install fsevent (for file-notify)" 77 | 78 | run: 79 | $(LISP) --load ciel.asd \ 80 | --eval '(asdf:load-system :ciel)' \ 81 | --eval '(in-package :ciel-user)' 82 | 83 | run-repl: 84 | $(LISP) --load ciel.asd \ 85 | --eval '(asdf:load-system :ciel)' \ 86 | --eval '(asdf:load-system :ciel/repl)' \ 87 | --eval '(sbcli:repl)' 88 | 89 | image: 90 | $(LISP) --load build-image.lisp 91 | 92 | build: 93 | $(LISP) --non-interactive \ 94 | --eval '(ql:quickload "cl+ssl")' \ 95 | --load ciel.asd \ 96 | --eval '(ql:quickload :swank)' \ 97 | --eval '(ql:quickload :ciel)' \ 98 | --eval '(ql:quickload :ciel/repl)' \ 99 | --eval '(asdf:make :ciel/repl)' \ 100 | --eval '(quit)' 101 | 102 | gen-dependencies-list: 103 | ./find-dependencies.lisp > docs/dependencies.md 104 | 105 | serve-docs: 106 | docsify serve docs/ 107 | 108 | clean: 109 | rm ciel 110 | -------------------------------------------------------------------------------- /src/scripts/ffmpeg.lisp: -------------------------------------------------------------------------------- 1 | (in-package :ciel-user) 2 | 3 | ;;; 4 | ;;; Search files and transform them to mp3 with ffmpeg. 5 | ;;; 6 | ;;; Usage: 7 | ;;; 8 | ;;; ciel src/scripts/ffmpeg search terms 9 | ;;; 10 | ;;; Todo: 11 | ;;; - choose search directories (do we even search on the current directory?) 12 | ;;; - choose output format, etc. 13 | ;;; 14 | ;;; TIL: we need to escape file names if they contain characters such as [ ]. 15 | ;;; 16 | 17 | (defparameter *directories* '("~/Music/" "~/Downloads/" "~/zique/")) 18 | 19 | (defvar *music-type-extensions* '("aac" "ac3" "aiff" "amr" "ape" "dts" "f4a" "f4b" "flac" "gsm" 20 | "m3u" "m4a" "midi" "mlp" "mka" "mp2" "mp3" "oga" "ogg" "opus" "pva" 21 | "ra" "ram" "raw" "rf64" "spx" "tta" "wav" "wavpack" "wma" "wv") 22 | "A bunch of audio extensions. Should be supported by ffmpeg. Thanks ready-player.el.") 23 | 24 | (defun music-file-p (file) 25 | "Return non-NIL if this file (pathname) has a music file extension as of *music-type-extensions*." 26 | (find (pathname-type file) *music-type-extensions* :test #'equalp)) 27 | 28 | 29 | (defun find-on-directory (root params) 30 | "Search on default directories. 31 | 32 | PARAMS (list)" 33 | (finder:finder* 34 | :root root 35 | ;; This "and"s the params: 36 | :predicates (apply #'finder:every-path~ params) 37 | ;; This would do a "or": 38 | ;; :predicates (apply #'finder:path~ params) 39 | )) 40 | 41 | (defun find-files (&optional params) 42 | "Find files matching PARAMS (a list of strings) on the default directories. 43 | 44 | PARAMS is an 'OR'. I would prefer to 'and' the matches actually (see find-on-directory)." 45 | (unless params 46 | (format *error-output* "No search terms supplied.~&Usage: finder.lisp search terms.~&") 47 | (return-from find-files)) 48 | (let ((str:*ignore-case* t) 49 | (params (ensure-list params))) 50 | (flatten 51 | (loop for root in *directories* 52 | collect 53 | (find-on-directory root params))))) 54 | 55 | (defun pprint-for-shell (list) 56 | "Pretty-print this list of files (with full path), one per line." 57 | (mapcar (lambda (p) 58 | (format t "~s~&" (finder:path p))) 59 | list) 60 | (terpri)) 61 | 62 | (defun change-extension (file) 63 | (when (finder:file? file) 64 | (setf file (finder:path file))) 65 | (let ((extension (pathname-type file))) 66 | (when extension 67 | (values 68 | (str:replace-all extension "mp3" file) 69 | extension)))) 70 | 71 | ;; warn! 72 | (defun escape-file-name (name) 73 | "Escape [ and ] with double \\, 74 | 75 | Or test the file exists with: 76 | 77 | (probe-file (make-pathname :name name :type extension)) 78 | 79 | this doesn't choke with wildcard characters such as [ and ]. 80 | 81 | otherwise uiop:file-exists-p returns NIL for an existing file." 82 | ;; This works on upstream file-finder <2025-09-09> 83 | ;; (when (finder:file? name) 84 | ;; (setf name (finder:path name))) 85 | (str:replace-using '("[" "\\[" 86 | "]" "\\]") 87 | name)) 88 | 89 | (defun run-ffmpeg (file) 90 | "Run ffmpeg on FILE, transform to mp3." 91 | (let ((target (change-extension file))) 92 | (if (uiop:file-exists-p (escape-file-name target)) 93 | (progn 94 | (format t "~&mp3 already exists: ~a~&" target) 95 | target) 96 | (uiop:run-program (list "ffmpeg" 97 | "-i" 98 | (finder:path file) 99 | target) 100 | :output :interactive 101 | :error-output t)))) 102 | 103 | (defun ffmpeg-on-files (files) 104 | "Transform files to mp3 with ffmpeg. 105 | 106 | Search on the default directories by AND-ing the search terms." 107 | (loop for file in files 108 | for path = (finder:path file) 109 | when (and (music-file-p path) 110 | (uiop:file-exists-p (escape-file-name path))) 111 | do (format t "~&transforming: ~a~&" file) 112 | (run-ffmpeg file) 113 | and collect file into processed 114 | finally 115 | (format t "~&~%done for files: ~&") 116 | (pprint-for-shell processed))) 117 | 118 | #+ciel 119 | (ffmpeg-on-files (find-files (rest *script-args*))) 120 | -------------------------------------------------------------------------------- /src/json-pointer-minus.lisp: -------------------------------------------------------------------------------- 1 | ;; 2 | ;; JSON-POINTER 3 | ;; 4 | ;; cl-json-pointer has lengthy functions: get-by-json-pointer, add-by-json-pointer, etc. 5 | ;; Let's create shorter ones: get-by etc. 6 | ;; 7 | ;; update: oops, they have the cl-json-pointer/synonyms system with even shorter "get", "set" etc. 8 | ;; Let's use it. 9 | ;; 10 | ;; But why doesn't it accept a JSON string as input?! 11 | 12 | (in-package :ciel) 13 | 14 | (setf cl-json-pointer:*json-object-flavor* :shasht) 15 | 16 | (defun json-pointer-get-by (obj pointer &key (flavor cl-json-pointer:*json-object-flavor*)) 17 | "Traverse OBJ with POINTER and return three values: 18 | 19 | - the found value (`nil' if not found), 20 | - a generalized boolean saying the existence of the place pointed by POINTER, 21 | - and NIL. 22 | 23 | GET-BY is a shorter name of cl-json-pointer:get-by-json-pointer added by CIEL. 24 | 25 | In CIEL, we use the SHASHT library to handle JSON. 26 | SHASHT returns a hash-table. 27 | JSON-POINTER functions take a dict (hash-table) as first argument. 28 | 29 | Examples: 30 | 31 | (json-pointer:get-by (dict \"a\" 32 | (dict \"aa\" 11)) 33 | \"/a/aa\") 34 | ;; => 11 35 | 36 | Parse a JSON string with SHASHT:READ-JSON before feeding the result to json-pointer: 37 | 38 | (defvar *json-string* \"{\\\"foo\\\": [\\\"1\\\", \\\"2\\\"]}\") 39 | 40 | (let ((obj (shasht:read-json *json-string*))) 41 | (json-pointer:get-by obj \"/foo\")) 42 | ;; => 43 | #(\"1\" \"2\") 44 | T 45 | NIL 46 | " 47 | (cl-json-pointer:get-by-json-pointer obj pointer :flavor flavor)) 48 | 49 | #+(or) 50 | (defvar *json-string* "{\"foo\": [\"1\", \"2\"]}") 51 | 52 | (defun json-pointer-set-by (obj pointer value &key (flavor cl-json-pointer:*json-object-flavor*)) 53 | "Traverse OBJ with POINTER, set VALUE into the pointed place, and return the modified OBJ." 54 | (cl-json-pointer:set-by-json-pointer obj pointer value :flavor flavor)) 55 | 56 | (defun json-pointer-update-by (place pointer value &key (flavor cl-json-pointer:*json-object-flavor*)) 57 | "Set the result of SET-BY to the referred PLACE. 58 | 59 | UPDATE-BY is a modify macro for SET-BY (like PUSH or INCF)." 60 | (cl-json-pointer:update-by-json-pointer place pointer value :flavor flavor)) 61 | 62 | (defun json-pointer-add-by (obj pointer value &key (flavor cl-json-pointer:*json-object-flavor*)) 63 | "Works as `set-by', except this tries to make a new list when setting to lists." 64 | (cl-json-pointer:add-by-json-pointer obj pointer value :flavor flavor)) 65 | 66 | (defun json-pointer-delete-by (obj pointer &key (flavor cl-json-pointer:*json-object-flavor*)) 67 | "Traverse OBJ with POINTER, delete the pointed place, and return the modified OBJ." 68 | (cl-json-pointer:delete-by-json-pointer obj pointer :flavor flavor)) 69 | 70 | (defun json-pointer-deletef-by (place pointer &key (flavor cl-json-pointer:*json-object-flavor*)) 71 | "Set the result of DELETE-BY to the referred PLACE. 72 | 73 | This is a modify macro for DELETE-BY (like PUSH or INCF)." 74 | (cl-json-pointer:deletef-by-json-pointer place pointer :flavor flavor)) 75 | 76 | (defun json-pointer-remove-by (obj pointer &key (flavor cl-json-pointer:*json-object-flavor*)) 77 | "Like `delete-by', except this tries to make a new list when deleting from lists." 78 | (cl-json-pointer:remove-by-json-pointer obj pointer :flavor flavor)) 79 | 80 | (defun json-pointer-exists-p-by (obj pointer &key (flavor cl-json-pointer:*json-object-flavor*)) 81 | "Traverse OBJ with POINTER and return the existence of the place pointed by POINTER." 82 | (cl-json-pointer:exists-p-by-json-pointer obj pointer :flavor flavor)) 83 | 84 | (defun json-pointer-shorten-functions () 85 | "Shorten function names inside the cl-json-pointer package. 86 | (not so useful anymore: they have shorter synonyms: get, set etc)" 87 | (let ((tuples (list 88 | (list "GET-BY" #'json-pointer-get-by) 89 | ;; NAME NEW FUNCTION 90 | (list "ADD-BY" #'json-pointer-add-by) 91 | (list "SET-BY" #'json-pointer-set-by) 92 | (list "UPDATE-BY" #'json-pointer-update-by) 93 | (list "DELETE-BY" #'json-pointer-delete-by) 94 | (list "DELETEF-BY" #'json-pointer-deletef-by) 95 | (list "REMOVE-BY" #'json-pointer-remove-by) 96 | (list "EXISTS-P-BY" #'json-pointer-exists-p-by)))) 97 | (loop for tuple in tuples 98 | for sym = (intern (first tuple) 'cl-json-pointer) 99 | do (export sym 'cl-json-pointer) 100 | (setf (symbol-function sym) (second tuple)) 101 | collect sym))) 102 | 103 | (json-pointer-shorten-functions) 104 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CIEL Is an Extended Lisp 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 |
20 |

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

26 |
27 | 28 |

CIEL Is an Extended Lisp

29 | 32 |

33 | GitHub 34 | Show me

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

44 |

$ ./myscript.lisp
45 |

46 | 47 |

48 |

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

50 | 51 |
52 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /docs/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/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. Or, you know `append` and you want is non-consing (destructive) counterpart. In pure CL, it's `nconc`, we ship an `nappend` alias. 85 | 86 | We **enhance the docstrings** of built-in functions and macros with more 87 | explanations and examples, so you don't have to reach to external 88 | documentation just yet in order to understand and try out things like 89 | `mapcar` or `loop` (look 'ma, LOOP has no docstring by default). 90 | 91 | Moreover, we bring a **user friendly REPL on the terminal**, 92 | with bells and whistles useful to the developer and people living in a 93 | terminal window. For example, our [repl for the terminal](repl.md) has readline support, multi-line editing, optional syntax highlighting, a shell passthrough, and more goodies. 94 | 95 | We bring **scripting capabilities**. Just run `ciel myscript.lisp`, and use all the high-level CIEL libraries and Common Lisp features in your script. It starts up fast. 96 | 97 | 98 | *We are only started. You can sponsor us [on GitHub sponsors](https://github.com/sponsors/vindarel/), thank you!* 99 | 100 | [![ko-fi](https://www.ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/K3K828W0V) 101 | 102 | *If you need to learn Common Lisp the efficient way, I have [a course on videos](https://www.udemy.com/course/common-lisp-programming/?referralCode=2F3D698BBC4326F94358), with many real-world practical stuff in and still growing. If you are a student, drop me a line for a coupon. Thank you for your support!* 103 | 104 | 105 | # Install 106 | 107 | Let's get started. See the [installation instructions](install.md). 108 | 109 | # Scripting & libraries 110 | 111 | Then, have a look at CIEL's [scripting capabilies](scripting.md), and on the [libraries](libraries.md) we ship. 112 | 113 | 114 | # Final words 115 | 116 | That was your life in CL: 117 | 118 |

119 | and now: 120 | 121 |

122 | -------------------------------------------------------------------------------- /docs/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 | -------------------------------------------------------------------------------- /src/scripts/simpleHTTPserver.lisp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ciel 2 | ;;; 3 | ;;; Run with: 4 | ;;; $ ciel -s simpleHTTPserver 4242 5 | ;;; 6 | ;;; or 7 | ;;; 8 | ;;; $ ./simpleHTTPserver.lisp 9 | ;;; 10 | ;;; Use -b to open the web browser. 11 | 12 | (in-package :ciel-user) 13 | 14 | (use-package :spinneret) 15 | 16 | ;; ;;;;;;;;;;;;;;;;;;;;;;, 17 | ;; CLI args. 18 | ;; ;;;;;;;;;;;;;;;;;;;;;;, 19 | (defparameter *my-cli-args* *script-args* 20 | "Copy of the CLI options given to this script. 21 | When we call 22 | 23 | $ ciel -v simpleHTTPserver.lisp -b 4444 24 | 25 | then the list '(simpleHTTPserver.lisp -b 4444) is given in *script-args*.") 26 | 27 | ;; CLI args: the script name, an optional port number. 28 | (defparameter *port* 9000 29 | "Default port. Change it with a free argument on the command-line.") 30 | 31 | (defparameter *acceptor* nil 32 | "Hunchentoot's server instance. Create it with `make-acceptor'.") 33 | 34 | (defun make-acceptor (&key (port *port*)) 35 | "Return the existing acceptor or create one." 36 | ;; I prefer to put this in a function to develop interactively. 37 | (or *acceptor* 38 | (setf *acceptor* (make-instance 'hunchentoot:easy-acceptor 39 | :document-root "./" 40 | :port port)))) 41 | 42 | ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 43 | ;; "Templates" (with s-expressions and Spinneret) 44 | ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 45 | 46 | (defmacro with-page ((&key title) &body body) 47 | `(with-html-string 48 | (:doctype) 49 | (:html 50 | (:head 51 | (:title ,title)) 52 | (:body ,@body)))) 53 | 54 | (defun file-or-directory-namestring (path) 55 | "Return the name of this file or of this directory. 56 | XXX: we want the name of the directory, not the full path." 57 | (unless (fboundp 'str:ensure-suffix) 58 | (print "WARN: str:ensure-suffix is in the latest cl-str. You'll need to clone it into ~/quicklisp/local-projects/")) 59 | (if (uiop:file-pathname-p path) 60 | (file-namestring path) 61 | ;; How to simply get the directory name, not full path? 62 | ;; pathname-directory -> (:relative "path" "to" "dir") 63 | ;; INFO: this is merged in cl-str but not in Quicklisp yet. 64 | (str:ensure-suffix "/" 65 | (first (last (pathname-directory path)))))) 66 | 67 | (defun show-file-list (file-list &key title) 68 | "List files in a HTML list." 69 | (with-page (:title title) 70 | (:header 71 | (:h2 title)) 72 | (:ol (dolist (item file-list) 73 | (:li (:a :href 74 | (format nil "~a" (file-or-directory-namestring item)) 75 | (format nil "~a" (file-or-directory-namestring item)))))) 76 | (:br) 77 | (:footer :style "color: dimgrey" ("Powered by CIEL Is an Extended Lisp" )))) 78 | 79 | (defun file-list () 80 | (show-file-list (append 81 | ;; This is how to list directories, 82 | ;; but we have to serve their content now. 83 | ;; (uiop:subdirectories (uiop:getcwd)) 84 | (uiop:directory-files (uiop:getcwd))) 85 | :title (format nil "Files for ~a" (uiop:getcwd)))) 86 | 87 | ;; ;;;;;;;;;;;;;;;;;;;;;; 88 | ;; web-server config. 89 | ;;;;;;;;;;;;;;;;;;;;;;;;, 90 | 91 | ;; On the root URL "/" show the listing, but when clicking on a file let the server serve it. 92 | (defun serve-root () 93 | (push 94 | (hunchentoot:create-regex-dispatcher "/$" #'file-list) 95 | hunchentoot:*dispatch-table*)) 96 | 97 | #+(or) 98 | (setf hunchentoot:*dispatch-table* nil) 99 | 100 | (defun serve-static-assets () 101 | ;; Serve static assets under a static/ directory (optional). 102 | (push (hunchentoot:create-folder-dispatcher-and-handler 103 | "/static/" "static/" ;; starts without a / 104 | ) 105 | hunchentoot:*dispatch-table*)) 106 | 107 | ;; main function: 108 | (defun simplehttpserver (&key browse (port *port*)) 109 | "Create a Hunchentoot dispatcher on our root directory, 110 | serve static assets, 111 | start Hunchentoot and keep it on the foreground." 112 | (serve-root) 113 | (serve-static-assets) 114 | (handler-case 115 | (progn 116 | ;; Start the webserver. 117 | (hunchentoot:start (make-acceptor :port port)) 118 | (format! t "~&Serving files on port ~a…~&" port) 119 | (format! t "~&~&~t ⤷ http://127.0.0.1:~a ~&~&" port) 120 | 121 | #+unix 122 | (when browse 123 | (uiop:format! t "Open web browser…~&") 124 | (uiop:run-program (list 125 | "xdg-open" 126 | (format nil "http://localhost:~a" port)))) 127 | 128 | ;; Wait in the foreground. 129 | (sleep most-positive-fixnum)) 130 | 131 | (usocket:address-in-use-error () 132 | (format! *error-output* "This port is already in use. Quitting.~&")) 133 | (sb-sys:interactive-interrupt () 134 | (format! t "Bye!") 135 | (hunchentoot:stop *acceptor*)) 136 | (error () 137 | (format! t "An error occured. Quitting.") 138 | (hunchentoot:stop *acceptor*)))) 139 | 140 | 141 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 142 | ;; Parse CLI arguments with Clingon. 143 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 144 | (defparameter cli/options 145 | (list 146 | (clingon:make-option 147 | :flag 148 | :description "Show help" 149 | :short-name #\h 150 | :key :help) 151 | (clingon:make-option 152 | :flag 153 | :description "Open browser" 154 | :short-name #\b 155 | :long-name "browse" 156 | :key :browse)) 157 | "Our script's options.") 158 | 159 | (defun cli/handler (cmd) 160 | "Look at our CLI args and eventually start the web server. 161 | 162 | cmd: a Clingon command built with cli/command." 163 | (let* ((help (clingon:getopt cmd :help)) 164 | (browse (clingon:getopt cmd :browse)) 165 | ;; freeargs always have the script name?? 166 | (freeargs (rest (clingon:command-arguments cmd))) 167 | (port *port*)) 168 | (when help 169 | ;; This funcall is to avoid a style warning: the cli/command function 170 | ;; is not yet defined. 171 | (clingon:print-usage (funcall 'cli/command) t) 172 | (return-from cli/handler)) 173 | (when freeargs 174 | (setf port (or (ignore-errors 175 | (parse-integer (first freeargs))) 176 | *port*))) 177 | (simplehttpserver :browse browse :port port) 178 | )) 179 | 180 | (defun cli/command () 181 | "Create a top-level Clingon command." 182 | (clingon:make-command 183 | :name "simpleHTTPserver.lisp" 184 | :description "Serve the local directory with a simple web server." 185 | :usage "[-h] [-b] [PORT]" 186 | :version "0.1" 187 | :license "todo" 188 | :authors '("vindarel") 189 | :options cli/options 190 | :handler #'cli/handler)) 191 | 192 | #| 193 | A note on using Clingon's free arguments in scripts. 194 | (we get them with (clingon:command-arguments)) 195 | 196 | $ ciel -s simplehttpserver 4242 197 | only has 4242 as free argument, but 198 | 199 | $ ./simpleHTTPserver.lisp 4242 200 | under the hood equals to 201 | $ ciel simpleHTTPserver.lisp 4242 202 | and this has two free arguments: simplehttpserver.lisp and 4242. 203 | 204 | To fix this, to make it always coherent so we can run this script with 205 | -s or with the shebang, CIEL sets *script-args* to always have 206 | the script name. 207 | 208 | |# 209 | 210 | ;; Call the main function only when running this as a script, 211 | ;; not when developing on the REPL. 212 | #+ciel 213 | (clingon:run (cli/command) *script-args*) 214 | -------------------------------------------------------------------------------- /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/more-common-names" 163 | :components 164 | ((:file "names"))) 165 | (:module "src" 166 | :components 167 | ((:file "packages") 168 | (:file "json-pointer-minus") 169 | (:file "ciel") 170 | (:file "csv") 171 | (:file "gui"))) 172 | (:file "utils") 173 | (:module "src/more-docstrings" 174 | :components 175 | ((:file "docstrings")))) 176 | ) 177 | 178 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 179 | ;;; Sub-system for the terminal REPL. 180 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 181 | (asdf:defsystem "ciel/repl" 182 | :description "readline REPL for CIEL with quality of life improvements." 183 | :depends-on (;; :ciel ;; let's avoid, it could run side effects twice (like a defparameter set then reset). 184 | ;; deps 185 | :cl-readline 186 | :lisp-critic ;; it would be nice to integrate it with Slime. 187 | :magic-ed) 188 | :components ((:file "repl") 189 | (:file "utils") 190 | (:file "scripting") 191 | (:file "repl-utils") 192 | 193 | ;; I define them here, for good practice (for me), 194 | ;; but I don't use them. 195 | ;; static-file is important, otherwise the scripts would be run. 196 | (:module "src/scripts" 197 | :components 198 | ((:static-file "quicksearch") 199 | (:static-file "simpleHTTPserver"))) 200 | ) 201 | 202 | :build-operation "program-op" 203 | :build-operation "program-op" 204 | :build-pathname "ciel" 205 | :entry-point "ciel::main") 206 | 207 | ;;; This defines ciel.asd. It is enough to quickload CIEL. 208 | ;;; But to build a binary, 209 | ;;; see build-config.lisp for extra config. 210 | 211 | ;; build a smaller executable with SBCL's core compression: 212 | ;; from 119MB to 28MB, however startup time increases from 0.02 to 0.35s (noticeable). 213 | #+sb-core-compression 214 | (defmethod asdf:perform ((o asdf:image-op) (c asdf:system)) 215 | (uiop:dump-image (asdf:output-file o c) :executable t :compression t)) 216 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/more-docstrings/docstrings.lisp: -------------------------------------------------------------------------------- 1 | (uiop:define-package :more-docstrings 2 | (:use :cl)) 3 | 4 | (in-package :more-docstrings) 5 | 6 | #| 7 | Add more documentation and add examples to the docstrings. 8 | 9 | Now, when we read a function documentation from our editor or with the 10 | built-in `documentation` function, we can learn more about it, and see 11 | examples. We have less the need to reach for external resources. 12 | 13 | The goal is still to ease the first contact of newcomers with CL. 14 | For example, give examples on how to use MAP, MAPCAR, MAPCAN. 15 | 16 | XXX: gotchas 17 | 18 | - if we quickload :ciel twice, the docstrings 19 | are appended twice too :S The hash-table cache doesn't help in that 20 | case. 21 | - we are modifying the symbols in the :cl package, not the ones in :ciel. 22 | 23 | |# 24 | 25 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 26 | ;;; One function to do the job: 27 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 28 | 29 | (defvar *docstrings-cache* (make-hash-table) 30 | "Cache the original docstring of functions and macros we are augmenting. 31 | Mainly to ease our tests at the REPL.") 32 | 33 | (defun documentation-with-cache (symbol &optional (doc-type 'function)) 34 | (let ((cached (gethash symbol *docstrings-cache*))) 35 | (if cached 36 | cached 37 | 38 | (let ((doc (documentation symbol doc-type))) 39 | (setf (gethash symbol *docstrings-cache*) 40 | ;; don't store a NIL docstring. 41 | (or doc "")) 42 | doc)))) 43 | 44 | (defun docstring-append (symbol s &optional (doc-type 'function)) 45 | "Add S to the docstring of SYMBOL (to designate a function or a macro). 46 | DOC-TYPE is the required argument of DOCUMENTATION, by default 'function (for functions and macros), otherwise use 'variable." 47 | (let ((doc (documentation-with-cache symbol doc-type))) 48 | (setf (documentation symbol doc-type) 49 | (str:concat doc s)))) 50 | 51 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 52 | ;;; Now use it. 53 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 54 | 55 | ;;; Variables 56 | 57 | (docstring-append '*default-pathname-defaults* " 58 | 59 | An implementation-dependent pathname, typically in the working directory that was current when Common Lisp was started up. 60 | 61 | Read more: 62 | 63 | - https://cl-community-spec.github.io/pages/002adefault_002dpathname_002ddefaults_002a.html" 64 | 'variable) 65 | 66 | ;;; mapcar 67 | (docstring-append 'mapcar " 68 | 69 | For example: 70 | 71 | (mapcar #'+ '(1 2 3) '(10 20 30)) ;; => (11 22 33) 72 | 73 | (mapcar (lambda (x) 74 | (format t \"~a is ~R~&\" x x)) 75 | '(1 2 3)) 76 | ;; => 77 | 1 is one 78 | 2 is two 79 | 3 is three 80 | (NIL NIL NIL) 81 | ") 82 | 83 | 84 | ;;; mapcan 85 | (docstring-append 'mapcan " 86 | 87 | NCONC concatenates lists destructively.") 88 | 89 | 90 | ;;; sort 91 | (docstring-append 'sort " 92 | 93 | Since SORT is destructive, use COPY-LIST: 94 | 95 | (setq mylist (list 1 3 2)) 96 | (sort (copy-list mylist) #'<) 97 | 98 | See also STABLE-SORT.") 99 | 100 | 101 | ;;; loop 102 | (docstring-append 'loop " 103 | The basic LOOP structure is 104 | 105 | (loop for x in (list x y z) 106 | do …) 107 | 108 | \"do\" is for side effects. 109 | 110 | Use \"collect\" to return results: 111 | 112 | (loop for x in (list 1 2 3) 113 | collect (* x 10)) 114 | 115 | To iterate over arrays, use \"across\" instead of \"in\". 116 | 117 | To iterate over hash-tables… try MAPHASH first :D 118 | 119 | For many examples, see the CL Cookbook: 120 | https://lispcookbook.github.io/cl-cookbook/iteration.html") 121 | 122 | 123 | ;;; maphash 124 | (docstring-append 'maphash " 125 | 126 | Example: 127 | 128 | (maphash (lambda (key value) 129 | (format t \"key is: ~a, value is: ~a~&\" key value)) 130 | (dict :a 'one)) 131 | ;; => key is: A, value is: ONE") 132 | 133 | 134 | ;;; defun 135 | (docstring-append 'defun " 136 | 137 | Example: 138 | 139 | (defun hello (name) 140 | \"Say \\\"hello\\\" to NAME.\" 141 | (format t \"Hello ~a!\" name)) 142 | 143 | Define named parameters with &key: 144 | 145 | (defun hello (name &key lisper) 146 | ...) 147 | 148 | and use it like so: (hello \"you\" :lisper t) 149 | 150 | Key parameters are NIL by default. Give them another default value like this: 151 | 152 | (defun hello (name &key (lisper t)) 153 | ...) 154 | 155 | Read more: 156 | https://gigamonkeys.com/book/functions.html 157 | https://lispcookbook.github.io/cl-cookbook/functions.html") 158 | 159 | 160 | ;;; defmacro 161 | (docstring-append 'defmacro "Macros operate on code, which they see as lists of lists of symbols. 162 | 163 | Macros, unlike functions, do not evaluate their arguments. They 164 | expand (at compile time) into another piece of code, that will 165 | eventually be evaluated. 166 | 167 | First rule for macros: don't write a macro when a function can do. 168 | 169 | Example macros: DEFUN LOOP SETF WITH-OPEN-FILE 170 | 171 | See also: QUOTE BACKQUOTE GENSYM MACROEXPAND 172 | 173 | Read more: 174 | https://lispcookbook.github.io/cl-cookbook/macros.html 175 | https://gigamonkeys.com/book/macros-standard-control-constructs.html 176 | https://www.youtube.com/watch?v=ygKXeLKhiTI Little bits of Lisp video 177 | ") 178 | 179 | ;;; defclass 180 | (docstring-append 'defclass "The macro defclass defines a new named class. It returns the new class object as its result. 181 | 182 | Example: 183 | 184 | (defclass living-being () ()) 185 | 186 | (defclass person (living-being) 187 | ((name 188 | :initarg :name 189 | :initform \"\" 190 | :accessor name) 191 | (lisper 192 | :initarg :lisper 193 | :initform nil 194 | :accessor lisper 195 | :documentation \"Set to non-nil if this person fancies Lisp.\"))) 196 | 197 | Slots are unbound by default, here we prefer them to be the empty string and nil. 198 | 199 | An :accessor creates a generic method. You can have the same accessor name in different classes. 200 | 201 | Create an instance of that class with MAKE-INSTANCE: 202 | 203 | (make-instance 'person :name \"Alice\" :lisper t) 204 | 205 | Define how to pretty-print an object with PRINT-OBJECT. 206 | 207 | After we change a class definition (slots are modified, added or removed), we can control how an object is updated with UPDATE-INSTANCE-FOR-REDEFINED-CLASS. 208 | 209 | Read more: 210 | https://lispcookbook.github.io/cl-cookbook/clos.html 211 | https://cl-community-spec.github.io/pages/defclass.html 212 | ") 213 | 214 | ;;; to be continued. 215 | 216 | (docstring-append 'print-object "The generic function print-object writes the printed representation of object to stream. The function print-object is called by the Lisp printer; it should not be called by the user. 217 | 218 | Example: 219 | 220 | (defmethod print-object ((obj person) stream) 221 | (print-unreadable-object (obj stream :type t :identity t) 222 | (with-slots (name lisper) obj 223 | (format stream \"~a, lisper: ~a\" name lisper)))) 224 | 225 | (make-instance 'person :name \"Alice\") 226 | ;; => 227 | # 228 | (1) (2) (3) 229 | 1 tells the reader that this object can't be read back in 230 | 2 is the object type 231 | 3 is the object identity (address). 232 | 233 | Read more: 234 | https://cl-community-spec.github.io/pages/print_002dobject.html 235 | https://lispcookbook.github.io/cl-cookbook/clos.html#pretty-printing 236 | ") 237 | 238 | (docstring-append 'defstruct " 239 | 240 | Example: 241 | 242 | (defstruct person 243 | name age) 244 | 245 | Creates the `make-person' constructor function, the `person-p' predicate as well as the `person-name' and `person-age' setf-able functions: 246 | 247 | (person-name (make-person :name \"lisper\")) 248 | ;; => \"lisper\" 249 | 250 | Read more: 251 | 252 | - https://lispcookbook.github.io/cl-cookbook/data-structures.html#structures 253 | - https://cl-community-spec.github.io/pages/defstruct.html") 254 | 255 | (docstring-append 'defgeneric " 256 | 257 | A generic function is a lisp function which is associated 258 | with a set of methods and dispatches them when it's invoked. All 259 | the methods with the same function name belong to the same generic 260 | function. 261 | 262 | The `defgeneric` form defines the generic function. If we write a 263 | `defmethod` without a corresponding `defgeneric`, a generic function 264 | is automatically created. 265 | 266 | Example: 267 | 268 | (defgeneric greet (obj) 269 | (:documentation \"says hi\") 270 | (:method (obj) 271 | (format t \"Hi\"))) 272 | 273 | ") 274 | 275 | (docstring-append 'find " 276 | Search for ITEM in SEQUENCE, return ITEM. 277 | 278 | Example: 279 | 280 | (find 20 '(10 20 30)) ;; => 20 281 | (find \"foo\" '(\"abc\" \"foo\") :test #'string-equal) ;; => \"foo\" 282 | 283 | See also: `find-if', `position', `search', `index', `elt'… 284 | 285 | Read more: 286 | 287 | - https://cl-community-spec.github.io/pages/find.html 288 | - https://lispcookbook.github.io/cl-cookbook/data-structures.html") 289 | 290 | (docstring-append 'with-open-file " 291 | Example: 292 | 293 | write to a file: 294 | 295 | (with-open-file (f \"/path/to/file.txt\" :direction :output 296 | :if-exists :supersede 297 | :if-does-not-exist :create) 298 | (write-sequence \"hello file\" f)) 299 | 300 | This binds a stream to the `f' variable and we write content to it. 301 | 302 | You can read files with :direction :input as well as UIOP: uiop:read-file-string, uiop:read-file-lines etc. 303 | 304 | :if-exists options are :error :supersede :append :rename and NIL (this binds the stream variable to NIL). 305 | 306 | :if-does-not-exist options are :error :create and NIL (this binds the stream variable to NIL). 307 | 308 | 309 | Read more: 310 | 311 | - https://lispcookbook.github.io/cl-cookbook/files.html 312 | - https://cl-community-spec.github.io/pages/with_002dopen_002dfile.html 313 | ") 314 | 315 | (docstring-append 'round " 316 | 317 | See also: 318 | 319 | - `fround', that returns the rounded value as a float 320 | - `ceiling', `floor' and `truncate' (and their f… equivalent). 321 | 322 | Read more: 323 | 324 | - https://lispcookbook.github.io/cl-cookbook/numbers.html 325 | - https://cl-community-spec.github.io/pages/floor.html") 326 | 327 | #+ciel 328 | (docstring-append 'function-cache:defcached " 329 | 330 | Example: 331 | 332 | (defcached (foo :timeout 10) (arg) 333 | (sleep 3) 334 | arg) 335 | 336 | The functions's result is cached for 10 seconds, for the given argument. A second call returns immediately.") 337 | -------------------------------------------------------------------------------- /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 | 286 | ## Post-installation tips 287 | 288 | ### Zsh completion 289 | 290 | zsh users can add a one-liner to their .zshrc to add TAB-completion for CIEL arguments: 291 | 292 | compdef _gnu_generic ciel 293 | 294 | this `_gnu_generic` zsh helper scrapes the output of `ciel --help` to 295 | offer you the completion. It shows like this, after a `ciel -` (with a dash): 296 | 297 | ```sh 298 | $ ciel - 299 | --eval -e -- eval a lisp form 300 | --help -- display usage information and exit 301 | --noinform -- Don't print the welcome banner. 302 | --no-userinit -- Don't load the ~/.cielrc init file at start-up (for the CIEL terminal 303 | --script -s -- run a lisp file 304 | --scripts -z -- list available scripts. 305 | --verbose -v -- verbosity level (default- 0) 306 | --version -- display version and exit 307 | ``` 308 | 309 | If anyone got to get this *and* the completion of ciel scripts, please ping us. 310 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /scripting.lisp: -------------------------------------------------------------------------------- 1 | 2 | (in-package :ciel) 3 | 4 | (defparameter *ciel-version* #.(asdf:component-version (asdf:find-system :ciel)) 5 | "CIEL's version, read from the .asd.") 6 | 7 | (defparameter *scripts* (dict 'equalp) 8 | "Available scripts. 9 | Hash-table: file name (sans extension) -> file content (string). 10 | The name is case-insensitive (it's easier for typing things in the terminal).") 11 | 12 | ;; eval 13 | (defun wrap-user-code (s) 14 | "Wrap this user code to handle common conditions, such as a C-c C-c to quit gracefully." 15 | ;; But is it enough when we run a shell command? 16 | `(handler-case 17 | ,s ;; --eval takes one form only so no need of ,@ 18 | (sb-sys:interactive-interrupt (c) 19 | (declare (ignore c)) 20 | (format! *error-output* "Bye!~%")) 21 | (error (c) 22 | (format! *error-output* "~a" c)))) 23 | 24 | 25 | (defun register-builtin-scripts () 26 | "Find available scripts in src/scripts, register them in *SCRIPTS*. 27 | Call this before creating the CIEL binary." 28 | ;; We save the file's content as a string. 29 | ;; We will run them with LOAD (and an input stream from the string). 30 | ;; 31 | ;; Example: 32 | ;; 33 | ;; (load (make-string-input-stream (str:from-file "src/scripts/simpleHTTPserver.lisp"))) 34 | (loop for file in (uiop:directory-files "src/scripts/") 35 | if (equal "lisp" (pathname-type file)) 36 | do (format t "~t scripts: registering ~a~&" (pathname-name file)) 37 | (setf (gethash (pathname-name file) *scripts*) 38 | (str:from-file file)))) 39 | 40 | (defun run-script (name) 41 | "If NAME is registered in *SCRIPTS*, run this script." 42 | (bind (((:values content exists) (gethash name *scripts*))) 43 | (cond 44 | ((and exists (str:blankp content) 45 | (format *error-output* "uh the script ~s has no content?~&" name))) 46 | ((not exists) 47 | (format *error-output* "The script ~s was not found.~&" name)) 48 | (t 49 | ;; Run it! 50 | ;; We first add a symbol in the feature list, so a script nows when it is being executed. 51 | (push :ciel ciel-user::*features*) 52 | ;; We ignore the shebang line, if there is one. 53 | ;; We can call scripts either with ciel -s or with ./script 54 | (load (maybe-ignore-shebang 55 | (make-string-input-stream content))))))) 56 | 57 | (defun top-level/command () 58 | "Creates and returns the top-level command" 59 | (clingon:make-command 60 | :name "ciel" 61 | :description "CIEL Is an Extended Lisp. It's Common Lisp, batteries included." 62 | :version *ciel-version* 63 | :license "todo" 64 | :authors '("vindarel ") 65 | :usage (format nil "accepts optional command-line arguments.~% 66 | ~t~tWith no arguments, run the CIEL readline REPL.~% 67 | ~t~tWith a file as argument, run it as a script.~% 68 | ~t~tWith --eval / -e
, eval a Lisp form.~% 69 | ~t~tWith --script / -s 409 | 410 | which can be: 411 | 412 | ~~~javascript 413 | // ciel.js 414 | alert("hello CIEL!"); 415 | ~~~ 416 | 417 | Example output: 418 | 419 | ``` 420 | $ ciel -s simpleHTTPserver 4242 421 | Serving files on port 4242… 422 | 423 | ⤷ http://127.0.0.1:4242 424 | 425 | [click on the index.html file] 426 | 427 | 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" 428 | ``` 429 | 430 | ### Quicksearch 431 | 432 | Search for Lisp libraries on Quicklisp, Cliki and Github. 433 | 434 | see `src/scripts/quicksearch.lisp`. 435 | 436 | ```lisp 437 | $ ciel -s quicksearch color 438 | 439 | SEARCH-RESULTS: "color" 440 | ======================= 441 | 442 | Quicklisp 443 | --------- 444 | cl-colors 445 | /home/vince/quicklisp/dists/quicklisp/software/cl-colors-20180328-git/ 446 | http://beta.quicklisp.org/archive/cl-colors/2018-03-28/cl-colors-20180328-git.tgz 447 | http://quickdocs.org/cl-colors/ 448 | […] 449 | Cliki 450 | ----- 451 | colorize 452 | http://www.cliki.net/colorize 453 | Colorize is an Application for colorizing chunks of Common Lisp, Scheme, 454 | Elisp, C, C++, or Java code 455 | […] 456 | GitHub 457 | ------ 458 | colorize 459 | https://github.com/kingcons/colorize 460 | A Syntax Highlighting library 461 | cl-colors 462 | https://github.com/tpapp/cl-colors 463 | Simple color library for Common Lisp 464 | […] 465 | ``` 466 | 467 | ### API Pointer 468 | 469 | Call a JSON API and access nested data with a JSON pointer: 470 | 471 | ciel -s apipointer URL "/json/pointer" 472 | 473 | Example: 474 | 475 | $ ciel -s apipointer https://fakestoreapi.com/products\?limit\=3 "/0/rating/rate" 476 | 3.9 477 | 478 | We welcome more capable, expanded scripts! 479 | 480 | --- 481 | 482 | Now, let us iron out the details ;) 483 | 484 | ### Simple web app with routes 485 | 486 | See [`scr/scripts/webapp.lisp`](https://github.com/ciel-lang/CIEL/blob/master/src/scripts/webapp.lisp) for inspiration. 487 | 488 | This creates one route on `/` with an optional `name` parameter. Go to `localhost:4567/?name=you` and see. 489 | 490 | ```lisp 491 | #!/usr/bin/env ciel 492 | ;;; 493 | ;;; Run with: 494 | ;;; $ ./webapp.lisp 495 | ;;; 496 | 497 | (in-package :ciel-user) 498 | 499 | (routes:defroute route-root "/" (&get name) 500 | (format nil "Hello ~a!" (or name (os:getenv "USER") "lisper"))) 501 | 502 | (defvar *server* nil) 503 | 504 | (defun start-webapp () 505 | (setf *server* (make-instance 'routes:easy-routes-acceptor :port 4567)) 506 | (hunchentoot:start *server*)) 507 | 508 | (defun stop-webapp () 509 | (hunchentoot:stop *server*)) 510 | 511 | #+ciel 512 | (progn 513 | (start-webapp) 514 | (format t "~&App started on localhost:4567…~&") 515 | (sleep most-positive-fixnum)) 516 | ``` 517 | 518 | At this point you'll certainly want to live-reload your changes. 519 | 520 | ### Auto-reload 521 | 522 | In this snippet: 523 | [`webapp-notify.lisp`](https://github.com/ciel-lang/CIEL/blob/master/src/scripts/webapp-notify.lisp), 524 | we use the [file-notify](https://github.com/shinmera/file-notify) 525 | library (shipped in CIEL) to watch write changes to our lisp file, and load 526 | it again. 527 | 528 | This allows you to have a dumb "live reload" workflow with a simple editor and a terminal. 529 | 530 | > 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… 531 | 532 | > INFO: you need `inotify` on Linux and `fsevent` on MacOS. 533 | 534 | ~~~lisp 535 | (defun simple-auto-reload () 536 | (notify:watch "webapp.lisp") 537 | (notify:with-events (file change :timeout T) 538 | ;; Print the available list of events: 539 | ;; (print (list file change)) 540 | (when (equal change :close-write) 541 | (format! t "~%~%Reloading ~a…~&" file) 542 | (handler-case 543 | (ciel::load-without-shebang "webapp.lisp") 544 | (reader-error () 545 | ;; Catch some READ errors, such as parenthesis not closed, etc. 546 | (format! t "~%~%read error, waiting for change…~&")))))) 547 | 548 | #+ciel 549 | (unless *server* 550 | (start-webapp) 551 | (format t "~&App started on localhost:4567…~&") 552 | (simple-auto-reload) 553 | (sleep most-positive-fixnum)) 554 | ~~~ 555 | 556 | ## Misc 557 | 558 | ### Load your scripts in the REPL 559 | 560 | Calling your scripts from the shell is pretty cool, what if you could 561 | *also* have them available at your fingertips in a Lisp REPL? 562 | 563 | TLDR; 564 | 565 | ```lisp 566 | ;; in ~/.cielrc 567 | (ciel::load-without-shebang "~/path/to/yourscript.lisp") 568 | ``` 569 | 570 | 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). 571 | 572 | Y'know, sometimes you live longer in a Lisp REPL than in a shell 573 | without noticing. Or simply, manipulating real objects in a text 574 | buffer can be more practical than copy-pasting text in a rigid 575 | terminal (even though Emacs' 576 | [vterm](https://github.com/akermu/emacs-libvterm) is an excellent improvement too). 577 | 578 | > 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. 579 | 580 | ### Searching files 581 | 582 | We use the `file-finder` library 583 | ([`file-finder`](https://github.com/lisp-maintainers/file-finder/), a new library meant to 584 | supersede `find` to search for files recursively: 585 | 586 | ~~~lisp 587 | (defun find-on-directory (root params) 588 | (file-finder:finder* 589 | :root root 590 | :predicates (apply #'finder:path~ (ensure-list params)))) 591 | 592 | (find-on-directory "~/Music/" "mp3") 593 | ~~~ 594 | 595 | and this returns a list of `finder:file` objects. Get their real name as 596 | a string with `finder:path`. 597 | 598 | Of course, you can also outsource the work to Unix commands, with 599 | `cmd:cmd` (prints to standard output) or `cmd:$cmd` (returns a 600 | string): 601 | 602 | ~~~lisp 603 | (-> (cmd:$cmd "find . -iname \"*mp3\"") 604 | str:lines) 605 | 606 | ;; With find alternative fd: 607 | ;; https://github.com/sharkdp/fd 608 | ;; apt install fd-find 609 | (-> (cmd:$cmd "fdfind mp3") 610 | str:lines) 611 | 612 | ;; Play music: 613 | (cmd:cmd "fdfind mp3 -X mpv") 614 | ~~~ 615 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 |

CIEL

4 |

Common Lisp, batteries included.

5 |

6 | 7 |

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

14 | 15 |

16 | 17 | # CIEL Is an Extended Lisp 18 | 19 | STATUS: ~~highly~~ WIP, the API WILL change, but it is usable. 20 | 21 | I am dogfooding it in public and private projects. 22 | 23 | 24 | ## What is this ? 25 | 26 | CIEL is a ready-to-use collection of libraries. 27 | 28 | It's Common Lisp, batteries included. 29 | 30 | It comes in 3 forms: 31 | 32 | - a binary, to run CIEL **scripts**: fast start-up times, standalone image, built-in utilities. 33 | - a simple full-featured **REPL** for the terminal. 34 | - a **Lisp library** and a **core image**. 35 | 36 | Questions, doubts? See the [FAQ](docs/FAQ.md). 37 | 38 | NEW: we now have a Docker file. 39 | 40 | ```lisp 41 | #!/usr/bin/env ciel 42 | 43 | (-> "https://fakestoreapi.com/products?limit=5" 44 | http:get 45 | json:read-json 46 | (elt 0) 47 | (access "title")) 48 | ``` 49 | 50 | ```bash 51 | $ chmod +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 x86-64 | 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 | Implementations support: 175 | 176 | - CIEL is primarily developed and tested with SBCL. 177 | - it was reported (thanks fosskers) to compile on CCL. 178 | - ECL, ABCL and Allegro got issues. 179 | - LispWorks: cannot test due to the free version limitations. You're welcome to offer me a licence :) 180 | 181 | 182 | ## Dependencies 183 | 184 | ### System dependencies 185 | 186 | You will probably need the following system dependencies (names for a 187 | Debian Bullseye system): 188 | 189 | zlib1g-dev # from deploy for SBCL < 2.2.6 190 | 191 | If your SBCL version is >= 2.2.6 you might want to use the more 192 | performant `libzstd-dev` library instead of `zlib1g-dev`. 193 | 194 | libzstd-dev # from deploy for SBCL >= 2.2.6 195 | 196 | On Linux: 197 | 198 | inotify-tools 199 | 200 | On MacOS: 201 | 202 | fsevent 203 | 204 | You can run: `make debian-deps` or `make macos-deps`. 205 | 206 | 207 | ### ASDF >= 3.3.4 (local-nicknames) 208 | 209 | ASDF is the de-facto system definition facility of Common Lisp, that 210 | lets you define your system's metadata (author, dependencies, sources, 211 | modules…). 212 | 213 | Please ensure that you have ASDF >= 3.3.4. It is for instance not the case with SBCL 2.2.9. 214 | 215 | Ask the version with our script: 216 | 217 | $ make check-asdf-version 218 | 219 | or yourself with`(asdf:asdf-version)` on a Lisp REPL, or with 220 | this one-liner from a terminal: 221 | 222 | $ sbcl --eval '(and (print (asdf:asdf-version)) (quit))' 223 | 224 | Here's a one-liner to update ASDF: 225 | 226 | $ mkdir ~/common-lisp/ 227 | $ ( 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 ) 228 | 229 | 230 | ### Install Quicklisp 231 | 232 | To build CIEL on your machine, you need the [Quicklisp library 233 | manager](https://quicklisp.org/beta/). Quicklisp downloads and 234 | installs a library and its dependencies on your machine. It's very 235 | slick, we can install everything from the REPL without restarting our 236 | Lisp process. It follows a "distrubution" approach, think Debian 237 | releases, where libraries are tested to load. 238 | 239 | 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). 240 | 241 | Install it: 242 | 243 | ```sh 244 | curl -O https://beta.quicklisp.org/quicklisp.lisp 245 | sbcl --load quicklisp.lisp --eval "(quicklisp-quickstart:install)" --quit 246 | sbcl --load ~/quicklisp/setup.lisp --eval "(ql:add-to-init-file)" --quit 247 | ``` 248 | 249 | It creates a `~/quicklisp/` directory. Read its installation instructions to know more. 250 | 251 | ### Install our Lisp dependencies [MANDATORY] 252 | 253 | One library that we use is not included in Quicklisp (as of 254 | <2025-02-03>), [termp](https://github.com/vindarel/termp). It is a 255 | small and trivial library, you can clone it into your 256 | ~/quicklisp/local-projects: 257 | 258 | git clone https://github.com/vindarel/termp/ ~/quicklisp/local-projects/termp 259 | 260 | For a number of other libraries we need the Quicklisp version of August, 2024, or later. 261 | 262 | For those, you should either: 263 | * ensure that your Quicklisp version is recent enough (with `(ql:dist-version "quicklisp")`) and maybe update it (with `(ql:update-dist "quicklisp")`) 264 | * clone our dependencies locally with the command below. 265 | 266 | If you need it, clone all the required dependencies into your `~/quicklisp/local-projects/` with this command: 267 | 268 | make ql-deps 269 | 270 | NB: other tools exist for this (Qlot, ocicl…), we are just not using them yet. 271 | 272 | 273 | ## How to load CIEL with Quicklisp 274 | 275 | You need the dependencies above: Quicklisp, a good ASDF version, our up-to-date Lisp dependencies. 276 | 277 | This shows you how to load CIEL and all its goodies, in order to use it in your current editor. 278 | 279 | CIEL is not on Quicklisp yet, but it is on [Ultralisp](https://ultralisp.org). 280 | 281 | So, either clone this repository: 282 | 283 | git clone https://github.com/ciel-lang/CIEL ~/quicklisp/local-projects/CIEL 284 | 285 | or install the Ultralisp distribution and pull the library from there: 286 | 287 | ~~~lisp 288 | (ql-dist:install-dist "http://dist.ultralisp.org/" :prompt nil) 289 | ~~~ 290 | 291 | Now, in both cases, you can load the `ciel.asd` file (with `asdf:load-asd` 292 | or `C-c C-k` in Slime) and quickload "ciel": 293 | 294 | ```lisp 295 | CL-USER> (ql:quickload "ciel") 296 | ``` 297 | 298 | be sure to enter the `ciel-user` package: 299 | 300 | ```lisp 301 | (in-package :ciel-user) 302 | ``` 303 | you now have access to all CIEL's packages and functions. 304 | 305 | 306 | ## How to build a CIEL binary and a core image 307 | 308 | You need the dependencies above: Quicklisp, a good ASDF version, our up-to-date Lisp dependencies. 309 | 310 | To build CIEL's binary, use: 311 | 312 | $ make build 313 | 314 | This creates a `ciel` binary in the current directory. 315 | 316 | To create a Lisp image: 317 | 318 | $ make image 319 | # or 320 | $ sbcl --load build-image.lisp 321 | 322 | This creates the `ciel-core` Lisp image. 323 | 324 | Unlike a binary, we can not distribute core images. It is dependent on the machine it was built on. 325 | 326 | The way we use a core image is to load it at startup like this: 327 | 328 | sbcl --core ciel-core --eval '(in-package :ciel-user)' 329 | 330 | It loads fast and you have all CIEL libraries and goodies at your disposal. 331 | 332 | Then you have to configure your editor, like Slime, to have the choice of the Lisp image to 333 | start. See below. 334 | 335 | 336 | ## Docker 337 | 338 | We have a Dockerfile. It uses SBCL 2.3.8. It works on Apple Silicon. 339 | 340 | Build your CIEL image: 341 | 342 | docker build -t ciel . 343 | 344 | The executable is built in `/usr/local/bin/ciel` of the Docker image. 345 | 346 | Get a CIEL REPL: 347 | 348 | docker run --rm -it ciel /usr/local/bin/ciel 349 | 350 | Run a script on your filesystem: 351 | 352 | docker run --rm -it ciel /usr/local/bin/ciel path/to/your/lisp/script.lisp 353 | 354 | Run a built-in script: 355 | 356 | docker run --rm -it ciel /usr/local/bin/ciel -s simpleHTTPserver 357 | 358 | So, save you some typing with a shell alias: 359 | 360 | alias ciel="sudo docker run --rm -it ciel /usr/local/bin/ciel" 361 | 362 | 363 | # Usage 364 | 365 | ## Scripting 366 | 367 | > [!NOTE] 368 | > this is brand new! Expect limitations and changes. 369 | 370 | Get the `ciel` binary and call it with your .lisp script: 371 | 372 | $ ciel script.lisp 373 | 374 | Use the `#!/usr/bin/env ciel` shebang line to directly call your files: 375 | 376 | $ ./script 377 | 378 | Call built-in scripts: 379 | 380 | $ ciel -s simpleHTTPserver 9000 381 | 382 | See available built-in scripts with `--scripts`. 383 | 384 | See [the scripts documentation](https://ciel-lang.github.io/CIEL/#/scripting). 385 | 386 | ## Terminal REPL 387 | 388 | CIEL ships a terminal REPL for the terminal which is more user friendly than the default SBCL one: 389 | 390 | - it has readline capabilities, meaning that the arrow keys work by 391 | default (woohoo!) and there is a persistent history, like in any 392 | shell. 393 | - it has **multiline input**. 394 | - it has **TAB completion**. 395 | - including for files (after a bracket) and binaries in the PATH. 396 | - it handles errors gracefully: you are not dropped into the debugger 397 | and its sub-REPL, you simply see the error message. 398 | - it has optional **syntax highlighting**. 399 | - it has a **shell pass-through**: try `!ls`. 400 | - it runs **interactive commands**: try `!htop`, `!vim test.lisp`, `!emacs -nw test.lisp` or `!env FOO=BAR sudo -i top`. 401 | - it has **documentation lookup** shorthands: use `:doc symbol` or `?` 402 | after a symbol to get its documentation: `ciel-user> (dict ?`. 403 | - it has **developer friendly** macros: use `(printv code)` for an 404 | annotated trace output. 405 | - it has an optional **lisp critic** that scans the code you enter at 406 | the REPL for instances of bad practices. 407 | - and it defines some more helper commands. 408 | - it works on Slime (to a certain extent) 409 | 410 | The CIEL terminal REPL loads the `~/.cielrc` init file at start-up if present. Don't load it with `--no-userinit`. 411 | 412 | See more in [*the documentation*](https://ciel-lang.github.io/CIEL/#/repl). 413 | 414 | > [!NOTE] 415 | > 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 416 | 417 | 418 | Run `ciel` with no arguments: 419 | 420 | ```bash 421 | $ ciel 422 | 423 | CIEL's REPL version 0.1.5 424 | Read more on packages with readme or summary. For example: (summary :str) 425 | Special commands: 426 | %help => Prints this general help message 427 | %doc => Print the available documentation for this symbol. 428 | %? => Gets help on a symbol : :? str 429 | %w => Writes the current session to a file 430 | %d => Dumps the disassembly of a symbol 431 | %t => Prints the type of a expression 432 | %q => Ends the session. 433 | %lisp-critic => Enable or disable the lisp critic. He critizes the code you type before compiling it. 434 | %edit => Edit a file with EDITOR and evaluate it. 435 | Press CTRL-D or type :q to exit 436 | 437 | ciel-user> 438 | ``` 439 | 440 | It is freely based on [sbcli](https://github.com/hellerve/sbcli). 441 | 442 | 443 | ## CIEL as a library: "use" :ciel in your Lisp systems 444 | 445 | You can install and `quickload` CIEL like any other Common Lisp library. 446 | 447 | To use it in your project, create a package and "use" `ciel` in addition 448 | of `cl`: 449 | 450 | ```lisp 451 | (defpackage yourpackage 452 | (:use :cl :ciel)) 453 | ``` 454 | 455 | You can also use `generic-ciel`, based on 456 | [generic-cl](https://github.com/alex-gutev/generic-cl/) (warn: 457 | generic-ciel is less tested at the moment). 458 | 459 | ~~~lisp 460 | (defpackage yourpackage 461 | (:use :cl :generic-ciel)) 462 | ~~~ 463 | 464 | generic-cl allows us to define our `+` or `equalp` methods for our own 465 | objects (and more). 466 | 467 | ## Core image: configure your editor 468 | 469 | The advantage of a core image is that it loads instantly, faster than 470 | a `(ql:quickload "ciel")`. We'll ask our editor to start SBCL with our 471 | CIEL core image. 472 | 473 | We'll configure SLIME for [multiple Lisps](https://common-lisp.net/project/slime/doc/html/Multiple-Lisps.html#Multiple-Lisps). 474 | 475 | You need to add this to your Emacs init file: 476 | 477 | ```lisp 478 | (setq slime-lisp-implementations 479 | `((sbcl ("sbcl" "--dynamic-space-size" "2000")) ;; default. Adapt if needed. 480 | (ciel-sbcl ("sbcl" "--core" "/path/to/ciel/ciel-core" "--eval" "(in-package :ciel-user)")))) 481 | (setq slime-default-lisp 'ciel-sbcl) 482 | ``` 483 | 484 | and start a Lisp process with `M-x slime`. 485 | 486 | If you didn't set `ciel-sbcl` as the default, then start the Lisp 487 | process with `M-- M-x slime` (alt-minus prefix), and choose 488 | `ciel-sbcl`. You can start more than one Lisp process from SLIME. 489 | 490 | The Lisp process should start instantly, as fast as the default SBCL, 491 | you won't wait for the quicklisp libraries to load. 492 | 493 | 494 | # Libraries 495 | 496 | We import, use and document libraries to fill various use cases: generic 497 | access to data structures, functional data structures, string 498 | manipulation, JSON, database access, web, URI handling, iteration 499 | helpers, type checking helpers, syntax extensions, developer utilities, 500 | etc. 501 | 502 | See the documentation. 503 | 504 | To see the full list of dependencies, see the `ciel.asd` project 505 | definition or this [dependencies list](docs/dependencies.md). 506 | 507 | # Language extensions 508 | 509 | We provide arrow macros, easy type declaratons in the function lambda 510 | list, macros for exhaustiveness type checking, pattern matching, etc. 511 | 512 | See [the documentation](https://ciel-lang.github.io/CIEL/#/language-extensions). 513 | 514 | # Final words 515 | 516 | That was your life in CL: 517 | 518 |

519 | 520 | and now: 521 | 522 |

523 | 524 | # Misc: how to generate the documentation 525 | 526 | See `src/ciel.lisp` and run `(generate-dependencies-page-reference)`. 527 | 528 | # Contributors 529 | 530 | Special big thanks to @cinerion, [@themarcelor](https://github.com/themarcelor) and everyone who helped (@agam, @patrixl, @bo-tato…). 531 | 532 | 533 | # Lisp?! 534 | 535 | - [awesome-cl](https://github.com/CodyReichert/awesome-cl) 536 | - [the Common Lisp Cookbook](https://lispcookbook.github.io/cl-cookbook/) 537 | - [editor support](https://lispcookbook.github.io/cl-cookbook/editor-support.html) (Emacs, Vim, VSCode, Atom, Pulsar, Jetbrains, Sublime, Jupyter notebooks…) 538 | - [Lisp companies](https://github.com/azzamsa/awesome-lisp-companies/) 539 | - blog posts: 540 | - [these years in Lisp: 2022 in review](https://lisp-journey.gitlab.io/blog/these-years-in-common-lisp-2022-in-review/) 541 | - [Python VS Common Lisp, workflow and ecosystem](https://lisp-journey.gitlab.io/pythonvslisp/) 542 | - [A road to Common Lisp](https://stevelosh.com/blog/2018/08/a-road-to-common-lisp/) 543 | - 🎥 [Youtube showcases](https://www.youtube.com/@vindarel): 544 | - [Debugging Lisp: fix and resume a program from any point in the stack](https://www.youtube.com/watch?v=jBBS4FeY7XM) 545 | - [How to call a REST API in Common Lisp: HTTP requests, JSON parsing, CLI arguments, binaries](https://www.youtube.com/watch?v=TAtwcBh1QLg) 546 | - 🎥 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. 547 | -------------------------------------------------------------------------------- /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 | ### More commonly-named functions 120 | 121 | We currently only add 2 aliases: `nappend`, the "non-consing" 122 | (destructive, memory-efficient) counterpart of `append`, also known as 123 | `nconc`, and `nremove` as an alias for `delete`. 124 | 125 | 126 | ### String manipulation (str) 127 | 128 | Available with the `str` prefix. 129 | 130 | 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`… 131 | 132 | See and https://lispcookbook.github.io/cl-cookbook/strings.html 133 | 134 | ## Arrow macros 135 | 136 | We provide the Clojure-like arrow macros and "diamond wands" from the [arrow-macros](https://github.com/hipeta/arrow-macros) library. 137 | 138 | 139 | #### **CIEL** 140 | 141 | ```lisp 142 | ;; -> inserts the previous value as its first argument: 143 | (-> " hello macros " 144 | str:upcase 145 | str:words) ; => ("HELLO" "MACROS") 146 | 147 | ;; ->> inserts it as its second argument: 148 | (->> " hello macros " 149 | str:upcase 150 | str:words 151 | (mapcar #'length)) ; => (5 6) 152 | 153 | 154 | ;; use as-> to be flexible on the position of the argument: 155 | (as-> 4 x 156 | (1+ x) 157 | (+ x x)) ; => 10 158 | ``` 159 | 160 | #### **CL** 161 | 162 | ```lisp 163 | ;; In pure CL, just wrap function calls… 164 | (mapcar #'length (str:words (str:upcase " hello world "))) 165 | 166 | ;; … or use let* and intermediate variables: 167 | (let* ((var "hello macros") 168 | (upcased (str:upcase var)) 169 | (words (str:words upcased))) 170 | words) 171 | ``` 172 | 173 | 174 | 175 | And there is more. All the available macros are: 176 | 177 | ``` txt 178 | :-> 179 | :->> 180 | :some-> 181 | :some->> 182 | :as-> 183 | :cond-> 184 | :cond->> 185 | :-<> 186 | :-<>> 187 | :some-<> 188 | :some-<>> 189 | ``` 190 | 191 | ## Functions 192 | 193 | ### Partial application 194 | 195 | We import Serapeum's `partial` and Alexandria's `rcurry`. They allow 196 | partial application of functions. 197 | 198 | `partial` is similar to `alexandria:curry` but is always inlined. 199 | 200 | We import Serapeum's `juxt`. 201 | 202 | You can check [Serapeum's function helpers](https://github.com/ruricolist/serapeum/blob/master/REFERENCE.md#functions), 203 | the non-imported ones are only a `serapeum` package prefix away in CIEL if you need 204 | them. Other noticeable functions: `distinct`, `once`, `throttle`, `trampoline`, `do-nothing`… 205 | 206 | ### Memoization 207 | 208 | We import `defcached` from 209 | [function-cache](https://github.com/AccelerationNet/function-cache), a 210 | library that provides extensible caching/memoization. 211 | 212 | For example: 213 | 214 | ```lisp 215 | (defcached (foo :timeout 10) (arg) 216 | (sleep 3) 217 | arg) 218 | ``` 219 | 220 | Run `(foo 1)`, it sleeps 3 seconds and returns 1. Run `(foo 1)` again 221 | and it returns 1 immediately. Its result (for this argument) is cached 222 | for 10 seconds. 223 | 224 | 225 | 226 | ## Bind, more destructuring in `let` (metabang-bind) 227 | 228 | 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)). 229 | 230 | The idiomatic way to declare local variables is `let` (and `let*`), 231 | the way to declare local functions is `flet` (and `labels`). Use them 232 | if it is fine for you. 233 | 234 | However, if you ever noticed you write convoluted `let` forms, adding 235 | list destructuring, multiple values or slot access into the mix, and 236 | if you use a `flet` *and then* a `let`, then read on. 237 | 238 | `bind` integrates more variable binding and list destructuring idioms. It has two goals. Quoting: 239 | 240 | > 1. reduce the number of nesting levels 241 | 242 | > 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. 243 | 244 | ### Bind in CIEL 245 | 246 | We import the `bind` macro. However, the package has more external 247 | symbols that we don't import, such as its error type (`bind-error`) and 248 | its extension mechanism. 249 | 250 | > Note: if you like object destructuring in general, you'll like [pattern matching](/language-extensions?id=pattern-matching). 251 | 252 | 253 | ### Bind is a replacement for `let` and `let*`. 254 | 255 | You can use `bind` in lieue of `let*`. 256 | 257 | So, its simpler form is: 258 | 259 | ~~~lisp 260 | (bind (a) 261 | (do-something a)) 262 | ~~~ 263 | 264 | `a` is initialized to `nil`. 265 | 266 | To give it a default value, use a pair, as in `(a 1)` below: 267 | 268 | ~~~lisp 269 | (bind ((a 1) 270 | (b 2)) 271 | ...) 272 | ~~~ 273 | 274 | Below, we'll use indicators for `a`, the binding on the left-hand 275 | side, so we can have a `bind` form that starts with three 276 | parenthesis. But it's OK, you know how to read them. 277 | 278 | ~~~lisp 279 | (bind (((:values a b c) (a-function))) 280 | ...) 281 | ~~~ 282 | 283 | 284 | ### Bind with multiple-values: `(:values ...)` 285 | 286 | Use `(:values ...)` in the left-hand side of the binding: 287 | 288 | ~~~lisp 289 | (bind (((:values a b) (truncate 4.5)) 290 | ... 291 | ~~~ 292 | 293 | In pure CL, you'd use `multiple-value-bind` (aka mvb for completion). 294 | 295 | 296 | ### Ignore values: the `_` placeholder 297 | 298 | As in: 299 | 300 | ~~~lisp 301 | (bind (((_ value-1 value-2) (some-function-returning-3-values))) 302 | ...) 303 | ~~~ 304 | 305 | ### Destructuring lists 306 | 307 | Use a list in the left-hand side: 308 | 309 | ~~~lisp 310 | (defun return-list (a b) (list a b)) 311 | 312 | (bind (((a b) (return-list 3 4))) 313 | (list a b)) 314 | ;; => (3 4) 315 | ~~~ 316 | 317 | You can use usual lambda parameters for more destructuring: 318 | 319 | ~~~lisp 320 | (bind ((a 2) 321 | ((b &rest args &key (c 2) &allow-other-keys) '(:a :c 5 :d 10 :e 54)) 322 | ((:values d e) (truncate 4.5))) 323 | (list a b c d e args)) 324 | ~~~ 325 | 326 | ### Bind with plists, arrays, classes, structures, regular expressions, flet and labels 327 | 328 | It's all well explained [in the documentation](https://common-lisp.net/project/metabang-bind/user-guide.html)! 329 | 330 | 331 | Conditions 332 | ---------- 333 | 334 | See 335 | 336 | From Serapeum, we import [`ignoring`](https://github.com/ruricolist/serapeum/blob/master/REFERENCE.md#ignoring-type-body-body). 337 | 338 | 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. 339 | 340 | `ignoring` forces you to specify the kind of error you want to ignore: 341 | 342 | ```lisp 343 | (ignoring parse-error 344 | ...) 345 | ``` 346 | 347 | ## Iteration 348 | 349 | See for examples, including about the good old `loop`. 350 | 351 | We import macros from [trivial-do](https://github.com/yitzchak/trivial-do/), that provides `dolist`-like macro to iterate over more structures: 352 | 353 | - `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. 354 | 355 | - `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. 356 | 357 | - `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. 358 | 359 | - `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. 360 | 361 | - `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. 362 | 363 | - `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. 364 | 365 | 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…). 366 | 367 | 368 | ## Lambda shortcut 369 | 370 | `^` is a synonym macro for `lambda`. 371 | 372 | ```lisp 373 | (^ (x) (+ x 10)) 374 | => 375 | (lambda (x) (+ x 10)) 376 | ``` 377 | 378 | ## Pythonic triple quotes docstring 379 | 380 | We can enable the syntax to use triple quotes for docstrings, and double quotes within them: 381 | 382 | 383 | 384 | #### **CIEL** 385 | ```lisp 386 | (ciel:enable-pythonic-string-syntax) 387 | 388 | (defun foo () 389 | """foo "bar".""" 390 | t) 391 | ``` 392 | 393 | #### **CL** 394 | 395 | ~~~lisp 396 | ;; Normally single quotes must be escaped. 397 | (defun foo () 398 | "foo \"bar\"." 399 | t) 400 | 401 | ;; use: 402 | (pythonic-string-reader:enable-pythonic-string-syntax) 403 | ~~~ 404 | 405 | 406 | To disable this syntax, do: 407 | 408 | ~~~lisp 409 | (ciel:disable-pythonic-string-syntax) 410 | ~~~ 411 | 412 | We use [pythonic-string-reader](https://github.com/smithzvk/pythonic-string-reader). 413 | 414 | !> This syntax conflicts with libraries that use cl-syntax to use triple quotes 415 | too, even only internally. It happens with the Jonathan library. 416 | 417 | 418 | ## Packages 419 | 420 | `defpackage` is nice and well, until you notice some shortcomings. That's why we import UIOP's `define-package`. You'll get: 421 | 422 | - less warnings when you remove an exported symbol 423 | - a `:reexport` option (as well as `:use-reexport` and `:mix-reeport`) 424 | - `:recycle` and `:mix` options. 425 | 426 | It is a drop-in replacement. 427 | 428 | Here's [uiop:define-package documentation](https://asdf.common-lisp.dev/uiop.html#UIOP_002fPACKAGE). 429 | 430 | ### Packages local nicknames 431 | 432 | CIEL defines local nicknames for other libraries. 433 | 434 | For example, `csv` is a shorter nickname for `cl-csv`. `time` is a 435 | shorter nickname for `local-time`. 436 | 437 | They are available when you are "inside" the CIEL-USER package (when you do `(in-package :ciel-user)`). 438 | 439 | If you define a new package that "uses" CIEL, you might want to also 440 | get this set of nicknames. Here's the full list: 441 | 442 | ~~~lisp 443 | (uiop:define-package myproject 444 | (:use :cl :ciel) 445 | (:local-nicknames (:/os :uiop/os) 446 | (:os :uiop/os) 447 | (:filesystem :uiop/filesystem) 448 | (:finder :file-finder) 449 | (:notify :org.shirakumo.file-notify) 450 | (:alex :alexandria) 451 | (:csv :cl-csv) 452 | (:http :dexador) 453 | (:json :shasht) 454 | (:json-pointer :cl-json-pointer/synonyms) 455 | (:time :local-time) 456 | (:routes :easy-routes)) 457 | (:documentation "My package, using CIEL and defining the same local nicknames.")) 458 | ~~~ 459 | 460 | 461 | 462 | Pattern matching 463 | ---------------- 464 | 465 | We use [Trivia](https://github.com/guicho271828/trivia/) (see 466 | [its wiki](https://github.com/guicho271828/trivia/wiki/What-is-pattern-matching%3F-Benefits%3F)), from which we import `match`. 467 | 468 | 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: 469 | 470 | ~~~lisp 471 | (match '(1 2) 472 | ((list x y) ;; <-- pattern 473 | (print x) 474 | (print y)) 475 | (_ ;; <-- failover clause 476 | :else)) 477 | ;; 1 478 | ;; 2 479 | ~~~ 480 | 481 | 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. 482 | 483 | If you wanted `y` to match the rest of the list, use `list*`: 484 | 485 | ~~~lisp 486 | (match '(1 2 3) 487 | ((list* x y) 488 | (print x) 489 | (print y)) 490 | (_ :else)) 491 | ;; 1 492 | ;; (2 3) 493 | ~~~ 494 | 495 | This could also be achieved with the `cons` pattern: 496 | 497 | ~~~lisp 498 | (match '(1 2 3) 499 | ((cons x y) 500 | (print x) 501 | (print y)) 502 | (_ :else)) 503 | ;; 1 504 | ;; (2 3) 505 | ~~~ 506 | 507 | You can of course use `_` placeholders: 508 | 509 | ~~~lisp 510 | (match '(1 2 3) 511 | ((list* x _) 512 | (print x)) 513 | (_ :else)) 514 | ;; 1 515 | ~~~ 516 | 517 | As we saw with `list` and `cons`, Trivia has patterns to match against types (vectors, alists, plists, arrays), including classes and structures. 518 | 519 | You can use [numeric patterns](https://github.com/guicho271828/trivia/wiki/Numeric-Patterns) (`=`, `<` and friends, that behave as you expect): 520 | 521 | ~~~lisp 522 | (let ((x 5)) 523 | (match x 524 | ((< 10) 525 | :lower))) 526 | ;; :LOWER 527 | ~~~ 528 | 529 | Then, you can combine them with [logic based patterns and guards](https://github.com/guicho271828/trivia/wiki/Logic-Based-Patterns). For example: 530 | 531 | ~~~lisp 532 | (match x 533 | ((or (list 1 a) 534 | (cons a 3)) 535 | a)) 536 | ~~~ 537 | 538 | guards allow to check the matches against a predicate. For example: 539 | 540 | ~~~lisp 541 | (match (list 2 5) 542 | ((guard (list x y) ; subpattern1 543 | (= 10 (* x y))) ; test-form 544 | t)) 545 | ~~~ 546 | 547 | Above we use the `list` pattern, and we verify a predicate. 548 | 549 | 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. 550 | 551 | You migth also be interested in exhaustiveness type checking explained just below. 552 | 553 | 554 | ## Regular expressions 555 | 556 | Use `ppcre`. 557 | 558 | See and 559 | 560 | 561 | Type declarations 562 | ----------------- 563 | 564 | Use the `-->` macro to gradually add type declarations. 565 | 566 | Alternatively, use `defun*`, `defgeneric*`, `defmethod*`, `defparameter*` and `defvar*` to add type declarations directly in the lambda list. 567 | 568 | These notations are not strictly equivalent though. 569 | 570 | `-->` 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 …))` 571 | 572 | 573 | 574 | #### **CIEL** 575 | 576 | ```lisp 577 | (--> mod-fixnum+ (fixnum fixnum) fixnum) 578 | (defun mod-fixnum+ (x y) ...) 579 | 580 | ;; --> comes straight from serapeum:-> 581 | ``` 582 | 583 | #### **CL** 584 | 585 | ```lisp 586 | (declaim (ftype (function (fixnum fixnum) fixnum) mod-fixnum+)) 587 | (defun mod-fixnum+ (x y) ...) 588 | ``` 589 | 590 | 591 | 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: 592 | 593 | 594 | 595 | #### **CIEL** 596 | 597 | ```lisp 598 | (defun* foo ((a integer)) 599 | (:returns integer) 600 | (* 10 a)) 601 | ``` 602 | 603 | #### **CL** 604 | 605 | ```lisp 606 | ;; In pure CL, type the functions at its boundaries with ftype. 607 | ;; It is a bit verbose, but it has the advantage, being not tied to defun, 608 | ;; that we can easily refine types during development. 609 | (declaim (ftype (function (integer) integer) foo)) 610 | ;; ^^ inputs ^^ output [optional] ^^ function name 611 | 612 | ;; defstar adds the internal "declare" and "the…". 613 | ;; "the" is a promise made to the compiler, that will optimize things out. 614 | (defun foo (a) 615 | (declare (type integer a)) 616 | (the integer (* 10 a))) 617 | 618 | ``` 619 | 620 | 621 | A type declaration for a parameter: 622 | 623 | 624 | 625 | #### **CIEL** 626 | 627 | ```lisp 628 | (defparameter* (*file-position* (integer 0)) 0) 629 | ``` 630 | 631 | #### **CL** 632 | 633 | ```lisp 634 | 635 | ;; Normal defparameter: 636 | (defparameter *file-position* 0) 637 | 638 | ;; Assigning a bad value works: 639 | (setf *file-position* "8") 640 | ;; "8" 641 | 642 | ;; We add a type declaration: 643 | (declaim (type (integer 0) *file-position*)) 644 | 645 | ;; and now: 646 | (setf *file-position* "8") 647 | ;; 648 | ;; Value of #1="8" in (THE INTEGER "8") is #1#, not a INTEGER. 649 | ;; [Condition of type SIMPLE-TYPE-ERROR] 650 | ;; 651 | ;; we get a type error. 652 | ``` 653 | 654 | 655 | We can use any type specifier: 656 | 657 | ~~~lisp 658 | (deftype natural () '(real 0)) 659 | (defun* sum ((a natural) (b natural)) 660 | (:returns natural) 661 | (+ a b)) 662 | ~~~ 663 | 664 | Now, we get type errors at compile time: 665 | 666 | ~~~lisp 667 | (foo "3") 668 | ;; => 669 | The value 670 | "3" 671 | is not of type 672 | INTEGER 673 | when binding A 674 | [Condition of type TYPE-ERROR] 675 | 676 | Restarts: […] 677 | 678 | Backtrace: 679 | 0: (FOO "3") [external] 680 | 1: (SB-INT:SIMPLE-EVAL-IN-LEXENV (FOO "a") #) 681 | 2: (EVAL (FOO "3")) 682 | ~~~ 683 | 684 | and we get compile-time warnings on type mismatches (but to be honest on simple cases like this SBCL is already quite good): 685 | 686 | ~~~lisp 687 | (defun* bad-foo ((a integer)) 688 | (:returns integer) 689 | (format t "~a" (* 10 a))) 690 | ; 691 | ; in: DEFUN* BAD-FOO 692 | ; (THE INTEGER (FORMAT T "~a" (* 10 CIEL::A))) 693 | ; 694 | ; caught WARNING: 695 | ; Constant NIL conflicts with its asserted type INTEGER. 696 | ; See also: 697 | ; The SBCL Manual, Node "Handling of Types" 698 | ; 699 | ; compilation unit finished 700 | ; caught 1 WARNING condition 701 | BAD-FOO 702 | ~~~ 703 | 704 | We could add extra protection and a `check-type`, evaluated at runtime. 705 | Defstar can add them automatically if `defstar:*check-argument-types-explicitly?*` is non-nil. 706 | 707 | 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. 708 | 709 | 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). 710 | 711 | > 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. 712 | 713 | > Note: there is no "undeclaim" form :] You can unintern a symbol and re-define it. 714 | 715 | See also: 716 | 717 | - [declarations](http://clhs.lisp.se/Body/03_c.htm) in the Common Lisp Hyper Spec. 718 | - https://lispcookbook.github.io/cl-cookbook/type.html 719 | - the article [Static type checking in SBCL](https://medium.com/@MartinCracauer/static-type-checking-in-the-programmable-programming-language-lisp-79bb79eb068a), by Martin Cracauer 720 | - 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. 721 | 722 | 723 | Type checking: exhaustiveness type checking 724 | ------------------------------------------- 725 | 726 | Write a "case" and get a compile-time warning if you don't cover all cases. 727 | 728 | From Serapeum, we import: 729 | 730 | ```lisp 731 | :etypecase-of 732 | :ctypecase-of 733 | :typecase-of 734 | :case-of 735 | :ccase-of 736 | ``` 737 | 738 | `etypecase-of` allows to do [compile-time exhaustiveness type checking](https://github.com/ruricolist/serapeum#compile-time-exhaustiveness-checking%0A). 739 | 740 | ### Example with enums 741 | 742 | We may call a type defined using member an enumeration. Take an enumeration like this: 743 | 744 | ```lisp 745 | (deftype switch-state () 746 | '(member :on :off :stuck :broken)) 747 | ``` 748 | 749 | Now we can use `ecase-of` to take all the states of the switch into account. 750 | 751 | ```lisp 752 | (defun flick (switch) 753 | (ecase-of switch-state (state switch) 754 | (:on (switch-off switch)) 755 | (:off (switch-on switch)))) 756 | => Warning 757 | ``` 758 | 759 | ```lisp 760 | (defun flick (switch) 761 | (ecase-of switch-state (state switch) 762 | (:on (switch-off switch)) 763 | (:off (switch-on switch)) 764 | ((:stuck :broken) (error "Sorry, can't flick ~a" switch)))) 765 | => No warning 766 | ``` 767 | 768 | ### Example with union types 769 | 770 | ```lisp 771 | (defun negative-integer? (n) 772 | (etypecase-of t n 773 | ((not integer) nil) 774 | ((integer * -1) t) 775 | ((integer 1 *) nil))) 776 | => Warning 777 | 778 | (defun negative-integer? (n) 779 | (etypecase-of t n 780 | ((not integer) nil) 781 | ((integer * -1) t) 782 | ((integer 1 *) nil) 783 | ((integer 0) nil))) 784 | => No warning 785 | ``` 786 | 787 | See [Serapeum's reference](https://github.com/ruricolist/serapeum/blob/master/REFERENCE.md#control-flow). 788 | 789 | ## trivial-types: more type definitions 790 | 791 | From [trivial-types](https://github.com/m2ym/trivial-types), we import 792 | 793 | - `association-list-p` 794 | - `type-expand` 795 | - `string-designator` 796 | - `property-list` 797 | - `tuple` 798 | - `association-list` 799 | - `character-designator` 800 | - `property-list-p` 801 | - `file-associated-stream-p` 802 | - `type-specifier-p` 803 | - `list-designator` 804 | - `package-designator` 805 | - `tuplep` 806 | - `non-nil` 807 | - `file-associated-stream` 808 | - `stream-designator` 809 | - `function-designator` 810 | - `file-position-designator` 811 | - `pathname-designator` 812 | --------------------------------------------------------------------------------