├── .github └── FUNDING.yml ├── .gitignore ├── AppendixA.jpg ├── AppendixB.jpg ├── AppendixC.jpg ├── CHANGELOG ├── CONTRIBUTING.md ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── Makefile ├── README.md ├── STYLEGUIDE.md ├── _config.yml ├── _includes ├── code │ ├── w32-appendix-a.lisp │ ├── w32-appendix-b.lisp │ ├── w32-appendix-c.lisp │ ├── w32-appendix-d.c │ └── w32-appendix-d.lisp └── footer.html ├── _layouts ├── chapter.html └── default.html ├── arrays.md ├── assets ├── atom-slime.png ├── cl-flamegraph.png ├── cl-logo-blue.png ├── cl-repl.png ├── clack-errors.png ├── commonlisp-vscode-alive.png ├── commonlisp-vscode.png ├── coverage.png ├── editor-sublime.png ├── emacs-company-elisp.png ├── emacs-helpful.png ├── emacs-teaser.png ├── emacs-which-key-minibuffer.png ├── geany.png ├── github.css ├── gui │ ├── gtk3-hello-buttons.png │ ├── ltk-on-macos.png │ ├── mediaplayer-nodgui-arc.png │ ├── nodgui-feet2meters-yaru.png │ ├── nodgui-treeview-yaru.png │ ├── nuklear-test.png │ ├── nuklear.png │ └── qtools-intro.png ├── highlight-lisp.js ├── img-ci-build.png ├── iup-demo.png ├── jetbrains-slt.png ├── jquery-3.2.1.min.js ├── jquery.toc │ ├── jquery.toc.js │ └── jquery.toc.min.js ├── jupyterpreview.png ├── lem-sdl2.png ├── lem-terminal.png ├── lem1.png ├── lispworks-graphical-inspector.png ├── lispworks │ ├── class-browser.png │ ├── function-call-browser.png │ ├── process-browser.png │ ├── stepper.gif │ ├── toolbar-debugger.png │ └── two-sided-view.png ├── log4cl.png ├── portacle.png ├── prove-report.png ├── slime-inspector.png ├── slime-menu.png ├── slime.png ├── slimv.jpg ├── sockets-lisp-chat.gif ├── style.scss ├── toggle-toc.js ├── vscode-gifs │ ├── attach-repl.gif │ ├── compile.gif │ ├── debug-abort.gif │ ├── debug-fix.gif │ ├── disassemble.gif │ ├── docker-connect.gif │ ├── eval.gif │ ├── in-line-eval.gif │ ├── macro-expand.gif │ └── skeleton.gif ├── weblocks-quickstart-check-task.gif ├── youtube-little-bits-lisp.jpg └── zenity-prompt.png ├── cl21.md ├── clos.md ├── contributors.md ├── dandelion.png ├── data-structures.md ├── databases.md ├── dates_and_times.md ├── debugging.md ├── drafts └── defmodel.lisp.md ├── dynamic-libraries.md ├── editor-support.md ├── emacs-ide.md ├── equality.md ├── error_handling.md ├── ffi.md ├── files.md ├── fix-epub-links.sed ├── foreword.md ├── functions.md ├── getting-started.md ├── gui.md ├── index.md ├── io.md ├── iteration.md ├── license.md ├── lispworks.md ├── macros.md ├── make-cookbook.lisp ├── manifest.scm ├── metadata.txt ├── misc.md ├── numbers.md ├── numbertower.png ├── orly-cover.png ├── os.md ├── packages.md ├── pattern_matching.md ├── performance.md ├── process.md ├── regexp.md ├── scripting.md ├── simple-restarts.png ├── sockets.md ├── strings.md ├── systems.md ├── testing.md ├── trace-dialog.png ├── type.md ├── vscode-alive.md ├── web-scraping.md ├── web.md ├── websockets.md ├── win32.md └── windows.md /.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 | # and again, up to 4 custom links: 7 | # custom: [link1, …, link4] 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _site 2 | vendor 3 | .bundle 4 | -------------------------------------------------------------------------------- /AppendixA.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/AppendixA.jpg -------------------------------------------------------------------------------- /AppendixB.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/AppendixB.jpg -------------------------------------------------------------------------------- /AppendixC.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/AppendixC.jpg -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | After August 2nd, 2015, this repository has lived in Git. Please see `git log` for further details. 2 | 3 | 2019-09-18 4 | Files: added a "traverse (walk) directories" section. 5 | 6 | 2019-09-10 7 | Web: added easy-routes, more capable than default Hunchentoot routing. 8 | 9 | [changelog to update] 10 | 11 | 2007-11-01 12 | Changed Alberto's address 13 | Updated link section 14 | 15 | 2007-02-01 16 | Added loop tutorial (skeptomai) 17 | 18 | 2007-01-28 19 | updated strings, clisp location, minor spelling corrections 20 | (skeptomai) 21 | 22 | 2007-01-06 23 | Added "Testing" section (Austin King) 24 | 25 | 2005-08-03 26 | Corrected the Emacs for Windows URL in windows.html and thus fixed/closed 27 | the bug related to it. 28 | 29 | 2005-08-02 30 | Added the replace-all function definition and one example for its 31 | usage to strings.html. 32 | Clarification about idle process in process.html. 33 | Updated index.html and added Emre Sevinc to list of contributors. 34 | 35 | 2005-04-17 36 | Updated copyright notice and some links. 37 | 38 | 2003-12-07 39 | Added "Fast Bulk I/O" entry by Zach Beane 40 | Added Zach Beane to list of contributors 41 | Updated links to CLHS (now at www.lispworks.com) 42 | 43 | 2003-11-01 44 | Updated windows.html, and .emacs by Bill Clementson to add support 45 | for Corman Lisp v2.5. 46 | Added "Using Emacs as a Lisp IDE" material. 47 | 48 | 2003-10-21 49 | Added CLOS tutorial. 50 | 51 | 2003-10-09 52 | Updated .emacs by Bill Clementson to support re-indentation of 53 | top-level sexp/defun. 54 | 55 | 2003-10-04 56 | Updated index.html, and added emacs-ide.html by Bill 57 | Clementson. Added place-holder page for the "Using Emacs as a Lisp 58 | IDE" page that I will post after my ILC2003 presentation. 59 | 60 | 2003-10-03 61 | Updated windows.html, and .emacs by Bill Clementson - added back 62 | in Corman Lisp support (nobody else seemed to be having problems 63 | with it but me) and added support for the info version of the ANSI 64 | CL documentation. 65 | 66 | 2003-09-30 67 | Updated windows.html, and .emacs by Bill Clementson - removed 68 | Corman Lisp support since haven't been able to get Corman releases 69 | after 1.5 to work in either inferior lisp mode or ilisp. 70 | 71 | 2003-09-25 72 | Updated .emacs by Bill Clementson - allow evals in info. 73 | 74 | 2003-09-15 75 | Updated windows.html, and .emacs by Bill Clementson - minor key 76 | mods. 77 | 78 | 2003-09-14 79 | Updated windows.html, and .emacs by Bill Clementson - minor mods. 80 | 81 | 2003-09-03 82 | Updated windows.html, and .emacs by Bill Clementson to support 83 | clisp 2.31. 84 | 85 | 2003-09-02 86 | Updated windows.html and index.html by Bill Clementson-minor edits and 87 | update of cua-emul.el download site. 88 | 89 | 2003-09-01 90 | Updated windows.html, index.html, and .emacs by Bill Clementson to 91 | provide inital support for Mac OS X, add additional optional packages, 92 | and simplify installation. 93 | 94 | 2003-08-22 95 | Fixed typo in io.html 96 | 97 | 2003-07-01 98 | Added link to Erann Gat's "Idiot's Guide" 99 | Formatting in macros.html 100 | 101 | 2003-06-02 102 | Fixed link to "Common Lisp Hints" 103 | 104 | 2003-05-28 105 | Updated windows.html by Bill Clementson to support emacs version 21.3. 106 | 107 | 2003-04-15 108 | Fixed typo in Mark Watson link 109 | Changed CLiki link in pattern_matching.html 110 | Added link to The Dynamic Learning Center 111 | 112 | 2003-03-18 113 | Changed cw-usedefault to cw_usedefault in some code 114 | 115 | 2003-03-18 116 | Added win32.html by Jeff Caldwell last week 117 | Incoporating suggested changes from Kenny Tilton and Edi Weitz 118 | Maintaining CHANGELOG 119 | Including Paul Tarvydas and Kenny Tilton as contributors 120 | Provided an introduction to Paul Tarvydas's code 121 | Changed link to Petzold's book 122 | Changed wm-close to wm_close, making the move from Petzold to the Lisp code a slightly more seamless 123 | A few stylistic changes 124 | Included :dbcs decision made at run time, not compile time. 125 | 126 | 2002-12-26 127 | Fixed some typos in misc.html, license.html, and in this file 128 | 129 | 2002-12-25 130 | Slight changes to macros.html to make it look like the rest of the cookbook 131 | Added initial entry for macros.html to CHANGELOG 132 | Added new chapter 'Miscellaneous' 133 | Linked misc.html from index.html 134 | Added section 'Re-using complex data structures' (originally by Marco Antoniotti) to 'Miscellaneous' 135 | Added section 'Using ADJUST-ARRAY instead of consing up...' (originally by Pierpaolo Bernardi) to 'Miscellaneous' 136 | Added Marco Antoniotti and Pierpaolo Bernardi to the list of contributors 137 | Added Pascal Constanza's guide to link section 138 | Added Mark Watson's book to link section 139 | 140 | 2002-10-22 141 | Updated windows.html and .emacs by Bill Clementson to revert to Corman Lisp 1.5 due to problems with running clconsole.exe inside of emacs in version 2.0. 142 | 143 | 2002-10-21 144 | Updated windows.html and .emacs by Bill Clementson to add support for Corman Lisp 2.0, removed instructions for the w3 utility (not necessary for viewing documentation) and provide support in .emacs for auto-detecting which lisp implementations have been installed. 145 | 146 | 2002-10-15 147 | Updated windows.html by Bill Clementson to include instructions to change lisp-used, EMACSDIR & ILISPDIR variables. 148 | 149 | 2002-09-23 150 | Updated windows.html and .emacs by Bill Clementson to support CLISP v2.30. 151 | 152 | 2002-09-22 153 | Updated .emacs by Bill Clementson to add generic eval-last-sexp support on C-x C-e for elisp & inferior lisps. 154 | 155 | 2002-09-08 156 | New chapter macros.html by Drew McDermott 157 | 158 | 2002-09-03 159 | Updated windows.html to adjust location of environmental variable instructions. 160 | 161 | 2002-08-29 162 | Updated windows.html and .emacs by Bill Clementson to not show separate ACL listener window on startup. 163 | 164 | 2002-08-29 165 | Updated windows.html and .emacs by Bill Clementson as follows: 166 | 1. Changes for ACL6.2 (use alisp.exe instead of allegro-ansi.exe due to heap size limitations in trial edition). 167 | 2. Eliminate clisp-indent.el from setup to eliminate need to modify .emacs if clisp isn't installed. 168 | 169 | 2002-08-20 170 | Updated windows.html and .emacs by Bill Clementson as follows: 171 | 1. Support for new versions of Emacs, ILISP, CLISP & ACL 172 | 2. Removed dependency on .bat files 173 | 3. Simplified installation 174 | 4. Made it easier to install only a single (or a few) Lisp implementations 175 | 5. Added shading to pre code blocks as in other Cookbook pages 176 | 6. No longer byte-compile the .emacs file 177 | 178 | 2002-08-02 179 | Linked systems.html from index.html 180 | 181 | 2002-08-01 182 | New chapter systems.html by Alberto Riva 183 | 184 | 2002-07-08 185 | Typos to process.html. In particular, fix the mailboxes example code. 186 | 187 | 2002-07-03 188 | Flesh out process.html - complete as it's going to get for now. 189 | 190 | 2002-06-28 191 | Cleanups to process.html and add section on thread state. 192 | 193 | 2002-06-26 194 | Add mp:process-wait to process.html. 195 | 196 | 2002-06-25 197 | Start chapter on threads. 198 | 199 | 2002-06-21 200 | Rearrangements to os.html and process.html, ready for chapter on threads. 201 | 202 | 2002-05-23 203 | Changes in sockets.html by Alberto Riva (due to hints by John Wiseman and John Foderaro) 204 | 205 | 2002-05-22 206 | Changes in string.html to get rid of literal constants (due to a hint by Peter Van Eynde) 207 | Initial files chapter (Edi Weitz) 208 | Cosmetic changes to PRE tag 209 | Replaced hhtm stuff in footer with CVS header 210 | 211 | 2002-04-25 212 | Changed LW example in io.html (due to a hint by Marc Battyani) 213 | Null change - just testing out my cvs (NDL) 214 | 215 | 2002-04-24 216 | NDL added self as contributor 217 | 218 | 2002-04-04 219 | Added introduction to hashes.html, minor cosmetic changes 220 | 221 | 2002-04-03 222 | Added link to Nick Levine's 'Lisp and Elements of Style' to index.html 223 | 224 | 2002-03-31 225 | .emacs & windows.html: mods for CLISP 2.28 (new version) and changes suggested by Luke Crook 226 | 227 | 2002-03-28 228 | Corrections in "faithful output" entry (also by Jörg) 229 | 230 | 2002-03-21 231 | Added "faithful output" entry (by Jörg-Cyril Höhle) in IO chapter 232 | 233 | 2002-03-08 234 | .emacs & windows.html: added support for LispWorks 235 | 236 | 2002-03-02 237 | Removed buildilisp.bat because it's part of ILISP now 238 | 239 | 2002-03-01 240 | .emacs: mods to reflect fixes made to clisp install.lisp in CLISP CVS and eliminate compile-time warnings (Bill Clementson) 241 | windows.html: mods to reflect fixes made to clisp install.lisp in CVS and the introduction of icompile.bat in ILISP CVS. 242 | 243 | 2002-02-25 244 | index.html: added link to CHANGELOG (Edi Weitz) 245 | 246 | 2002-02-24 247 | Added chapter about sockets by Alberto Riva 248 | 249 | Windows IDE chapter: modified comments re ILISP compatibility with Emacs 21.1 (Bill Clementson) 250 | 251 | Added HTML table (fixed width) to Windows IDE chapter 252 | 253 | 2002-02-23 254 | Windows IDE chapter: mods to CLISP/CormanLisp installs (Bill Clementson) 255 | 256 | 2002-02-19 257 | Added chapter about Windows IDE (including sample .emacs and batch file) by Bill Clementson 258 | 259 | Added chapter about processes and threads which currently consists of the 'forking with CMUCL' example by Martin Cracauer 260 | 261 | Added Bill Clementson and Martin Cracauer to the list of contributors 262 | 263 | 2002-02-18 264 | Moved cookbook to Sourceforge. Imported the contents from agharta.de web server into the CVS repository. 265 | 266 | The cookbook now consists of: 267 | - chapter about strings by Edi Weitz 268 | with corrections and additions by Rudi Schlatte, Paul Foley, 269 | and Lieven Marchand 270 | - chapter about dates and times by Alberto Riva, 271 | plus one entry about computing the day of the week by Gerald Doussot 272 | - chapter about hash tables by Edi Weitz 273 | with corrections and additions by Paul Foley and Kalman Reti 274 | - page about pattern matching and regular expressions 275 | which mainly consists of a link to the CLiki page 276 | - chapter about functions, 277 | entry about functions which return functions by Edi Weitz 278 | with corrections by Paul Foley, 279 | entry about currying functions by Paul Foley 280 | - chapter about input/output, 281 | currently one entry about redirecting stdout by Rudi Schlatte 282 | - chapter about packages, 283 | currently one entry about listing all symbols by Frederic Brunel 284 | - chapter about OS interfacing, 285 | currently one entry about accessing environment variables 286 | by Paul Foley with additons by Jörg-Cyril Höhle and Edi Weitz 287 | - chapter about foreign function interfaces, 288 | gethostname CLISP example by Jörg-Cyril Höhle 289 | with additions by Reini Urban, 290 | gethostname ACL example by Alberto Riva 291 | - starting page (index.html) including the list of contributors 292 | - license 293 | 294 | 2002-02-05 295 | Cookbook started at with two chapters initially - a chapter about dates and times by Alberto Riva and a chapter about strings by Edi Weitz 296 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Some notes on contribution 2 | 3 | 1. If filing a new article, please check the bug tracker and, 4 | if the issue is not already there, please file a bug before 5 | doing the work. There is likely to be discussion which could 6 | help. Why waste your own time? 7 | 8 | 2. Please contribute in English (poor English is **OK**, the 9 | maintainers can help and it'll **be improved!**). Please verify 10 | all Lisp code works, noting the specific Lisp implementation 11 | you used to verify. Please verify all links work. 12 | 13 | 3. Please check the license. Your work may be edited, copied, and changed, 14 | and you should be OK with that! And, please do not copy and paste code you 15 | don't have the copyright to! 16 | 17 | 4. Please don't be afraid to file a bug as the method of communication. It's 18 | much easier to find for others when you do that. 19 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:2.5 2 | 3 | COPY . /cl-cookbook 4 | WORKDIR /cl-cookbook 5 | 6 | RUN gem install bundler -v 2.1.2 7 | # This spawns warnings..., let's ignore them using || true trick 8 | RUN bundle install || true 9 | 10 | EXPOSE 4000 11 | 12 | CMD ["bundle", "exec", "jekyll", "serve", "--host=0.0.0.0", "--incremental"] 13 | 14 | # HOWTO RUN 15 | # docker build -t cl-cookbook . 16 | # docker run -p 4000:4000 -v $(pwd):/cl-cookbook cl-cookbook 17 | 18 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | # The version must match github-pages'. 3 | # https://pages.github.com/versions/ 4 | gem 'github-pages', "~> 202", group: :jekyll_plugins 5 | # gem 'jekyll', '= 3.8.3' 6 | # gem 'rubyzip', '>= 1.2.2' 7 | # gem 'ffi', '>= 1.9.24' 8 | # gem 'nokogiri', '>= 1.8.5' 9 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | activesupport (4.2.11.1) 5 | i18n (~> 0.7) 6 | minitest (~> 5.1) 7 | thread_safe (~> 0.3, >= 0.3.4) 8 | tzinfo (~> 1.1) 9 | addressable (2.7.0) 10 | public_suffix (>= 2.0.2, < 5.0) 11 | coffee-script (2.4.1) 12 | coffee-script-source 13 | execjs 14 | coffee-script-source (1.11.1) 15 | colorator (1.1.0) 16 | commonmarker (0.17.13) 17 | ruby-enum (~> 0.5) 18 | concurrent-ruby (1.1.6) 19 | dnsruby (1.61.3) 20 | addressable (~> 2.5) 21 | em-websocket (0.5.1) 22 | eventmachine (>= 0.12.9) 23 | http_parser.rb (~> 0.6.0) 24 | ethon (0.12.0) 25 | ffi (>= 1.3.0) 26 | eventmachine (1.2.7) 27 | execjs (2.7.0) 28 | faraday (1.0.0) 29 | multipart-post (>= 1.2, < 3) 30 | ffi (1.12.2) 31 | forwardable-extended (2.6.0) 32 | gemoji (3.0.1) 33 | github-pages (202) 34 | activesupport (= 4.2.11.1) 35 | github-pages-health-check (= 1.16.1) 36 | jekyll (= 3.8.5) 37 | jekyll-avatar (= 0.6.0) 38 | jekyll-coffeescript (= 1.1.1) 39 | jekyll-commonmark-ghpages (= 0.1.6) 40 | jekyll-default-layout (= 0.1.4) 41 | jekyll-feed (= 0.11.0) 42 | jekyll-gist (= 1.5.0) 43 | jekyll-github-metadata (= 2.12.1) 44 | jekyll-mentions (= 1.4.1) 45 | jekyll-optional-front-matter (= 0.3.0) 46 | jekyll-paginate (= 1.1.0) 47 | jekyll-readme-index (= 0.2.0) 48 | jekyll-redirect-from (= 0.14.0) 49 | jekyll-relative-links (= 0.6.0) 50 | jekyll-remote-theme (= 0.4.0) 51 | jekyll-sass-converter (= 1.5.2) 52 | jekyll-seo-tag (= 2.5.0) 53 | jekyll-sitemap (= 1.2.0) 54 | jekyll-swiss (= 0.4.0) 55 | jekyll-theme-architect (= 0.1.1) 56 | jekyll-theme-cayman (= 0.1.1) 57 | jekyll-theme-dinky (= 0.1.1) 58 | jekyll-theme-hacker (= 0.1.1) 59 | jekyll-theme-leap-day (= 0.1.1) 60 | jekyll-theme-merlot (= 0.1.1) 61 | jekyll-theme-midnight (= 0.1.1) 62 | jekyll-theme-minimal (= 0.1.1) 63 | jekyll-theme-modernist (= 0.1.1) 64 | jekyll-theme-primer (= 0.5.3) 65 | jekyll-theme-slate (= 0.1.1) 66 | jekyll-theme-tactile (= 0.1.1) 67 | jekyll-theme-time-machine (= 0.1.1) 68 | jekyll-titles-from-headings (= 0.5.1) 69 | jemoji (= 0.10.2) 70 | kramdown (= 1.17.0) 71 | liquid (= 4.0.0) 72 | listen (= 3.1.5) 73 | mercenary (~> 0.3) 74 | minima (= 2.5.0) 75 | nokogiri (>= 1.10.4, < 2.0) 76 | rouge (= 3.11.0) 77 | terminal-table (~> 1.4) 78 | github-pages-health-check (1.16.1) 79 | addressable (~> 2.3) 80 | dnsruby (~> 1.60) 81 | octokit (~> 4.0) 82 | public_suffix (~> 3.0) 83 | typhoeus (~> 1.3) 84 | html-pipeline (2.12.3) 85 | activesupport (>= 2) 86 | nokogiri (>= 1.4) 87 | http_parser.rb (0.6.0) 88 | i18n (0.9.5) 89 | concurrent-ruby (~> 1.0) 90 | jekyll (3.8.5) 91 | addressable (~> 2.4) 92 | colorator (~> 1.0) 93 | em-websocket (~> 0.5) 94 | i18n (~> 0.7) 95 | jekyll-sass-converter (~> 1.0) 96 | jekyll-watch (~> 2.0) 97 | kramdown (~> 1.14) 98 | liquid (~> 4.0) 99 | mercenary (~> 0.3.3) 100 | pathutil (~> 0.9) 101 | rouge (>= 1.7, < 4) 102 | safe_yaml (~> 1.0) 103 | jekyll-avatar (0.6.0) 104 | jekyll (~> 3.0) 105 | jekyll-coffeescript (1.1.1) 106 | coffee-script (~> 2.2) 107 | coffee-script-source (~> 1.11.1) 108 | jekyll-commonmark (1.3.1) 109 | commonmarker (~> 0.14) 110 | jekyll (>= 3.7, < 5.0) 111 | jekyll-commonmark-ghpages (0.1.6) 112 | commonmarker (~> 0.17.6) 113 | jekyll-commonmark (~> 1.2) 114 | rouge (>= 2.0, < 4.0) 115 | jekyll-default-layout (0.1.4) 116 | jekyll (~> 3.0) 117 | jekyll-feed (0.11.0) 118 | jekyll (~> 3.3) 119 | jekyll-gist (1.5.0) 120 | octokit (~> 4.2) 121 | jekyll-github-metadata (2.12.1) 122 | jekyll (~> 3.4) 123 | octokit (~> 4.0, != 4.4.0) 124 | jekyll-mentions (1.4.1) 125 | html-pipeline (~> 2.3) 126 | jekyll (~> 3.0) 127 | jekyll-optional-front-matter (0.3.0) 128 | jekyll (~> 3.0) 129 | jekyll-paginate (1.1.0) 130 | jekyll-readme-index (0.2.0) 131 | jekyll (~> 3.0) 132 | jekyll-redirect-from (0.14.0) 133 | jekyll (~> 3.3) 134 | jekyll-relative-links (0.6.0) 135 | jekyll (~> 3.3) 136 | jekyll-remote-theme (0.4.0) 137 | addressable (~> 2.0) 138 | jekyll (~> 3.5) 139 | rubyzip (>= 1.2.1, < 3.0) 140 | jekyll-sass-converter (1.5.2) 141 | sass (~> 3.4) 142 | jekyll-seo-tag (2.5.0) 143 | jekyll (~> 3.3) 144 | jekyll-sitemap (1.2.0) 145 | jekyll (~> 3.3) 146 | jekyll-swiss (0.4.0) 147 | jekyll-theme-architect (0.1.1) 148 | jekyll (~> 3.5) 149 | jekyll-seo-tag (~> 2.0) 150 | jekyll-theme-cayman (0.1.1) 151 | jekyll (~> 3.5) 152 | jekyll-seo-tag (~> 2.0) 153 | jekyll-theme-dinky (0.1.1) 154 | jekyll (~> 3.5) 155 | jekyll-seo-tag (~> 2.0) 156 | jekyll-theme-hacker (0.1.1) 157 | jekyll (~> 3.5) 158 | jekyll-seo-tag (~> 2.0) 159 | jekyll-theme-leap-day (0.1.1) 160 | jekyll (~> 3.5) 161 | jekyll-seo-tag (~> 2.0) 162 | jekyll-theme-merlot (0.1.1) 163 | jekyll (~> 3.5) 164 | jekyll-seo-tag (~> 2.0) 165 | jekyll-theme-midnight (0.1.1) 166 | jekyll (~> 3.5) 167 | jekyll-seo-tag (~> 2.0) 168 | jekyll-theme-minimal (0.1.1) 169 | jekyll (~> 3.5) 170 | jekyll-seo-tag (~> 2.0) 171 | jekyll-theme-modernist (0.1.1) 172 | jekyll (~> 3.5) 173 | jekyll-seo-tag (~> 2.0) 174 | jekyll-theme-primer (0.5.3) 175 | jekyll (~> 3.5) 176 | jekyll-github-metadata (~> 2.9) 177 | jekyll-seo-tag (~> 2.0) 178 | jekyll-theme-slate (0.1.1) 179 | jekyll (~> 3.5) 180 | jekyll-seo-tag (~> 2.0) 181 | jekyll-theme-tactile (0.1.1) 182 | jekyll (~> 3.5) 183 | jekyll-seo-tag (~> 2.0) 184 | jekyll-theme-time-machine (0.1.1) 185 | jekyll (~> 3.5) 186 | jekyll-seo-tag (~> 2.0) 187 | jekyll-titles-from-headings (0.5.1) 188 | jekyll (~> 3.3) 189 | jekyll-watch (2.2.1) 190 | listen (~> 3.0) 191 | jemoji (0.10.2) 192 | gemoji (~> 3.0) 193 | html-pipeline (~> 2.2) 194 | jekyll (~> 3.0) 195 | kramdown (1.17.0) 196 | liquid (4.0.0) 197 | listen (3.1.5) 198 | rb-fsevent (~> 0.9, >= 0.9.4) 199 | rb-inotify (~> 0.9, >= 0.9.7) 200 | ruby_dep (~> 1.2) 201 | mercenary (0.3.6) 202 | mini_portile2 (2.4.0) 203 | minima (2.5.0) 204 | jekyll (~> 3.5) 205 | jekyll-feed (~> 0.9) 206 | jekyll-seo-tag (~> 2.1) 207 | minitest (5.14.0) 208 | multipart-post (2.1.1) 209 | nokogiri (1.10.8) 210 | mini_portile2 (~> 2.4.0) 211 | octokit (4.16.0) 212 | faraday (>= 0.9) 213 | sawyer (~> 0.8.0, >= 0.5.3) 214 | pathutil (0.16.2) 215 | forwardable-extended (~> 2.6) 216 | public_suffix (3.1.1) 217 | rb-fsevent (0.10.3) 218 | rb-inotify (0.10.1) 219 | ffi (~> 1.0) 220 | rouge (3.11.0) 221 | ruby-enum (0.7.2) 222 | i18n 223 | ruby_dep (1.5.0) 224 | rubyzip (2.2.0) 225 | safe_yaml (1.0.5) 226 | sass (3.7.4) 227 | sass-listen (~> 4.0.0) 228 | sass-listen (4.0.0) 229 | rb-fsevent (~> 0.9, >= 0.9.4) 230 | rb-inotify (~> 0.9, >= 0.9.7) 231 | sawyer (0.8.2) 232 | addressable (>= 2.3.5) 233 | faraday (> 0.8, < 2.0) 234 | terminal-table (1.8.0) 235 | unicode-display_width (~> 1.1, >= 1.1.1) 236 | thread_safe (0.3.6) 237 | typhoeus (1.3.1) 238 | ethon (>= 0.9.0) 239 | tzinfo (1.2.6) 240 | thread_safe (~> 0.1) 241 | unicode-display_width (1.6.1) 242 | 243 | PLATFORMS 244 | ruby 245 | 246 | DEPENDENCIES 247 | github-pages (~> 202) 248 | 249 | BUNDLED WITH 250 | 2.1.4 251 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | run: 2 | bundle exe jekyll serve --incremental 3 | 4 | epub: 5 | sbcl --load make-cookbook.lisp --eval '(uiop:quit)' 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lisp Cookbook 2 | 3 | A Cookbook is an invaluable resource, as it shows how to do various things in a clear fashion without all the theoretical context. Sometimes you just need to look things up. While cookbooks can never replace proper documentation such as the [HyperSpec][hs] or books such as [Practical Common Lisp][pcl], every language deserves a good cookbook, Common Lisp included. 4 | 5 | The CL Cookbook aims to tackle all sort of topics, for the beginner as for the more advanced developer. 6 | 7 | 8 | ## Contributing 9 | 10 | Thanks for contributing to the Cookbook. 11 | 12 | You can start by having a look at the [style guide](STYLEGUIDE.md). 13 | 14 | When adding new content, please ensure it renders properly. 15 | 16 | There are three ways to do this: 17 | 18 | ### Install Jekyll system-wide 19 | 20 | The first option is to install [Jekyll][jekyll] globally and to run `jekyll serve` in a folder where this repository was checked out. 21 | 22 | Then open `http://127.0.0.1:4000/cl-cookbook/` (the last `/` is important). 23 | 24 | ### Install system locally using Ruby gems 25 | 26 | Another option is to install the Jekyll version of this repository locally with Ruby gems. Since bundler 1.17.3 requires Ruby 2.5 that is rather old, it is recommended to install it using `rbenv`: 27 | 28 | 1. Install [rbenv](https://github.com/rbenv/rbenv) using your package manager, or follow [these instructions](https://github.com/rbenv/rbenv#basic-github-checkout) to install it manually. 29 | 2. Install [ruby-build](https://github.com/rbenv/ruby-build#installation). If you did a manual installation in the previous step, it is recommended to install ruby-build as a rbenv plugin. 30 | 3. Run `rbenv install 2.5.0` to install Ruby 2.5.0. Run `which gem` to make sure it points to `~/.rbenv/shims/gem`. 31 | 4. Run `gem install bundler -v 2.1.4` to install bundler. 32 | 5. `cd` to the `cl-cookbook` directory and run `bundle install --path vendor/bundle` to install Jekyll locally. 33 | 6. Run `bundle exec jekyll serve` to generate the site and host it. 34 | 35 | ### Use a Docker container 36 | 37 | Since it can be a bit troublesome to install older versions of Ruby onto newer Linux-based systems, another option is to use `docker`. 38 | 39 | 1. Build the container by executing `sudo docker build -t cl-cookbook .` in this directory. 40 | 2. Run Jekyll inside the container `sudo docker run -p 4000:4000 -v $(pwd):/cl-cookbook cl-cookbook` from this directory. 41 | 3. Open your web browser and navigate to `http://127.0.0.1:4000/cl-cookbook/`. 42 | 43 | This command will mount the current working directory into the container and incremental builds are actived so you will be able to see your latest changes without restarting or rebuilding the container. 44 | 45 | ### Troubleshooting 46 | 47 | It can happen that you have older version of ruby installed in the system and 48 | bundler install will fail. To fix this, you need to update ruby. If system update 49 | is not an option, consider installing [rbenv][rbenv]. 50 | 51 | ~~~ sh 52 | # Check rbenv homepage for install instructions on systems other than Mac OS X 53 | brew install rbenv ruby-build 54 | 55 | # Add rbenv to bash so that it loads every time you open a terminal 56 | echo 'if which rbenv > /dev/null; then eval "$(rbenv init -)"; fi' >> ~/.bash_profile 57 | source ~/.bash_profile 58 | 59 | # Install Ruby 60 | rbenv install 2.5.0 61 | rbenv global 2.5.0 62 | ruby -v 63 | ~~~ 64 | 65 | After this you can proceed as usual: 66 | 67 | 1. `gem install bundler` 68 | 2. `bundle install --path vendor/bundle` 69 | 3. `bundle exec jekyll serve` 70 | 71 | Also, refer to the [CONTRIBUTING.md][contributing] file. 72 | 73 | ### Building the EPUB and the PDF 74 | 75 | Run `make epub`. See `make-cookbook.lisp`. 76 | 77 | You need a decently recent version of [Calibre](https://calibre-ebook.com/). They provide an easy binary installation. 78 | 79 | To exclude regions of text from the output (for example, embedded videos that makes no sense in a print format), use these flags: 80 | 81 | 82 | 83 | 84 | Our build script roughly does the following: 85 | 86 | - concatenate all markdown content into one file 87 | - change yaml frontmatters to a markdown title 88 | - delete the mark regions from the file 89 | - make internal links work on the EPUB. 90 | 91 | It uses some metadata in `metadata.txt`. 92 | 93 | We can check the resulting EPUB with `epubcheck`. 94 | 95 | 96 | ## Origins 97 | 98 | This is a fork of the [Common Lisp Cookbook][sf], moved from SourceForge. 99 | 100 | This project brings the Common Lisp Cookbook to this decade. Development of the original Common Lisp Cookbook in SourceForge halted in 2007. In the meantime, a lot has happened in the land of Common Lisp. Tools and implementations have been improving, and some have fallen out of favor. Most notably, Common Lisp users can now benefit from the [Quicklisp][ql] library manager. 101 | 102 | The main goal is making the Cookbook more modern and more accessible in addition to updating and expanding the content. 103 | 104 | [sf]: http://cl-cookbook.sourceforge.net/ 105 | [ql]: https://www.quicklisp.org/ 106 | [hs]: http://www.lispworks.com/documentation/HyperSpec/Front/X_Master.htm 107 | [pcl]: http://www.gigamonkeys.com/book/ 108 | [jekyll]: https://jekyllrb.com/docs/installation/ 109 | [rbenv]: https://github.com/rbenv/rbenv 110 | [contributing]: CONTRIBUTING.md 111 | [bundler-v2]: https://stackoverflow.com/questions/54087856/cant-find-gem-bundler-0-a-with-executable-bundle-gemgemnotfoundexceptio 112 | 113 | ## Development with GNU Guix 114 | 115 | To enter a development environment in which all the required software dependencies are made available, run `guix shell` in the project's root directory. The provided Guix [manifest file](manifest.scm) will be automatically sourced after giving [authorization](https://guix.gnu.org/manual/devel/en/html_node/Invoking-guix-shell.html) for Guix to do so. 116 | 117 | ## Support 118 | 119 | You can support the individuals that constantly improve the Cookbook. See the Github Sponsors icon. Thanks for them! 120 | -------------------------------------------------------------------------------- /STYLEGUIDE.md: -------------------------------------------------------------------------------- 1 | Thanks for contributing to the Cookbook. Please follow these 2 | guidelines. Some are only a convention, some are important for epub 3 | generation. 4 | 5 | ## Titles 6 | 7 | - (important) the first section in a chapter should be a subsection: `## title` (indeed, the section name in the epub is the chapter name). 8 | 9 | - titles are like a sentence, only the first word is capitalized. 10 | 11 | - if you refer to functions, use markdown syntax too (backtics). 12 | 13 | ## Lists 14 | 15 | - numbered bullet lists (`1.`) should be used only at the first level. For the next nested levels, use normal non-numbered lists. 16 | 17 | 18 | ## Code formatting 19 | 20 | - functions should generally be referenced with backtics. There is no need to capitalize them in the age of markdown: write `function` instead of FUNCTION. 21 | 22 | - indent code using the predominant style. If unsure, check what Emacs `lisp-mode` does. 23 | 24 | ## Code snippets 25 | 26 | - use `~~~lisp` for code snippets. 27 | 28 | - snippets must start at the beginning of a line. Don't indent the `~~~lisp` code fence. 29 | 30 | - code snippets must be preceded and followed by a newline: 31 | 32 | ``` 33 | 34 | Here's a snippet: 35 | 36 | ~~~lisp 37 | (defun oh ()) 38 | ~~~ 39 | 40 | This snippet... 41 | ``` 42 | 43 | - don't write the lisp prompt like "CL-USER> " unless it is very important for the example. This is mainly due to ease copy-pasting. 44 | 45 | - to show a snippet's result, use `;; => result` on the same line if the snippet was on one line, otherwise you can do: 46 | 47 | ~~~lisp 48 | (print :abc) 49 | ;; :abc 50 | ~~~ 51 | 52 | If the result is large, use another code block, without comments. 53 | 54 | - lines should not exceed 64 characters, otherwise the PDF will truncate them and show a horizontal scrollbar. 55 | 56 | 57 | ## EPUB gotchas 58 | 59 | ### Embedded content 60 | 61 | We should not abuse from embedded content, such as youtube videos. 62 | 63 | Please exclude them from the EPUB generation with these flags: 64 | 65 | 66 | 67 | 68 | ### Internal links (for the EPUB) 69 | 70 | We must tweak the markdown links so that internal links work in the 71 | EPUB reader. See `fix-epub-links.sed` to add your link or just ping us on GitHub. 72 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | title: Common Lisp Cookbook 2 | description: A collection of examples of how to use Common Lisp 3 | baseurl: "/cl-cookbook" 4 | 5 | # https://stackoverflow.com/questions/39057405/unable-to-build-a-jekyll-site-invalid-date 6 | exclude: [vendor] 7 | 8 | markdown: kramdown 9 | 10 | # Disable the default syntax highlighter, to make place to 11 | # HighlightLisp (in JS). 12 | kramdown: 13 | syntax_highlighter_opts: 14 | disable: true 15 | 16 | sass: 17 | style: :compressed 18 | 19 | defaults: 20 | - 21 | scope: 22 | path: "" 23 | values: 24 | layout: "chapter" 25 | -------------------------------------------------------------------------------- /_includes/code/w32-appendix-a.lisp: -------------------------------------------------------------------------------- 1 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 2 | ; 3 | ; Header-file kinds of code 4 | ; 5 | 6 | ; Determine character type 7 | (defun external-format () 8 | (if (string= (software-type) "Windows NT") 9 | :unicode 10 | :ascii)) 11 | 12 | (defparameter *class-name* "LWTestClass") 13 | 14 | ;--- Win32 SDK Constants 15 | 16 | ; Not all of these are verified by testing in code 17 | ; More are included than are used to allow play 18 | (defconstant COLOR_WINDOW 5) 19 | (defconstant CS_HREDRAW 1) 20 | (defconstant CS_VREDRAW 2) 21 | (defconstant CW_USEDEFAULT #x80000000) 22 | (defconstant IDC_ARROW 32512) 23 | (defconstant SW_SHOW 5) 24 | (defconstant WM_CLOSE #x00000010) 25 | (defconstant WM_DESTROY #x00000002) 26 | (defconstant WM_PAINT #x0000000f) 27 | (defconstant WS_BORDER #x00080000) 28 | (defconstant WS_CAPTION #x00C00000) 29 | (defconstant WS_CHILD #x40000000) 30 | (defconstant WS_DISABLED #x08000000) 31 | (defconstant WS_EX_CONTROLPARENT #x00010000) 32 | (defconstant WS_EX_APPWINDOW #x00040000) 33 | (defconstant WS_MAXIMIZEBOX #x00010000) 34 | (defconstant WS_MINIMIZEBOX #x00020000) 35 | (defconstant WS_OVERLAPPED #x00000000) 36 | (defconstant WS_POPUP #x80000000) 37 | (defconstant WS_SYSMENU #x00080000) 38 | (defconstant WS_THICKFRAME #x00040000) 39 | (defconstant WS_VISIBLE #x10000000) 40 | 41 | ; Aggregates 42 | (defconstant WS_POPUPWINDOW (logior ws_popup ws_border ws_sysmenu)) 43 | (defconstant WS_OVERLAPPEDWINDOW (logior ws_overlapped ws_caption 44 | ws_sysmenu ws_thickframe 45 | ws_minimizebox ws_maximizebox)) 46 | 47 | ;--- Win32 SDK data types 48 | 49 | (fli:define-c-typedef BOOL (:unsigned :long)) 50 | (fli:define-c-typedef DWORD (:unsigned :long)) 51 | (fli:define-c-typedef HANDLE (:unsigned :long)) 52 | (fli:define-c-typedef HDC (:unsigned :long)) 53 | (fli:define-c-typedef HINSTANCE (:unsigned :long)) 54 | (fli:define-c-typedef HMENU (:unsigned :long)) 55 | (fli:define-c-typedef HWND (:unsigned :long)) 56 | (fli:define-c-typedef INT :int) 57 | (fli:define-c-typedef LONG :long) 58 | (fli:define-c-typedef LPCTSTR :pointer) 59 | (fli:define-c-typedef LPSTR :pointer) 60 | (fli:define-c-typedef LPVOID :long) 61 | (fli:define-c-typedef LPFN :long) ;; Doesn't work as :pointer 62 | (fli:define-c-typedef UINT (:unsigned :int)) 63 | (fli:define-c-typedef ULONG (:unsigned :long)) 64 | (fli:define-c-typedef wBYTE (:unsigned :char)) 65 | (fli:define-c-typedef PTR (:unsigned :long)) 66 | 67 | ;--- Win32 SDK structures 68 | 69 | ; POINT 70 | (fli:define-c-struct sPOINT 71 | (x int) 72 | (y int)) 73 | (fli:define-c-typedef POINT sPOINT) 74 | 75 | ; MSG 76 | (fli:define-c-struct sMSG 77 | (hwnd hwnd) 78 | (wParam ulong) 79 | (lParam ulong) 80 | (time dword) 81 | (point point)) 82 | (fli:define-c-typedef MSG sMSG) 83 | 84 | ; PAINTSTRUCT 85 | (fli:define-c-struct sPAINTSTRUCT 86 | (HDC hdc) 87 | (fErase bool) 88 | (rcPaint-x uint) 89 | (rcPaint-y uint) 90 | (rcPaint-width uint) 91 | (rcPaint-height uint) 92 | (fRestore bool) 93 | (fIncUpdate bool) 94 | (rgbReserved (:c-array wBYTE 32))) 95 | (fli:define-c-typedef PAINTSTRUCT sPAINTSTRUCT) 96 | 97 | ; WndClass 98 | (fli:define-c-struct sWNDCLASS 99 | (style uint) 100 | (lpfnWndProc lpfn) 101 | (cbClsExtra int) 102 | (cbWndExtra int) 103 | (hInstance handle) 104 | (hIcon handle) 105 | (hCursor handle) 106 | (hBrBackground handle) 107 | (lpszMenuName ulong) ;; ulong so it can be set to null without error 108 | (lpszClassName lpctstr)) 109 | (fli:define-c-typedef WNDCLASS sWNDCLASS) 110 | 111 | ;--- Win32 SDK functions 112 | 113 | ; BeginPaint 114 | (fli:define-foreign-function 115 | (BeginPaint "BeginPaint" :calling-convention :stdcall) 116 | ((hwnd hwnd) (lpPaintStruct ptr)) 117 | :result-type hdc) 118 | 119 | ; CreateWindowEx 120 | (fli:define-foreign-function 121 | (CreateWindowEx "CreateWindowEx" :dbcs :calling-convention :stdcall) 122 | ((dwExStyle dword) (lpClassName lpctstr) (lpWindowName lpctstr) 123 | (dwStyle dword) (x uint) (y uint) (nWidth uint) (nHeight uint) 124 | (hwndParent hwnd) (hMenu hmenu) (hInstance hinstance) (lpParam lpvoid)) 125 | :result-type hwnd) 126 | 127 | ; DefWindowProc 128 | (fli:define-foreign-function 129 | (DefWindowProc "DefWindowProc" :dbcs :calling-convenction :stdcall) 130 | ((hwnd ulong) (msg ulong) (wparam ulong) (lparam ulong)) 131 | :result-type ulong) 132 | 133 | ; DispatchMessage 134 | (fli:define-foreign-function 135 | (DispatchMessage "DispatchMessage" :dbcs :calling-convention :stdcall) 136 | ((MsgPtr ptr)) ;; We're passing a pointer but Lisp doesn't know 137 | :result-type ulong) 138 | 139 | ; EndPaint 140 | (fli:define-foreign-function 141 | (EndPaint "EndPaint" :calling-convention :stdcall) 142 | ((hwnd hwnd) (lpPaintStruct ptr)) 143 | :result-type bool) 144 | 145 | ; GetLastError 146 | (fli:define-foreign-function 147 | (GetLastError "GetLastError" :calling-convention :stdcall) 148 | () 149 | :result-type dword) 150 | 151 | ; GetMessage 152 | (fli:define-foreign-function 153 | (GetMessage "GetMessage" :dbcs :calling-convention :stdcall) 154 | ((lpMsg ptr) (hwnd ulong) (MsgFiltMin ulong) (MsgFiltMax ulong)) 155 | :result-type bool) 156 | 157 | ; GetModuleHandle - with no module name specified; pass this a zero i.e. NULL 158 | (fli:define-foreign-function 159 | (GetModuleHandle-current "GetModuleHandle" 160 | :dbcs :calling-convenction :stdcall) 161 | ((lpModuleName :long)) 162 | :result-type handle) 163 | 164 | ; Lisp helper 165 | (defmacro current-module-handle () 166 | `(GetModuleHandle-current 0)) 167 | 168 | ; GetModuleHandle - pass this a foreign string naming the desired module 169 | (fli:define-foreign-function 170 | (GetModuleHandle "GetModuleHandle" :dbcs :calling-convenction :stdcall) 171 | ((lpModuleName lpctstr)) 172 | :result-type handle) 173 | 174 | ; LoadCursor 175 | (fli:define-foreign-function 176 | (LoadCursor "LoadCursor" :dbcs) 177 | ((hInstance handle) (param (:unsigned :long))) 178 | :result-type handle) 179 | 180 | ; PostQuitMessage 181 | (fli:define-foreign-function 182 | (PostQuitMessage "PostQuitMessage" :calling-convention :stdcall) 183 | ((nExitCode :int)) 184 | :result-type :void) 185 | 186 | ; RegisterClass 187 | (fli:define-foreign-function 188 | (RegisterClass "RegisterClass" :dbcs :calling-convention :stdcall) 189 | ((WndClass ptr)) 190 | :result-type bool) 191 | 192 | ; TextOut 193 | (fli:define-foreign-function 194 | (TextOut "TextOut" :dbcs :calling-convention :stdcall) 195 | ((HDC hdc) (nXStart uint) (nYStart uint) 196 | (lpString lpctstr) (cbString uint)) 197 | :result-type bool) 198 | 199 | ; TranslateMessage 200 | (fli:define-foreign-function 201 | (TranslateMessage "TranslateMessage" :calling-convention :stdcall) 202 | ((msg-ptr ulong)) 203 | :result-type bool) 204 | 205 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 206 | ; 207 | ; Code specific to one program 208 | ; 209 | 210 | ;--- Win32 SDK Callbacks 211 | 212 | ; WndProc -- Window procedure for the window we will create 213 | (fli:define-foreign-callable 214 | (wndproc :result-type :long :calling-convention :stdcall) 215 | ((hwnd hwnd) (msg (:unsigned :long)) 216 | (wparam (:unsigned :long)) (lparam (:unsigned :long))) 217 | (case msg 218 | (#.WM_PAINT (wndproc-paint hwnd msg wparam lparam)) 219 | #+console (#.WM_DESTROY (PostQuitMessage 0) 0) 220 | (t (DefWindowProc hwnd msg wparam lparam)))) 221 | 222 | ;--- Functions called from WndProc 223 | 224 | (defun wndproc-paint (hwnd msg wparam lparam) 225 | (fli:with-dynamic-foreign-objects () 226 | (let* ((ps (fli:allocate-dynamic-foreign-object :type 'paintstruct)) 227 | (hdc (beginpaint hwnd (fli:pointer-address ps)))) 228 | ; Paint here 229 | (fli:with-foreign-string (text-ptr ec bc :external-format (external-format)) 230 | "Hello, Lisp!" 231 | (textout hdc 0 0 text-ptr ec)) 232 | (endpaint hwnd (fli:pointer-address ps)) 233 | 0))) 234 | 235 | ;--- Main Functions 236 | 237 | (defun register-class () 238 | (fli:with-foreign-string (cn-p ec bc :external-format (external-format)) 239 | *class-name* 240 | ;; Below - use with-dynamic... for automatic freeing. Make some pointers. 241 | (let (rslt 242 | (wc-p (fli:allocate-foreign-object :type 'wndclass)) 243 | (wp-p (fli:make-pointer :symbol-name "wndproc"))) 244 | (unwind-protect 245 | (progn 246 | (setf (fli:foreign-slot-value wc-p 'style) (logior cs_hredraw cs_vredraw)) 247 | (setf (fli:foreign-slot-value wc-p 'lpfnWndProc) 248 | (fli:pointer-address wp-p)) 249 | (setf (fli:foreign-slot-value wc-p 'cbClsExtra) 0) 250 | (setf (fli:foreign-slot-value wc-p 'cbWndExtra) 0) 251 | (setf (fli:foreign-slot-value wc-p 'hInstance) (current-module-handle)) 252 | (setf (fli:foreign-slot-value wc-p 'hIcon) 0) 253 | (setf (fli:foreign-slot-value wc-p 'hCursor) (LoadCursor 0 IDC_ARROW)) 254 | (setf (fli:foreign-slot-value wc-p 'hbrBackground) (1+ color_window)) 255 | (setf (fli:foreign-slot-value wc-p 'lpszMenuName) 0) 256 | (setf (fli:foreign-slot-value wc-p 'lpszClassName) cn-p) 257 | (setq rslt (RegisterClass (fli:pointer-address wc-p)))) 258 | (fli:free-foreign-object wp-p) 259 | (fli:free-foreign-object wc-p) 260 | (if (/= rslt 0) 261 | rslt 262 | (error (format nil "~&RegisterClass failed in reg-class. Error: ~a" 263 | (GetLastError)))))))) 264 | 265 | (defvar *reg-class-atom* (register-class)) 266 | 267 | ; CreateWindow 268 | (defun create-toplevel-window-run (window-name) 269 | "See create-window." 270 | (fli:with-foreign-string ;; class name pointer 271 | (cn-p ec bc :external-format (external-format)) *class-name* 272 | (fli:with-foreign-string ;; window name pointer 273 | (wn-p ec bc :external-format (external-format)) window-name 274 | (let ((hwnd (createwindowex 0 cn-p wn-p 275 | (logior ws_visible ws_overlappedwindow) 276 | cw_usedefault cw_usedefault cw_usedefault cw_usedefault 277 | ;0 0 640 480 278 | 0 0 (GetModuleHandle-current 0) 0))) 279 | (when (zerop hwnd) 280 | (error (format nil "CreateWindow failed. GetLastError is ~a" 281 | (GetLastError)))) 282 | #-console hwnd ;; Should return wParam in thread, what about here? 283 | #+console ;; Message pump the thread 284 | (fli:with-dynamic-foreign-objects () 285 | (let ((ms (fli:allocate-dynamic-foreign-object :type 'msg))) 286 | (do ((gm (GetMessage (fli:pointer-address ms) 0 0 0) 287 | (GetMessage (fli:pointer-address ms) 0 0 0))) 288 | ((zerop gm) (fli:foreign-slot-value ms 'wParam)) 289 | (TranslateMessage (fli:pointer-address ms)) 290 | (DispatchMessage (fli:pointer-address ms))))))))) 291 | 292 | ; CloseWindow 293 | (defun close-window (hwnd) 294 | (sendmessage hwnd WM_CLOSE 0 0)) 295 | 296 | (defmacro create-toplevel-window (window-name) 297 | "Creates an overlapped window with title WINDOW-NAME. 298 | Returns the window handle." 299 | #-console `(create-toplevel-window-run ,window-name) 300 | #+console `(progn 301 | (when (null (mp:list-all-processes)) 302 | (mp:initialize-multiprocessing)) 303 | (mp:process-run-function ,window-name nil 304 | 'create-toplevel-window-run ,window-name))) 305 | -------------------------------------------------------------------------------- /_includes/code/w32-appendix-b.lisp: -------------------------------------------------------------------------------- 1 | (require "win-header") 2 | 3 | (in-package :wh) 4 | (export '(initialize-appendix-b)) 5 | 6 | (DefMsgHandler appendix-b-proc MsgMap () 7 | (WM_PAINT 8 | (text-out "Hello, Lisp!" 0 0 :color (RGB 0 0 255)) 9 | 0)) 10 | 11 | (defun initialize-appendix-b () 12 | (DefActiveTopWindow "Hello" 'appendix-b-proc 13 | :style ws_overlappedwindow :width 200 :height 100 :title "Hello Program")) 14 | -------------------------------------------------------------------------------- /_includes/code/w32-appendix-c.lisp: -------------------------------------------------------------------------------- 1 | (require "win-header") 2 | 3 | (in-package :wh) 4 | (export '(initialize-appendix-c)) 5 | 6 | ; Following helps group the radio buttons 7 | (defparameter *radio-list* nil) 8 | 9 | ; Define a message handler routine named testproc, inheriting 10 | ; from the base MsgMap. Others could inherit from this one... 11 | (DefMsgHandler appendix-c-proc MsgMap (:hbrbackground (1+ color_btnface)) 12 | (WM_CREATE 13 | 14 | (DefGroupBox hwndGroupBox "Wish List" 25 30 300 70) ;; left top width height 15 | 16 | (DefPushButton hwndB1 "More Money" 30 48 90 40 17 | ((mMessageBox "Quit buying Lisp books." "You requested more money."))) 18 | 19 | (DefPushButton hwndB2 "Youth" 121 48 90 40 20 | ((mMessageBox "Sorry, not even Lisp can help you there." "You requested Youth."))) 21 | 22 | (DefPushButton hwndB3 "Better Lisp Skills" 212 48 90 40 23 | ((mMessageBox "Keep practising." "You requested better Lisp skills."))) 24 | 25 | (DefRadioButton hwndRB1 "Lisp is cool" 40 105 100 20 26 | :hwndlist *radio-list*) 27 | 28 | (DefRadioButton hwndRB2 "Lisp is boss" 200 105 100 20 29 | :hwndlist *radio-list*) 30 | 31 | (SetCheck *hwndRB1*) 32 | 33 | (with-listview (DefListView hwndListView "" 25 150 300 200) 34 | ((col "Lisp Book Title" 90) 35 | (col "ISBN" 66) 36 | (col "Author" 150))) 37 | 38 | (DefEdit hwndEdit "Learning Lisp is fun" 40 372 160 20 ()) 39 | 40 | (DefAutoCheckBox hwndCheckBox "I like it" 210 372 70 20 41 | ((if (IsChecked *hwndCheckBox*) 42 | (mMessageBox "I'm glad you like it" "Atta boy!") 43 | (mMessageBox "Don't you like it?" "Whatsamatta U.")))) 44 | 0) 45 | 46 | (WM_PAINT 47 | (with-font "Arial" -20 font-normal 0 0 0 ;; points weight italic bold underline 48 | (text-out "I Like Lisp" 70 5 :color (RGB 0 0 255))) 49 | (with-font "Arial" -11 font-bold 0 0 0 50 | (text-out "Try it, you might like it, too!" 170 10 51 | :color (RGB 255 0 0))) 52 | 0)) 53 | 54 | (defun initialize-appendix-c () 55 | (DefActiveTopWindow "Test" 'appendix-c-proc 56 | :style ws_overlappedwindow :width 360 :height 450 57 | :title "First Lisp Screenshot")) 58 | -------------------------------------------------------------------------------- /_includes/code/w32-appendix-d.c: -------------------------------------------------------------------------------- 1 | // 2 | // Example of creating a windows class in 3 | // a DLL, then invoking it from Lisp. 4 | // 5 | // Convention for this example - routines 6 | // that are written in C and are called by 7 | // lisp are prefixed by "C_" and routines 8 | // written in lisp called by C are prefixed 9 | // by "Lisp_". Note that, using LWW's FLI functions 10 | // and declarations everything could be done in Lisp. 11 | // This example shows how to bounce between Lisp and 12 | // C in the event that one might wish to "reuse" 13 | // existing C code. 14 | 15 | // 16 | // The routines C_CreateClass and C_CreateWindow are 17 | // written in C and exported from the DLL to be called 18 | // by Lisp. Note that routines exported from a DLL 19 | // are expected to use the :stdcall calling convention. 20 | // 21 | // The routine Lisp_WndProc is written in Lisp and 22 | // exported (via FLI) so that it may be called 23 | // by Windows. Note that Windows expects Lisp_WndProc 24 | // to use the :stdcall calling convention. 25 | 26 | #include 27 | #include 28 | 29 | BOOL APIENTRY DllMain( HANDLE hModule, 30 | DWORD ul_reason_for_call, 31 | LPVOID lpReserved ) 32 | { 33 | return TRUE; 34 | } 35 | 36 | // Windows calls a WndProc function to deliver a windows 37 | // event to a widget. In this example, this callback 38 | // function is written in lisp. If the WndProc function 39 | // decides to handle the event, it should return 0, else 40 | // it should call DefWindowProc. 41 | // 42 | // To get at the function, C uses a function pointer. 43 | // Lisp must set this function pointer before calling 44 | // C_CreateClass. This variable is exported from 45 | // the dll, allowing lisp to see it and to put a value 46 | // into it. 47 | // 48 | // [Obviously, we could do this in other ways, for 49 | // example, we could pass the pointer to C_CreateClass as 50 | // a parameter. Choosing to use a pointer variable shows 51 | // off more of the FLI - the fact that lisp can manipulate 52 | // C variables which reside in the DLL itself.] 53 | // 54 | __declspec(dllexport) LRESULT (CALLBACK *Lisp_WndProc)(HWND window, 55 | UINT eventType, UINT wParam, LONG lParam); 56 | 57 | // 58 | // Example Window (ExWindow) 59 | // 60 | 61 | __declspec(dllexport) void C_CreateClass (void) { 62 | WNDCLASS wClass; 63 | 64 | /* setup the window class data and register the class */ 65 | 66 | wClass.style = CS_HREDRAW | CS_VREDRAW; 67 | wClass.lpfnWndProc = Lisp_WndProc; 68 | wClass.cbClsExtra = wClass.cbWndExtra = 0; 69 | wClass.hInstance = GetModuleHandle (NULL); 70 | wClass.hIcon = LoadIcon(NULL,IDI_APPLICATION); 71 | wClass.hCursor = LoadCursor(NULL,IDC_ARROW); 72 | wClass.hbrBackground = GetStockObject(LTGRAY_BRUSH); 73 | wClass.lpszMenuName = NULL; 74 | wClass.lpszClassName = "ExWindow"; 75 | RegisterClass(&wClass); 76 | } 77 | 78 | // 79 | // create and display a window and returns its HWND 80 | // 81 | __declspec(dllexport) HWND C_CreateWindow (void) { 82 | HWND mainWindow; 83 | STARTUPINFO startup; 84 | mainWindow = CreateWindow("ExWindow",// window class name 85 | "ExWindow", // Window caption 86 | WS_OVERLAPPEDWINDOW, // style 87 | CW_USEDEFAULT, // initial x position 88 | CW_USEDEFAULT, // initial y position 89 | CW_USEDEFAULT, // initial x extent 90 | CW_USEDEFAULT, // initial y extent 91 | NULL, //(HWND)parent, // parent window 92 | NULL, // menu handle 93 | GetModuleHandle (NULL), // program instance 94 | handle 95 | NULL); // creation parameters 96 | if (NULL == mainWindow) { 97 | long r = GetLastError(); 98 | } else { 99 | GetStartupInfo (&startup); 100 | ShowWindow(mainWindow,startup.wShowWindow); 101 | UpdateWindow(mainWindow); 102 | } 103 | return mainWindow; 104 | } 105 | -------------------------------------------------------------------------------- /_includes/code/w32-appendix-d.lisp: -------------------------------------------------------------------------------- 1 | ; 2 | ; Driver that initializes and calls a C dll 3 | ; to display a window. 4 | ; 5 | 6 | ; Define some Win32 types 7 | (fli:define-c-typedef HWND (:unsigned :long)) 8 | (fli:define-c-typedef HANDLE (:pointer :void)) 9 | (fli:define-c-typedef HDC (:pointer :void)) 10 | (fli:define-c-typedef BOOL :int) 11 | (fli:define-c-typedef wBYTE (:unsigned :char)) 12 | (fli:define-c-struct sRECT 13 | (left :LONG) 14 | (top :LONG) 15 | (right :LONG) 16 | (bottom :LONG)) 17 | (fli:define-c-typedef RECT sRECT) 18 | (fli:define-c-struct sPAINTSTRUCT ;; from winuser.h 19 | (hdc HDC) 20 | (fErase BOOL) 21 | (rcPaint RECT) 22 | (fRestore BOOL) 23 | (fIncUpdate BOOL) 24 | (rbgReserved (:c-array wBYTE 32))) 25 | (fli:define-c-typedef PAINTSTRUCT sPAINTSTRUCT) 26 | 27 | ; some constants from WINUSER.H 28 | (eval-when (compile load) 29 | (defconstant WM_COMMAND #x0111) 30 | (defconstant WM_PAINT #x000F) 31 | (defconstant WM_DESTROY #x0002)) 32 | (defconstant DT_TOP #x00000000) 33 | (defconstant DT_LEFT #x00000000) 34 | (defconstant DT_CENTER #x00000001) 35 | (defconstant DT_RIGHT #x00000002) 36 | (defconstant DT_VCENTER #x00000004) 37 | (defconstant DT_BOTTOM #x00000008) 38 | (defconstant DT_WORDBREAK #x00000010) 39 | (defconstant DT_SINGLELINE #x00000020) 40 | (defconstant DT_EXPANDTABS #x00000040) 41 | (defconstant DT_TABSTOP #x00000080) 42 | (defconstant DT_NOCLIP #x00000100) 43 | (defconstant DT_EXTERNALLEADING #x00000200) 44 | (defconstant DT_CALCRECT #x00000400) 45 | (defconstant DT_NOPREFIX #x00000800) 46 | (defconstant DT_INTERNAL #x00001000) 47 | 48 | ; some Win32 calls 49 | (fli:define-foreign-function (get-last-error "GetLastError") 50 | () :result-type :long :calling-convention :stdcall) 51 | 52 | (fli:define-foreign-function (GetClientRect "GetClientRect") 53 | ((h HWND) (l (:pointer RECT))) 54 | :result-type :long :calling-convention :stdcall) 55 | 56 | (fli:define-foreign-function (DrawText "DrawText" :dbcs) 57 | ((h HDC) (s :pointer) (nCount :int) (lprect :pointer) 58 | (uFormat (:unsigned :int))) 59 | :result-type :long :calling-convention :stdcall) 60 | 61 | (fli:define-foreign-function (BeginPaint "BeginPaint") 62 | ((h HWND) (lpPaint :pointer)) 63 | :result-type HDC :calling-convention :stdcall) 64 | 65 | (fli:define-foreign-function (EndPaint "EndPaint") 66 | ((h HWND) (lpPaint :pointer)) 67 | :result-type BOOL :calling-convention :stdcall) 68 | 69 | (fli:define-foreign-function (DefWindowProc "DefWindowProc" :dbcs) 70 | ((h HWND) (msg (:unsigned :int)) 71 | (wParam (:unsigned :int)) (lParam (:unsigned :int))) 72 | :result-type :long :calling-convention :stdcall) 73 | 74 | ; 75 | ; Exports from the DLL 76 | ; 77 | ; definition of pointer variable exported from the DLL 78 | (fli:define-foreign-variable (Lisp_WndProc "Lisp_WndProc") :type 79 | :pointer) 80 | 81 | ; definition of routines exported from the DLL 82 | (fli:define-foreign-function (C_CreateClass "C_CreateClass") 83 | () :result-type :void :calling-convention :stdcall) 84 | 85 | (fli:define-foreign-function (C_CreateWindow "C_CreateWindow") 86 | () :result-type HWND :calling-convention :stdcall) 87 | 88 | ; 89 | ; Exports from Lisp 90 | ; 91 | 92 | ; The WndProc that Windows will call when an event is 93 | ; sent to the window. Written in lisp, made callable 94 | ; from C/Windows (using :stdcall convention). 95 | ; The function closely resembles the hellowin.c code 96 | ; found in Petzhold (page 45, 5th ed), except that 97 | ; the "WM_CREATE" stuff is handled in C. 98 | ; (nb: we don't care what the name is - we use 99 | ; a pointer to this function) 100 | (fli:define-foreign-callable 101 | ("func_Lisp_WndProc" :result-type :long 102 | :calling-convention :stdcall) 103 | ((window HWND) (message (:unsigned :int)) 104 | (wParam (:unsigned :int)) (lParam (:unsigned :int))) 105 | 106 | (case message 107 | 108 | (#.WM_PAINT 109 | (fli:with-dynamic-foreign-objects ((hdc HDC) 110 | (ps PAINTSTRUCT) 111 | (rect RECT)) 112 | (fli:with-coerced-pointer (p-ps) ps 113 | (fli:with-coerced-pointer (p-rect) rect 114 | (setf hdc (BeginPaint window p-ps)) 115 | (GetClientRect window p-rect) 116 | (fli:with-foreign-string 117 | (p-str ecount bcount :external-format (ext-format)) 118 | "Hello" (declare (ignore ecount bcount)) 119 | (DrawText hdc p-str -1 p-rect 120 | (logior DT_SINGLELINE DT_CENTER DT_VCENTER)) 121 | (EndPaint window p-ps) 122 | 0))))) 123 | 124 | (#.WM_DESTROY 0) ;; if we use PostQuitMessage here, LWW will also 125 | exit 126 | 127 | (otherwise (DefWindowProc window message wParam lParam)))) 128 | 129 | (defun ext-format () 130 | (if (string= (software-type) "Windows NT") 131 | :unicode 132 | :ascii)) 133 | 134 | ; 135 | ; The test program 136 | ; 137 | 138 | (defun run () 139 | ; set up the DLL 140 | (fli:register-module "debug/wintest.dll") 141 | 142 | ; poke the function pointer for the lisp windows proc 143 | (setf (Lisp_WndProc) 144 | (fli:make-pointer :symbol-name "func_Lisp_WndProc")) 145 | 146 | ; create the Windows class for ExWindow 147 | (C_CreateClass) 148 | 149 | ; instantiate an ExWindow 150 | (C_CreateWindow) 151 | 152 | ; the windows loop takes care of the rest 153 | ; nb - we've hooked to the windows loop of LWW by 154 | ; using GetWindowHandle(0) in the C code. 155 | ) 156 | -------------------------------------------------------------------------------- /_includes/footer.html: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /_layouts/chapter.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 | 6 | 7 |

8 | 📢 🎓 ⭐ 9 | Learn Common Lisp efficiently in videos, by the Cookbook's main contributor. 11 | Learn more. 12 |

13 | 14 |

15 | 📕 Get the EPUB and PDF 16 |

17 | 18 |
22 | Page source: {{ page.path }} 23 |

24 |
25 | 26 | 42 | -------------------------------------------------------------------------------- /_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | {{ page.title }} 7 | 8 | 9 | 11 | 13 | 15 | 18 | 21 | 24 | 27 | 28 | 30 | 31 | 32 | 33 | 34 |

The Common Lisp Cookbook – {{ page.title }}

35 |
36 | 37 | 38 | 39 | 40 |
41 |
42 | 43 |
44 |
45 | 46 |
47 |
Table of Contents
48 |
    49 |
    50 |
    51 | 52 |
    53 |

    The Common Lisp Cookbook – {{ page.title }}

    54 | 55 | {{ content }} 56 | 57 | 58 |
    59 | {% include footer.html %} 60 |
    61 |
    T
    O
    C
    62 |
    63 | 64 | 67 | 68 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /assets/atom-slime.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/atom-slime.png -------------------------------------------------------------------------------- /assets/cl-flamegraph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/cl-flamegraph.png -------------------------------------------------------------------------------- /assets/cl-logo-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/cl-logo-blue.png -------------------------------------------------------------------------------- /assets/cl-repl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/cl-repl.png -------------------------------------------------------------------------------- /assets/clack-errors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/clack-errors.png -------------------------------------------------------------------------------- /assets/commonlisp-vscode-alive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/commonlisp-vscode-alive.png -------------------------------------------------------------------------------- /assets/commonlisp-vscode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/commonlisp-vscode.png -------------------------------------------------------------------------------- /assets/coverage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/coverage.png -------------------------------------------------------------------------------- /assets/editor-sublime.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/editor-sublime.png -------------------------------------------------------------------------------- /assets/emacs-company-elisp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/emacs-company-elisp.png -------------------------------------------------------------------------------- /assets/emacs-helpful.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/emacs-helpful.png -------------------------------------------------------------------------------- /assets/emacs-teaser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/emacs-teaser.png -------------------------------------------------------------------------------- /assets/emacs-which-key-minibuffer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/emacs-which-key-minibuffer.png -------------------------------------------------------------------------------- /assets/geany.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/geany.png -------------------------------------------------------------------------------- /assets/github.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Inspired by github's default code highlighting 3 | */ 4 | pre { white-space: pre; background-color: #f8f8f8; border: 1px solid #ccc; font-size: 13px; line-height: 19px; overflow: auto; padding: 6px 10px; border-radius: 3px; } 5 | pre code.hl-highlighted {white-space: pre; margin: 0; padding: 0; background: none; border: none; overflow-x: auto; font-size: 13px;} 6 | code.hl-highlighted {margin: 0 2px; padding: 0 5px; white-space: nowrap; font-family: Consolas, "Liberation Mono", Courier, monospace; background: #f8f8f8; border: 1px solid #eaeaea; border-radius: 3px;} 7 | 8 | code.hl-highlighted {color: #008080;} 9 | code.hl-highlighted .function {color: #008080;} 10 | code.hl-highlighted .function.known {color: #800603;} 11 | code.hl-highlighted .function.known.special {color: #2d2d2d; font-weight: bold;} 12 | code.hl-highlighted .keyword {color: #990073;} 13 | code.hl-highlighted .keyword.known {color: #990073;} 14 | code.hl-highlighted .symbol {color: #75a;} 15 | code.hl-highlighted .lambda-list {color: #966;} 16 | code.hl-highlighted .number {color: #800;} 17 | code.hl-highlighted .variable.known {color: #c3c;} 18 | code.hl-highlighted .variable.global {color: #939;} 19 | code.hl-highlighted .variable.constant {color: #229;} 20 | code.hl-highlighted .nil {color: #f00;} 21 | code.hl-highlighted .list {color: #222;} 22 | 23 | code.hl-highlighted .string, code.hl-highlighted .string * {color: #d14 !important;} 24 | code.hl-highlighted .comment, 25 | code.hl-highlighted .comment *, 26 | code.hl-highlighted .comment .string 27 | code.hl-highlighted .comment .string * {color: #777777 !important;} 28 | code.hl-highlighted .string .comment {color: #d14 !important;} 29 | 30 | code.hl-highlighted .list.active {display: inline-block; background: #aefff7;} 31 | -------------------------------------------------------------------------------- /assets/gui/gtk3-hello-buttons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/gui/gtk3-hello-buttons.png -------------------------------------------------------------------------------- /assets/gui/ltk-on-macos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/gui/ltk-on-macos.png -------------------------------------------------------------------------------- /assets/gui/mediaplayer-nodgui-arc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/gui/mediaplayer-nodgui-arc.png -------------------------------------------------------------------------------- /assets/gui/nodgui-feet2meters-yaru.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/gui/nodgui-feet2meters-yaru.png -------------------------------------------------------------------------------- /assets/gui/nodgui-treeview-yaru.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/gui/nodgui-treeview-yaru.png -------------------------------------------------------------------------------- /assets/gui/nuklear-test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/gui/nuklear-test.png -------------------------------------------------------------------------------- /assets/gui/nuklear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/gui/nuklear.png -------------------------------------------------------------------------------- /assets/gui/qtools-intro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/gui/qtools-intro.png -------------------------------------------------------------------------------- /assets/img-ci-build.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/img-ci-build.png -------------------------------------------------------------------------------- /assets/iup-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/iup-demo.png -------------------------------------------------------------------------------- /assets/jetbrains-slt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/jetbrains-slt.png -------------------------------------------------------------------------------- /assets/jquery.toc/jquery.toc.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Table of Contents jQuery Plugin - jquery.toc 3 | * 4 | * Copyright 2013-2016 Nikhil Dabas 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License 12 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 13 | * or implied. See the License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | (function ($) { 18 | "use strict"; 19 | 20 | // Builds a list with the table of contents in the current selector. 21 | // options: 22 | // content: where to look for headings 23 | // headings: string with a comma-separated list of selectors to be used as headings, ordered 24 | // by their relative hierarchy level 25 | var toc = function (options) { 26 | return this.each(function () { 27 | var root = $(this), 28 | data = root.data(), 29 | thisOptions, 30 | stack = [root], // The upside-down stack keeps track of list elements 31 | listTag = this.tagName, 32 | currentLevel = 0, 33 | headingSelectors; 34 | 35 | // Defaults: plugin parameters override data attributes, which override our defaults 36 | thisOptions = $.extend( 37 | {content: "body", headings: "h1,h2,h3"}, 38 | {content: data.toc || undefined, headings: data.tocHeadings || undefined}, 39 | options 40 | ); 41 | headingSelectors = thisOptions.headings.split(","); 42 | 43 | // Set up some automatic IDs if we do not already have them 44 | $(thisOptions.content).find(thisOptions.headings).attr("id", function (index, attr) { 45 | // In HTML5, the id attribute must be at least one character long and must not 46 | // contain any space characters. 47 | // 48 | // We just use the HTML5 spec now because all browsers work fine with it. 49 | // https://mathiasbynens.be/notes/html5-id-class 50 | var generateUniqueId = function (text) { 51 | // Generate a valid ID. Spaces are replaced with underscores. We also check if 52 | // the ID already exists in the document. If so, we append "_1", "_2", etc. 53 | // until we find an unused ID. 54 | 55 | if (text.length === 0) { 56 | text = "?"; 57 | } 58 | 59 | var baseId = text.replace(/\s+/g, "_"), suffix = "", count = 1; 60 | 61 | while (document.getElementById(baseId + suffix) !== null) { 62 | suffix = "_" + count++; 63 | } 64 | 65 | return baseId + suffix; 66 | }; 67 | 68 | return attr || generateUniqueId($(this).text()); 69 | }).each(function () { 70 | // What level is the current heading? 71 | var elem = $(this), level = $.map(headingSelectors, function (selector, index) { 72 | return elem.is(selector) ? index : undefined; 73 | })[0]; 74 | 75 | if (level > currentLevel) { 76 | // If the heading is at a deeper level than where we are, start a new nested 77 | // list, but only if we already have some list items in the parent. If we do 78 | // not, that means that we're skipping levels, so we can just add new list items 79 | // at the current level. 80 | // In the upside-down stack, unshift = push, and stack[0] = the top. 81 | var parentItem = stack[0].children("li:last")[0]; 82 | if (parentItem) { 83 | stack.unshift($("<" + listTag + "/>").appendTo(parentItem)); 84 | } 85 | } else { 86 | // Truncate the stack to the current level by chopping off the 'top' of the 87 | // stack. We also need to preserve at least one element in the stack - that is 88 | // the containing element. 89 | stack.splice(0, Math.min(currentLevel - level, Math.max(stack.length - 1, 0))); 90 | } 91 | 92 | // Add the list item 93 | $("
  • ").appendTo(stack[0]).append( 94 | $("").text(elem.text()).attr("href", "#" + elem.attr("id")) 95 | ); 96 | 97 | currentLevel = level; 98 | }); 99 | }); 100 | }, old = $.fn.toc; 101 | 102 | $.fn.toc = toc; 103 | 104 | $.fn.toc.noConflict = function () { 105 | $.fn.toc = old; 106 | return this; 107 | }; 108 | 109 | // Data API 110 | $(function () { 111 | toc.call($("[data-toc]")); 112 | }); 113 | }(window.jQuery)); 114 | -------------------------------------------------------------------------------- /assets/jquery.toc/jquery.toc.min.js: -------------------------------------------------------------------------------- 1 | /*! Table of Contents jQuery Plugin - jquery.toc * Copyright (c) 2013-2016 Nikhil Dabas * http://www.apache.org/licenses/LICENSE-2.0 */ 2 | !function(a){"use strict";var b=function(b){return this.each(function(){var c,d,e=a(this),f=e.data(),g=[e],h=this.tagName,i=0;c=a.extend({content:"body",headings:"h1,h2,h3"},{content:f.toc||void 0,headings:f.tocHeadings||void 0},b),d=c.headings.split(","),a(c.content).find(c.headings).attr("id",function(b,c){var d=function(a){0===a.length&&(a="?");for(var b=a.replace(/\s+/g,"_"),c="",d=1;null!==document.getElementById(b+c);)c="_"+d++;return b+c};return c||d(a(this).text())}).each(function(){var b=a(this),c=a.map(d,function(a,c){return b.is(a)?c:void 0})[0];if(c>i){var e=g[0].children("li:last")[0];e&&g.unshift(a("<"+h+"/>").appendTo(e))}else g.splice(0,Math.min(i-c,Math.max(g.length-1,0)));a("
  • ").appendTo(g[0]).append(a("").text(b.text()).attr("href","#"+b.attr("id"))),i=c})})},c=a.fn.toc;a.fn.toc=b,a.fn.toc.noConflict=function(){return a.fn.toc=c,this},a(function(){b.call(a("[data-toc]"))})}(window.jQuery); -------------------------------------------------------------------------------- /assets/jupyterpreview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/jupyterpreview.png -------------------------------------------------------------------------------- /assets/lem-sdl2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/lem-sdl2.png -------------------------------------------------------------------------------- /assets/lem-terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/lem-terminal.png -------------------------------------------------------------------------------- /assets/lem1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/lem1.png -------------------------------------------------------------------------------- /assets/lispworks-graphical-inspector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/lispworks-graphical-inspector.png -------------------------------------------------------------------------------- /assets/lispworks/class-browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/lispworks/class-browser.png -------------------------------------------------------------------------------- /assets/lispworks/function-call-browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/lispworks/function-call-browser.png -------------------------------------------------------------------------------- /assets/lispworks/process-browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/lispworks/process-browser.png -------------------------------------------------------------------------------- /assets/lispworks/stepper.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/lispworks/stepper.gif -------------------------------------------------------------------------------- /assets/lispworks/toolbar-debugger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/lispworks/toolbar-debugger.png -------------------------------------------------------------------------------- /assets/lispworks/two-sided-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/lispworks/two-sided-view.png -------------------------------------------------------------------------------- /assets/log4cl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/log4cl.png -------------------------------------------------------------------------------- /assets/portacle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/portacle.png -------------------------------------------------------------------------------- /assets/prove-report.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/prove-report.png -------------------------------------------------------------------------------- /assets/slime-inspector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/slime-inspector.png -------------------------------------------------------------------------------- /assets/slime-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/slime-menu.png -------------------------------------------------------------------------------- /assets/slime.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/slime.png -------------------------------------------------------------------------------- /assets/slimv.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/slimv.jpg -------------------------------------------------------------------------------- /assets/sockets-lisp-chat.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/sockets-lisp-chat.gif -------------------------------------------------------------------------------- /assets/style.scss: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | @charset 'utf-8'; 5 | 6 | img{ 7 | max-width:100%; 8 | } 9 | #toc a{ 10 | color: #696969; 11 | } 12 | #title-xs{ 13 | display:none; 14 | } 15 | #logo-container{ 16 | z-index:1; // so the TOC is above the announce and the "page source" bar. 17 | height:90px; 18 | width:23%; 19 | margin-left:1%; 20 | margin-right:1%; 21 | position:fixed; 22 | } 23 | 24 | @media only screen and (max-width: 576px) { 25 | #searchform-container { 26 | display: none; 27 | } 28 | } 29 | 30 | #logo{ 31 | width:auto; 32 | height:70px; 33 | margin:15px; 34 | } 35 | #toc-title{ 36 | display:none; 37 | } 38 | 39 | #content-container { 40 | margin-left:25%; 41 | width:75%; 42 | padding:2%; 43 | position:static; 44 | overflow-y:scroll; 45 | max-height:100%; 46 | } 47 | 48 | 49 | // toc-btn display is handled by toggle-toc.js, to be able to not render it on root 50 | 51 | @media only screen and (max-width:576px){ 52 | #logo{ 53 | display:none; 54 | } 55 | #title-xs{ 56 | padding:2%; 57 | width:100%; 58 | position:static; 59 | margin-left:0; 60 | display:block; 61 | } 62 | #title-non-xs{ 63 | display:none; 64 | } 65 | #toc-title{ 66 | display:block; 67 | background-color:#111111; 68 | color:orange; 69 | text-align:center; 70 | width:100%; 71 | padding:0; 72 | } 73 | #toc-container{ 74 | display:block; 75 | position:fixed; 76 | margin:0; 77 | padding:2%; 78 | overflow-y:auto; 79 | top:0; 80 | width:80%; 81 | height:100vh; 82 | background-color:#eeeeee; 83 | transition:all 0.2s ease; 84 | } 85 | .toc-close{ 86 | right:-85%; 87 | box-shadow:none; 88 | } 89 | .toc-open{ 90 | right:0; 91 | box-shadow:0vw 0vw 20vw black; 92 | } 93 | #content-container{ 94 | display:block; 95 | padding-top:0; 96 | margin:0; 97 | position:static; 98 | margin-left:0; 99 | width:100%; 100 | } 101 | #toc-btn{ 102 | z-index: 2; // above the toc-container 103 | position:fixed; 104 | right:0%; 105 | bottom:10%; 106 | width:45px; 107 | padding:5px; 108 | font-size:1.25em; 109 | text-align:center; 110 | border-radius:5px; 111 | border-style:solid; 112 | // display:block; 113 | background-color:#111111; 114 | color:orange; 115 | font-weight:700; 116 | } 117 | } 118 | 119 | @media only screen and (min-width:576px){ 120 | #toc-container { 121 | margin-left:1%; 122 | margin-right:1%; 123 | margin-top:25px; 124 | margin-bottom:10%; 125 | padding:1%; 126 | width:23%; 127 | position:fixed; 128 | height:calc(90% - 90px); // logo + searchbox 129 | overflow-y:auto; 130 | } 131 | } 132 | 133 | @media only screen and (min-width:576px) and (max-width:991px){ 134 | #toc-container { 135 | width: 30%; 136 | } 137 | #content-container { 138 | width:70%; 139 | margin-left:30%; 140 | } 141 | } 142 | 143 | footer { 144 | color: darkgrey; 145 | 146 | } 147 | 148 | .page-source { 149 | background-color: #add8e6; 150 | padding: 1em; 151 | text-align: center; 152 | opacity: 0.9; 153 | } 154 | 155 | .announce-neutral { 156 | background-color: #add8e6; 157 | padding: 1em; 158 | text-align: center; 159 | opacity: 0.9; 160 | } 161 | .announce { 162 | // background-color: #8dd090; 163 | padding: 1em; 164 | text-align: center; 165 | 166 | // Orange announce: 167 | // background-color: #f48224; 168 | // color: white; 169 | background-color: gold; 170 | // background-color: rgba(255, 0, 0, 0.6); 171 | color: #3b3b3b; 172 | margin: 1em 0 1em 0; 173 | } 174 | 175 | .announce-green { 176 | // background-color: MediumAquamarine 177 | background-color: MediumAquamarine 178 | } 179 | 180 | .announce a { 181 | text-decoration: underline; 182 | // color: white; 183 | color: #3b3b3b; 184 | } 185 | 186 | ul{ 187 | margin-left:0px; 188 | } 189 | ul ul{ 190 | margin-left:-16px; 191 | } 192 | ul ul ul{ 193 | margin-left:-26px; 194 | } 195 | 196 | // Alert / Info boxes 197 | 198 | .info-box { 199 | // note: if element is inside a

    then bootstrap adds 10px padding to the bottom. 200 | padding: 17px; 201 | } 202 | 203 | .danger { 204 | background-color: #ffdddd; 205 | border-left: 6px solid #f44336; 206 | } 207 | 208 | .success { 209 | background-color: #ddffdd; 210 | border-left: 6px solid #4CAF50; 211 | } 212 | 213 | .info { 214 | background-color: #e7f3fe; 215 | border-left: 6px solid #2196F3; 216 | } 217 | 218 | 219 | .warning { 220 | background-color: #ffffcc; 221 | border-left: 6px solid #ffeb3b; 222 | } 223 | -------------------------------------------------------------------------------- /assets/toggle-toc.js: -------------------------------------------------------------------------------- 1 | $(document).ready( function () { 2 | TOCVisible = false; 3 | 4 | function toggleTOCButton(e){ 5 | if (window.location.pathname == "/cl-cookbook/"){ 6 | document.getElementById("toc-btn").style.display = "none"; 7 | }else if ($(document).width() <= 576){ 8 | document.getElementById("toc-btn").style.display = "block"; 9 | }else{ 10 | document.getElementById("toc-btn").style.display = "none"; 11 | } 12 | } 13 | 14 | setInterval(toggleTOCButton, 200); 15 | 16 | toggleTOC = function(){ 17 | toc = document.getElementById("toc-container"); 18 | if (!TOCVisible){ 19 | toc.classList.remove("toc-close"); 20 | toc.classList.add("toc-open"); 21 | TOCVisible = true; 22 | } 23 | else{ 24 | toc.classList.remove("toc-open"); 25 | toc.classList.add("toc-close"); 26 | TOCVisible = false; 27 | } 28 | } 29 | 30 | $('#toc-container').click(function(e) { 31 | if (($(document).width() <= 576) && ($(e.target).is('a'))){ 32 | console.log("yes!"); 33 | toggleTOC(); 34 | } 35 | }) 36 | $('#content-container').click(function(e) { 37 | if (($(e.target).is('#toc-btn')) 38 | || (($(document).width() <= 576) && TOCVisible)){ 39 | toggleTOC(); 40 | } 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /assets/vscode-gifs/attach-repl.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/vscode-gifs/attach-repl.gif -------------------------------------------------------------------------------- /assets/vscode-gifs/compile.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/vscode-gifs/compile.gif -------------------------------------------------------------------------------- /assets/vscode-gifs/debug-abort.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/vscode-gifs/debug-abort.gif -------------------------------------------------------------------------------- /assets/vscode-gifs/debug-fix.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/vscode-gifs/debug-fix.gif -------------------------------------------------------------------------------- /assets/vscode-gifs/disassemble.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/vscode-gifs/disassemble.gif -------------------------------------------------------------------------------- /assets/vscode-gifs/docker-connect.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/vscode-gifs/docker-connect.gif -------------------------------------------------------------------------------- /assets/vscode-gifs/eval.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/vscode-gifs/eval.gif -------------------------------------------------------------------------------- /assets/vscode-gifs/in-line-eval.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/vscode-gifs/in-line-eval.gif -------------------------------------------------------------------------------- /assets/vscode-gifs/macro-expand.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/vscode-gifs/macro-expand.gif -------------------------------------------------------------------------------- /assets/vscode-gifs/skeleton.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/vscode-gifs/skeleton.gif -------------------------------------------------------------------------------- /assets/weblocks-quickstart-check-task.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/weblocks-quickstart-check-task.gif -------------------------------------------------------------------------------- /assets/youtube-little-bits-lisp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/youtube-little-bits-lisp.jpg -------------------------------------------------------------------------------- /assets/zenity-prompt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/assets/zenity-prompt.png -------------------------------------------------------------------------------- /cl21.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Common Lisp for the 21st century 3 | --- 4 | 5 | CAUTION: We were excited by `CL21`, but we must admit that the project 6 | is stalling and suffers from unresolved issues. We can't recommend it 7 | anymore. Hopefully, you'll now find many equivalent features in other 8 | libraries (`generic-cl`, `rutils`, `access` and many more). 9 | 10 | 11 | [CL21](http://cl21.org/) is an experimental project redesigning Common 12 | Lisp. It makes some common things that were tedious to do much easier 13 | and brings some new welcome features: 14 | 15 | * more functional programming facilities, 16 | * more object oriented, generic functions. 17 | * new syntax (for regular expressions, hash-tables, string-interpolation,…), 18 | * symbols organized into several packages, 19 | * written in pure Common Lisp. 20 | 21 | CL21 is a bit disruptive in the sense that it redefines usual symbols 22 | (specially for the generic functions). Nevertheless, in doubt, *you can always use a regular CL symbol* by accessing it in the `cl:` package. You might want to check 23 | other related projects, that go beyond 24 | [Alexandria](https://common-lisp.net/project/alexandria) and stay 25 | "good citizens" of the CL world: 26 | 27 | - [rutils](https://github.com/vseloved/rutils/blob/master/docs/ann-rutils.md) - 28 | a comprehensive and all-encompassing suite of syntactic utilities to 29 | support modern day-to-day Common Lisp development. It adds readtable 30 | literal syntax for shorter lambdas, hash-tables and vectors 31 | (#`…`, `#h` and `#{…}`, `#v`), some generic functions 32 | (whose name start with `generic-`), 33 | [shortcuts](https://github.com/vseloved/rutils/blob/master/core/abbr.lisp) 34 | for standard operators, and many helpful functions. 35 | - [Serapeum](https://github.com/TBRSS/serapeum) is a set of utilities 36 | beyond Alexandria, as a supplement. It defines a lot of helper 37 | functions (see its 38 | [reference](https://github.com/TBRSS/serapeum/blob/master/REFERENCE.md)) 39 | for macros, data structures (including more functions for trees, 40 | queues), sequences (`partition`, `keep`,…), strings, definitions 41 | (`defalias`,…),… no new reader macros here. 42 | - [generic-cl](https://github.com/alex-gutev/generic-cl/) is a generic interface to standard Common Lisp functions. 43 | 44 | ## Motivation 45 | 46 | From [http://cl21.org/](http://cl21.org/) : 47 | 48 | > Dear Common Lispers, 49 | > 50 | > Common Lisp has the most expressive power of any modern language. It has first class functions with lexical closures, an object system with multiple-dispatch and a metaobject protocol, true macros, and more. It is ANSI standardized and has numerous high-performance implementations, many of which are free software. 51 | > 52 | > In spite of this, it has not had much success (at least in Japan). Its community is very small compared to languages like Ruby and most young Lispers are hacking with Clojure. 53 | > 54 | > Why? Common Lisp is much faster than them. Ruby has no macros and even Clojure doesn't have reader macros. Why then? 55 | > 56 | > Because these languages are well-designed and work for most people for most purposes. These languages are easy to use and the speed isn't an issue. 57 | > 58 | > Is Common Lisp sufficiently well-designed? I don't think so. You use different functions to do the same thing to different data types (elt, aref, nth). You have long names for commonly used macros (destructuring-bind, multiple-value-bind). There is no consistency in argument order (getf and gethash). To put it simply, the language is time-consuming to learn. 59 | > 60 | > Given this, how can programmers coming from other languages believe Common Lisp is the most expressive one? 61 | > 62 | > Fortunately in Common Lisp we can improve the interface with abstractions such as functions, macros, and reader macros. If you believe our language is the most expressive and powerful language, then let's justify that belief. 63 | > 64 | > We should consider the future of the language, not only for ourselves but for the next generation. 65 | 66 | 67 | ## Install and use 68 | 69 | CL21 is in Quicklisp. 70 | 71 | To get its latest version, do: 72 | 73 | ~~~lisp 74 | (ql-dist:install-dist "http://dists.cl21.org/cl21.txt") 75 | (ql:quickload :cl21) 76 | ~~~ 77 | 78 | Use: 79 | 80 | ~~~lisp 81 | (in-package :cl21-user) 82 | (defpackage myapp (:use :cl21)) 83 | (in-package :myapp) 84 | ~~~ 85 | 86 | 87 | ## Features 88 | 89 | Please bear in mind that the following is only a summary of CL21 90 | features. To be sure not to miss anything, you should read CL21's 91 | [wiki](https://github.com/cl21/cl21/wiki/Language-Difference-between-CL21-and-Common-Lisp), 92 | its automated 93 | [list of major changes from standard CL](https://github.com/cl21/cl21/blob/master/CHANGES_AUTO.markdown) 94 | and better yet, its [sources](https://github.com/cl21/cl21/tree/master/). 95 | 96 | That said, go include CL21 in your new project and enjoy, it's worth it ! 97 | 98 | ### Functional programming 99 | 100 | #### Shorter lambda 101 | 102 | `lm` is a macro for creating an anonymous function: 103 | 104 | 105 | ~~~lisp 106 | (lm (x) (typep x 'cons)) 107 | ;=> # 108 | 109 | (map (lm (x) (+ 2 x)) '(1 2 3)) 110 | ;=> (3 4 5) 111 | ~~~ 112 | 113 | `^` is a reader macro which will be expanded to `lm`. 114 | 115 | ~~~lisp 116 | ^(typep % 'cons) 117 | ;=> # 118 | 119 | (map ^(+ 2 %) '(1 2 3)) 120 | ;=> (3 4 5) 121 | ~~~ 122 | 123 | Unused arguments will be ignored automatically. 124 | 125 | ~~~lisp 126 | (map ^(random 10) (iota 10)) 127 | ;=> (6 9 5 1 3 5 4 0 7 4) 128 | ~~~ 129 | 130 | `%n` designates the nth argument (1-based). `%` is a synonym for `%1`. 131 | 132 | ~~~lisp 133 | (sort '(6 9 5 1 3 5 4 0 7 4) ^(< %1 %2)) 134 | ;=> (0 1 3 4 4 5 5 6 7 9) 135 | ~~~ 136 | 137 | #### `function` and `compose` 138 | 139 | `function` is a special operator for getting a function value from a given form. 140 | 141 | If a symbol is given, `function` returns a function value of it. 142 | 143 | ~~~lisp 144 | (function integerp) 145 | ;=> # 146 | ~~~ 147 | 148 | If a form which starts with `compose`, `and`, `or` or `not`, `function` returns a composed function. 149 | 150 | ~~~lisp 151 | (function (compose - *)) 152 | <=> (compose (function -) (function *)) 153 | 154 | (function (and integerp evenp)) 155 | <=> (conjoin (function integerp) (function evenp)) 156 | 157 | (function (or oddp zerop)) 158 | <=> (disjoin (function oddp) (function zerop)) 159 | 160 | (function (not zerop)) 161 | <=> (complement (function zerop)) 162 | ~~~ 163 | 164 | `#'` is a reader macro for `function`. 165 | 166 | ~~~lisp 167 | #'(compose - *) 168 | #'(and integerp evenp) 169 | #'(or oddp zerop) 170 | #'(not zerop) 171 | #'(and integerp (or oddp zerop)) 172 | ~~~ 173 | 174 | #### Currying 175 | 176 | CL21 gets new symbols: `curry` and `rcurry`. See also 177 | [functions](functions.html#currying-functions). 178 | 179 | #### Lazy sequences 180 | 181 | Lazy sequences in CL21 182 | ([src](https://github.com/cl21/cl21/blob/master/src/stdlib/lazy.lisp)) 183 | use the new 184 | [abstract classes (wiki)](https://github.com/cl21/cl21/wiki/Abstract-Classes). 185 | 186 | ~~~lisp 187 | (use-package :cl21.lazy) 188 | 189 | (defun fib-seq () 190 | (labels ((rec (a b) 191 | (lazy-sequence (cons a (rec b (+ a b)))))) 192 | (rec 0 1))) 193 | 194 | (take 20 (fib-seq)) 195 | ;=> (0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181) 196 | 197 | (take 3 (drop-while (lambda (x) (< x 500)) (fib-seq))) 198 | ;=> (610 987 1597) 199 | ~~~ 200 | 201 | #### Immutable data structures 202 | 203 | This actually is not included in CL21 but may be worth the addition in 204 | this section. For immutable data structures, see the 205 | [Fset](https://github.com/slburson/fset) library (in Quicklisp). 206 | 207 | 208 | ### Generic functions 209 | 210 | There are several generic functions which have the same name to CL's normal functions. 211 | 212 | * getf 213 | * equalp 214 | * emptyp 215 | * coerce 216 | 217 | ~~~lisp 218 | (defvar *hash* #H(:name "Eitaro Fukamachi" :living "Japan")) 219 | 220 | (getf *hash* :name) 221 | ;=> "Eitaro Fukamachi" 222 | 223 | (coerce *hash* 'plist) 224 | ;=> (:LIVING "Japan" :NAME "Eitaro Fukamachi") 225 | ~~~ 226 | 227 | You can define these methods for your own classes. 228 | 229 | There are also new functions: 230 | 231 | * `append` 232 | * `flatten` 233 | * `elt`: Returns the element at position INDEX of SEQUENCE or signals 234 | a ABSTRACT-METHOD-UNIMPLMENETED error if the sequence method is not 235 | implemented for the class of SEQUENCE. 236 | * `emptyp` 237 | * `equalp` 238 | * `split`, `split-if` 239 | * `drop`, `drop-while` 240 | * `take`, `take-while` 241 | * `join` 242 | * `length` 243 | * `keep`, `keep-if`, `nkeep`, `nkeep-if` 244 | * `partition`, `partition-if` 245 | 246 | ### Mapping 247 | 248 | In Common Lisp, functions which have a name starts with "map" are higher-order functions take a function and a sequence. 249 | 250 | It is same to CL21, but in CL21, "map" functions always return a value. This aims to clarify the roles of "iteration" and "mapping". 251 | 252 | For that reason, CL21 doesn't have CL's `mapc` and `mapl`. `maphash` exists, but it returns a new hash table. 253 | 254 | ~~~lisp 255 | (maphash (lm (k v) 256 | (cons k (1+ v))) 257 | #H(:a 1 :b 2)) 258 | ;=> #H(:B 3 :A 2) 259 | ~~~ 260 | 261 | `map` is similar to Common Lisp's `mapcar` except it can take any kind of sequences, not only list. 262 | 263 | ~~~lisp 264 | (map #'- '(1 2 3 4)) 265 | ;=> (-1 -2 -3 -4) 266 | 267 | (map #'- #(1 2 3 4)) 268 | ;=> #(-1 -2 -3 -4) 269 | ~~~ 270 | 271 | CL21 doesn't have `mapcar`. Use `map` instead. 272 | 273 | Filter is provided with `keep`, `keep-if` (instead of 274 | `remove-if[-not]`) and `nkeep[-if]`. 275 | 276 | ### Iteration 277 | 278 | Common Lisp has simple iteration facilities: `dolist`, `dotimes` and `dolist`. 279 | 280 | In addition, CL21 provides another one: `doeach`. 281 | 282 | `doeach` is similar to `dolist`, but it can be used with any kind of sequences and hash-table. 283 | 284 | ~~~lisp 285 | (doeach (x '("al" "bob" "joe")) 286 | (when (> (length x) 2) 287 | (princ #"${x}\n"))) 288 | ;-> bob 289 | ; joe 290 | 291 | (doeach ((key value) #H('a 2 'b 3)) 292 | (when (> value 2) 293 | (print key))) 294 | ;=> B 295 | ~~~ 296 | 297 | Destructuring binding form can be placed at the variable position. 298 | 299 | ~~~lisp 300 | (doeach ((x y) '((1 2) (2 3) (3 4))) 301 | (print (+ x y))) 302 | ;-> 3 303 | ; 5 304 | ; 7 305 | ~~~ 306 | 307 | CL21 also gets a `while` keyword. 308 | 309 | ### New data types 310 | 311 | New data types were added. 312 | 313 | * proper-list 314 | * plist 315 | * alist 316 | * octet 317 | * file-associated-stream 318 | * character-designator 319 | * function-designator 320 | * file-position-designator 321 | * list-designator 322 | * package-designator 323 | * stream-designator 324 | * string-designator 325 | 326 | Most of these are imported from [trivial-types](https://github.com/m2ym/trivial-types). 327 | 328 | ### String 329 | 330 | A double quote character is a macro character which represents a string. 331 | 332 | ~~~lisp 333 | "Hello, World!" 334 | ;=> "Hello, World!" 335 | ~~~ 336 | 337 | A backslash followed by some characters will be treated special. 338 | 339 | ~~~lisp 340 | "Hello\nWorld!" 341 | ;=> "Hello 342 | ; World!" 343 | ~~~ 344 | 345 | #### String interpolation 346 | 347 | `#"` is similar to `"`, but it allows interpolation within strings. 348 | 349 | If `${...}` or `@{...}` is seen, it will be replaced by the result value of an expression (or the last expression) inside of it. 350 | 351 | ~~~lisp 352 | #"1 + 1 = ${(+ 1 1)}" 353 | ;=> "1 + 1 = 2" 354 | ~~~ 355 | 356 | ### Hash table 357 | 358 | CL21 provides a notation for hash-tables. 359 | 360 | ~~~lisp 361 | #H(:name "Eitaro Fukamachi" :living "Japan") 362 | ;=> #H(:LIVING "Japan" :NAME "Eitaro Fukamachi") 363 | ~~~ 364 | 365 | Note this always creates a hash-table whose test function is `EQUAL`. If you want to create it with another test function, a function `hash-table` is also available. 366 | 367 | ~~~lisp 368 | (hash-table 'eq :name "Eitaro Fukamachi") 369 | ;=> #H(:NAME "Eitaro Fukamachi") 370 | ; instead of 371 | ; (defvar *hash* (make-hash-table)) 372 | ; (setf (gethash :name *hash*) "Eitaro Fukamachi") 373 | ~~~ 374 | 375 | Accessing an element is also done with `getf` (instead of `gethash`): 376 | 377 | ~~~lisp 378 | (getf *hash* :name) 379 | ~~~ 380 | 381 | Looping over a hash-table: 382 | 383 | ~~~lisp 384 | (doeach ((key val) *hash*) 385 | (when (< (length key) 2) 386 | (princ #"${x}\n"))) 387 | ~~~ 388 | 389 | Transform a hash-table into a plist: 390 | 391 | ~~~lisp 392 | (coerce *hash* 'plist) 393 | ~~~ 394 | 395 | ### Vector 396 | 397 | `#(...)` is a notation for vectors. Unlike in Common Lisp, its elements will be evaluated and it creates an adjustable vector. 398 | 399 | ~~~lisp 400 | (let ((a 1) 401 | (b 2) 402 | (c 3)) 403 | #(a b c)) 404 | ;=> #(1 2 3) 405 | ~~~ 406 | 407 | ~~~lisp 408 | (defvar *vec* #(0)) 409 | 410 | (push 1 *vec*) 411 | ;=> #(1 0) 412 | 413 | (push-back 3 *vec*) 414 | ;=> #(1 0 3) 415 | 416 | (pop *vec*) 417 | ;=> 1 418 | ~~~ 419 | 420 | ### Regular Expressions 421 | 422 | This new regexp reader macro uses the new 423 | ["Syntax"](https://github.com/cl21/cl21/wiki/Language-Difference-between-CL21-and-Common-Lisp#syntax) 424 | extension. 425 | 426 | ~~~lisp 427 | (use-package :cl21.re) 428 | 429 | (#/^(\d{4})-(\d{2})-(\d{2})$/ "2014-01-23") 430 | 431 | (re-replace #/a/ig "Eitaro Fukamachi" "α") 432 | ~~~ 433 | 434 | ### Running external programs (cl21.process) 435 | 436 | With `run-process`: 437 | 438 | ~~~lisp 439 | (use-package :cl21.process) 440 | 441 | (run-process '("ls" "-l" "/Users")) 442 | ;-> total 0 443 | ; drwxrwxrwt 5 root wheel 170 Nov 1 18:00 Shared 444 | ; drwxr-xr-x+ 174 nitro_idiot staff 5916 Mar 5 21:41 nitro_idiot 445 | ;=> # 446 | ~~~ 447 | 448 | or the #` reader macro: 449 | 450 | ~~~ 451 | #`ls -l /Users` 452 | ;=> "total 0 453 | ; drwxrwxrwt 5 root wheel 170 Nov 1 18:00 Shared 454 | ; drwxr-xr-x+ 174 nitro_idiot staff 5916 Mar 5 21:41 nitro_idiot 455 | ; " 456 | ; "" 457 | ; 0 458 | ~~~ 459 | 460 | ### Naming of constants 461 | 462 | All constant variables were renamed to the name added "+" signs before and after. 463 | 464 | ~~~lisp 465 | +pi+ 466 | ;=> 3.141592653589793d0 467 | 468 | +array-rank-limit+ 469 | ;=> 65529 470 | ~~~ 471 | 472 | ### CL21 Standard Library 473 | 474 | CL21 Standard Library is a set of libraries that are distributed with CL21. It is intended to offer a wide range of facilities. 475 | 476 | We are working on increasing the number of it. Currently, the following packages are provided. 477 | 478 | * cl21.re 479 | * cl21.process 480 | * cl21.os 481 | * cl21.lazy 482 | * cl21.abbr 483 | -------------------------------------------------------------------------------- /contributors.md: -------------------------------------------------------------------------------- 1 | # APPENDIX: Contributors 2 | 3 | Thank you to all contributors, as well as to the people reviewing pull requests whose name won't appear here. 4 | 5 | The contributors on Github are: 6 | 7 | 8 | 9 | * vindarel 10 | * Paul Nathan 11 | * nhabedi [^nhabedi] 12 | * Fernando Borretti 13 | * bill_clementson 14 | * chuchana 15 | * Ben Dudson 16 | * YUE Daian 17 | * Pierre Neidhardt 18 | * Rommel MARTINEZ 19 | * digikar99 20 | * nicklevine 21 | * Dmitry Petrov 22 | * otjura 23 | * skeptomai 24 | * alx-a 25 | * jgart 26 | * thegoofist 27 | * Francis St-Amour 28 | * Johan Widén 29 | * emres 30 | * jdcal 31 | * Boutade 32 | * airfoyle 33 | * contrapunctus 34 | * mvilleneuve 35 | * Alex Ponomarev 36 | * Alexander Artemenko 37 | * Johan Sjölén 38 | * Mariano Montone 39 | * albertoriva 40 | * Blue 41 | * Daniel Keogh 42 | * David Pflug 43 | * David Sun 44 | * Jason Legler 45 | * Jiho Sung 46 | * Kilian M. Haemmerle 47 | * Matteo Landi 48 | * Nikolaos Chatzikonstantinou 49 | * Nisar Ahmad 50 | * Nisen 51 | * Vityok 52 | * ctoid 53 | * ozten 54 | * reflektoin 55 | * Ahmad Edrisy 56 | * Alberto Ferreira 57 | * Amol Dosanjh 58 | * Andrew 59 | * Andrew Hill 60 | * André Alexandre Gomes 61 | * Ankit Chandawala 62 | * August Feng 63 | * B1nj0y 64 | * Bibek Panthi 65 | * Bo Yao 66 | * Brandon Hale 67 | * Burhanuddin Baharuddin 68 | * Coin Okay 69 | * Colin Woodbury 70 | * Daniel Uber 71 | * Eric Timmons 72 | * Giorgos Makris 73 | * HiPhish 74 | * Inc0n 75 | * John Zhang 76 | * Justin 77 | * Kevin Layer 78 | * Kevin Secretan 79 | * LdBeth 80 | * Matthew Kennedy 81 | * Momozor 82 | * NCM 83 | * Noor 84 | * Paul Donnelly 85 | * Pavel Kulyov 86 | * Phi-Long Nguyen 87 | * R Primus 88 | * Ralf Doering 89 | * Salad Tea 90 | * Victor Anyakin 91 | * alaskasquirrel 92 | * blackeuler 93 | * contrapunctus-1 94 | * convert-repo 95 | * dangerdyke 96 | * grobe0ba 97 | * jthing 98 | * mavis 99 | * mwgkgk 100 | * paul-donnelly 101 | * various-and-sundry 102 | * Štěpán Němec 103 | 104 | (this list is sorted by number of commits) 105 | 106 | And the contributors on the original SourceForge version are: 107 | 108 | * Marco Antoniotti 109 | * [Zach Beane](mailto:xach@xach.com) 110 | * Pierpaolo Bernardi 111 | * [Christopher Brown](mailto:skeptomai@mac.com) 112 | * [Frederic Brunel](mailto:brunel@mail.dotcom.fr) 113 | * [Jeff Caldwell](mailto:jdcal@yahoo.com) 114 | * [Bill Clementson](mailto:bill_clementson@yahoo.com) 115 | * Martin Cracauer 116 | * [Gerald Doussot](mailto:gdoussot@yahoo.com) 117 | * [Paul Foley](mailto:mycroft@actrix.gen.nz) 118 | * Jörg-Cyril Höhle 119 | * [Nick Levine](mailto:ndl@ravenbrook.com) 120 | * [Austin King](mailto:shout@ozten.com) 121 | * [Lieven Marchand](mailto:mal@wyrd.be) 122 | * [Drew McDermott](mailto:drew.mcdermott@yale.edu) 123 | * [Kalman Reti](mailto:reti@ai.mit.edu) 124 | * [Alberto Riva](mailto:alb@chip.org) 125 | * [Rudi Schlatte](mailto:rschlatte@ist.tu-graz.ac.at) 126 | * [Emre Sevinç](mailto:emres@bilgi.edu.tr) 127 | * Paul Tarvydas 128 | * Kenny Tilton 129 | * [Reini Urban](mailto:rurban@x-ray.at) 130 | * [Matthieu Villeneuve](mailto:matthieu@matthieu-villeneuve.net) 131 | * [Edi Weitz](mailto:edi@agharta.de) 132 | 133 | Finally, the credit for finally giving birth to the project probably goes to 134 | Edi Weitz who posted [this message][msg] to [comp.lang.lisp][cll]. 135 | 136 | [cll]: news:comp.lang.lisp 137 | [msg]: http://groups.google.com/groups?selm=76be8851.0201222259.70ecbcb1%40posting.google.com 138 | 139 | [^nhabedi]: nhabedi is Edmund Weitz ;) 140 | -------------------------------------------------------------------------------- /dandelion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/dandelion.png -------------------------------------------------------------------------------- /drafts/defmodel.lisp.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: A "defmodel" macro 3 | --- 4 | 5 |   6 | 7 | 8 | ~~~lisp 9 | (defmacro defmodel (name slot-definitions) 10 | `(progn 11 | (defclass ,name () 12 | ((id :col-type serial :reader ,(symb name 'id)) 13 | ,@slot-definitions) 14 | (:metaclass dao-class) 15 | (:keys id)) 16 | (with-connection (db-params) 17 | (unless (table-exists-p ',name) 18 | (execute (dao-table-definition ',name)))) 19 | ;; Create 20 | (defmacro ,(symb name 'create) (&rest args) 21 | `(with-connection (db-params) 22 | (make-dao ',',name ,@args))) 23 | ;; Read 24 | (defun ,(symb name 'get-all) () 25 | (with-connection (db-params) 26 | (select-dao ',name))) 27 | (defun ,(symb name 'get) (id) 28 | (with-connection (db-params) 29 | (get-dao ',name id))) 30 | (defmacro ,(symb name 'select) (sql-test &optional sort) 31 | `(with-connection (db-params) 32 | (select-dao ',',name ,sql-test ,sort))) 33 | ;; Update 34 | (defun ,(symb name 'update) (,name) 35 | (with-connection (db-params) 36 | (update-dao ,name))) 37 | ;; Delete 38 | (defun ,(symb name 'delete) (,name) 39 | (with-connection (db-params) 40 | (delete-dao ,name))))) 41 | ~~~ 42 | -------------------------------------------------------------------------------- /editor-support.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Editor support 3 | --- 4 | 5 | The editor of choice is still [Emacs](https://www.gnu.org/software/emacs/), but it is not the only one. 6 | 7 | ## Emacs 8 | 9 | [SLIME](https://github.com/slime/slime/) is the Superior Lisp 10 | Interaction Mode for Emacs. It has support for interacting with a 11 | running Common Lisp process for compilation, debugging, documentation 12 | lookup, cross-references, and so on. It works with many implementations. 13 | 14 | [Portacle](https://shinmera.github.io/portacle/) is a portable and 15 | **multi-platform** Common Lisp environment. It ships Emacs, SBCL, 16 | Quicklisp, SLIME and Git. 17 | 18 | Portacle with an open Slime REPL 20 | 21 | [plain-common-lisp](https://github.com/pascalcombier/plain-common-lisp/) 22 | is a crafted, easy-to-install Common Lisp environment for 23 | **Windows**. It ships Emacs, SBCL, Slime, Quicklisp. It also shows how 24 | to display GUI windows with Win32, Tk, IUP, ftw and Opengl. 25 | 26 | plain-common-lisp is an easy to install Lisp environment for Windows 28 | 29 | 30 | ### Using Emacs as an IDE 31 | 32 | See ["Using Emacs as an IDE"](emacs-ide.html). 33 | 34 | 35 | ## Vim & Neovim 36 | 37 | [Slimv](https://github.com/kovisoft/slimv) is a full-blown 38 | environment for Common Lisp inside of Vim. 39 | 40 | [Vlime](https://github.com/vlime/vlime) is a Common Lisp dev 41 | environment for Vim (and Neovim), similar to SLIME for Emacs and SLIMV 42 | for Vim. 43 | 44 | The Slimv plugin with an open REPL 46 | 47 | [cl-neovim](https://github.com/adolenc/cl-neovim/) makes it possible to write 48 | Neovim plugins in Common Lisp. 49 | 50 | [quicklisp.nvim](https://gitlab.com/HiPhish/quicklisp.nvim) is a Neovim 51 | frontend for Quicklisp. 52 | 53 | [Slimv_box](https://github.com/justin2004/slimv_box) brings Vim, SBCL, ABCL, 54 | and tmux in a Docker container for a quick installation. 55 | 56 | See also: 57 | 58 | * [Lisp in Vim](https://susam.net/blog/lisp-in-vim.html) demonstrates usage and 59 | compares both Slimv and Vlime 60 | 61 | ## Pulsar (ex Atom) 62 | 63 | See [SLIMA](https://github.com/neil-lindquist/slima). This package 64 | allows you to interactively develop Common Lisp code, turning Atom, or 65 | now [Pulsar](https://github.com/pulsar-edit/pulsar), into a pretty 66 | good Lisp IDE. It features: 67 | 68 | * REPL 69 | * integrated debugger 70 | * (not a stepping debugger yet) 71 | * jump to definition 72 | * autocomplete suggestions based on your code 73 | * compile this function, compile this file 74 | * function arguments order 75 | * integrated profiler 76 | * interactive object inspection. 77 | 78 | It is based on the Swank backend, like Slime for Emacs. 79 | 80 | The SLIMA extension for Atom with an open Lisp REPL 82 | 83 | ## VSCode 84 | 85 | [Alive](https://marketplace.visualstudio.com/items?itemName=rheller.alive) makes 86 | VSCode a powerful Common Lisp development. It hooks directly into the Swank 87 | server that Emacs Slime uses and is fully compatible with VSCode's ability to 88 | develop remotely in containers, WSL, Remote machines, etc. It has no 89 | dependencies beyond a version of Common Lisp on which to run the Swank server. 90 | It can be configured to run with Quicklisp, CLPM, and Roswell. It currently 91 | supports: 92 | 93 | - Syntax highlighting 94 | - Code completion 95 | - Code formatter 96 | - Jump to definition 97 | - Snippets 98 | - REPL integration 99 | - Interactive Debugger 100 | - REPL history 101 | - Inline evaluation 102 | - Macro expand 103 | - Disassemble 104 | - Inspector 105 | - Hover Text 106 | - Rename function args and let bindings 107 | - Code folding 108 | 109 | The Alive VSCode plugin showing the interactive debugger. 110 | 111 | [commonlisp-vscode 112 | extension](https://marketplace.visualstudio.com/items?itemName=ailisp.commonlisp-vscode) 113 | works via the [cl-lsp](https://github.com/ailisp/cl-lsp) language server and 114 | it's possible to write LSP client that works in other editors. It depends 115 | heavily on [Roswell](https://roswell.github.io/Home.html). It currently 116 | supports: 117 | 118 | - running a REPL 119 | - evaluate code 120 | - auto indent, 121 | - code completion 122 | - go to definition 123 | - documentation on hover 124 | 125 | The VSCode extension with a Lisp REPL, code completion and a mini-map. 126 | 127 | ### Using VSCode with Alive 128 | 129 | See [Using VSCode with Alive](vscode-alive.html). 130 | 131 | ## JetBrains - NEW in Jan, 2023! 132 | 133 | [SLT](https://github.com/Enerccio/SLT) is a new (published on January, 134 | 2023) plugin for the suite of JetBrains' IDEs. It uses a modified SLIME/Swank 135 | protocol to commmunicate with SBCL, providing IDE capabilities for 136 | Common Lisp. 137 | 138 | It has a very good [user guide](https://github.com/Enerccio/SLT/wiki/User-Guide). 139 | 140 | At the time of writing, for its version 0.4, it supports: 141 | 142 | - REPL 143 | - symbol completion 144 | - send expressions to the REPL 145 | - interactive debugging, breakpoints 146 | - documentation display 147 | - cross-references 148 | - find symbol by name, global class/symbol search 149 | - inspector (read-only) 150 | - graphical threads list 151 | - SDK support, automatic download for Windows users 152 | - multiple implementations support: SBCL, CCL, ABCL and AllegroCL. 153 | 154 | SLT, a good Common Lisp plugin for JetBrains IDEs. 155 | 156 | 157 | ## Eclipse 158 | 159 | [Dandelion](https://github.com/Ragnaroek/dandelion) is a plugin for the 160 | Eclipse IDE. 161 | 162 | Available for Windows, Mac and Linux, built-in SBCL and CLISP support 163 | and possibility to connect other environments, interactive debugger 164 | with restarts, macro-expansion, parenthesis matching,… 165 | 166 | Dandelion, a simple Common Lisp plugin for Eclipse 167 | 168 | ## Lem 169 | 170 | [Lem](https://github.com/lem-project/lem/) is an editor tailored for Common Lisp development. Once you 171 | install it, you can start developing. Its interface resembles Emacs 172 | and SLIME (same shortcuts). It comes with an ncurses and an SDL2 173 | frontend, and other programming modes thanks to its built-in LSP client: 174 | Python, Go, Rust, JS, Nim, Scheme, HTML, CSS, plus a directory mode, a **vim layer**, and more. 175 | 176 | Lem running in a SDL2 GUI. 178 | 179 | It can be started as a REPL right away in the terminal. Run it with: 180 | 181 | lem --eval "(lem-lisp-mode:start-lisp-repl t)" 182 | 183 | So you probably want a shell alias: 184 | 185 | alias ilem='lem --eval "(lem-lisp-mode:start-lisp-repl t)"' 186 | 187 | There is more: 188 | 189 | * 🚀 [Lem on the cloud](https://www.youtube.com/watch?v=IMN7feOQOak) (video presentation) Rooms is a product that runs Lem, a text editor created in Common Lisp, in the Cloud and can be used by multiple users. 190 | * Lem on the cloud is NEW as of April, 2024. In private beta at the time of writing. 191 | 192 | Lem running in the terminal with the Lisp REPL full screen, showing a completion window. 193 | 194 | ## Sublime Text 195 | 196 | [Sublime Text](http://www.sublimetext.com/3) has now good support for 197 | Common Lisp. 198 | 199 | First install the "SublimeREPL" package and then see the options 200 | in Tools/SublimeREPL to choose your CL implementation. 201 | 202 | Then [Slyblime](https://github.com/s-clerc/slyblime) ships IDE-like 203 | features to interact with the running Lisp image. It is an 204 | implementation of SLY and it uses the same backend (SLYNK). It 205 | provides advanced features including a debugger with stack frame 206 | inspection. 207 | 208 | A Lisp REPL in Sublime Text 210 | 211 | ## LispWorks (proprietary) 212 | 213 | [LispWorks](http://www.lispworks.com/) is a Common Lisp implementation that 214 | comes with its own Integrated Development Environment (IDE) and its share of 215 | unique features, such as the CAPI GUI toolkit. It is **proprietary** and 216 | provides a **free limited version**. 217 | 218 | You can [read our LispWorks review here](lispworks.html). 219 | 220 | The LispWorks listener and the editor in the Mate desktop environment 221 | 222 | 223 | ## Geany (experimental) 224 | 225 | [Geany-lisp](https://github.com/jasom/geany-lisp) is an experimental 226 | lisp mode for the [Geany](https://geany.org/) editor. It features completion of symbols, 227 | smart indenting, jump to definition, compilation of the current file and 228 | highlighting of errors and warnings, a REPL, and a project skeleton creator. 229 | 230 | The Geany Lisp plugin showing compilation warnings 231 | 232 | 233 | ## Notebooks 234 | 235 | [common-lisp-jupyter](https://github.com/yitzchak/common-lisp-jupyter) is a Common Lisp 236 | kernel for Jupyter notebooks. 237 | 238 | You can [see a live Jupyter notebook written in Lisp here](https://nbviewer.jupyter.org/github/yitzchak/common-lisp-jupyter/blob/master/examples/about.ipynb). It is easy to install (Roswell, repo2docker and Docker recipes). 239 | 240 | A Jupyter notebook running a Common Lisp kernel, exploring the Lorentz system of differential equations, showing a colorful 3D plot with interactive controls (note: the code in the screenshot is actually not Lisp!) 242 | 243 | There is also [Darkmatter](https://github.com/tamamu/darkmatter), a notebook-style 244 | Common Lisp environment, built in Common Lisp. 245 | 246 | 247 | ## REPLs 248 | 249 | [cl-repl](https://github.com/lisp-maintainers/cl-repl) is an ipython-like REPL. It supports symbol completion, magic and shell commands, multi-line editing, editing command in a file and a simple debugger. 250 | 251 | It is available as a binary. 252 | 253 | You might also like [sbcli](https://github.com/hellerve/sbcli), an even simpler REPL with readline capabilities. It handles errors gracefully instead of showing a debugger. 254 | 255 | cl-repl 0.4.1 runnning in the terminal, built with Roswell, featuring multi-line prompts and syntax highlighting. 257 | 258 | 259 | ## Others 260 | 261 | There are some more editors out there, more or less discontinued, and 262 | free versions of other Lisp vendors, such as Allegro CL. 263 | -------------------------------------------------------------------------------- /ffi.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Foreign Function Interfaces 3 | --- 4 | 5 | The ANSI Common Lisp standard doesn't mention this topic. So almost everything that can be said here depends on your OS and your implementation. However these days, we can use the [CFFI](https://github.com/cffi/cffi) library, a portable and easy-to-use C foreign function interface. 6 | 7 | > CFFI, the Common Foreign Function Interface, purports to be a portable FFI for Common Lisp. It abstracts away the differences between the API of the native FFI's of the various Common Lisp implementations. 8 | 9 | We'll see an example right now. 10 | 11 | 12 | ## CFFI: calling a C function from the `math.h` header file. 13 | 14 | Let's use `defcfun` to interface with the foreign [ceil](https://en.cppreference.com/w/c/numeric/math/ceil) C function from `math.h`. 15 | 16 | [defcfun](https://cffi.common-lisp.dev/manual/html_node/defcfun.html) is a macro in the cffi library that generates a function with the name you give it. 17 | 18 | ~~~lisp 19 | CL-USER> (cffi:defcfun ("ceil" c-ceil) :double (number :double)) 20 | ~~~ 21 | 22 | We say that the "ceil" C function will be called "c-ceil" on our Lisp side, it takes one argument that is a double float, and it returns a number that is also a double float. 23 | 24 | Here is the above function macroexpanded with `macrostep-expand`: 25 | 26 | ~~~lisp 27 | (progn 28 | nil 29 | (defun c-ceil (number) 30 | (let ((#:g312 number)) 31 | (cffi-sys:%foreign-funcall "ceil" (:double #:g312 :double) :convention 32 | :cdecl :library :default)))) 33 | ~~~ 34 | 35 | The reason we called it `c-ceil` and not `ceil` is only for the example, so we know this is a wrapper around C. You can name it "ceil", since it doesn't designate a built-in Common Lisp function or macro. 36 | 37 | Now that we have a c-ceil function from `math.h`, let's use it! We must give it double float. 38 | 39 | ~~~lisp 40 | CL-USER> (c-ceil 5.4d0) 41 | 6.0d0 42 | ~~~ 43 | 44 | As you can see, it works! The double gets rounded up to `6.0d0` as expected. 45 | 46 | Let's try another one! This time, we'll use [floor](https://en.cppreference.com/w/c/numeric/math/floor), and we couldn't name it "floor" because this Common Lisp function exists. 47 | 48 | ~~~lisp 49 | CL-USER> (cffi:defcfun ("floor" c-floor) :double (number :double)) 50 | C-FLOOR 51 | CL-USER> (c-floor 5d0) 52 | 5.0d0 53 | CL-USER> (c-floor 5.4d0) 54 | 5.0d0 55 | ~~~ 56 | 57 | Great! 58 | 59 | One more, let's try `sqrt` from math.h, still with double floats: 60 | 61 | ~~~lisp 62 | CL-USER> (cffi:defcfun ("sqrt" c-sqrt) :double (number :double)) 63 | C-SQRT 64 | CL-USER> (c-sqrt 36.50d0) 65 | 6.041522986797286d0 66 | ~~~ 67 | 68 | We can do arithmetic with our new `c-sqrt`: 69 | 70 | ~~~lisp 71 | CL-USER> (+ 2 (c-sqrt 3d0)) 72 | 3.732050807568877d0 73 | ~~~ 74 | 75 | We can even use our new shiny `c-sqrt` to map over a list of doubles and take the square root of all of them! 76 | 77 | ~~~lisp 78 | CL-USER> (mapcar #'c-sqrt '(3d0 4d0 5d0 6d0 7.5d0 12.75d0)) 79 | (1.7320508075688772d0 2.0d0 2.23606797749979d0 2.449489742783178d0 80 | 2.7386127875258306d0 3.570714214271425d0) 81 | ~~~ 82 | -------------------------------------------------------------------------------- /fix-epub-links.sed: -------------------------------------------------------------------------------- 1 | # sed script to change internal links in markdown file 2 | # to links pandoc can use when generating EPUB from markdown. 3 | # Currently, the transformed links do not work in the web server, 4 | # so the script should change a file that is not used in the web server. 5 | # Examples of how links are transformed: 6 | # - In error_handling.md: 7 | # "[debugging section](debugging.html)" to "[debugging section][Debugging]" 8 | # It is expected that there is a section header with the title "Debugging". 9 | # - In data-structures.md: 10 | # "[strings](strings.html)" to "[strings](#strings)" 11 | # pandoc associates each section header with a unique key, here "strings". 12 | # We specify the target header represented by "key", using the syntax (#key). 13 | # This has to be used when the target syntax [section header] is ambiguous. 14 | # Usage: 15 | # sed -i -f fix-epub-links.sed full.md 16 | # Note: 17 | # The links with format like (#numbers), are there for a reason. 18 | # The (#foo) format is used when the [foo] format is ambiguous. 19 | # 20 | # arrays.md 21 | s/\(\[\(Arrays and vectors\)\]\)(data-structures.html)/\1[\2]/ 22 | # databases.md 23 | s/\(\[CLOS\]\)(clos.html)/\1[Fundamentals of CLOS]/ 24 | s/\(\[clos\]\)(clos.html)/\1[Fundamentals of CLOS]/ 25 | # data-structures.md 26 | s/\(\[pattern matching\]\)(pattern_matching.html)/\1[Pattern Matching]/ 27 | s/\(\[strings\]\)(strings.html)/\1(#strings)/ 28 | s/\(\[CLOS section\]\)(clos.html)/\1[Fundamentals of CLOS]/ 29 | # debugging.md 30 | s/\(\[error handling\]\)(error_handling.html)/\1[Error and exception handling]/ 31 | s/\(\[testing\]\)(testing.html)/\1[Testing the code]/ 32 | # dynamic-libraries.md 33 | s/\(\[\(foreign function interface\)\]\)(ffi.html)/\1[Foreign Function Interfaces]/ 34 | # editor-support.md 35 | s/\(\["\(Using Emacs as an IDE\)"\]\)(emacs-ide.html)/\1[\2]/ 36 | s/\(\[\(Using VSCode with Alive\)\]\)(vscode-alive.html)/\1[\2]/ 37 | s/\(\[read our LispWorks review here\]\)(lispworks.html)/\1[LispWorks review]/ 38 | # error_handling.md 39 | s/\(\[debugging section\]\)(debugging.html)/\1[Debugging]/ 40 | # getting-started.md 41 | s/\(\[editor-support\]\)(editor-support.html)/\1[Editor support]/ 42 | # index.md 43 | s/\(\[\(License\)\]\)(license.html)/\1[\2]/ 44 | s/\(\[Getting started\]\)(getting-started.html)/\1[Getting started with Common Lisp]/ 45 | s/\(\[\(Editor support\)\]\)(editor-support.html)/\1[\2]/ 46 | s/\(\[\(Using Emacs as an IDE\)\]\)(emacs-ide.html)/\1[\2]/ 47 | s/\(\[The LispWorks IDE\]\)(lispworks.html)/\1[LispWorks review]/ 48 | s/\(\[Functions\]\)(functions.html)/\1(#functions)/ 49 | s/\(\[Data Structures\]\)(data-structures.html)/\1[Data structures]/ 50 | s/\(\[Strings\]\)(strings.html)/\1(#strings)/ 51 | s/\(\[\(Regular Expressions\)\]\)(regexp.html)/\1[\2]/ 52 | s/\(\[Numbers\]\)(numbers.html)/\1(#numbers)/ 53 | s/\(\[Loops, iteration, mapping\]\)(iteration.html)/\1[Loop, iteration, mapping]/ 54 | s/\(\[Multidimensional Arrays\]\)(arrays.html)/\1[Multidimensional arrays]/ 55 | s/\(\[\(Dates and Times\)\]\)(dates_and_times.html)/\1[\2]/ 56 | s/\(\[\(Pattern Matching\)\]\)(pattern_matching.html)/\1[\2]/ 57 | s/\(\[\(Input\/Output\)\]\)(io.html)/\1[\2]/ 58 | s/\(\[\(Files and Directories\)\]\)(files.html)/\1[\2]/ 59 | s/\(\[Error and condition handling\]\)(error_handling.html)/\1[Error and exception handling]/ 60 | s/\(\[\(Packages\)\]\)(packages.html)/\1[\2]/ 61 | s/\(\[Macros and Backquote\]\)(macros.html)/\1[Macros]/ 62 | s/\(\[CLOS (the Common Lisp Object System)\]\)(clos.html)/\1[Fundamentals of CLOS]/ 63 | s/\(\[\(Type System\)\]\)(type.html)/\1[\2]/ 64 | s/\(\[Sockets\]\)(sockets.html)/\1[TCP\/UDP programming with sockets]/ 65 | s/\(\[\(Interfacing with your OS\)\]\)(os.html)/\1[\2]/ 66 | s/\(\[\(Foreign Function Interfaces\)\]\)(ffi.html)/\1[\2]/ 67 | s/\(\[\(Building Dynamic Libraries\)\]\)(dynamic-libraries.html)/\1[\2]/ 68 | s/\(\[\(Threads\)\]\)(process.html)/\1[\2]/ 69 | s/\(\[\(Defining Systems\)\]\)(systems.html)/\1[\2]/ 70 | s/\(\[\(Using the Win32 API\)\]\)(win32.html)/\1[\2]/ 71 | s/\(\[\(Debugging\)\]\)(debugging.html)/\1[\2]/ 72 | s/\(\[Performance Tuning\]\)(performance.html)/\1[Performance Tuning and Tips]/ 73 | s/\(\[Scripting. Building executables\]\)(scripting.html)/\1[Scripting. Command line arguments. Executables.]/ 74 | s/\(\[Testing and Continuous Integration\]\)(testing.html)/\1[Testing the code]/ 75 | s/\(\[Databases\]\)(databases.html)/\1[Database Access and Persistence]/ 76 | s/\(\[GUI programming\]\)(gui.html)/\1[GUI toolkits]/ 77 | s/\(\[\(Web development\)\]\)(web.html)/\1[\2]/ 78 | s/\(\[\(Web Scraping\)\]\)(web-scraping.html)/\1[\2]/ 79 | s/\(\[\(WebSockets\)\]\)(websockets.html)/\1[\2]/ 80 | s/\(\[\(Miscellaneous\)\]\)(misc.html)/\1[\2]/ 81 | # iteration.md 82 | s/\(\[data-structures chapter\]\)(data-structures.html)/\1[Data structures]/ 83 | # scripting.md 84 | s/\(\[error and condition handling\]\)(error_handling.html)/\1[Error and exception handling]/ 85 | # strings.md 86 | s/\(\[regexp\]\)(regexp.html)/\1[Regular Expressions]/ 87 | # web.md 88 | s/\(\[databases section\]\)(databases.html)/\1[Database Access and Persistence]/ 89 | -------------------------------------------------------------------------------- /foreword.md: -------------------------------------------------------------------------------- 1 | # Foreword 2 | 3 | 4 | 5 | > Cookbook, n. 6 | > a book containing recipes and other information about the preparation and cooking of food. 7 | 8 | The Common Lisp Cookbook is a collaborative resource to help you learn 9 | Common Lisp the language, its ecosystem and to get you started in a 10 | wide range of programming areas. It can be used by Lisp newcomers as a 11 | tutorial (getting started, functions, etc) and by everybody as a 12 | reference (loop!). 13 | 14 | We hope that these EPUB and PDF versions make the learning experience 15 | even more practical and enjoyable. 16 | 17 | 18 | > Vincent "vindarel" Dardel, for the Cookbook contributors 19 | -------------------------------------------------------------------------------- /index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: The Common Lisp Cookbook 3 | --- 4 | 5 | > Cookbook, n. 6 | > a book containing recipes and other information about the preparation and cooking of food. 7 | 8 | A Cookbook is an invaluable resource, as it shows how to do various things in a clear fashion without all the theoretical context. Sometimes you just need to look things up. While cookbooks can never replace proper documentation such as the HyperSpec or books such as Practical Common Lisp, every language deserves a good cookbook, Common Lisp included. 9 | 10 | The CL Cookbook aims to tackle all sort of topics, for the beginner and for the more advanced developer. 11 | 12 | 13 | # Content 14 | 15 | ### Getting started 16 | 17 | * [License](license.html) 18 | * [Getting started](getting-started.html) 19 | * How to install a Common Lisp implementation 20 | * How to start a Lisp REPL 21 | * How to install third-party libraries with Quicklisp 22 | * How to work with projects 23 | * [Editor support](editor-support.html) 24 | * [Using Emacs as an IDE](emacs-ide.html) 25 | * [The LispWorks IDE](lispworks.html) 26 | * [Using VSCode with Alive](vscode-alive.html) 27 | 28 | ### Language basics 29 | 30 |

    31 | 32 | * [Functions](functions.html) 33 | * [Data Structures](data-structures.html) 34 | * [Strings](strings.html) 35 | + [format](strings.html#string-formatting-format) 36 | * [Regular Expressions](regexp.html) 37 | * [Numbers](numbers.html) 38 | * [Equality](equality.html) 39 | * [Loops, iteration, mapping](iteration.html) 40 | * [Multidimensional Arrays](arrays.html) 41 | * [Dates and Times](dates_and_times.html) 42 | * [Pattern Matching](pattern_matching.html) 43 | * [Input/Output](io.html) 44 | * [Files and Directories](files.html) 45 | * [CLOS (the Common Lisp Object System)](clos.html) 46 | 47 | ### Advanced topics 48 | 49 |

    50 | 51 | * [Packages](packages.html) 52 | * [Defining Systems](systems.html) 53 | * [Error and condition handling](error_handling.html) 54 | * [Debugging](debugging.html) 55 | * [Macros and Backquote](macros.html) 56 | * [Type System](type.html) 57 | * [Concurrency and Parallelism](process.html) 58 | * [Performance Tuning](performance.html) 59 | * [Testing and Continuous Integration](testing.html) 60 | * [Scripting. Building executables](scripting.html) 61 | 62 | * [Miscellaneous](misc.html) 63 | 64 | 65 | ### Outside world 66 | 67 |

    68 | 69 | * [Interfacing with your OS](os.html) 70 | * [Databases](databases.html) 71 | * [Foreign Function Interfaces](ffi.html) 72 | * [Building Dynamic Libraries](dynamic-libraries.html) 73 | * [GUI programming](gui.html) 74 | * [Sockets](sockets.html) 75 | * [WebSockets](websockets.html) 76 | * [Web development](web.html) 77 | * [Web Scraping](web-scraping.html) 78 | 79 | * [Using the Win32 API](win32.html) 80 | 81 | 82 | 83 | ## Download in EPUB 84 | 85 | The Cookbook is also available in EPUB (and PDF) format. 86 | 87 | You can [download it directly in EPUB](https://github.com/LispCookbook/cl-cookbook/releases/download/2023-12-13/common-lisp-cookbook.epub) and [PDF](https://github.com/LispCookbook/cl-cookbook/releases/download/2023-12-13/common-lisp-cookbook.pdf), and you can **pay what you want** to further support its development: 88 | 89 | 90 | 91 |
    92 | 93 | 94 |
    95 | Donate and download the EPUB version 96 | 97 | 98 | 99 |
    100 | 101 | 102 | Thank you! 103 | 104 | 105 | ## Translations 106 | 107 | The Cookbook has been translated to: 108 | 109 | * [Chinese simplified](https://oneforalone.github.io/cl-cookbook-cn/#/) ([Github](https://github.com/oneforalone/cl-cookbook-cn)) 110 | * [Portuguese (Brazilian)](https://lisp.com.br/cl-cookbook/) ([Github](https://github.com/commonlispbr/cl-cookbook)) 111 | 112 | # Other CL Resources 113 | 114 |

    115 | 116 | * [lisp-lang.org](http://lisp-lang.org/): success stories, tutorials and style guide 117 | * [Awesome-cl](https://github.com/CodyReichert/awesome-cl), a curated list of libraries 118 | * [List of Lisp Communities](https://github.com/CodyReichert/awesome-cl#community) 119 | * [Lisp Koans](https://github.com/google/lisp-koans/) - a language learning exercise, which guides the learner progressively through many language features. 120 | * [Learn X in Y minutes - Where X = Common Lisp](https://learnxinyminutes.com/docs/common-lisp/) - Small Common Lisp tutorial covering the essentials. 121 | * [Common Lisp Libraries Read the Docs](https://common-lisp-libraries.readthedocs.io/) - the documentation of popular libraries ported to the modern and good looking Read The Docs style. 122 | * [lisp-tips](https://github.com/lisp-tips/lisp-tips/issues/) 123 | * [Common Lisp and CLOG tutorial series](https://github.com/rabbibotton/clog/blob/main/LEARN.md), a tutorial for Common Lisp and CLOG, a GUI-like library for Common Lisp based on web technologies. 124 | * [Lisp and Elements of Style](http://web.archive.org/web/20190316190256/https://www.nicklevine.org/declarative/lectures/) by Nick Levine 125 | * Pascal Costanza's [Highly Opinionated Guide to Lisp](http://www.p-cos.net/lisp/guide.html) 126 | * [Cliki](http://www.cliki.net/), Common Lisp's wiki 127 | * 📹 [Common Lisp programming: from novice to effective developer](https://www.udemy.com/course/common-lisp-programming/?referralCode=2F3D698BBC4326F94358), a video course on the Udemy platform (paywall), by one of the main Cookbook contributor. *"Thanks for supporting my work on Udemy. You can ask me for a free coupon if you are a student."* vindarel 128 | 129 | and also: [Common Lisp Pitfalls](https://github.com/LispCookbook/cl-cookbook/issues/479) by Jeff Dalton. 130 | 131 | 132 | 133 | Books 134 | 135 | * [Practical Common Lisp](http://www.gigamonkeys.com/book/) by Peter Seibel 136 | * [Common Lisp Recipes](http://weitz.de/cl-recipes/) by Edmund Weitz, published in 2016, 137 | * [Common Lisp: A Gentle Introduction to Symbolic Computation](http://www-2.cs.cmu.edu/~dst/LispBook/) by David S. Touretzky 138 | * [Successful Lisp: How to Understand and Use Common Lisp](https://successful-lisp.blogspot.com/p/httpsdrive.html) by David B. Lamkins 139 | * [On Lisp](http://www.paulgraham.com/onlisptext.html) by Paul Graham 140 | * [Common Lisp the Language, 2nd Edition](http://www-2.cs.cmu.edu/Groups/AI/html/cltl/cltl2.html) by Guy L. Steele 141 | * [CLtL2, in PDF format](https://github.com/mmontone/cltl2-doc) 142 | * [A Tutorial on Good Lisp Style](https://www.cs.umd.edu/%7Enau/cmsc421/norvig-lisp-style.pdf) by Peter Norvig and Kent Pitman 143 | 144 | Advanced books 145 | 146 | * [Loving Lisp - the Savy Programmer's Secret Weapon](https://leanpub.com/lovinglisp/) by Mark Watson 147 | * [Programming Algorithms](https://leanpub.com/progalgs) - A comprehensive guide to writing efficient programs with examples in Lisp. 148 | 149 | 150 | Specifications 151 | 152 | * [The Common Lisp HyperSpec](http://www.lispworks.com/documentation/HyperSpec/Front/index.htm) by Kent M. Pitman (also available in [Dash](https://kapeli.com/dash), [Zeal](https://zealdocs.org/) and [Velocity](https://velocity.silverlakesoftware.com/)) 153 | * [The Common Lisp Community Spec](https://cl-community-spec.github.io/pages/index.html) - a new rendering produced from the ANSI specification draft, that everyone has the right to edit. 154 | 155 | # Further remarks 156 | 157 | This is a collaborative project that aims to provide for Common Lisp something 158 | similar to the [Perl Cookbook][perl] published by O'Reilly. More details about 159 | what it is and what it isn't can be found in this [thread][thread] from 160 | [comp.lang.lisp][cll]. 161 | 162 | If you want to contribute to the CL Cookbook, please send a pull request in or 163 | file a ticket! 164 | 165 | Yes, we're talking to you! We need contributors - write a chapter that's missing 166 | and add it, find an open question and provide an answer, find bugs and report 167 | them, (If you have no idea what might be missing but would like to help, take a 168 | look at the [table of contents][toc] of the Perl Cookbook.) Don't worry about 169 | the formatting, just send plain text if you like - we'll take care about that 170 | later. 171 | 172 | Thanks in advance for your help! 173 | 174 | The pages here on Github are kept up to date. You can also download a 175 | [up to date zip file][zip] for offline browsing. More info can be found at the 176 | [Github project page][gh]. 177 | 178 | 179 | 180 |
    181 | Oh, really? book cover 182 |
    183 | 184 | 185 | [cll]: news:comp.lang.lisp 186 | [perl]: http://www.oreilly.com/catalog/cookbook/ 187 | [thread]: http://groups.google.com/groups?threadm=m3it9soz3m.fsf%40bird.agharta.de 188 | [toc]: http://www.oreilly.com/catalog/cookbook/ 189 | [zip]: https://github.com/LispCookbook/cl-cookbook/archive/master.zip 190 | [gh]: https://github.com/LispCookbook/cl-cookbook 191 | -------------------------------------------------------------------------------- /io.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Input/Output 3 | --- 4 | 5 | Let's see some useful patterns for input/output. 6 | 7 | ## Asking for user input: `read`, `read-line` 8 | 9 | The [`read`](https://cl-community-spec.github.io/pages/read.html) 10 | function, when called with no other argument, stops the world and 11 | waits for user input: 12 | 13 | ~~~lisp 14 | CL-USER> (read) 15 | | ;; <---- point waiting for input. 16 | ~~~ 17 | 18 | Type in `(1+ 2)` and you will see this result: 19 | 20 | ```lisp 21 | (1+ 2) 22 | ``` 23 | 24 | Did you expect to see 3? 25 | 26 | Let's try again: 27 | 28 | ~~~lisp 29 | (read) 30 | hello ;; our input 31 | HELLO ;; return value: a symbol, hence capitalized 32 | 33 | (read) 34 | (list a b c) ;; our input 35 | (LIST A B C) ;; return value: some s-expression. 36 | ~~~ 37 | 38 | The results were *not* evaluated. 39 | 40 | The `read` function reads source code. Not strings! It returns an 41 | s-expression. 42 | 43 | If you want a string, write it inside quotes (or see the next section). 44 | 45 | It doesn't evaluate the code yet. For this, we have `eval`: 46 | 47 | ~~~lisp 48 | (read) 49 | (1+ 2) ;; input 50 | (1+ 2) ;; return value 51 | 52 | (eval *) 53 | ;; 3 54 | ~~~ 55 | 56 | This is how we can build the most primite REPL: a Read-Eval-Print-Loop. 57 | 58 | ~~~lisp 59 | CL-USER> (loop (print (eval (read)))) 60 | (1+ 1) ;; input 61 | 62 | 2 (1+ 2) ;; result + my next input 63 | 64 | 3 65 | ~~~ 66 | 67 | The above line will loop forever and you have no way to escape it, 68 | appart `(quit)` which quits your top-level REPL too. Here's a very 69 | simple loop that quits when seeing the `q` symbol (not a string): 70 | 71 | ~~~lisp 72 | (loop for input = (read) 73 | while (not (equal input 'q)) 74 | do (print (eval input))) 75 | ~~~ 76 | 77 | 78 | ### Read input as string: `read-line` 79 | 80 | To read the input as a *string*, use [`read-line`](https://cl-community-spec.github.io/pages/read_002dline.html): 81 | 82 | ~~~lisp 83 | CL-USER> (defparameter *input* (read-line)) 84 | This is a longer input. 85 | *INPUT* 86 | 87 | CL-USER> *input* 88 | "This is a longer input." 89 | ~~~ 90 | 91 | It doesn't read multiple lines. 92 | 93 | 94 | ## Redirecting the standard output of your program 95 | 96 | You do it like this: 97 | 98 | ~~~lisp 99 | (let ((*standard-output* )) 100 | ...) 101 | ~~~ 102 | 103 | Because 104 | [`*standard-output*`](http://www.lispworks.com/documentation/HyperSpec/Body/v_debug_.htm) 105 | is a dynamic variable, all references to it during execution of the body of the 106 | `LET` form refer to the stream that you bound it to. After exiting the `LET` 107 | form, the old value of `*STANDARD-OUTPUT*` is restored, no matter if the exit 108 | was by normal execution, a `RETURN-FROM` leaving the whole function, an 109 | exception, or what-have-you. (This is, incidentally, why global variables lose 110 | much of their brokenness in Common Lisp compared to other languages: since they 111 | can be bound for the execution of a specific form without the risk of losing 112 | their former value after the form has finished, their use is quite safe; they 113 | act much like additional parameters that are passed to every function.) 114 | 115 | If the output of the program should go to a file, you can do the following: 116 | 117 | ~~~lisp 118 | (with-open-file (*standard-output* "somefile.dat" 119 | :direction :output 120 | :if-exists :supersede) 121 | ...) 122 | ~~~ 123 | 124 | [`with-open-file`](http://www.lispworks.com/documentation/HyperSpec/Body/m_w_open.htm) 125 | opens the file - creating it if necessary - binds `*standard-output*`, executes 126 | its body, closes the file, and restores `*standard-output*` to its former 127 | value. It doesn't get more comfortable than this! 128 | 129 | ## Faithful output with character streams 130 | 131 | By _faithful output_ I mean that characters with codes between 0 and 255 will be 132 | written out as is. It means, that I can `(princ (code-char 0..255) s)` to a 133 | stream and expect 8-bit bytes to be written out, which is not obvious in the 134 | times of Unicode and 16 or 32 bit character representations. It does _not_ 135 | require that the characters ä, ß, or þ must have their 136 | [`char-code`](http://www.lispworks.com/documentation/HyperSpec/Body/f_char_c.htm) 137 | in the range 0..255 - the implementation is free to use any code. But it does 138 | require that no `#\Newline` to CRLF translation takes place, among others. 139 | 140 | Common Lisp has a long tradition of distinguishing character from byte (binary) 141 | I/O, 142 | e.g. [`read-byte`](http://www.lispworks.com/documentation/HyperSpec/Body/f_rd_by.htm) 143 | and 144 | [`read-char`](http://www.lispworks.com/documentation/HyperSpec/Body/f_rd_cha.htm) 145 | are in the standard. Some implementations let both functions be called 146 | interchangeably. Others allow either one or the other. (The 147 | [simple stream proposal](https://www.cliki.net/simple-stream) defines the 148 | notion of a _bivalent stream_ where both are possible.) 149 | 150 | Varying element-types are useful as some protocols rely on the ability to send 151 | 8-Bit output on a channel. E.g. with HTTP, the header is normally ASCII and 152 | ought to use CRLF as line terminators, whereas the body can have the MIME type 153 | application/octet-stream, where CRLF translation would destroy the data. (This 154 | is how the Netscape browser on MS-Windows destroys data sent by incorrectly 155 | configured Webservers which declare unknown files as having MIME type 156 | text/plain - the default in most Apache configurations). 157 | 158 | What follows is a list of implementation dependent choices and behaviours and some code to experiment. 159 | 160 | ### SBCL 161 | 162 | To load arbitrary bytes into a string, use the `:iso-8859-1` external format. For example: 163 | 164 | ~~~lisp 165 | (uiop:read-file-string "/path/to/file" :external-format :iso-8859-1) 166 | ~~~ 167 | 168 | ### CLISP 169 | 170 | On CLISP, faithful output is possible using 171 | 172 | ~~~lisp 173 | :external-format 174 | (ext:make-encoding :charset 'charset:iso-8859-1 175 | :line-terminator :unix) 176 | ~~~ 177 | 178 | You can also use `(SETF (STREAM-ELEMENT-TYPE F) '(UNSIGNED-BYTE 8))`, where the 179 | ability to `SETF` is a CLISP-specific extension. Using `:EXTERNAL-FORMAT :UNIX` 180 | will cause portability problems, since the default character set on MS-Windows 181 | is `CHARSET:CP1252`. `CHARSET:CP1252` doesn't allow output of e.g. `(CODE-CHAR 182 | #x81)`: 183 | 184 | ~~~ 185 | ;*** - Character #\u0080 cannot be represented in the character set CHARSET:CP1252 186 | ~~~ 187 | 188 | Characters with code > 127 cannot be represented in ASCII: 189 | 190 | ~~~ 191 | ;*** - Character #\u0080 cannot be represented in the character set CHARSET:ASCII 192 | ~~~ 193 | 194 | ### AllegroCL 195 | 196 | `#+(AND ALLEGRO UNIX) :DEFAULT` (untested) - seems enough on UNIX, but would not 197 | work on the MS-Windows port of AllegroCL. 198 | 199 | ### LispWorks 200 | 201 | `:EXTERNAL-FORMAT '(:LATIN-1 :EOL-STYLE :LF)` (confirmed by Marc Battyani) 202 | 203 | ### Example 204 | 205 | Here's some sample code to play with: 206 | 207 | ~~~lisp 208 | (defvar *unicode-test-file* "faithtest-out.txt") 209 | 210 | (defun generate-256 (&key (filename *unicode-test-file*) 211 | #+CLISP (charset 'charset:iso-8859-1) 212 | external-format) 213 | (let ((e (or external-format 214 | #+CLISP (ext:make-encoding :charset charset 215 | :line-terminator :unix)))) 216 | (describe e) 217 | (with-open-file (f filename :direction :output 218 | :external-format e) 219 | (write-sequence 220 | (loop with s = (make-string 256) 221 | for i from 0 to 255 222 | do (setf (char s i) (code-char i)) 223 | finally (return s)) 224 | f) 225 | (file-position f)))) 226 | 227 | ;(generate-256 :external-format :default) 228 | ;#+CLISP (generate-256 :external-format :unix) 229 | ;#+CLISP (generate-256 :external-format 'charset:ascii) 230 | ;(generate-256) 231 | 232 | (defun check-256 (&optional (filename *unicode-test-file*)) 233 | (with-open-file (f filename :direction :input 234 | :element-type '(unsigned-byte 8)) 235 | (loop for i from 0 236 | for c = (read-byte f nil nil) 237 | while c 238 | unless (= c i) 239 | do (format t "~&Position ~D found ~D(#x~X)." i c c) 240 | when (and (= i 33) (= c 32)) 241 | do (let ((c (read-byte f))) 242 | (format t "~&Resync back 1 byte ~D(#x~X) - cause CRLF?." c c) )) 243 | (file-length f))) 244 | 245 | #| CLISP 246 | (check-256 *unicode-test-file*) 247 | (progn (generate-256 :external-format :unix) (check-256)) 248 | ; uses UTF-8 -> 385 bytes 249 | 250 | (progn (generate-256 :charset 'charset:iso-8859-1) (check-256)) 251 | 252 | (progn (generate-256 :external-format :default) (check-256)) 253 | ; uses UTF-8 + CRLF(on MS-Windows) -> 387 bytes 254 | 255 | (progn (generate-256 :external-format 256 | (ext:make-encoding :charset 'charset:iso-8859-1 :line-terminator :mac)) (check-256)) 257 | (progn (generate-256 :external-format 258 | (ext:make-encoding :charset 'charset:iso-8859-1 :line-terminator :dos)) (check-256)) 259 | |# 260 | ~~~ 261 | 262 | 263 | 264 | ## Fast bulk I/O 265 | 266 | If you need to copy a lot of data and the source and destination are both 267 | streams (of the same 268 | [element type](http://www.lispworks.com/documentation/HyperSpec/Body/26_glo_e.htm#element_type)), 269 | it's very fast to use 270 | [`read-sequence`](http://www.lispworks.com/documentation/HyperSpec/Body/f_rd_seq.htm) 271 | and 272 | [`write-sequence`](http://www.lispworks.com/documentation/HyperSpec/Body/f_wr_seq.htm): 273 | 274 | ~~~lisp 275 | (let ((buf (make-array 4096 :element-type (stream-element-type input-stream)))) 276 | (loop for pos = (read-sequence buf input-stream) 277 | while (plusp pos) 278 | do (write-sequence buf output-stream :end pos))) 279 | ~~~ 280 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: License 3 | --- 4 | 5 | Redistribution and use of the "Common Lisp Cookbook" in its original form (HTML) 6 | or in 'derived' forms (PDF, Postscript, RTF and so forth) with or without 7 | modification, are permitted provided that the following condition is met: 8 | 9 | * Redistributions must reproduce the above copyright notice, this and the 10 | following disclaimer in the document itself and/or other materials provided 11 | with the distribution. 12 | 13 | **IMPORTANT:** This document is provided by the Common Lisp Cookbook Project "as 14 | is" and any expressed or implied warranties, including, but not limited to, the 15 | implied warranties of merchantability and fitness for a particular purpose are 16 | disclaimed. In no event shall the Common Lisp Cookbook Project be liable for any 17 | direct, indirect, incidental, special, exemplary, or consequential damages 18 | (including, but not limited to, procurement of substitute goods or services; 19 | loss of use, data, or profits; or business interruption) however caused and on 20 | any theory of liability, whether in contract, strict liability, or tort 21 | (including negligence or otherwise) arising in any way out of the use of this 22 | documentation, even if advised of the possibility of such damage. 23 | 24 | LispCookbook Github Group addendum: this document is now managed in a modified format. 25 | 26 | Copyright: 27 | 2015-2022 LispCookbook Github Group 28 | 2002-2007 The Common Lisp Cookbook Project, 29 | -------------------------------------------------------------------------------- /make-cookbook.lisp: -------------------------------------------------------------------------------- 1 | 2 | ;; 3 | ;; pandoc to epub 4 | ;; calibre from epub to pdf 5 | ;; 6 | ;; To generate the EPUB, just load this file. 7 | ;; 8 | ;; Metadata is in metadata.txt 9 | ;; -> change the date 10 | 11 | (require 'asdf) 12 | 13 | (defparameter chapters 14 | (list 15 | "index.md" 16 | "license.md" 17 | "foreword.md" 18 | "getting-started.md" 19 | "editor-support.md" 20 | "emacs-ide.md" 21 | "vscode-alive.md" 22 | "lispworks.md" 23 | "functions.md" 24 | "data-structures.md" 25 | "strings.md" 26 | "numbers.md" 27 | "iteration.md" 28 | "arrays.md" 29 | "dates_and_times.md" 30 | "pattern_matching.md" 31 | "regexp.md" 32 | "io.md" 33 | "files.md" 34 | "error_handling.md" 35 | "packages.md" 36 | "macros.md" 37 | "clos.md" 38 | "type.md" 39 | "sockets.md" 40 | "os.md" 41 | "ffi.md" 42 | "dynamic-libraries.md" 43 | "process.md" 44 | "systems.md" 45 | ;; "win32.md" ; Excluded because: Out of date 46 | "debugging.md" 47 | "performance.md" 48 | "scripting.md" 49 | "testing.md" 50 | "databases.md" 51 | "gui.md" 52 | "web.md" 53 | "web-scraping.md" 54 | "websockets.md" 55 | ;; "misc.md" ; Excluded because: Lack of relevant content 56 | ;; "awesome-cl.md" 57 | "contributors.md" 58 | )) 59 | 60 | (defparameter *full-markdown* "full.md") 61 | (defparameter *bookname* "common-lisp-cookbook.epub") 62 | (defparameter *epub-command-placeholder* "pandoc -o ~a --toc metadata.txt ~a" 63 | "format with book name and sources file.") 64 | 65 | (defun reset-target () 66 | (uiop:run-program (format nil "echo > ~a" *full-markdown*))) 67 | 68 | (defun full-editing () 69 | "Transform markdown frontmatters to a title, etc." 70 | (format t "Edit the markdown...~&") 71 | (uiop:run-program (format nil "sed -i \"s/title:/# /g\" ~a" *full-markdown*)) 72 | (uiop:run-program (format nil "sed -i \"/^---/s/---/ /g\" ~a" *full-markdown*)) 73 | ;; Exclude regions that don't export correctly, like embedded videos. 74 | (uiop:run-program (format nil "sed -i \"/<\!-- epub-exclude-start -->/,/<\!-- epub-exclude-end -->/d\" ~a" *full-markdown*)) 75 | ;; Make internal links work in the generated EPUB. 76 | (uiop:run-program (format nil "sed -i -f fix-epub-links.sed ~a" *full-markdown*)) 77 | ) 78 | 79 | (defun to-epub () 80 | (format t "~&Generating ~a...~&" *bookname*) 81 | (uiop:run-program (format nil *epub-command-placeholder* *bookname* *full-markdown*))) 82 | 83 | (defun to-pdf () 84 | "Needs calibre." 85 | (format t "~&Generating the pdf...~&") 86 | (uiop:run-program (format nil "ebook-convert ~a common-lisp-cookbook.pdf" *bookname*))) 87 | 88 | (defun build-full-source () 89 | (format t "Creating the full source into ~a...~&" *full-markdown*) 90 | (loop for chap in chapters 91 | for cmd = (format nil "cat ~a >> ~a" chap *full-markdown*) 92 | do (uiop:run-program cmd)) 93 | (full-editing)) 94 | 95 | (defun generate () 96 | (reset-target) 97 | (build-full-source) 98 | (to-epub) 99 | (to-pdf)) 100 | 101 | (generate) 102 | -------------------------------------------------------------------------------- /manifest.scm: -------------------------------------------------------------------------------- 1 | (specifications->manifest 2 | '("calibre" 3 | "jekyll" 4 | "pandoc" 5 | "sbcl")) 6 | -------------------------------------------------------------------------------- /metadata.txt: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | - type: main 4 | text: The Common Lisp Cookbook 5 | - type: subtitle 6 | text: Diving into the programmable programming language 7 | creator: 8 | - role: author 9 | text: The Common Lisp Cookbook contributors 10 | rights: © January 2025, vindarel . 11 | This e-book is free of charge, but you can [pay what you want](https://ko-fi.com/s/01fee22a32) for it. 12 | cover-image: orly-cover.png 13 | ... 14 | -------------------------------------------------------------------------------- /misc.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Miscellaneous 3 | --- 4 | 5 | 6 | 7 | 8 | ## Re-using complex data structures 9 | 10 | Sometimes you want your functions to behave in a 'functional' way, i.e. return [fresh](http://www.lispworks.com/documentation/HyperSpec/Body/26_glo_f.htm#fresh) results without side effects, sometimes you want them to re-use and modify existing data in a destructive way - consider the difference between [`append`](http://www.lispworks.com/documentation/HyperSpec/Body/f_append.htm) and [`nconc`](http://www.lispworks.com/documentation/HyperSpec/Body/f_nconc.htm) for an example. 11 | 12 | Well, you can have your cake and eat it too, by using optional (or keyword) parameters. Here's an example: Let's assume you're writing a function `complex-matrix-stuff` which takes two matrices `m1` and `m2` as its arguments and computes and returns a resulting matrix the size of which depends on `m1` and `m2`, i.e. for a fresh result you'll need an empty matrix which'll be created by, say, `(make-appropriate-result-matrix-for m1 m2)`. 13 | 14 | The classical textbook way to implement this function will more or less look like this: 15 | 16 | ~~~lisp 17 | (defun complex-matrix-stuff (m1 m2) 18 | (let ((result (make-appropriate-result-matrix-for m1 m2))) 19 | ;; ... compute storing the results in RESULT 20 | result)) 21 | ~~~ 22 | 23 | And you'll use it like this: 24 | 25 | ~~~lisp 26 | (setq some-matrix (complex-matrix-stuff A B)) 27 | ~~~ 28 | 29 | But why not write it like so: 30 | 31 | ~~~lisp 32 | (defun complex-matrix-stuff (m1 m2 33 | &optional 34 | (result 35 | (make-appropriate-result-matrix-for m1 m2))) 36 | ;; ... compute storing the results in RESULT 37 | result) 38 | ~~~ 39 | 40 | Now you have it both ways. You can still "make up results" on the fly as in: 41 | 42 | ~~~lisp 43 | (setq some-matrix (complex-matrix-stuff A B)) 44 | ~~~ 45 | 46 | But you can also (destructively) re-use previously allocated matrices: 47 | 48 | ~~~lisp 49 | (complex-matrix-stuff A B some-appropriate-matrix-I-built-before) 50 | ~~~ 51 | 52 | Or use your function like this: 53 | 54 | ~~~lisp 55 | (setq some-other-matrix 56 | (complex-matrix-stuff A B some-appropriate-matrix-I-built-before)) 57 | ~~~ 58 | 59 | in which case you'll end up with: 60 | 61 | ~~~lisp 62 | * (eq some-other-matrix some-appropriate-matrix-I-built-before) 63 | 64 | T 65 | ~~~ 66 | 67 | 68 | 69 | 70 | ## Using `ADJUST-ARRAY` instead of consing up new sequences with `SUBSEQ` 71 | 72 | Most CL functions operating on sequences will accept `start` and `end` keywords so you can make them operate on a sub-sequence without actually creating it, i.e. instead of 73 | 74 | ~~~lisp 75 | (count #\a (subseq long-string from to)) 76 | ~~~ 77 | 78 | you should of course use 79 | 80 | ~~~lisp 81 | (count #\a long-string :start from :end to) 82 | ~~~ 83 | 84 | which'll yield the same result but not create an unnecessary intermediate sub-sequence. 85 | 86 | However, sometimes it looks like you can't avoid creating new data. Consider a hash table the keys of which are strings. If the key you're looking for is a sub-string of another string you'll most likely end up with 87 | 88 | ~~~lisp 89 | (gethash (subseq original-string from to) 90 | hash-table) 91 | ~~~ 92 | 93 | But you don't have to. You can create _one_ [displaced](http://www.lispworks.com/documentation/HyperSpec/Body/26_glo_d.htm#displaced_array) string and reuse it multiple times with [`adjust-array`](http://www.lispworks.com/documentation/HyperSpec/Body/f_adjust.htm): 94 | 95 | ~~~lisp 96 | (let ((substring (make-array 0 97 | :element-type 'character 98 | :displaced-to "" 99 | :displaced-index-offset 0))) 100 | ;; more code 101 | (gethash 102 | (adjust-array substring (- to from) 103 | :displaced-to original-string 104 | :displaced-index-offset from) 105 | hash-table) 106 | ;; even more code 107 | ) 108 | ~~~ 109 | -------------------------------------------------------------------------------- /numbertower.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/numbertower.png -------------------------------------------------------------------------------- /orly-cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/orly-cover.png -------------------------------------------------------------------------------- /packages.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Packages 3 | --- 4 | 5 | See: [The Complete Idiot's Guide to Common Lisp Packages][guide] 6 | 7 | ## Creating a package 8 | 9 | Here's an example package definition. It takes a name, and you 10 | probably want to `:use` the Common Lisp symbols and functions. 11 | 12 | ~~~lisp 13 | (defpackage :my-package 14 | (:use :cl)) 15 | ~~~ 16 | 17 | To start writing code for this package, go inside it: 18 | 19 | ~~~lisp 20 | (in-package :my-package) 21 | ~~~ 22 | 23 | This `in-package` macro puts you "inside" a package: 24 | 25 | - any new variable or function will be created in this package, aka in the "namespace" of this package. 26 | - you can call all this package's symbols directly, without using the package prefix. 27 | 28 | Just try! 29 | 30 | We can also use `in-package` to try packages on the REPL. Note that on 31 | a new Lisp REPL session, we are "inside" the CL-USER package. It is a 32 | regular package. 33 | 34 | Let's show you an example. We open a new .lisp file and we create a new 35 | package with a function inside our package: 36 | 37 | ~~~lisp 38 | ;; in test-package.lisp 39 | (defpackage :my-package 40 | (:use :cl)) 41 | 42 | (in-package :my-package) 43 | 44 | (defun hello () 45 | (print "Hello from my package.")) 46 | ~~~ 47 | 48 | This "hello" function lives inside "my-package". It is not exported yet. 49 | 50 | Continue below to see how to call it. 51 | 52 | ### Accessing symbols from a package 53 | 54 | As soon as you have defined a package or loaded one (with Quicklisp, 55 | or if it was defined as a dependency in your `.asd` system 56 | definition), you can access its symbols with `package:a-symbol`, using 57 | a colon as delimiter. 58 | 59 | For example: 60 | 61 | ~~~lisp 62 | (str:concat …) 63 | ~~~ 64 | 65 | When the symbol is not exported (it is "private"), use a double colon: 66 | 67 | ~~~lisp 68 | (package::non-exported-symbol) 69 | (my-package::hello) 70 | ~~~ 71 | 72 | Continuing our example: in the REPL, be sure to be in `my-package` and not in `CL-USER`. There you can call "hello" directly: 73 | 74 | ~~~lisp 75 | CL-USER> (in-package :my-package) 76 | # 77 | ;; ^^^ this creates a package object. 78 | MY-PACKAGE> (hello) 79 | ;; ^^^^ the REPL shows you the current package. 80 | "Hello from my package." 81 | ~~~ 82 | 83 | But now, come back to the CL-USER package and try to call "hello": we get an error. 84 | 85 | ~~~lisp 86 | MY-PACKAGE> (in-package :cl-user) 87 | # 88 | CL-USER> (hello) 89 | 90 | => you get the interactive debugger that says: 91 | 92 | The function COMMON-LISP-USER::HELLO is undefined. 93 | 94 | (quit) 95 | ~~~ 96 | 97 | We have to "namespace" our hello function with its package name: 98 | 99 | ~~~lisp 100 | CL-USER> (my-package::hello) 101 | "Hello from my package." 102 | ~~~ 103 | 104 | Let's export the function. 105 | 106 | ### Exporting symbols 107 | 108 | Augment our `defpackage` declaration to export our "hello" function like so: 109 | 110 | ~~~lisp 111 | (defpackage :my-package 112 | (:use :cl) 113 | (:export 114 | #:hello)) 115 | ~~~ 116 | 117 | Compile this (`C-c C-c` in Slime), and now you can call 118 | 119 | ~~~lisp 120 | CL-USER> (my-package:hello) 121 | ~~~ 122 | 123 | with a single colon. 124 | 125 | You can also use the `export` function: 126 | 127 | ~~~lisp 128 | (in-package :my-package) 129 | (export #:hello) 130 | ~~~ 131 | 132 | Observation: 133 | 134 | - exporting `:hello` without the sharpsign (`#:hello`) works too, but it will always create a new symbol. The `#:` notation does not create a new symbol. More precisely: it doesn't *intern* a new symbol in our current package. It is a detail and at this point, a personal preference to use it or not. It can be helpful to not clutter our symbols namespace, specially when we import and re-export symbols from other libraries. That way, our editor's symbols completion only shows relevant results. It is not useful for us at this point, don't worry. 135 | 136 | Now we might want to import individual symbols in order to access them right 137 | away, without the package prefix. 138 | 139 | 140 | ### Importing symbols from another package 141 | 142 | You can import exactly the symbols you need with `:import-from`: 143 | 144 | ~~~lisp 145 | (defpackage :my-package 146 | (:import-from :ppcre #:regex-replace) 147 | (:use :cl)) 148 | ~~~ 149 | 150 | Now you can call `regex-replace` from inside `my-package`, without the `ppcre` package prefix. `regex-replace` is a new symbol inside your package. It is not exported. 151 | 152 | Sometimes, we see `(:import-from :ppcre)`, without an explicit 153 | import. This helps people using ASDF's *package inferred system*. 154 | 155 | You can also use the `import` function from outside a package definition: 156 | 157 | ~~~lisp 158 | CL-USER> (import 'ppcre:regex-replace) 159 | CL-USER> (regex-replace …) 160 | ~~~ 161 | 162 | ### Importing all symbols 163 | 164 | It is a better practice to carefully choose what symbols you import from another package (read below), but we can also import all symbols at once with `:use`: 165 | 166 | ~~~lisp 167 | (defpackage :my-package 168 | (:use :cl :ppcre)) 169 | ~~~ 170 | 171 | Now you can access all variables, functions and macros of `cl-ppcre` from your `my-package` package. 172 | 173 | You can also use the `use-package` function: 174 | 175 | ~~~lisp 176 | CL-USER> (use-package 'cl-ppcre) 177 | ~~~ 178 | 179 | 180 | ### About "use"-ing packages being a bad practice 181 | 182 | `:use` is a well spread idiom. You could do: 183 | 184 | ~~~lisp 185 | (defpackage :my-package 186 | (:use :cl :ppcre)) 187 | ~~~ 188 | 189 | and now, **all** symbols that are exported by `cl-ppcre` (aka `ppcre`) 190 | are available to use directly in your package. However, this should be 191 | considered bad practice, unless you `use` another package of your 192 | project that you control. Indeed, if the external package adds a 193 | symbol, it could conflict with one of yours, or you could add one 194 | which will hide the external symbol and you might not see a warning. 195 | 196 | To quote [this thorough explanation](https://gist.github.com/phoe/2b63f33a2a4727a437403eceb7a6b4a3) (a recommended read): 197 | 198 | > USE is a bad idea in contemporary code except for internal packages that you fully control, where it is a decent idea until you forget that you mutate the symbol of some other package while making that brand new shiny DEFUN. USE is the reason why Alexandria cannot nowadays even add a new symbol to itself, because it might cause name collisions with other packages that already have a symbol with the same name from some external source. 199 | 200 | 201 | ## List all Symbols in a Package (do-external-symbols) 202 | 203 | Common Lisp provides some macros to iterate through the symbols of a 204 | package. The two most interesting are: 205 | [`DO-SYMBOLS` and `DO-EXTERNAL-SYMBOLS`][do-sym]. `DO-SYMBOLS` iterates over the 206 | symbols accessible in the package and `DO-EXTERNAL-SYMBOLS` only iterates over 207 | the external symbols (you can see them as the real package API). 208 | 209 | To print all exported symbols of a package named "PACKAGE", you can write: 210 | 211 | ~~~lisp 212 | (do-external-symbols (s (find-package "PACKAGE")) 213 | (print s)) 214 | ~~~ 215 | 216 | You can also collect all these symbols in a list by writing: 217 | 218 | ~~~lisp 219 | (let (symbols) 220 | (do-external-symbols (s (find-package "PACKAGE")) 221 | (push s symbols)) 222 | symbols) 223 | ~~~ 224 | 225 | Or you can do it with [`LOOP`][loop]. 226 | 227 | ~~~lisp 228 | (loop for s being the external-symbols of (find-package "PACKAGE") 229 | collect s) 230 | ~~~ 231 | 232 | ## Package nickname 233 | 234 | #### Package Local Nicknames (PLN) 235 | 236 | Sometimes it is handy to give a local name to an imported package to 237 | save some typing, especially when the imported package does not 238 | provide nice global nicknames. 239 | 240 | Many implementations (SBCL, CCL, ECL, Clasp, ABCL, ACL, LispWorks >= 7.2…) support Package Local Nicknames (PLN). 241 | 242 | 243 | To use a PLN you can simply do the following, for example, if you'd like to try out a local nickname in an ad-hoc fashion: 244 | 245 | ~~~lisp 246 | (uiop:add-package-local-nickname :a :alexandria) 247 | (a:iota 12) ; (0 1 2 3 4 5 6 7 8 9 10 11) 248 | ~~~ 249 | 250 | You can also set up a PLN in a `defpackage` form. The effect of PLN is totally within `mypackage` i.e. the `nickname` won't work in other packages unless defined there too. So, you don't have to worry about unintended package name clash in other libraries. 251 | 252 | ~~~lisp 253 | (defpackage :mypackage 254 | (:use :cl) 255 | (:local-nicknames (:nickname :original-package-name) 256 | (:alex :alexandria) 257 | (:re :cl-ppcre))) 258 | 259 | (in-package :mypackage) 260 | 261 | ;; You can use :nickname instead of :original-package-name 262 | (nickname:some-function "a" "b") 263 | ~~~ 264 | 265 | Another facility exists for adding nicknames to packages. The function [`RENAME-PACKAGE`](http://www.lispworks.com/documentation/HyperSpec/Body/f_rn_pkg.htm) can be used to replace the name and nicknames of a package. But it's use would mean that other libraries may not be able to access the package using the original name or nicknames. There is rarely any situation to use this. Use Package Local Nicknames instead. 266 | 267 | ### Nickname Provided by Packages 268 | 269 | When defining a package, it is trivial to give it a nickname for 270 | better user experience. But this mechanism is *global*, a nickname 271 | defined here is visible by all other packages everywhere. If you were 272 | thinking in giving a short name to a package you use often, you can 273 | get a conflict with another package. That's why *package-local* 274 | nicknames appeared. You should use them instead. 275 | 276 | Here's an example anyways, from the `prove` package: 277 | 278 | ~~~lisp 279 | (defpackage prove 280 | (:nicknames :cl-test-more :test-more) 281 | (:export #:run 282 | #:is 283 | #:ok) 284 | ~~~ 285 | 286 | Afterwards, a user may use a nickname instead of the package name to refer to this 287 | package. For example: 288 | 289 | ~~~lisp 290 | (prove:run) 291 | (cl-test-more:is) 292 | (test-more:ok) 293 | ~~~ 294 | 295 | Please note that although Common Lisp allows defining multiple nicknames for 296 | one package, too many nicknames may bring maintenance complexity to the 297 | users. Thus the nicknames shall be meaningful and straightforward. For 298 | example: 299 | 300 | ~~~lisp 301 | (defpackage #:iterate 302 | (:nicknames #:iter)) 303 | 304 | (defpackage :cl-ppcre 305 | (:nicknames :ppcre) 306 | ~~~ 307 | 308 | 309 | ### Package locks 310 | 311 | The package `common-lisp` and SBCL internal implementation packages are locked 312 | by default, including `sb-ext`. 313 | 314 | In addition, any user-defined package can be declared to be locked so that it 315 | cannot be modified by the user. Attempts to change its symbol table or 316 | redefine functions which its symbols name result in an error. 317 | 318 | More detailed information can be obtained from documents of 319 | [SBCL][sbcl-package-lock] and [CLisp][clisp-package-lock]. 320 | 321 | For example, if you try the following code: 322 | 323 | ~~~lisp 324 | (asdf:load-system :alexandria) 325 | (rename-package :alexandria :alex) 326 | ~~~ 327 | 328 | You will get the following error (on SBCL): 329 | 330 | ~~~ 331 | Lock on package ALEXANDRIA violated when renaming as ALEX while 332 | in package COMMON-LISP-USER. 333 | [Condition of type PACKAGE-LOCKED-ERROR] 334 | See also: 335 | SBCL Manual, Package Locks [:node] 336 | 337 | Restarts: 338 | 0: [CONTINUE] Ignore the package lock. 339 | 1: [IGNORE-ALL] Ignore all package locks in the context of this operation. 340 | 2: [UNLOCK-PACKAGE] Unlock the package. 341 | 3: [RETRY] Retry SLIME REPL evaluation request. 342 | 4: [*ABORT] Return to SLIME's top level. 343 | 5: [ABORT] abort thread (#) 344 | 345 | ... 346 | ~~~ 347 | 348 | If a modification is required anyway, a package named 349 | [cl-package-lock][cl-package-lock] can be used to ignore package locks. For 350 | example: 351 | 352 | ~~~lisp 353 | (cl-package-locks:without-package-locks 354 | (rename-package :alexandria :alex)) 355 | ~~~ 356 | 357 | ## See also 358 | 359 | - [Package Local Nicknames in Common Lisp](https://gist.github.com/phoe/2b63f33a2a4727a437403eceb7a6b4a3) article. 360 | 361 | [guide]: http://www.flownet.com/gat/packages.pdf 362 | [do-sym]: http://www.lispworks.com/documentation/HyperSpec/Body/m_do_sym.htm 363 | [loop]: http://www.lispworks.com/documentation/HyperSpec/Body/06_a.htm 364 | [rename-package]: http://www.lispworks.com/documentation/HyperSpec/Body/f_rn_pkg.htm 365 | [sbcl-package-lock]: http://www.sbcl.org/manual/#Package-Locks 366 | [clisp-package-lock]: https://clisp.sourceforge.io/impnotes/pack-lock.html 367 | [cl-package-lock]: https://www.cliki.net/CL-PACKAGE-LOCKS 368 | -------------------------------------------------------------------------------- /pattern_matching.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Pattern Matching 3 | --- 4 | 5 | The ANSI Common Lisp standard does not include facilities for pattern 6 | matching, but libraries existed for this task and 7 | [Trivia](https://github.com/guicho271828/trivia) became a community 8 | standard. 9 | 10 | For an introduction to the concepts of pattern matching, see [Trivia's wiki](https://github.com/guicho271828/trivia/wiki/What-is-pattern-matching%3F-Benefits%3F). 11 | 12 | Trivia matches against *a lot* of lisp objects and is extensible. 13 | 14 | The library is in Quicklisp: 15 | 16 | ~~~lisp 17 | (ql:quickload "trivia") 18 | ~~~ 19 | 20 | For the following examples, let's `use` the library: 21 | 22 | ~~~lisp 23 | (use-package :trivia) 24 | ~~~ 25 | 26 | If you prefer, you can create a package for our tests: 27 | 28 | ~~~lisp 29 | (defpackage :trivia-tests (:use :cl :trivia)) 30 | (in-package :trivia-tests) 31 | ~~~ 32 | 33 | 34 | ## Common destructuring patterns 35 | 36 | ### Match on simple values 37 | 38 | You can use `match` to test a variable against other values: 39 | 40 | ~~~lisp 41 | (let ((x 3)) 42 | (match x 43 | (0 :no-match) 44 | (1 :still-no-match) 45 | (3 :yes) 46 | (_ :other))) 47 | ;; => :yes 48 | ~~~ 49 | 50 | The same, with a list: 51 | 52 | ~~~lisp 53 | (let ((x (list :a))) 54 | (match x 55 | (0 :nope) 56 | (1 :still-nope) 57 | (3 :yes) 58 | ((list :a) :a-list) ;; <-- match (list :a) entirely. Next we'll match on the content. 59 | (_ :other))) 60 | ;; => :A-LIST 61 | ~~~ 62 | 63 | It works with strings too (wherease CL's built-in `case` doesn't): 64 | 65 | ~~~lisp 66 | (let ((x "a string")) 67 | (match x 68 | ("a string" :a-string) 69 | (_ :other))) 70 | ;; => :A-STRING 71 | ~~~ 72 | 73 | We can use this knowledge to write an elegant recursive Fibonacci function: 74 | 75 | ~~~lisp 76 | (defun fib (n) 77 | (match n 78 | (0 0) 79 | (1 1) 80 | (_ (+ (fib (- n 1)) (fib (- n 2)))))) 81 | ~~~ 82 | 83 | It would be more interesting to match against the content of our 84 | variable `x`, when it is a compound data structure: that's what we'll 85 | do in the next sections with Trivia's patterns. 86 | 87 | 88 | ### The fall-through is `_` 89 | 90 | Observe how we used `_` (the underscore) for the fall-through cause, 91 | and not `t` as it is used in `cond` or `case`. 92 | 93 | 94 | ### `cons` 95 | 96 | The `cons` pattern matches againts lists and other cons cells. 97 | 98 | The lengths of the matched object and of the pattern can be 99 | different. Below, `y` receives all the rest that is not matched by `x`. 100 | 101 | ~~~lisp 102 | (match '(1 2 3) 103 | ((cons x y) 104 | ; ^^ pattern 105 | (print x) 106 | (print y))) 107 | ;; |-> 1 108 | ;; |-> (2 3) 109 | ~~~ 110 | 111 | ### `list`, `list*` 112 | 113 | `list` is a strict pattern, it expects the length of the matched 114 | object to be the same length as its subpatterns. 115 | 116 | ~~~lisp 117 | (match '(something 2 3) 118 | ((list a b _) 119 | (values a b))) 120 | SOMETHING 121 | 2 122 | ~~~ 123 | 124 | Without the `_` placeholder, it would not match: 125 | 126 | ~~~lisp 127 | (match '(something 2 3) 128 | ((list a b) 129 | (values a b))) 130 | NIL 131 | ~~~ 132 | 133 | The `list*` pattern is flexible on the object's length: 134 | 135 | ~~~lisp 136 | (match '(something 2 3) 137 | ((list* a b) 138 | (values a b))) 139 | SOMETHING 140 | (2 3) 141 | ~~~ 142 | 143 | ~~~lisp 144 | (match '(1 2 . 3) 145 | ((list* _ _ x) 146 | x)) 147 | 3 148 | ~~~ 149 | 150 | However pay attention that if `list*` receives only one object, that 151 | object is returned, regardless of whether or not it is a list: 152 | 153 | ~~~lisp 154 | (match #(0 1 2) 155 | ((list* a) 156 | a)) 157 | #(0 1 2) 158 | ~~~ 159 | 160 | This is related to the definition of `list*` in the HyperSpec: http://clhs.lisp.se/Body/f_list_.htm. 161 | 162 | 163 | ### `vector`, `vector*` 164 | 165 | `vector` checks if the object is a vector, if the lengths are the 166 | same, and if the contents matches against each subpatterns. 167 | 168 | `vector*` is similar, but called a soft-match variant that allows if 169 | the length is larger-than-equal to the length of subpatterns. 170 | 171 | ~~~lisp 172 | (match #(1 2 3) 173 | ((vector _ x _) 174 | x)) 175 | ;; -> 2 176 | ~~~ 177 | 178 | ~~~lisp 179 | (match #(1 2 3 4) 180 | ((vector _ x _) 181 | x)) 182 | ;; -> NIL : does not match 183 | ~~~ 184 | 185 | ~~~lisp 186 | (match #(1 2 3 4) 187 | ((vector* _ x _) 188 | x)) 189 | ;; -> 2 : soft match. 190 | ~~~ 191 | 192 | ~~~ 193 | : vector | simple-vector 194 | bit-vector | simple-bit-vector 195 | string | simple-string 196 | base-string | simple-base-string | sequence 197 | ( &rest subpatterns) 198 | ~~~ 199 | 200 | ### Class and structure pattern 201 | 202 | There are three styles that are equivalent: 203 | 204 | ~~~lisp 205 | (defstruct foo bar baz) 206 | (defvar *x* (make-foo :bar 0 :baz 1) 207 | 208 | (match *x* 209 | ;; make-instance style 210 | ((foo :bar a :baz b) 211 | (values a b)) 212 | ;; with-slots style 213 | ((foo (bar a) (baz b)) 214 | (values a b)) 215 | ;; slot name style 216 | ((foo bar baz) 217 | (values bar baz))) 218 | ~~~ 219 | 220 | ### `type`, `satisfies` 221 | 222 | The `type` pattern matches if the object is of type. `satisfies` matches 223 | if the predicate returns true for the object. A lambda form is 224 | acceptable. 225 | 226 | ### `assoc`, `property`, `alist`, `plist` 227 | 228 | All these patterns first check if the pattern is a list. If that is 229 | satisfied, then they obtain the contents, and the value is matched 230 | against the subpattern. The `assoc` and `property` patterns match 231 | single values. The `alist` and `plist` patterns effectively `and` 232 | several patterns. 233 | 234 | ~~~lisp 235 | (match '(:a 1 :b 2) 236 | ((property :a 1) 'found)) 237 | ;; -> FOUND 238 | ~~~ 239 | 240 | ~~~lisp 241 | (match '(:a 1 :b 2) 242 | ((property :a n) n)) 243 | ;; -> 1 244 | ~~~ 245 | 246 | ~~~lisp 247 | (match '(:a 1 :b 2) 248 | ((property :d n) n)) 249 | ;; -> NIL 250 | ~~~ 251 | 252 | Like `cl:getf` you can add a default value to the `property` pattern. 253 | Unlike `cl:getf` you can further add a flag to catch if the default 254 | is being used. 255 | 256 | ~~~lisp 257 | (match '(:a 1 :b 2) 258 | ((property :c c 3) c)) 259 | ;; -> 3 260 | ~~~ 261 | 262 | ~~~lisp 263 | (match '(:a 1 :b 2) 264 | ((property :c c 3 foundp) (list c foundp))) 265 | ;; -> (3 NIL) 266 | ~~~ 267 | 268 | The pattern `property!` will only match if the key is actually present. 269 | 270 | ~~~lisp 271 | (match '(:a 1 :b 2) 272 | ((property :d n) (list n)) 273 | (_ 'fail)) 274 | ;; -> (NIL) 275 | ~~~ 276 | 277 | ~~~lisp 278 | (match '(:a 1 :b 2) 279 | ((property! :d n) (list n)) 280 | (_ 'fail)) 281 | ;; -> FAIL 282 | ~~~ 283 | 284 | Several properties can be matched with `plist`. 285 | 286 | ~~~lisp 287 | (match '(:a 1 :b 2) 288 | ((plist :a 1 :b x) x)) 289 | ;; -> 2 290 | ~~~ 291 | 292 | The pattern `assoc` matches association lists. It can take the `:test` keyword like `cl:assoc`. 293 | 294 | ~~~lisp 295 | (match '((a . 1) (b . 2) (c . 3)) 296 | ((assoc 'a 1) 'ok)) 297 | ;; -> OK 298 | ~~~ 299 | 300 | ~~~lisp 301 | (match '((a . 1) (b . 2) (c . 3)) 302 | ((assoc 'b x) x)) 303 | ;; -> 2 304 | ~~~ 305 | 306 | ~~~lisp 307 | (match '(("one" . 1) ("two" . 2)) 308 | ((assoc "one" x :test #'string-equal) x)) 309 | ;; -> 1 310 | ~~~ 311 | 312 | The pattern `alist` matches several elements in an association list. 313 | 314 | ~~~lisp 315 | (match '((a . 1) (b . 2) (c . 3)) 316 | ((alist ('a . 1) ('c . n)) n)) 317 | ;; -> 3 318 | ~~~ 319 | 320 | ### Array, simple-array, row-major-array patterns 321 | 322 | See https://github.com/guicho271828/trivia/wiki/Type-Based-Destructuring-Patterns#array-simple-array-row-major-array-pattern ! 323 | 324 | ## Logic based patterns 325 | 326 | We can combine any pattern with some logic. 327 | 328 | ### `and`, `or` 329 | 330 | The following: 331 | 332 | ~~~lisp 333 | (match x 334 | ((or (list 1 a) 335 | (cons a 3)) 336 | a)) 337 | ~~~ 338 | 339 | matches against both `(1 2)` and `(4 . 3)` and returns 2 and 4, respectively. 340 | 341 | ### `not` 342 | 343 | It does not match when subpattern matches. The variables used in the 344 | subpattern are not visible in the body. 345 | 346 | ## Guards 347 | 348 | Guards allow us to use patterns *and* to verify them against a predicate. 349 | 350 | The syntax is `guard` + `subpattern` + `a test form`, and the body. 351 | 352 | ~~~lisp 353 | (match (list 2 5) 354 | ((guard (list x y) ; subpattern1 355 | (= 10 (* x y))) ; test-form 356 | :ok)) 357 | ~~~ 358 | 359 | If the subpattern is true, the test form is evaluated, and if it is 360 | true it is matched against subpattern1. 361 | 362 | 363 | ## Nesting patterns 364 | 365 | Patterns can be nested: 366 | 367 | ~~~lisp 368 | (match '(:a (3 4) 5) 369 | ((list :a (list _ c) _) 370 | c)) 371 | ~~~ 372 | 373 | returns `4`. 374 | 375 | ## See more 376 | 377 | See [special patterns](https://github.com/guicho271828/trivia/wiki/Special-Patterns): `place`, `bind` and `access`. 378 | -------------------------------------------------------------------------------- /regexp.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Regular Expressions 3 | --- 4 | 5 | The [ANSI Common Lisp 6 | standard](http://www.lispworks.com/documentation/HyperSpec/index.html) 7 | does not include facilities for regular expressions, but a couple of 8 | libraries exist for this task, for instance: 9 | [cl-ppcre](https://github.com/edicl/cl-ppcre). 10 | 11 | See also the respective [Cliki: 12 | regexp](http://www.cliki.net/Regular%20Expression) page for more 13 | links. 14 | 15 | Note that some CL implementations include regexp facilities, notably 16 | [CLISP](http://clisp.sourceforge.net/impnotes.html#regexp) and 17 | [ALLEGRO 18 | CL](https://franz.com/support/documentation/current/doc/regexp.htm). If 19 | in doubt, check your manual or ask your vendor. 20 | 21 | The description provided below is far from complete, so don't forget 22 | to check the reference manual that comes along with the CL-PPCRE 23 | library. 24 | 25 | ## PPCRE 26 | 27 | [CL-PPCRE](https://github.com/edicl/cl-ppcre) (abbreviation for 28 | Portable Perl-compatible regular expressions) is a portable regular 29 | expression library for Common Lisp with a broad set of features and 30 | good performance. It has been ported to a number of Common Lisp 31 | implementations and can be easily installed (or added as a dependency) 32 | via Quicklisp: 33 | 34 | ~~~lisp 35 | (ql:quickload "cl-ppcre") 36 | ~~~ 37 | 38 | Basic operations with the CL-PPCRE library functions are described 39 | below. 40 | 41 | 42 | ### Looking for matching patterns: scan, create-scanner 43 | 44 | The `scan` function tries to match the given pattern and on success 45 | returns four multiple-values values - the start of the match, the end 46 | of the match, and two arrays denoting the beginnings and ends of 47 | register matches. On failure returns `NIL`. 48 | 49 | A regular expression pattern can be compiled with the `create-scanner` 50 | function call. A "scanner" will be created that can be used by other 51 | functions. 52 | 53 | For example: 54 | 55 | ~~~lisp 56 | (let ((ptrn (ppcre:create-scanner "(a)*b"))) 57 | (ppcre:scan ptrn "xaaabd")) 58 | ~~~ 59 | 60 | will yield the same results as: 61 | 62 | ~~~lisp 63 | (ppcre:scan "(a)*b" "xaaabd") 64 | ~~~ 65 | 66 | but will require less time for repeated `scan` calls as parsing the 67 | expression and compiling it is done only once. 68 | 69 | 70 | ### Extracting information 71 | 72 | CL-PPCRE provides several ways to extract matching fragments. 73 | 74 | #### all-matches, all-matches-as-strings 75 | 76 | The function `all-matches-as-strings` is very handy: it returns a list of matches: 77 | 78 | ~~~lisp 79 | (ppcre:all-matches-as-strings "\\d+" "numbers: 1 10 42") 80 | ;; => ("1" "10" "42") 81 | ~~~ 82 | 83 | The function `all-matches` is similar, but it returns a list of positions: 84 | 85 | ~~~lisp 86 | (ppcre:all-matches "\\d+" "numbers: 1 10 42") 87 | ;; => (9 10 11 13 14 16) 88 | ~~~ 89 | 90 | Look carefully: it actually return a list containing the start and end 91 | positions of all matches: 9 and 10 are the start and end for the first 92 | number (1), and so on. 93 | 94 | If you wanted to extract integers from this example string, simply map 95 | `parse-integer` to the result: 96 | 97 | ~~~lisp 98 | CL-USER> (ppcre:all-matches-as-strings "\\d+" "numbers: 1 10 42") 99 | ;; ("1" "10" "42") 100 | CL-USER> (mapcar #'parse-integer *) 101 | (1 10 42) 102 | ~~~ 103 | 104 | The two functions accept the usual `:start` and `:end` key arguments. Additionnaly, `all-matches-as-strings` accepts a `:sharedp` argument: 105 | 106 | > If SHAREDP is true, the substrings may share structure with TARGET-STRING. 107 | 108 | #### count-matches (new in 2.1.2, April 2024) 109 | 110 | `(count-matches regex target-string)` returns a count of all matches of `regex` against `target-string`: 111 | 112 | 113 | ~~~lisp 114 | CL-USER> (ppcre:count-matches "a" "foo bar baz") 115 | 2 116 | 117 | CL-USER> (ppcre:count-matches "\\w*" "foo bar baz") 118 | 6 119 | ~~~ 120 | 121 | 122 | 123 | #### scan-to-strings, register-groups-bind 124 | 125 | The `scan-to-strings` function is similar to `scan` but returns 126 | substrings of target-string instead of positions. This function 127 | returns two values on success: the whole match as a string plus an 128 | array of substrings (or NILs) corresponding to the matched registers. 129 | 130 | The `register-groups-bind` function tries to match the given pattern 131 | against the target string and binds matching fragments with the given 132 | variables. 133 | 134 | ~~~lisp 135 | (ppcre:register-groups-bind (first second third fourth) 136 | ("((a)|(b)|(c))+" "abababc" :sharedp t) 137 | (list first second third fourth)) 138 | ;; => ("c" "a" "b" "c") 139 | ~~~ 140 | 141 | CL-PPCRE also provides a shortcut for calling a function before 142 | assigning the matching fragment to the variable: 143 | 144 | ~~~lisp 145 | (ppcre:register-groups-bind 146 | (fname lname (#'parse-integer date month year)) 147 | ("(\\w+)\\s+(\\w+)\\s+(\\d{1,2})\\.(\\d{1,2})\\.(\\d{4})" 148 | "Frank Zappa 21.12.1940") 149 | (list fname lname date month year)) 150 | ;; => ("Frank" "Zappa" 21 12 1940) 151 | ~~~ 152 | 153 | ### Replacing text: regex-replace, regex-replace-all 154 | 155 | ~~~lisp 156 | (ppcre:regex-replace "a" "abc" "A") ;; => "Abc" 157 | ;; or 158 | (let ((pat (ppcre:create-scanner "a"))) 159 | (ppcre:regex-replace pat "abc" "A")) 160 | ~~~ 161 | 162 | ## See more 163 | 164 | - [cl-ppcre on common-lisp-libraries.readthedocs.io](https://common-lisp-libraries.readthedocs.io/cl-ppcre/) and read on: `do-matches`, `do-matches-as-strings`, 165 | `do-register-groups`, `do-scans`, `parse-string`, `regex-apropos`, 166 | `quote-meta-chars`, `split`… 167 | -------------------------------------------------------------------------------- /simple-restarts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/simple-restarts.png -------------------------------------------------------------------------------- /sockets.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: TCP/UDP programming with sockets 3 | --- 4 | 5 | This is a short guide to TCP/IP and UDP/IP client/server programming in Common 6 | Lisp using [usockets](https://github.com/usocket/usocket). 7 | 8 | 9 | ## TCP/IP 10 | 11 | As usual, we will use quicklisp to load usocket. 12 | 13 | (ql:quickload "usocket") 14 | 15 | Now we need to create a server. There are 2 primary functions that we need 16 | to call. `usocket:socket-listen` and `usocket:socket-accept`. 17 | 18 | `usocket:socket-listen` binds to a port and listens on it. It returns a socket 19 | object. We need to wait with this object until we get a connection that we 20 | accept. That's where `usocket:socket-accept` comes in. It's a blocking call 21 | that returns only when a connection is made. This returns a new socket object 22 | that is specific to that connection. We can then use that connection to 23 | communicate with our client. 24 | 25 | So, what were the problems I faced due to my mistakes? 26 | 27 | Mistake 1 - My initial understanding was that `socket-accept` would return 28 | a stream object. NO.... It returns a socket object. In hindsight, its correct 29 | and my own mistake cost me time. So, if you want to write to the socket, you 30 | need to actually get the corresponding stream from this new socket. The socket 31 | object has a stream slot and we need to explicitly use that. And how does one 32 | know that? `(describe connection)` is your friend! 33 | 34 | Mistake 2 - You need to close both the new socket and the server socket. 35 | Again this is pretty obvious but since my initial code was only closing 36 | the connection, I kept running into a socket in use problem. Of course 37 | one more option is to reuse the socket when we listen. 38 | 39 | Once you get past these mistakes, it's pretty easy to do the rest. Close 40 | the connections and the server socket and boom you are done! 41 | 42 | 43 | ~~~lisp 44 | (defun create-server (port) 45 | (let* ((socket (usocket:socket-listen "127.0.0.1" port)) 46 | (connection (usocket:socket-accept socket :element-type 47 | 'character))) 48 | (unwind-protect 49 | (progn 50 | (format (usocket:socket-stream connection) 51 | "Hello World~%") 52 | (force-output (usocket:socket-stream connection))) 53 | (progn 54 | (format t "Closing sockets~%") 55 | (usocket:socket-close connection) 56 | (usocket:socket-close socket))))) 57 | ~~~ 58 | 59 | Now for the client. This part is easy. Just connect to the server port 60 | and you should be able to read from the server. The only silly mistake I 61 | made here was to use read and not read-line. So, I ended up seeing only a 62 | "Hello" from the server. I went for a walk and came back to find the issue 63 | and fix it. 64 | 65 | 66 | ~~~lisp 67 | (defun create-client (port) 68 | (usocket:with-client-socket (socket stream "127.0.0.1" port 69 | :element-type 'character) 70 | (unwind-protect 71 | (progn 72 | (usocket:wait-for-input socket) 73 | (format t "Input is: ~a~%" (read-line stream))) 74 | (usocket:socket-close socket)))) 75 | ~~~ 76 | 77 | So, how do you run this? You need two REPLs, one for the server 78 | and one for the client. Load this file in both REPLs. Create the 79 | server in the first REPL. 80 | 81 | (create-server 12321) 82 | 83 | Now you are ready to run the client on the second REPL 84 | 85 | (create-client 12321) 86 | 87 | Voilà! You should see "Hello World" on the second REPL. 88 | 89 | 90 | ## UDP/IP 91 | 92 | As a protocol, UDP is connection-less, and therefore there is no 93 | concept of binding and accepting a connection. Instead we only do a 94 | `socket-connect` but pass a specific set of parameters to make sure that 95 | we create an UDP socket that's waiting for data on a particular port. 96 | 97 | So, what were the problems I faced due to my mistakes? 98 | Mistake 1 - Unlike TCP, you don't pass host and port to `socket-connect`. 99 | If you do that, then you are indicating that you want to send a packet. 100 | Instead, you pass `nil` but you set `:local-host` and `:local-port` to the address 101 | and port that you want to receive data on. This part took some time to 102 | figure out, because the documentation didn't cover it. Instead reading 103 | a bit of code from 104 | [blackthorn-engine-3d](https://code.google.com/p/blackthorn-engine-3d/source/browse/src/examples/usocket/usocket.lisp) helped a lot. 105 | 106 | Also, since UDP is connectionless, anyone can send data to it at any 107 | time. So, we need to know which host/port did we get data from so 108 | that we can respond on it. So we bind multiple values to `socket-receive` 109 | and use those values to send back data to our peer "client". 110 | 111 | ~~~lisp 112 | (defun create-server (port buffer) 113 | (let* ((socket (usocket:socket-connect nil nil 114 | :protocol :datagram 115 | :element-type '(unsigned-byte 8) 116 | :local-host "127.0.0.1" 117 | :local-port port))) 118 | (unwind-protect 119 | (multiple-value-bind (buffer size client receive-port) 120 | (usocket:socket-receive socket buffer 8) 121 | (format t "~A~%" buffer) 122 | (usocket:socket-send socket (reverse buffer) size 123 | :port receive-port 124 | :host client)) 125 | (usocket:socket-close socket)))) 126 | ~~~ 127 | 128 | 129 | Now for the sender/receiver. This part is pretty easy. Create a socket, 130 | send data on it and receive data back. 131 | 132 | ~~~lisp 133 | (defun create-client (port buffer) 134 | (let ((socket (usocket:socket-connect "127.0.0.1" port 135 | :protocol :datagram 136 | :element-type '(unsigned-byte 8)))) 137 | (unwind-protect 138 | (progn 139 | (format t "Sending data~%") 140 | (replace buffer #(1 2 3 4 5 6 7 8)) 141 | (format t "Receiving data~%") 142 | (usocket:socket-send socket buffer 8) 143 | (usocket:socket-receive socket buffer 8) 144 | (format t "~A~%" buffer)) 145 | (usocket:socket-close socket)))) 146 | ~~~ 147 | 148 | 149 | So, how do you run this? You need again two REPLs, one for the server 150 | and one for the client. Load this file in both REPLs. Create the 151 | server in the first REPL. 152 | 153 | (create-server 12321 (make-array 8 :element-type '(unsigned-byte 8))) 154 | 155 | Now you are ready to run the client on the second REPL 156 | 157 | (create-client 12321 (make-array 8 :element-type '(unsigned-byte 8))) 158 | 159 | Voilà! You should see a vector `#(1 2 3 4 5 6 7 8)` on the first REPL 160 | and `#(8 7 6 5 4 3 2 1)` on the second one. 161 | 162 | 163 | ## Credit 164 | 165 | This guide originally comes from [shortsightedsid](https://gist.github.com/shortsightedsid/71cf34282dfae0dd2528) 166 | -------------------------------------------------------------------------------- /systems.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Defining Systems 3 | --- 4 | 5 | A **system** is a collection of Lisp files that together constitute an application or a library, and that should therefore be managed as a whole. A **system definition** describes which source files make up the system, what the dependencies among them are, and the order they should be compiled and loaded in. 6 | 7 | 8 | ## ASDF 9 | 10 | [ASDF](https://gitlab.common-lisp.net/asdf/asdf) is the standard build 11 | system for Common Lisp. It is shipped in most Common Lisp 12 | implementations. It includes 13 | [UIOP](https://gitlab.common-lisp.net/asdf/asdf/blob/master/uiop/README.md), 14 | _"the Utilities for Implementation- and OS- Portability"_. You can read 15 | [its manual](https://common-lisp.net/project/asdf/asdf.html) and the 16 | [tutorial and best practices](https://gitlab.common-lisp.net/asdf/asdf/blob/master/doc/best_practices.md). 17 | 18 | 19 | 20 | ## Simple examples 21 | 22 | ### Loading a system definition 23 | 24 | When you start your Lisp, it knows about its internal modules and, by 25 | default, it has no way to know that your shiny new project is located 26 | under your `~/code/foo/bar/new-ideas/` directory. So, in order to load 27 | your project in your image, you have one of three ways: 28 | 29 | - use ASDF or Quicklisp defaults 30 | - configure where ASDF or Quicklisp look for project definitions 31 | - load your project definition explicitely. 32 | 33 | Please read our section on the [getting started#how-to-load-an-existing-project](https://lispcookbook.github.io/cl-cookbook/getting-started.html#how-to-load-an-existing-project) page. 34 | 35 | 36 | ### Loading a system 37 | 38 | Once your Lisp knows what your system is and where it lives, you can load it. 39 | 40 | The most trivial use of ASDF is by calling `asdf:load-system` to load your library. 41 | Then you can use it. 42 | For instance, if it exports a function `some-fun` in its package `foobar`, 43 | then you will be able to call it with `(foobar:some-fun ...)` or with: 44 | 45 | ~~~lisp 46 | (in-package :foobar) 47 | (some-fun ...) 48 | ~~~ 49 | 50 | You can also use Quicklisp. 51 | 52 | Quicklisp calls ASDF under the hood, with the advantage that it will download and install any dependency if they are not already installed. 53 | 54 | ~~~lisp 55 | (ql:quickload "foobar") 56 | ;; => 57 | ;; installs all dependencies 58 | ;; and loads the system. 59 | ~~~ 60 | 61 | Also, you can use SLIME to load a system, using the `M-x slime-load-system` Emacs command or the `, load-system` comma command in the prompt. 62 | The interesting thing about this way of doing it is that SLIME collects all the system warnings and errors in the process, 63 | and puts them in the `*slime-compilation*` buffer, from which you can interactively inspect them after the loading finishes. 64 | 65 | ### Testing a system 66 | 67 | To run the tests for a system, you may use: 68 | 69 | ~~~lisp 70 | (asdf:test-system :foobar) 71 | ~~~ 72 | 73 | The convention is that an error SHOULD be signalled if tests are unsuccessful. 74 | 75 | ### Designating a system 76 | 77 | The proper way to designate a system in a program is with lower-case 78 | strings, not symbols, as in: 79 | 80 | ~~~lisp 81 | (asdf:load-system "foobar") 82 | (asdf:test-system "foobar") 83 | ~~~ 84 | 85 | ### How to write a trivial system definition 86 | 87 | A trivial system would have a single Lisp file called `foobar.lisp`, located at the project's root. 88 | That file would depend on some existing libraries, 89 | say `alexandria` for general purpose utilities, 90 | and `trivia` for pattern-matching. 91 | To make this system buildable using ASDF, 92 | you create a system definition file called `foobar.asd`, 93 | with the following contents: 94 | 95 | ~~~lisp 96 | (asdf:defsystem "foobar" 97 | :depends-on ("alexandria" "trivia") 98 | :components ((:file "foobar"))) 99 | ~~~ 100 | 101 | 102 | Note how the type `lisp` of `foobar.lisp` 103 | is implicit in the name of the file above. 104 | As for contents of that file, they would look like this: 105 | 106 | ~~~lisp 107 | (defpackage :foobar 108 | (:use :common-lisp :alexandria :trivia) 109 | (:export 110 | #:some-function 111 | #:another-function 112 | #:call-with-foobar 113 | #:with-foobar)) 114 | 115 | (in-package :foobar) 116 | 117 | (defun some-function (...) 118 | ...) 119 | ... 120 | ~~~ 121 | 122 | Instead of `using` multiple complete packages, you might want to just import parts of them: 123 | 124 | ~~~lisp 125 | (defpackage :foobar 126 | (:use #:common-lisp) 127 | (:import-from #:alexandria 128 | #:some-function 129 | #:another-function)) 130 | (:import-from #:trivia 131 | #:some-function 132 | #:another-function)) 133 | ...) 134 | ~~~ 135 | 136 | 137 | #### Using the system you defined 138 | 139 | Assuming your system is installed under `~/common-lisp/`, 140 | `~/quicklisp/local-projects/` or some other filesystem hierarchy 141 | already configured for ASDF, you can load it with: `(asdf:load-system "foobar")`. 142 | 143 | If your Lisp was already started when you created that file, 144 | you may have to, either: 145 | 146 | - load the new .asd file: `(asdf:load-asd "path/to/foobar.asd")`, or with `C-c C-k` in Slime to compile and load the whole file. 147 | - note: avoid using the built-in `load` for ASDF files, it may work but `asdf:load-asd` is preferred. 148 | - `(asdf:clear-configuration)` to re-process the configuration. 149 | 150 | 151 | ### How to write a trivial testing definition 152 | 153 | Even the most trivial of systems needs some tests, 154 | if only because it will have to be modified eventually, 155 | and you want to make sure those modifications don't break client code. 156 | Tests are also a good way to document expected behavior. 157 | 158 | The simplest way to write tests is to have a file `foobar-tests.lisp` 159 | and modify the above `foobar.asd` as follows: 160 | 161 | ~~~lisp 162 | (asdf:defsystem "foobar" 163 | :depends-on ("alexandria" "trivia") 164 | :components ((:file "foobar")) 165 | :in-order-to ((test-op (test-op "foobar/tests")))) 166 | 167 | (asdf:defsystem "foobar/tests" 168 | :depends-on ("foobar" "fiveam") 169 | :components ((:file "foobar-tests")) 170 | :perform (test-op (o c) (symbol-call :fiveam '#:run! :foobar))) 171 | ~~~ 172 | 173 | The `:in-order-to` clause in the first system 174 | allows you to use `(asdf:test-system :foobar)` 175 | which will chain into `foobar/tests`. 176 | The `:perform` clause in the second system does the testing itself. 177 | 178 | In the test system, `fiveam` is the name of a popular test library, 179 | and the content of the `perform` method is how to invoke this library 180 | to run the test suite `:foobar`. 181 | Obvious YMMV if you use a different library. 182 | 183 | ## Create a project skeleton 184 | 185 | [cl-project](https://github.com/fukamachi/cl-project) can be used to 186 | generate a project skeleton. It will create a default ASDF definition, 187 | generate a system for unit testing, etc. 188 | 189 | Install with 190 | 191 | (ql:quickload "cl-project") 192 | 193 | Create a project: 194 | 195 | ~~~lisp 196 | (cl-project:make-project #p"lib/cl-sample/" 197 | :author "Eitaro Fukamachi" 198 | :email "e.arrows@gmail.com" 199 | :license "LLGPL" 200 | :depends-on '(:clack :cl-annot)) 201 | ;-> writing /Users/fukamachi/Programs/lib/cl-sample/.gitignore 202 | ; writing /Users/fukamachi/Programs/lib/cl-sample/README.markdown 203 | ; writing /Users/fukamachi/Programs/lib/cl-sample/cl-sample-test.asd 204 | ; writing /Users/fukamachi/Programs/lib/cl-sample/cl-sample.asd 205 | ; writing /Users/fukamachi/Programs/lib/cl-sample/src/hogehoge.lisp 206 | ; writing /Users/fukamachi/Programs/lib/cl-sample/t/hogehoge.lisp 207 | ;=> T 208 | ~~~ 209 | 210 | And you're done. 211 | -------------------------------------------------------------------------------- /trace-dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LispCookbook/cl-cookbook/b5320251bb01d98b64d41c2b7d56fcc6efabfa8d/trace-dialog.png -------------------------------------------------------------------------------- /web-scraping.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Web Scraping 3 | --- 4 | 5 | The set of tools to do web scraping in Common Lisp is pretty complete 6 | and pleasant. In this short tutorial we'll see how to make http 7 | requests, parse html, extract content and do asynchronous requests. 8 | 9 | Our simple task will be to extract the list of links on the CL 10 | Cookbook's index page and check if they are reachable. 11 | 12 | We'll use the following libraries: 13 | 14 | - [Dexador](https://github.com/fukamachi/dexador) - an HTTP client 15 | (that aims at replacing the venerable Drakma), 16 | - [Plump](https://shinmera.github.io/plump/) - a markup parser, that works on malformed HTML, 17 | - [Lquery](https://shinmera.github.io/lquery/) - a DOM manipulation 18 | library, to extract content from our Plump result, 19 | - [lparallel](https://lparallel.org/pmap-family/) - a library for parallel programming (read more in the [process section](process.html)). 20 | 21 | Before starting let's install those libraries with Quicklisp: 22 | 23 | ~~~lisp 24 | (ql:quickload '("dexador" "plump" "lquery" "lparallel")) 25 | ~~~ 26 | 27 | ## HTTP Requests 28 | 29 | Easy things first. Install Dexador. Then we use the `get` function: 30 | 31 | ~~~lisp 32 | (defvar *url* "https://lispcookbook.github.io/cl-cookbook/") 33 | (defvar *request* (dex:get *url*)) 34 | ~~~ 35 | 36 | This returns a list of values: the whole page content, the return code 37 | (200), the response headers, the uri and the stream. 38 | 39 | ``` 40 | " 41 | 42 | 43 | Home – the Common Lisp Cookbook 44 | […] 45 | " 46 | 200 47 | # 48 | # 49 | #> 50 | 51 | ``` 52 | 53 | Remember, in Slime we can inspect the objects with a right-click on 54 | them. 55 | 56 | ## Parsing and extracting content with CSS selectors 57 | 58 | We'll use `lquery` to parse the html and extract the 59 | content. 60 | 61 | - [https://shinmera.github.io/lquery/](https://shinmera.github.io/lquery/) 62 | 63 | We first need to parse the html into an internal data structure. Use 64 | `(lquery:$ (initialize ))`: 65 | 66 | ~~~lisp 67 | (defvar *parsed-content* (lquery:$ (initialize *request*))) 68 | ;; => # 69 | ~~~ 70 | 71 | lquery uses [Plump](https://shinmera.github.io/plump/) internally. 72 | 73 | Now we'll extract the links with CSS selectors. 74 | 75 | __Note__: to find out what should be the CSS selector of the element 76 | I'm interested in, I right click on an element in the browser and I 77 | choose "Inspect element". This opens up the inspector of my browser's 78 | web dev tool and I can study the page structure. 79 | 80 | So the links I want to extract are in a page with an `id` of value 81 | "content", and they are in regular list elements (`li`). 82 | 83 | Let's try something: 84 | 85 | ~~~lisp 86 | (lquery:$ *parsed-content* "#content li") 87 | ;; => #(# # 88 | ;; # # 89 | ;; # # 90 | ;; # # 91 | ;; # # 92 | ;; # # 93 | ;; […] 94 | ~~~ 95 | 96 | Wow it works ! We get here a vector of plump elements. 97 | 98 | I'd like to easily check what those elements are. To see the entire 99 | html, we can end our lquery line with `(serialize)`: 100 | 101 | ~~~lisp 102 | (lquery:$ *parsed-content* "#content li" (serialize)) 103 | #("
  • License
  • " 104 | "
  • Getting started
  • " 105 | "
  • Editor support
  • " 106 | […] 107 | ~~~ 108 | 109 | And to see their *textual* content (the user-visible text inside the 110 | html), we can use `(text)` instead: 111 | 112 | ~~~lisp 113 | (lquery:$ *parsed-content* "#content" (text)) 114 | #("License" "Editor support" "Strings" "Dates and Times" "Hash Tables" 115 | "Pattern Matching / Regular Expressions" "Functions" "Loop" "Input/Output" 116 | "Files and Directories" "Packages" "Macros and Backquote" 117 | "CLOS (the Common Lisp Object System)" "Sockets" "Interfacing with your OS" 118 | "Foreign Function Interfaces" "Threads" "Defining Systems" 119 | […] 120 | "Pascal Costanza’s Highly Opinionated Guide to Lisp" 121 | "Loving Lisp - the Savy Programmer’s Secret Weapon by Mark Watson" 122 | "FranzInc, a company selling Common Lisp and Graph Database solutions.") 123 | ~~~ 124 | 125 | All right, so we see we are manipulating what we want. Now to get their 126 | `href`, a quick look at lquery's doc and we'll use `(attr 127 | "some-name")`: 128 | 129 | 130 | ~~~lisp 131 | (lquery:$ *parsed-content* "#content li a" (attr :href)) 132 | ;; => #("license.html" "editor-support.html" "strings.html" "dates_and_times.html" 133 | ;; "hashes.html" "pattern_matching.html" "functions.html" "loop.html" "io.html" 134 | ;; "files.html" "packages.html" "macros.html" 135 | ;; "/cl-cookbook/clos-tutorial/index.html" "os.html" "ffi.html" 136 | ;; "process.html" "systems.html" "win32.html" "testing.html" "misc.html" 137 | ;; […] 138 | ;; "http://www.nicklevine.org/declarative/lectures/" 139 | ;; "http://www.p-cos.net/lisp/guide.html" "https://leanpub.com/lovinglisp/" 140 | ;; "https://franz.com/") 141 | ~~~ 142 | 143 | *Note*: using `(serialize)` after `attr` leads to an error. 144 | 145 | Nice, we now have the list (well, a vector) of links of the 146 | page. We'll now write an async program to check and validate they are 147 | reachable. 148 | 149 | External resources: 150 | 151 | - [CSS selectors](https://developer.mozilla.org/en-US/docs/Glossary/CSS_Selector) 152 | 153 | ## Async requests 154 | 155 | In this example we'll take the list of url from above and we'll check 156 | if they are reachable. We want to do this asynchronously, but to see 157 | the benefits we'll first do it synchronously ! 158 | 159 | We need a bit of filtering first to exclude the email addresses (maybe 160 | that was doable in the CSS selector ?). 161 | 162 | We put the vector of urls in a variable: 163 | 164 | ~~~lisp 165 | (defvar *urls* (lquery:$ *parsed-content* "#content li a" (attr :href))) 166 | ~~~ 167 | 168 | We remove the elements that start with "mailto:": (a quick look at the 169 | [strings](strings.html) page will help) 170 | 171 | ~~~lisp 172 | (remove-if (lambda (it) 173 | (string= it "mailto:" :start1 0 174 | :end1 (length "mailto:"))) 175 | *urls*) 176 | ;; => #("license.html" "editor-support.html" "strings.html" "dates_and_times.html" 177 | ;; […] 178 | ;; "process.html" "systems.html" "win32.html" "testing.html" "misc.html" 179 | ;; "license.html" "http://lisp-lang.org/" 180 | ;; "https://github.com/CodyReichert/awesome-cl" 181 | ;; "http://www.lispworks.com/documentation/HyperSpec/Front/index.htm" 182 | ;; […] 183 | ;; "https://franz.com/") 184 | ~~~ 185 | 186 | Actually before writing the `remove-if` (which works on any sequence, 187 | including vectors) I tested with a `(map 'vector …)` to see that the 188 | results where indeed `nil` or `t`. 189 | 190 | As a side note, there is a handy `starts-with-p` function in the "str" library 191 | available in Quicklisp. So we could do: 192 | 193 | ~~~lisp 194 | (map 'vector (lambda (it) 195 | (str:starts-with-p "mailto:" it)) 196 | *urls*) 197 | ~~~ 198 | 199 | While we're at it, we'll only consider links starting with "http", in 200 | order not to write too much stuff irrelevant to web scraping: 201 | 202 | ~~~lisp 203 | (remove-if-not (lambda (it) 204 | (string= it "http" :start1 0 :end1 (length "http"))) 205 | *) 206 | ~~~ 207 | 208 | All right, we put this result in another variable: 209 | 210 | ~~~lisp 211 | (defvar *filtered-urls* *) 212 | ~~~ 213 | 214 | and now to the real work. For every url, we want to request it and 215 | check that its return code is 200. We have to ignore certain 216 | errors. Indeed, a request can timeout, be redirected (we don't want 217 | that) or return an error code. 218 | 219 | To be in real conditions we'll add a link that times out in our list: 220 | 221 | ~~~lisp 222 | (setf (aref *filtered-urls* 0) "http://lisp.org") ;; :/ 223 | ~~~ 224 | 225 | We'll take the simple approach to ignore errors and return `nil` in 226 | that case. If all goes well, we return the return code, that should be 227 | 200. 228 | 229 | As we saw at the beginning, `dex:get` returns many values, including 230 | the return code. We'll access only this one with `nth-value` (instead 231 | of all of them with `multiple-value-bind`) and we'll use 232 | `ignore-errors`, that returns nil in case of an error. We could also 233 | use `handler-case` and handle specific error types (see examples in 234 | dexador's documentation). 235 | 236 | (*ignore-errors has the caveat that when there's an error, we can not 237 | return the element it comes from. We'll get to our ends though.*) 238 | 239 | 240 | ~~~lisp 241 | (map 'vector (lambda (it) 242 | (ignore-errors 243 | (nth-value 1 (dex:get it)))) 244 | *filtered-urls*) 245 | ~~~ 246 | 247 | we get: 248 | 249 | ``` 250 | #(NIL 200 200 200 200 200 200 200 200 200 200 NIL 200 200 200 200 200 200 200 251 | 200 200 200 200) 252 | ``` 253 | 254 | it works, but *it took a very long time*. How much time precisely ? 255 | with `(time …)`: 256 | 257 | ``` 258 | Evaluation took: 259 | 21.554 seconds of real time 260 | 0.188000 seconds of total run time (0.172000 user, 0.016000 system) 261 | 0.87% CPU 262 | 55,912,081,589 processor cycles 263 | 9,279,664 bytes consed 264 | ``` 265 | 266 | 21 seconds ! Obviously this synchronous method isn't efficient. We 267 | wait 10 seconds for links that time out. It's time to write and 268 | measure an async version. 269 | 270 | After installing `lparallel` and looking at 271 | [its documentation](https://lparallel.org/), we see that the parallel 272 | map [pmap](https://lparallel.org/pmap-family/) seems to be what we 273 | want. And it's only a one word edit. Let's try: 274 | 275 | ~~~lisp 276 | (time (lparallel:pmap 'vector 277 | (lambda (it) 278 | (ignore-errors 279 | (let ((status (nth-value 1 (dex:get it)))) status))) 280 | *filtered-urls*) 281 | ;; Evaluation took: 282 | ;; 11.584 seconds of real time 283 | ;; 0.156000 seconds of total run time (0.136000 user, 0.020000 system) 284 | ;; 1.35% CPU 285 | ;; 30,050,475,879 processor cycles 286 | ;; 7,241,616 bytes consed 287 | ;; 288 | ;;#(NIL 200 200 200 200 200 200 200 200 200 200 NIL 200 200 200 200 200 200 200 289 | ;; 200 200 200 200) 290 | ~~~ 291 | 292 | Bingo. It still takes more than 10 seconds because we wait 10 seconds 293 | for one request that times out. But otherwise it proceeds all the http 294 | requests in parallel and so it is much faster. 295 | 296 | Shall we get the urls that aren't reachable, remove them from our list 297 | and measure the execution time in the sync and async cases ? 298 | 299 | What we do is: instead of returning only the return code, we check it 300 | is valid and we return the url: 301 | 302 | ~~~lisp 303 | ... (if (and status (= 200 status)) it) ... 304 | (defvar *valid-urls* *) 305 | ~~~ 306 | 307 | we get a vector of urls with a couple of `nil`s: indeed, I thought I 308 | would have only one unreachable url but I discovered another 309 | one. Hopefully I have pushed a fix before you try this tutorial. 310 | 311 | But what are they ? We saw the status codes but not the urls :S We 312 | have a vector with all the urls and another with the valid ones. We'll 313 | simply treat them as sets and compute their difference. This will show 314 | us the bad ones. We must transform our vectors to lists for that. 315 | 316 | ~~~lisp 317 | (set-difference (coerce *filtered-urls* 'list) 318 | (coerce *valid-urls* 'list)) 319 | ;; => ("http://lisp-lang.org/" "http://www.psg.com/~dlamkins/sl/cover.html") 320 | ~~~ 321 | 322 | Gotcha ! 323 | 324 | BTW it takes 8.280 seconds of real time to me to check the list of 325 | valid urls synchronously, and 2.857 seconds async. 326 | 327 | Have fun doing web scraping in CL ! 328 | 329 | 330 | More helpful libraries: 331 | 332 | - we could use [VCR](https://github.com/tsikov/vcr), a store and 333 | replay utility to set up repeatable tests or to speed up a bit our 334 | experiments in the REPL. 335 | - [cl-async](https://github.com/orthecreedence/cl-async), 336 | [carrier](https://github.com/orthecreedence/carrier) and others 337 | network, parallelism and concurrency libraries to see on the 338 | [awesome-cl](https://github.com/CodyReichert/awesome-cl) list, 339 | [Cliki](http://www.cliki.net/) or 340 | [Quickdocs](https://quickdocs.org/-/search?q=web). 341 | -------------------------------------------------------------------------------- /websockets.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: WebSockets 3 | --- 4 | 5 | The Common Lisp ecosystem boasts a few approaches to building WebSocket servers. 6 | First, there is the excellent 7 | [Hunchensocket](https://github.com/joaotavora/hunchensocket) that is written as 8 | an extension to [Hunchentoot](https://edicl.github.io/hunchentoot/), the classic 9 | web server for Common Lisp. I have used both and I find them to be wonderful. 10 | 11 | Today, however, you will be using the equally excellent 12 | [websocket-driver](https://github.com/fukamachi/websocket-driver) to build a WebSocket server with 13 | [Clack](https://github.com/fukamachi/clack). The Common Lisp web development community has expressed a 14 | slight preference for the Clack ecosystem because Clack provides a uniform interface to 15 | a variety of backends, including Hunchentoot. That is, with Clack, you can pick and choose the 16 | backend you prefer. 17 | 18 | In what follows, you will build a simple chat server and connect to it from a 19 | web browser. The tutorial is written so that you can enter the code into your 20 | REPL as you go, but in case you miss something, the full code listing can be found at the end. 21 | 22 | As a first step, you should load the needed libraries via quicklisp: 23 | 24 | ~~~lisp 25 | 26 | (ql:quickload '(clack websocket-driver alexandria)) 27 | 28 | ~~~ 29 | 30 | 31 | ## The websocket-driver Concept 32 | 33 | In websocket-driver, a WebSocket connection is an instance of the `ws` class, 34 | which exposes an event-driven API. You register event handlers by passing your 35 | WebSocket instance as the second argument to a method called `on`. For example, 36 | calling `(on :message my-websocket #'some-message-handler)` would invoke 37 | `some-message-handler` whenever a new message arrives. 38 | 39 | The `websocket-driver` API provides handlers for the following events: 40 | 41 | - `:open`: When a connection is opened. Expects a handler with zero arguments. 42 | - `:message` When a message arrives. Expects a handler with one argument, the message received. 43 | - `:close` When a connection closes. Expects a handler with two keyword args, a 44 | "code" and a "reason" for the dropped connection. 45 | - `:error` When some kind of protocol level error occurs. Expects a handler with 46 | one argument, the error message. 47 | 48 | For the purposes of your chat server, you will want to handle three cases: when 49 | a new user arrives to the channel, when a user sends a message to the channel, 50 | and when a user leaves. 51 | 52 | ## Defining Handlers for Chat Server Logic 53 | 54 | In this section you will define the functions that your event handlers will 55 | eventually call. These are helper functions that manage the chat server logic. 56 | You will define the WebSocket server in the next section. 57 | 58 | First, when a user connects to the server, you need to give that user a nickname 59 | so that other users know whose chats belong to whom. You will also need a data 60 | structure to map individual WebSocket connections to nicknames: 61 | 62 | ~~~lisp 63 | 64 | ;; make a hash table to map connections to nicknames 65 | (defvar *connections* (make-hash-table)) 66 | 67 | ;; and assign a random nickname to a user upon connection 68 | (defun handle-new-connection (con) 69 | (setf (gethash con *connections*) 70 | (format nil "user-~a" (random 100000)))) 71 | 72 | ~~~ 73 | 74 | Next, when a user sends a chat to the room, the rest of the room should be 75 | notified. The message that the server receives is prepended with the nickname of 76 | the user who sent it. 77 | 78 | ~~~lisp 79 | 80 | (defun broadcast-to-room (connection message) 81 | (let ((message (format nil "~a: ~a" 82 | (gethash connection *connections*) 83 | message))) 84 | (loop :for con :being :the :hash-key :of *connections* :do 85 | (websocket-driver:send con message)))) 86 | ~~~ 87 | 88 | Finally, when a user leaves the channel, by closing the browser tab or 89 | navigating away, the room should be notified of that change, and the user's 90 | connection should be dropped from the `*connections*` table. 91 | 92 | ~~~lisp 93 | (defun handle-close-connection (connection) 94 | (let ((message (format nil " .... ~a has left." 95 | (gethash connection *connections*)))) 96 | (remhash connection *connections*) 97 | (loop :for con :being :the :hash-key :of *connections* :do 98 | (websocket-driver:send con message)))) 99 | ~~~ 100 | 101 | ## Defining A Server 102 | 103 | Using Clack, a server is started by passing a function to `clack:clackup`. You 104 | will define a function called `chat-server` that you will start by 105 | calling `(clack:clackup #'chat-server :port 12345)`. 106 | 107 | A Clack server function accepts a single plist as its argument. That plist 108 | contains environment information about a request and is provided by the system. 109 | Your chat server will not make use of that environment, but if you want to learn 110 | more you can check out Clack's documentation. 111 | 112 | When a browser connects to your server, a websocket will be instantiated and 113 | handlers will be defined on it for each of the the events you want to support. 114 | A WebSocket "handshake" will then be sent back to the browser, indicating 115 | that the connection has been made. Here's how it works: 116 | 117 | ~~~lisp 118 | (defun chat-server (env) 119 | (let ((ws (websocket-driver:make-server env))) 120 | 121 | (websocket-driver:on :open ws 122 | (lambda () (handle-new-connection ws))) 123 | 124 | (websocket-driver:on :message ws 125 | (lambda (msg) 126 | (broadcast-to-room ws msg))) 127 | 128 | (websocket-driver:on :close ws 129 | (lambda (&key code reason) 130 | (declare (ignore code reason)) 131 | (handle-close-connection ws))) 132 | 133 | (lambda (responder) 134 | (declare (ignore responder)) 135 | ;; Send the handshake: 136 | (websocket-driver:start-connection ws)))) 137 | ~~~ 138 | 139 | You may now start your server, running on port `12345`: 140 | 141 | ~~~lisp 142 | ;; Keep the handler around so that 143 | ;; you can stop your server later on: 144 | (defvar *chat-handler* (clack:clackup #'chat-server :port 12345)) 145 | ~~~ 146 | 147 | 148 | ## A Quick HTML Chat Client 149 | 150 | So now you need a way to talk to your server. Using Clack, define a simple 151 | application that serves a web page to display and send chats. First the web page: 152 | 153 | ~~~lisp 154 | 155 | (defvar *html* 156 | " 157 | 158 | 159 | 160 | 161 | LISP-CHAT 162 | 163 | 164 | 165 |
      166 |
    167 |
    168 | 169 |
    170 | 192 | 193 | 194 | ") 195 | 196 | 197 | (defun client-server (env) 198 | (declare (ignore env)) 199 | `(200 (:content-type "text/html") 200 | (,*html*))) 201 | 202 | ~~~ 203 | 204 | You might prefer to put the HTML into a file, as escaping quotes is kind of annoying. 205 | Keeping the page data in a `defvar` was simpler for the purposes of this 206 | tutorial. 207 | 208 | You can see that the `client-server` function just serves the HTML content. Go 209 | ahead and start it, this time on port `8080`: 210 | 211 | ~~~lisp 212 | (defvar *client-handler* (clack:clackup #'client-server :port 8080)) 213 | ~~~ 214 | 215 | ## Check it out! 216 | 217 | Now open up two browser tabs and point them to `http://localhost:8080` and you 218 | should see your chat app! 219 | 220 | Chat app demo between two browser windows 222 | 223 | ## All The Code 224 | 225 | ~~~lisp 226 | (ql:quickload '(clack websocket-driver alexandria)) 227 | 228 | (defvar *connections* (make-hash-table)) 229 | 230 | (defun handle-new-connection (con) 231 | (setf (gethash con *connections*) 232 | (format nil "user-~a" (random 100000)))) 233 | 234 | (defun broadcast-to-room (connection message) 235 | (let ((message (format nil "~a: ~a" 236 | (gethash connection *connections*) 237 | message))) 238 | (loop :for con :being :the :hash-key :of *connections* :do 239 | (websocket-driver:send con message)))) 240 | 241 | (defun handle-close-connection (connection) 242 | (let ((message (format nil " .... ~a has left." 243 | (gethash connection *connections*)))) 244 | (remhash connection *connections*) 245 | (loop :for con :being :the :hash-key :of *connections* :do 246 | (websocket-driver:send con message)))) 247 | 248 | (defun chat-server (env) 249 | (let ((ws (websocket-driver:make-server env))) 250 | (websocket-driver:on :open ws 251 | (lambda () (handle-new-connection ws))) 252 | 253 | (websocket-driver:on :message ws 254 | (lambda (msg) 255 | (broadcast-to-room ws msg))) 256 | 257 | (websocket-driver:on :close ws 258 | (lambda (&key code reason) 259 | (declare (ignore code reason)) 260 | (handle-close-connection ws))) 261 | (lambda (responder) 262 | (declare (ignore responder)) 263 | (websocket-driver:start-connection ws)))) 264 | 265 | (defvar *html* 266 | " 267 | 268 | 269 | 270 | 271 | LISP-CHAT 272 | 273 | 274 | 275 |
      276 |
    277 |
    278 | 279 |
    280 | 302 | 303 | 304 | ") 305 | 306 | (defun client-server (env) 307 | (declare (ignore env)) 308 | `(200 (:content-type "text/html") 309 | (,*html*))) 310 | 311 | (defvar *chat-handler* (clack:clackup #'chat-server :port 12345)) 312 | (defvar *client-handler* (clack:clackup #'client-server :port 8080)) 313 | ~~~ 314 | -------------------------------------------------------------------------------- /windows.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Setting up Emacs on Windows or Mac 3 | --- 4 | 5 | Emacs is the preferred Lisp source code editor for most CL developers. 6 | 7 | If you want to get going easily, get 8 | [Portacle](https://shinmera.github.io/portacle/), a **portable** and 9 | **multi-platform** CL development environment. You get: 10 | 11 | - Emacs, slightly customized, 12 | - Slime, 13 | - SBCL, 14 | - Quicklisp, 15 | - Git. 16 | 17 | You only need to download Portacle and double-click to run it. 18 | --------------------------------------------------------------------------------