From aa4aea440e4387b6ecaf24976b16246d74dda60b Mon Sep 17 00:00:00 2001 From: Marcus Kammer Date: Wed, 15 May 2024 18:18:38 +0200 Subject: [PATCH] Update lispcookbook --- .../cl-cookbook/arrays.html | 23 +- lispcookbook.github.io/cl-cookbook/clos.html | 40 +- .../cl-cookbook/data-structures.html | 71 +- .../cl-cookbook/databases.html | 23 +- .../cl-cookbook/dates_and_times.html | 23 +- .../cl-cookbook/debugging.html | 91 +- .../cl-cookbook/drafts/defmodel.lisp.html | 23 +- .../cl-cookbook/dynamic-libraries.html | 551 ++++++++ .../cl-cookbook/editor-support.html | 59 +- .../cl-cookbook/emacs-ide.html | 1140 +++++++++-------- .../cl-cookbook/error_handling.html | 23 +- lispcookbook.github.io/cl-cookbook/ffi.html | 146 +-- lispcookbook.github.io/cl-cookbook/files.html | 23 +- .../cl-cookbook/functions.html | 23 +- .../cl-cookbook/getting-started.html | 63 +- lispcookbook.github.io/cl-cookbook/gui.html | 23 +- lispcookbook.github.io/cl-cookbook/index.html | 24 +- lispcookbook.github.io/cl-cookbook/io.html | 23 +- .../cl-cookbook/iteration.html | 35 +- .../cl-cookbook/license.html | 23 +- .../cl-cookbook/lispworks.html | 51 +- .../cl-cookbook/macros.html | 23 +- lispcookbook.github.io/cl-cookbook/misc.html | 23 +- .../cl-cookbook/numbers.html | 23 +- lispcookbook.github.io/cl-cookbook/os.html | 33 +- .../cl-cookbook/packages.html | 23 +- .../cl-cookbook/pattern_matching.html | 23 +- .../cl-cookbook/performance.html | 23 +- .../cl-cookbook/process.html | 23 +- .../cl-cookbook/regexp.html | 34 +- .../cl-cookbook/scripting.html | 23 +- .../cl-cookbook/sockets.html | 23 +- .../cl-cookbook/strings.html | 42 +- .../cl-cookbook/systems.html | 23 +- .../cl-cookbook/testing.html | 23 +- .../cl-cookbook/trace-dialog.png | Bin 0 -> 65573 bytes lispcookbook.github.io/cl-cookbook/type.html | 23 +- .../cl-cookbook/vscode-alive.html | 23 +- .../cl-cookbook/web-scraping.html | 23 +- lispcookbook.github.io/cl-cookbook/web.html | 35 +- .../cl-cookbook/websockets.html | 23 +- lispcookbook.github.io/cl-cookbook/win32.html | 23 +- 42 files changed, 1998 insertions(+), 1015 deletions(-) create mode 100644 lispcookbook.github.io/cl-cookbook/dynamic-libraries.html create mode 100644 lispcookbook.github.io/cl-cookbook/trace-dialog.png diff --git a/lispcookbook.github.io/cl-cookbook/arrays.html b/lispcookbook.github.io/cl-cookbook/arrays.html index 63b122d..439cc1b 100644 --- a/lispcookbook.github.io/cl-cookbook/arrays.html +++ b/lispcookbook.github.io/cl-cookbook/arrays.html @@ -53,14 +53,19 @@

The Common Lisp Cookbook – Multidimensional arrays

-

- 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

+ + + + + + + + + +

+ 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

+

📕 Get the EPUB and PDF

@@ -1101,7 +1106,7 @@ The #i reader macro recognises many of these, and uses the
© 2002–2023 the Common Lisp Cookbook Project
- 📹 Discover vindarel's Lisp course on Udemy + 📹 Discover our contributor's Common Lisp video course on Udemy
diff --git a/lispcookbook.github.io/cl-cookbook/clos.html b/lispcookbook.github.io/cl-cookbook/clos.html index 49e91f5..6195ec1 100644 --- a/lispcookbook.github.io/cl-cookbook/clos.html +++ b/lispcookbook.github.io/cl-cookbook/clos.html @@ -53,14 +53,19 @@

The Common Lisp Cookbook – Fundamentals of CLOS

-

- 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

+ + + + + + + + + +

+ 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

+

📕 Get the EPUB and PDF

@@ -880,6 +885,23 @@ closer-mop:standard-accessor-method

See also

+

Slime export class symbols

+ +

The command M-x slime-export-class will add the class symbols to the “:export” clause of your package definition. This way, you can export dozens of symbols all at once.

+ +

Imagine you have this class:

+ +
(defclass test ()
+  ((foo :accessor foo)
+   (bar :reader bar)))
+
+ +

Using “M-x slime-export-class RET test RET” will export “test”, “foot” and “bar”.

+ +

Removing a slot from the class definition will alas not remove it from the export clause.

+ +

This works also on structures (only on SBCL and Clozure CL).

+

defclass/std: write shorter classes

The library defclass/std @@ -1900,7 +1922,7 @@ this new class, and compute new slots intelligently. Use


© 2002–2023 the Common Lisp Cookbook Project
- 📹 Discover vindarel's Lisp course on Udemy + 📹 Discover our contributor's Common Lisp video course on Udemy
diff --git a/lispcookbook.github.io/cl-cookbook/data-structures.html b/lispcookbook.github.io/cl-cookbook/data-structures.html index a63fe0e..1010cb5 100644 --- a/lispcookbook.github.io/cl-cookbook/data-structures.html +++ b/lispcookbook.github.io/cl-cookbook/data-structures.html @@ -53,14 +53,19 @@

The Common Lisp Cookbook – Data structures

-

- 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

+ + + + + + + + + +

+ 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

+

📕 Get the EPUB and PDF

@@ -76,8 +81,8 @@ also have many more details:

  • Practical CL, by Peter Seibel
  • CL Recipes, by E. Weitz, full of explanations and tips,
  • the -CL standard -with a nice TOC, functions reference, extensive descriptions, more +CL standard +with – in the sidebar of your PDF reader – a nice TOC, functions reference, extensive descriptions, more examples and warnings (i.e: everything). PDF mirror
  • a Common Lisp quick reference
  • @@ -254,11 +259,15 @@ containing the elements of all its arguments:

    nconc is the recycling equivalent.

    -

    push (item, place)

    +

    push, pushnew (item, place)

    push prepends item to the list that is stored in place, stores the resulting list in place, and returns the list.

    +

    pushnew is similar, but it does nothing if the element already exists in the place.

    + +

    See also adjoin below that doesn’t modify the target list.

    +
    (defparameter mylist '(1 2 3))
     (push 0 mylist)
     ;; => (0 1 2 3)
    @@ -281,6 +290,8 @@ more costly operation (have to traverse the whole list). So if you
     need to do this: either consider using another data structure, either
     just reverse your list when needed.

    +

    pushnew accepts key arguments: :key, :test, :test-not.

    +

    pop

    a destructive operation.

    @@ -784,6 +795,8 @@ list-a ;; => (0 1 2 3) ;; <-- original list unmodified.
    +

    You can also use pushnew, that modifies the list (see above).

    +

    Check if this is a subset (subsetp)

    (subsetp '(1 2 3) list-a)
    @@ -879,18 +892,36 @@ vector.

    ;; => #(1 2 3)
    -

    vector-push (foo vector): replace the vector element pointed to by -the fill pointer by foo. Can be destructive.

    +

    The following interface is available for vectors (or vector-like arrays):

    -

    vector-push-extend (foo vector [extension-num])t

    - -

    vector-pop (vector): return the element of vector its fill pointer -points to.

    - -

    fill-pointer (vector). setfable.

    +

    and see also the sequence functions.

    +

    The following shows how to create an array that can be pushed to and popped from arbitrarily, growing its storage capacity as needed. This is roughly equivalent to a list in Python, an ArrayList in Java, or a vector<T> in C++ – though note that elements are not erased when they’re popped.

    + +
    CL-USER> (defparameter *v* (make-array 0 :fill-pointer t :adjustable t))
    +*V*
    +CL-USER> *v*
    +#()
    +CL-USER> (vector-push-extend 42 *v*)
    +0
    +CL-USER> (vector-push-extend 43 *v*)
    +1
    +CL-USER> (vector-pop *v*)
    +43
    +CL-USER> *v*
    +#(42)
    +CL-USER> (aref *v* 1) ; beware, the element is still there!
    +43
    +CL-USER> (setf (aref *v* 1) nil) ; manually erase elements if necessary
    +
    +

    Transforming a vector to a list.

    If you’re mapping over it, see the map function whose first parameter @@ -1974,7 +2005,7 @@ an intermediary key doesn’t exist.


    © 2002–2023 the Common Lisp Cookbook Project
    - 📹 Discover vindarel's Lisp course on Udemy + 📹 Discover our contributor's Common Lisp video course on Udemy
    diff --git a/lispcookbook.github.io/cl-cookbook/databases.html b/lispcookbook.github.io/cl-cookbook/databases.html index 56d92a9..6a093f7 100644 --- a/lispcookbook.github.io/cl-cookbook/databases.html +++ b/lispcookbook.github.io/cl-cookbook/databases.html @@ -53,14 +53,19 @@

    The Common Lisp Cookbook – Database Access and Persistence

    -

    - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

    + + + + + + + + + +

    + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

    +

    📕 Get the EPUB and PDF

    @@ -944,7 +949,7 @@ tables, runs the code and connects back to the original DB connection.


    © 2002–2023 the Common Lisp Cookbook Project
    - 📹 Discover vindarel's Lisp course on Udemy + 📹 Discover our contributor's Common Lisp video course on Udemy
    diff --git a/lispcookbook.github.io/cl-cookbook/dates_and_times.html b/lispcookbook.github.io/cl-cookbook/dates_and_times.html index e4a3ead..8e90d1a 100644 --- a/lispcookbook.github.io/cl-cookbook/dates_and_times.html +++ b/lispcookbook.github.io/cl-cookbook/dates_and_times.html @@ -53,14 +53,19 @@

    The Common Lisp Cookbook – Dates and Times

    -

    - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

    + + + + + + + + + +

    + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

    +

    📕 Get the EPUB and PDF

    @@ -561,7 +566,7 @@ local-time library:


    © 2002–2023 the Common Lisp Cookbook Project
    - 📹 Discover vindarel's Lisp course on Udemy + 📹 Discover our contributor's Common Lisp video course on Udemy
    diff --git a/lispcookbook.github.io/cl-cookbook/debugging.html b/lispcookbook.github.io/cl-cookbook/debugging.html index a184b79..f4c6342 100644 --- a/lispcookbook.github.io/cl-cookbook/debugging.html +++ b/lispcookbook.github.io/cl-cookbook/debugging.html @@ -53,14 +53,19 @@

    The Common Lisp Cookbook – Debugging

    -

    - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

    + + + + + + + + + +

    + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

    +

    📕 Get the EPUB and PDF

    @@ -281,9 +286,6 @@ traced:

    The output is printed to *trace-output* (see the CLHS).

    -

    In Slime, we also have an interactive trace dialog with M-x -slime-trace-dialog bound to C-c T.

    -

    Trace options

    trace accepts options. For example, you can use :break t to invoke @@ -437,6 +439,50 @@ your implementation’s documentation:

    See the CLOS section for a tad more information.

    +

    Interactive Trace Dialog

    + +

    Both SLIME and SLY provide an interactive view for traces that features better visualization of traces, and also access to the arguments and return values in their real form, via inspectors, not just the printed representation.

    + +

    trace-dialog

    + +

    How it works: (the following instructions are for SLIME)

    + +
      +
    1. Select the functions to trace using M-x slime-trace-dialog-toggle-trace bound to C-c M-t.
    2. +
    3. Evaluate code that calls the traced functions.
    4. +
    5. Open the trace dialog tool via M-x slime-trace-dialog bound to C-c T.
    6. +
    7. The list of traced functions appear under Traced specs. +Traces are fetched in batches. So use the the [refresh] button to update status information about tracing (number of available traces that can be fetched).
    8. +
    9. Then use either the [fetch next batch] or [fetch all] buttons to fetch the traces. Traces appear under Traced specs after that, and you can use the SLIME inspector to visualize their data (arguments and return values).
    10. +
    11. After more code that calls the traced functions is evaluated, repeat the process (go to step 4).
    12. +
    + +

    But, that flow can get a bit tedious, because of the separation between updating the status of the traces and fetching them. Sometimes it is better to just fetch the traces without updating the status first. We can do that invoking the command M-x slime-trace-dialog-fetch-traces bound to G. So, instead of steps 4 and 5, just press G to update the user interface.

    + +

    These are some of the Emacs commands bound to useful keys:

    + +

    g +M-x slime-trace-dialog-fetch-status

    + +
    Update information on the trace collection and traced specs.
    +
    + +

    G +M-x slime-trace-dialog-fetch-traces

    + +
    Fetch the next batch of outstanding (not fetched yet) traces. With a C-u prefix argument, repeat until no more outstanding traces.
    +
    + +

    C-k +M-x slime-trace-dialog-clear-fetched-traces

    + +
    Prompt for confirmation, then clear all traces, both fetched and outstanding.
    +
    + +

    Finally, the arguments and return values for each trace entry are interactive buttons. Clicking them opens the SLIME inspector on them. Invoking M-RET M-x slime-trace-dialog-copy-down-to-repl returns them to the REPL for manipulation . The number left of each entry indicates its absolute position in the calling order, which might differ from display order in case multiple threads call the same traced function.

    + +

    M-x slime-trace-dialog-hide-details-mode hides arguments and return values so you can concentrate on the calling logic. Additionally, M-x slime-trace-dialog-autofollow-mode will automatically display additional detail about an entry when the cursor moves over it.

    +

    The interactive debugger

    Whenever an exceptional situation happens (see @@ -486,7 +532,7 @@ With arguments: [Condition of type SB-EXT:STEP-FORM-CONDITION] Restarts: - 0: [STEP-CONTINUE] Resume normal execution <---------- stepping actions + 0: [STEP-CONTINUE] Resume normal execution <-------------------- stepping actions 1: [STEP-OUT] Resume stepping after returning from this function 2: [STEP-NEXT] Step over call 3: [STEP-INTO] Step into call @@ -495,7 +541,7 @@ Restarts: --more-- Backtrace: - 0: (FACTORIAL 3) <----------- press Enter to fold/unfold. + 0: (FACTORIAL 3) <----------- press Enter to fold/unfold. Fix your code and press "r" to restart it. Locals: N = 3 <----------- want to check? Move the point here and press "e" to evaluate code on that frame. @@ -565,16 +611,25 @@ value. behaviour of a function a lot, it may be a sign that you need to simplify it and divide it in smaller pieces.

    -

    And again, LispWorks has a graphical stepper.

    +

    And again, LispWorks has a graphical stepper.

    -

    Resume a program execution from anywhere in the stack

    +
    + +TIP: the slime-breakpoints package adds stepping and breaking buttons to Slime too. +
    + +

    + +

    Resume a program execution from anywhere in the stack (demo)

    In this video you will find a demo that shows the process explained above: how to fix a buggy -function and how to resume the program execution from anywhere in the +function and how to resume the program execution from anywhere in the stack, without running everything from zero again. The video shows it with Emacs and Slime, the Lem editor, both with SBCL.

    +

    They key point is to use r (sldb-restart-frame) on a stack frame to restart it.

    + @@ -605,7 +660,7 @@ re-compile a function at runtime and resume the program execution from where it stopped (using the “step-continue” restart or using r (“restart frame”) on a given stackframe).

    -

    See also the Slime-star Emacs extension to set breakpoints without code annotations.

    +

    See also the Slime-star Emacs extension mentioned above to set breakpoints without code annotations.

    Advise and watch

    @@ -780,7 +835,7 @@ in 1999:


    © 2002–2023 the Common Lisp Cookbook Project
    - 📹 Discover vindarel's Lisp course on Udemy + 📹 Discover our contributor's Common Lisp video course on Udemy
    diff --git a/lispcookbook.github.io/cl-cookbook/drafts/defmodel.lisp.html b/lispcookbook.github.io/cl-cookbook/drafts/defmodel.lisp.html index bf3ea46..d6a6dc3 100644 --- a/lispcookbook.github.io/cl-cookbook/drafts/defmodel.lisp.html +++ b/lispcookbook.github.io/cl-cookbook/drafts/defmodel.lisp.html @@ -53,14 +53,19 @@

    The Common Lisp Cookbook – A "defmodel" macro

    -

    - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

    + + + + + + + + + +

    + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

    +

    📕 Get the EPUB and PDF

    @@ -133,7 +138,7 @@
    © 2002–2023 the Common Lisp Cookbook Project
    - 📹 Discover vindarel's Lisp course on Udemy + 📹 Discover our contributor's Common Lisp video course on Udemy
    diff --git a/lispcookbook.github.io/cl-cookbook/dynamic-libraries.html b/lispcookbook.github.io/cl-cookbook/dynamic-libraries.html new file mode 100644 index 0000000..83ff1fa --- /dev/null +++ b/lispcookbook.github.io/cl-cookbook/dynamic-libraries.html @@ -0,0 +1,551 @@ + + + + + Building Dynamic Libraries + + + + + + + + + + + + + + + +

    The Common Lisp Cookbook – Building Dynamic Libraries

    +
    + + + + +
    +
    + +
    +
    + +
    +
    Table of Contents
    +
      +
      +
      + +
      +

      The Common Lisp Cookbook – Building Dynamic Libraries

      + + + + + + + + + + + +

      + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

      + +

      + 📕 Get the EPUB and PDF +

      + + +
      Although the vast majority of Common Lisp implementations have +some kind of foreign function interface which allows you to +call functions from libraries which use C ABI, the other way around, +i.e. compiling your CL library as a library callable via C ABI from +other languages, might be rare.

      + +

      Commercial implementations like LispWorks and Allegro CL usually +offer this functionality, and they are well documented 1.

      + +

      This chapter describes a project called SBCL-Librarian, +an opinionated way to create libraries callable from C (anything which has C FFI) and Python using +the excellent open-source and free-to-use implementation SBCL (Steel Bank Common Lisp).

      + +

      SBCL-Librarian does support callbacks so you can integrate your Lisp library with any code, +including Python code which might use its great machine learning and statistical libraries.

      + +

      The way SBCL-Librarian works is that it generates C source files, a C header and a Python module.

      + +

      The C source file is compiled first into a dynamic library which is (using the provided header file) +loadable from any C project or by any project in a language which supports loading C libraries.

      + +

      The generated Python module loads the compiled library into the Python process. This means that the C +library needs to be compiled before your Lisp library is used from Python code. This fact has two +main consequences:

      + +
        +
      • on one hand the Lisp library is all efficient native code, which is great. The Python interpreter can be quite slow and many libraries, especially the ones for machine learning +and statistics, are all compiled to native code. You can achieve the same efficiency with Common Lisp.
      • +
      • on the other hand, your library can only use the C interface to communicate with Python - primitive data +types from C, structures, functions and pointers (including pointers to functions). Some basic knowledge +of C is required.
      • +
      + +
      + +NOTE: The team behind SBCL-Librarian works on quantum computing in the industry. More precisely on a programming language for quantum computing called Quil, and its ecosystem. +
      + +

      Preparing the Environment

      + +

      Build SBCL with Shared Library Support

      + +

      Binary distributions of SBCL usually do not come with SBCL built as a shared library, which is necessary for SBCL-Librarian. +You can download it either from the SBCL git repository +or by using Roswell and running the command ros install sbcl-source.

      + +

      SBCL also requires a working Common Lisp system to bootstrap the compilation process. An easy trick is to download a binary installation from Roswell and add it to your PATH variable.

      + +

      SBCL depends on the zstd library. On Linux-based systems, you can obtain both the library and its header files from the package manager, where it is usually named libzstd-dev. On Windows, the recommended approach is to use +MSYS2 which includes Roswell, zstd, and its headers.

      + +

      Navigate to the directory with the sources and run:

      + +
      # Bash
      +
      +# (assuming the version of your SBCL installed via Roswell is 2.4.1)
      +export PATH=~/.roswell/impls/x86-64/linux/sbcl-bin/2.4.1/bin/:$PATH
      +
      +./make-config.sh --fancy
      +./make.sh --fancy
      +./make-shared-library.sh --fancy
      +
      + +

      Note that the shared library has a .so extension even on Windows and Mac, but it seems to work just fine. If you use Roswell in MSYS2, it can use your Windows home directory rather than your MSYS2 home directory, which are different paths. Therefore, the path to Roswell might be $USERPROFILE/.roswell (or /C/Users/<username>/.roswell), +not ~/.roswell/.

      + +

      Download and Setup SBCL-Librarian

      + +

      Clone the SBCL-Librarian repostiory:

      + +
      git clone https://github.com/quil-lang/sbcl-librarian.git
      +
      + +

      Hello World from Lisp

      + +

      Although SBCL-Librarian comes with some documentation and a couple of examples, it doesn’t really have anything like a basic tutorial. In this chapter we’ll make a basic function which adds two numbers and we’ll call it from Python.

      + +

      Let’s set a couple of environment variables for convenience:

      + +
      # Directory with SBCL sources
      +export SBCL_SRC=~/.roswell/src/sbcl-2.4.1
      +# Directory with this project, don't forget the double slash at the end
      +# or it might not work
      +export CL_SOURCE_REGISTRY="~/prg/sbcl-librarian//"
      +
      + +

      Libraries are usually not searched for in the current directory on more modern Linux-based systems. The paths which Python searches libraries on are usually not set to the current working directory either. Let’s set them this way for convenience.

      + +
      export LD_LIBRARY_PATH=.:
      +export PATH=.:$PATH
      +
      + +

      Now we can create a file helloworld.lisp with the following content:

      + +
      (require '#:asdf)
      +(asdf:load-system :sbcl-librarian)
      +
      +(defpackage libhelloworld
      +  (:use :cl :sbcl-librarian))
      +
      +(in-package libhelloworld)
      +
      +;; will be called from Python
      +(defun hello-world (a b)
      +  (+ a b))
      +
      +
      +;; error enum to be used in C/Python code for error handling
      +(define-enum-type error-type "err_t"
      +  ("ERR_SUCCESS" 0)
      +  ("ERR_FAIL" 1))
      +
      +;; mapping Common Lisp conditions to C enums
      +;; in this simple example, all conditions are mapped to number 1
      +;; which is "ERR_FAIL" in `error-type` enum
      +(define-error-map error-map error-type 0
      +  ((t (lambda (condition)
      +        (declare (ignore condition))
      +        (return-from error-map 1)))))
      +
      +;; structure of the generated C source file
      +(define-api libhelloworld-api (:error-map error-map              ; error enum
      +                               :function-prefix "helloworld_")   ; prefix for all function names (C doesn't have namespaces)
      +  (:literal "/* types */")        ; just a comment (whatever is there will be printed as-is)
      +  (:type error-type)              ; outputs the error enum
      +  (:literal "/* functions */")
      +  (:function                      ; function declaration - name, return type, argument types
      +     (hello-world :int ((a :int) (b :int)))))
      +
      +;; definition of the whole library - what is there
      +(define-aggregate-library libhelloworld (:function-linkage "LIBHELLOWORLD_API")
      +  sbcl-librarian:handles sbcl-librarian:environment libhelloworld-api)
      +
      +;; builds the bindings
      +(build-bindings libhelloworld ".")
      +(build-python-bindings libhelloworld ".")
      +
      +;; outputs the Lisp core
      +(build-core-and-die libhelloworld "." :compression t)
      +
      + +

      The macro define-enum-type creates a mapping between conditions signaled by Common Lisp functions and a return type for the wrapping C functions. If a condition is signaled from Common Lisp, it is translated into a number — a C function return value — within define-error-map. The enumeration type adds a C enum, so instead of:

      + +
      if (1 == cl_function()) {
      +
      + +

      you can write:

      + +
      if (ERR_FAIL == cl_function()) {
      +
      + +

      which is more readable.

      + +

      define-api outlines the structure of the library code to be created, specifying the error map, types, functions, and their order (:literal is used for comments in this case).

      + +

      define-aggregate-library defines the entire library, specifying what should be included and in what order.

      + +

      You can compile the file with the following commands:

      + +
      $SBCL_SRC/run-sbcl.sh --script "helloworld.lisp"
      +cc -shared -fpic -o libhelloworld.so libhelloworld.c -L$SBCL_SRC/src/runtime -lsbcl
      +cp $SBCL_SRC/src/runtime/libsbcl.so .
      +
      + +

      You can run a Python console and check that the helloworld module was created successfully:

      + +
      import helloworld
      +
      +dir(helloworld)
      +
      + +

      The function helloworld_hello_world should be present in the printed dictionary.

      + +

      This function follows a C standard that the return value of the function is its error code +(0 is for success, other numbers should be defined in err_t class which follows the error-map definitions), +the last parameter of the function is its return value. Since this is a pointer to integer in this case, +an integer needs to be created using ctypes library and helloworld_hello_world has to be called with +a pointer to the result value.

      + +

      The following program should print 11:

      + +
      import helloworld
      +import ctypes
      +
      +rv = ctypes.c_int(0)
      +helloworld.helloworld_hello_world(5, 6, ctypes.pointer(rv))
      +print(rv.value)
      +
      + +

      There are two common problems which can occur, depending on your system.

      + +

      First is a rather cryptic error from Python:

      + +
      >>> import helloworld
      +Traceback (most recent call last):
      +  File "<stdin>", line 1, in <module>
      +ImportError: dynamic module does not define module export function (PyInit_helloworld)
      +
      + +

      This means that Python tries to open helloworld.so as a Python module rather than helloworld.py. Since +helloworld.so is just an ordinary dynamic library and not a natively-compiled Python module, it +will not work.

      + +
      cp ./helloworld.py ./py_helloworld.py
      +
      + +

      and in Python import py_helloworld.

      + +

      If you experience a following exception being raised:

      + +
      Traceback (most recent call last):
      +  ...
      +    raise Exception('Unable to locate libhelloworld') from e
      +Exception: Unable to locate libhelloworld
      +
      + +

      the first, check that all required dependencies - libsbcl and libzstd in this case - are either copied +to the output directory or are in the path which your operating system loads libraries from. If it still doesn’t +work, it might be a problem with the mechanism Python locates libraries on your particular system.

      + +

      Open helloworld.py (or py_helloworld.py if you renamed it as suggested earlier) and change the line

      + +
      libpath = Path(find_library('libcallback')).resolve()
      +
      + +

      to a path for your operating system, e.g.

      + +
      libpath = Path('./libhelloworld.so').resolve()
      +
      + +

      More Complex Example: Callback Example

      + +

      SBCL-Librarian includes several examples, one of which is a simple callback to Python code. This example comes with a Makefile and with a properly defined system using asdf.

      + +

      ASDF system definition

      + +

      The system definition in libcallback.asd declares a dependency on SBCL-Librarian:

      + +
      (asdf:defsystem #:libcallback
      +  :defsystem-depends-on (#:sbcl-librarian)
      +  :depends-on (#:sbcl-librarian)
      +
      + +

      The ASDF system needs to know where to find the SBCL-Librarian sources. One way to specify this is by setting the CL_SOURCE_REGISTRY environment variable to include its directory, as seen above, or to clone the project in a location whene ASDF can find it (~/common-lisp/, ~/quicklisp/local-projects/).

      + +

      Bindings.lisp

      + +

      bindings.lisp contains the crucial elements for generating the C bindings:

      + +
      (defun call-callback (callback outbuffer)
      +  (sb-alien:with-alien ((str sb-alien:c-string "I guess "))
      +    (sb-alien:alien-funcall callback str outbuffer)))
      +
      + +

      This function is key to the example; it is invoked from Python code and calls back a Python method (thecallback parameter). As SBCL-Librarian generates both a C library and a Python module that wraps it, this function can be called from either C or Python. This example focuses on Python.

      + +

      SBCL-Librarian utilizes sb-alien, an SBCL package for interfacing with C functions. with-alien creates a resource (here it is str of type c-string) that is valid within its scope and is automatically disposed of afterward, preventing memory leaks. alien-funcall is used to call a C function, in this case callback, with a newly created string and a string buffer passed in as arguments.

      + +
      (sbcl-librarian::define-type :callback
      +  :c-type "void*"
      +  :alien-type (sb-alien:* (sb-alien:function sb-alien:void sb-alien:c-string (sb-alien:* sb-alien:char)))
      +  :python-type "c_void_p")
      +
      +(sbcl-librarian::define-type :char-buffer
      +  :c-type "char*"
      +  :alien-type (sb-alien:* sb-alien:char)
      +  :python-type "c_char_p")
      +
      + +

      This section defines the types callback and char-buffer in C, Python, and Common Lisp. The C and Python types for both are void* and char*, respectively. The Common Lisp type for callback specifies a function prototype: a pointer to a function that returns void and takes two parameters, a c-string and a pointer to a char. The sb-alien:* indicates a pointer, so :callback is a pointer to a function. The :char-buffer type represents a char* in all three languages.

      + +

      The rest of this file is similar to what was described in the Hello World section.

      + +

      Compile LISP Code

      + +

      script.lisp is a straightforward Lisp script for compiling the Lisp sources and outputting the wrapper code and the Lisp core.

      + +
      (require '#:asdf)
      +
      +(asdf:load-system '#:libcallback)
      +
      +(in-package #:sbcl-librarian/example/libcallback)
      +
      +(build-bindings libcallback ".")
      +(build-python-bindings libcallback ".")
      +(build-core-and-die libcallback "." :compression t)
      +
      + +

      Now you have a couple of new files.

      + +

      libcallback.c is the source code for the library:

      + +
      #define CALLBACKING_API_BUILD
      +
      +#include "libcallback.h"
      +
      +void (*lisp_release_handle)(void* handle);
      +int (*lisp_handle_eq)(void* a, void* b);
      +void (*lisp_enable_debugger)();
      +void (*lisp_disable_debugger)();
      +void (*lisp_gc)();
      +err_t (*callback_call_callback)(void* fn, char* out_buffer);
      +
      +extern int initialize_lisp(int argc, char **argv);
      +
      +CALLBACKING_API int init(char* core) {
      +  static int initialized = 0;
      +  char *init_args[] = {"", "--core", core, "--noinform", };
      +  if (initialized) return 1;
      +  if (initialize_lisp(4, init_args) != 0) return -1;
      +  initialized = 1;
      +  return 0; }
      +
      + +

      At the top, you’ll find several SBCL-related functions, such as lisp_gc, which signals to the Lisp garbage collector that it is a good time to run. Then there is a pointer to the callback_call_callback function. Finally, the init function, which should be run before executing any Lisp code.

      + +

      SBCL (as of version 2.4.2) didn’t support de-initialize the Lisp core so there are no functions for doing so.

      + +

      libcallback.h is a header file that should be included in both lispcallback.c and any calling C code. It contains prototypes of functions and function pointers in lispcallback.c, includes the error enum, and any comments added in bindings.lisp:

      + +
      typedef enum { ERR_SUCCESS = 0, ERR_FAIL = 1, } err_t;
      +
      + +

      The last file, lispcallback.py, is a Python wrapper around the library. The most notable part is this:

      + +
      from ctypes import *
      +from ctypes.util import find_library
      +
      +try:
      +    libpath = Path(find_library('libcallback')).resolve()
      +except TypeError as e:
      +    raise Exception('Unable to locate libcallback') from e
      +
      + +

      The rest of the file is similar to the C header file.

      + +

      This setup loads a compiled C library (shared object, DLL, dylib) and informs the Python interpreter about the functions and types included in the library. It also initializes the Lisp core when loaded by the Python interpreter. The initialization needs to be called manually when the generated library is called from C.

      + +

      Compile C Code

      + +
      cc -shared -fpic -o libcallback.so libcallback.c -L$SBCL_SRC/src/runtime -lsbcl
      +
      + +

      On Mac OS the command might be a bit different:

      + +
      cc -dynamiclib -o libcallback.dylib libcallback.c -L$SBCL_SRC/src/runtime -lsbcl
      +
      + +

      If you do not have $SBCL_SRC/src/runtime in your $PATH, you should copy the $SBCL_SRC/src/runtime/libsbcl.so file to the current directory.

      + +

      Run

      + +

      Now that everything is set up, you can run the example code using the following command:

      + +
      $ python3 ./example.py
      +
      + +

      If it’s successful, you should see the output:

      + +
      I guess  it works!
      +
      + +

      Makefile

      + +

      Each example comes with a Makefile designed for building on Mac. It even automatically builds the libsbcl.so library and copies it into the current directory. However, the command for building the project (e.g., libcallback) needs to be modified to work on Linux-based operating systems and on Windows (with MSYS2).

      + +

      CMake

      + +

      Using CMake is relatively straightforward. Unfortunately, there is currently no CMake-aware library or a vcpkg/conan package, so you’ll need to use HINTS with find_library to locate the necessary libraries.

      + +

      Assuming you would like to compile a project named my_project and would like to add a LISP library, you could proceed as follows:

      + +
      # If there is a better way, let me know.
      +if(WIN32)
      +    set(DIR_SEPARATOR ";")
      +else()
      +    set(DIR_SEPARATOR ":")
      +endif()
      +
      +# Set the ENV Vars for building the LISP part
      +set(SBCL_SRC "$ENV{SBCL_SRC}" CACHE PATH "Path to SBCL sources directory.")
      +set(SBCL_LIBRARIAN_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../sbcl-librarian" CACHE PATH "Source codes of SBCL-LIBRARIAN project.")
      +set(CL_SOURCE_REGISTRY "${CMAKE_CURRENT_SOURCE_DIR}${DIR_SEPARATOR}${SBCL_LIBRARIAN_DIR}" CACHE PATH "ASDF registry for building of the libray.")
      +
      +# Find the SBCL library
      +find_library(libsbcl NAMES sbcl HINTS ${SBCL_SRC}/src/runtime/)
      +
      +# Link the library to the C project
      +target_link_libraries(my_project ${libsbcl})
      +
      +# Build LISP part of the project
      +add_custom_command(OUTPUT my_project-lisp.core my_project-lisp.c my_project-lisp.h my_project-lisp.py
      +    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
      +    COMMAND ${CMAKE_COMMAND} -E env CL_SOURCE_REGISTRY="${CL_SOURCE_REGISTRY}"
      +        ${SBCL_SRC}/run-sbcl.sh ARGS --script script.lisp
      +    COMMAND ${CMAKE_COMMAND} -E copy_if_different my_project-lisp.core $<TARGET_FILE_DIR:my_project>
      +    COMMAND ${CMAKE_COMMAND} -E copy_if_different my_project-lisp.c $<TARGET_FILE_DIR:my_project>
      +    COMMAND ${CMAKE_COMMAND} -E copy_if_different my_project-lisp.h $<TARGET_FILE_DIR:my_project>
      +    COMMAND ${CMAKE_COMMAND} -E copy_if_different my_project-lisp.py $<TARGET_FILE_DIR:my_project>
      +    COMMAND ${CMAKE_COMMAND} -E rm my_project-lisp.core my_project-lisp.c my_project-lisp.h my_project-lisp.py
      +
      +# Copy SBCL library if newer
      +add_custom_command(TARGET my_project POST_BUILD
      +    COMMAND ${CMAKE_COMMAND} -E copy_if_different
      +        "${libsbcl}"
      +        $<TARGET_FILE_DIR:my_project>)
      +
      + +

      This concludes our tutorial on getting started with SBCL-librarian. We hope it expands your imagination in what you can build with Common Lisp and that it put you in the right tracks.

      + + + + +

      + Page source: dynamic-libraries.md +

      +
      + + + + + +
      + + +
      +
      T
      O
      C
      +
      + + + + + + + + + diff --git a/lispcookbook.github.io/cl-cookbook/editor-support.html b/lispcookbook.github.io/cl-cookbook/editor-support.html index 8beff56..7c258aa 100644 --- a/lispcookbook.github.io/cl-cookbook/editor-support.html +++ b/lispcookbook.github.io/cl-cookbook/editor-support.html @@ -53,14 +53,19 @@

      The Common Lisp Cookbook – Editor support

      -

      - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

      + + + + + + + + + +

      + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

      +

      📕 Get the EPUB and PDF

      @@ -82,32 +87,6 @@ Quicklisp, SLIME and Git.

      Portacle with an open Slime REPL

      -

      Installing SLIME

      - -

      SLIME is in the official GNU ELPA repository of Emacs Lisp packages -(in Emacs24 and forward). Install with:

      - -
      M-x package-install RET slime RET
      -
      - -

      Since SLIME is heavily modular and the defaults only do the bare minimum (not -even the SLIME REPL), you might want to enable more features with

      - -
      (slime-setup '(slime-fancy slime-quicklisp slime-asdf))
      -
      - -

      For more details, consult the -documentation (also available -as an Info page).

      - -

      Now you can run SLIME with M-x slime and/or M-x slime-connect.

      - -

      See also:

      - - -

      Using Emacs as an IDE

      See “Using Emacs as an IDE”.

      @@ -275,6 +254,16 @@ Python, Go, Rust, JS, Nim, Scheme, HTML, CSS, plus a directory mode, a v
      alias ilem='lem --eval "(lem-lisp-mode:start-lisp-repl t)"'
       
      +

      There is more:

      + + +

      Lem running in the terminal with the Lisp REPL full screen, showing a completion window.

      Sublime Text

      @@ -370,7 +359,7 @@ free versions of other Lisp vendors, such as Allegro CL.


      © 2002–2023 the Common Lisp Cookbook Project
      - 📹 Discover vindarel's Lisp course on Udemy + 📹 Discover our contributor's Common Lisp video course on Udemy
      diff --git a/lispcookbook.github.io/cl-cookbook/emacs-ide.html b/lispcookbook.github.io/cl-cookbook/emacs-ide.html index 0a5ac18..8d06bb6 100644 --- a/lispcookbook.github.io/cl-cookbook/emacs-ide.html +++ b/lispcookbook.github.io/cl-cookbook/emacs-ide.html @@ -3,7 +3,7 @@ - Emacs + Using Emacs as an IDE -

      The Common Lisp Cookbook – Emacs

      +

      The Common Lisp Cookbook – Using Emacs as an IDE

      -

      The Common Lisp Cookbook – Emacs

      +

      The Common Lisp Cookbook – Using Emacs as an IDE

      -

      - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

      + + + + + + + + + +

      + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

      +

      📕 Get the EPUB and PDF

      +

      This page is meant to provide an introduction to using Emacs as a Lisp IDE.

      -

      Using Emacs as an IDE

      +

      We divided it roughly into 2 sections: how to use Slime or Sly, and complementary information on built-in Emacs commands to work with Lisp code.

      -

      This page is meant to provide an introduction to using Emacs as a Lisp IDE.

      - -

      Portacle is a portable and -multi-platform CL development environment shipping Emacs, SBCL and +

      We want to bring Portacle to your attention. It is a portable and +multi-platform CL development environment shipping Emacs, Slime, SBCL, git and necessary extensions. It is a straightforward way to get going.

      -

      + -

      Why Use Emacs?

      +

      Why Use Emacs?

      • Emacs has fantastic support for working with Lisp code
      • @@ -99,116 +102,29 @@ necessary extensions. It is a straightforward way to get going.

      • Vast number of extensions: awesome-emacs.
      -

      - -

      Emacs Lisp vs Common Lisp

      - -

      It isn’t necessary to use Emacs Lisp to use Emacs with Slime or Sly for Common Lisp

      - -

      However learning Emacs Lisp can be useful and is similar (but different) from CL:

      -
        -
      • Dynamic scope is everywhere
      • -
      • There are no reader (or reader-related) functions
      • -
      • Does not support all the types that are supported in CL
      • -
      • Incomplete implementation of CLOS (with the add-on EIEIO package)
      • -
      • No numerical tower support
      • -
      - -

      Some good Emacs Lisp learning resources:

      - - -

      - -

      SLIME: Superior Lisp Interaction Mode for Emacs

      +

      SLIME: Superior Lisp Interaction Mode for Emacs

      SLIME is the goto major mode -for CL programming.

      +for CL programming. It has a lot of features that make it a powerful, integrated and very interactive development environment.

        -
      • Pros: -
          -
        • Provides REPL which is hooked to implementation directly in Emacs
        • -
        • Has integrated Common Lisp debugger with Emacs interface
        • -
        • Interactive object-inspector in Emacs buffer
        • -
        • Has its own minor mode which enhances lisp-mode in many ways
        • -
        • Supports every common Common Lisp implementation
        • -
        • Readily available from MELPA
        • -
        • Actively maintained
        • -
        • Symbol completion
        • -
        • Cross-referencing
        • -
        • Can perform macroexpansions
        • -
        -
      • -
      • Setup: -
          -
        • Installing it from MELPA is straightforward. Search package-list-packages for ‘slime’ and click to install. It will install itself and all dependencies.
        • -
        • Enable the desired contribs (SLIME does very little by defaults), e.g. (slime-setup '(slime-fancy slime-quicklisp slime-asdf)).
        • -
        • Run SLIME with M-x slime.
        • -
        • See also your GNU/Linux distribution for a “slime” package.
        • -
        -
      • +
      • it provides a REPL which is hooked to the running image, directly in Emacs,
      • +
      • it integrates the Common Lisp debugger with an Emacs interface
      • +
      • it provides symbol completion,
      • +
      • code evaluation, compilation, macroexpansion
      • +
      • cross-referencing,
      • +
      • breaking, stepping, tracing,
      • +
      • go to definition,
      • +
      • online documentation,
      • +
      • fuzzy searching functions and symbols, system names, documentation,
      • +
      • an interactive object inspector,
      • +
      • it supports every common Common Lisp implementation,
      • +
      • multiple connections and multiple listener buffers (mrepl)
      • +
      • it is readily available from MELPA
      • +
      • it is actively maintained.
      -

      Check out this video tutorial ! (and the author’s channel, full of great stuff)

      - -
      SLIME fancy, contrib packages and other extensions
      - -

      SLIME’s functionalities live in packages and so-called contrib -modules -must be loaded to add further functionalities. The default -slime-fancy includes:

      - -
        -
      • slime-autodoc
      • -
      • slime-c-p-c
      • -
      • slime-editing-commands
      • -
      • slime-fancy-inspector
      • -
      • slime-fancy-trace
      • -
      • slime-fontifying-fu
      • -
      • slime-fuzzy
      • -
      • slime-mdot-fu
      • -
      • slime-macrostep
      • -
      • slime-presentations
      • -
      • slime-references
      • -
      • slime-repl
      • -
      • slime-scratch
      • -
      • slime-package-fu
      • -
      • slime-trace-dialog
      • -
      - -

      SLIME also has some nice extensions like -Helm-SLIME which features, among -others:

      - -
        -
      • Fuzzy completion,
      • -
      • REPL and connection listing,
      • -
      • Fuzzy-search of the REPL history,
      • -
      • Fuzzy-search of the apropos documentation.
      • -
      - -
      REPL interactions
      - -

      From the SLIME REPL, press , to prompt for commands. There is completion -over the available systems and packages. Examples:

      - -
        -
      • ,load-system
      • -
      • ,reload-system
      • -
      • ,in-package
      • -
      • ,restart-inferior-lisp
      • -
      - -

      and many more.

      - -

      With the slime-quicklisp contrib, you can also ,ql to list all systems -available for installation.

      - -

      SLY: Sylvester the Cat’s Common Lisp IDE

      +

      SLY: Sylvester the Cat’s Common Lisp IDE

      SLY is a SLIME fork that contains the following improvements:

      @@ -231,242 +147,120 @@ the following improvements:

      -

      Finding one’s way into Emacs’ built-in documentation

      +

      Sly is shipped by default in Doom Emacs.

      -

      Emacs comes with built-in tutorials and documentation. Moreover, it is -a self-documented and self-discoverable editor, capable of introspection to let you -know about the current keybindings, to let you search about function documentation, -available variables,source code, tutorials, etc. Whenever you ask yourself questions like -“what are the available shortcuts to do x” or “what does this -keybinding really do”, the answer is most probably a keystroke away, -right inside Emacs. You should learn a few keybindings to be able to -discover Emacs with Emacs flawlessly.

      +

      Installing SLIME or SLY

      -

      The help on the topic is here:

      +

      Manually

      + +

      On Ubuntu, SLIME is easily installed alongside Emacs and SBCL:

      + +
      sudo apt install emacs slime sbcl
      +
      + +

      Otherwise, install SLIME by adding this code to your ~/.emacs.d/init.el file:

      + +
      (require 'package)
      +(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
      +(dolist (package '(slime))
      +  (unless (package-installed-p package)
      +    (package-install package)))
      +(require 'slime)
      +
      + +

      assuming you’ve also instealled Emacs and SBCL.

      + +

      Since SLIME is heavily modular and the defaults only do the bare minimum (not +even the SLIME REPL), you might want to enable more features with

      + +
      (require 'slime)
      +(slime-setup '(slime-fancy slime-quicklisp slime-asdf slime-mrepl))
      +
      + +

      After this you can press Alt-X on your keyboard and type slime and try Common Lisp!

      + +

      (Alt-X is often written M-x in Emacs-world.)

      + +

      For more details, consult the +documentation (also available +as an Info page).

      + +

      Now you can run SLIME with, as mentioned, M-x slime and/or M-x slime-connect.

      + +

      See also:

      -

      The help keybindings start with either C-h or F1. Important ones are:

      +

      Portacle

      + +

      Portacle is a portable and +multi-platform CL development environment with which you can start +developping in Lisp in a few clicks. Portacle includes Emacs, SBCL, Slime, git, and useful Emacs extensions (which-key, treeview…).

      + +

      It is a straightforward way to get going.

      + +

      Doom Emacs

      + +

      Doom Emacs is a popular Emacs configuration. You can easily enable its Sly integration.

      + +

      SLIME fancy and contrib packages

      + +

      SLIME’s functionalities live in packages and so-called contrib +modules +must be loaded to add further functionalities. The afored mentioned +slime-fancy includes:

        -
      • C-h k <keybinding>: what function does this keybinding call?
      • -
      • C-h f <function name>: what keybinding is linked to this function?
      • -
      • C-h a <topic>: show a list of commands whose name match the given topic. It accepts a keyword, a list of keywords or a regular expression.
      • -
      • C-h i: show the Info page, a menu of major topics.
      • +
      • slime-autodoc
      • +
      • slime-c-p-c
      • +
      • slime-editing-commands
      • +
      • slime-fancy-inspector
      • +
      • slime-fancy-trace
      • +
      • slime-fontifying-fu
      • +
      • slime-fuzzy
      • +
      • slime-mdot-fu
      • +
      • slime-macrostep
      • +
      • slime-presentations
      • +
      • slime-references
      • +
      • slime-repl
      • +
      • slime-scratch
      • +
      • slime-package-fu
      • +
      • slime-trace-dialog
      • +
      • slime-mrepl (multiple REPLs)
      -

      Some Emacs packages give even more help.

      - -

      More help and discoverability packages

      - -

      Sometimes, you start typing a key sequence but you can’t remember it -completely. Or, you wonder what other keybindings are related. Comes -which-key-mode. This -packages will display all possible keybindings starting with the key(s) you just typed.

      - -

      For example, I know there are useful keybindings under C-x but I don’t remember which ones… I just type C-x, I wait for half a second, and which-key shows all the ones available.

      - -

      - -

      Just try it with C-h too!

      - -

      See also Helpful, an alternative to the built-in Emacs help that provides much more contextual information.

      - -

      - -

      Learn Emacs with the built-in tutorial

      - -

      Emacs ships its own tutorial. You should give it a look to learn the most important keybindings and concepts.

      - -

      Call it with M-x help-with-tutorial (where M-x is alt-x).

      - -

      - -

      Working with Lisp Code

      - -

      In this short tutorial we’ll see how to:

      +

      SLIME also has some nice extensions like +Helm-SLIME which features, among +others:

        -
      • edit Lisp code
      • -
      • evaluate and compile Lisp code
      • -
      • search Lisp code
      • +
      • Fuzzy completion,
      • +
      • REPL and connection listing,
      • +
      • Fuzzy-search of the REPL history,
      • +
      • Fuzzy-search of the apropos documentation.
      -

      Packages for structured editing

      +

      Working with SLIME (or SLY)

      -

      In addition to the built-in Emacs commands, you have several packages at your disposal -that will help to keep the parens and/or the indentation balanced. -The list below is somewhat sorted by age of the -extension, according to the -history of Lisp editing:

      +

      One of the first things you might want to do is to compile and load some Lisp code. Use C-c C-c on a function or C-c C-k to compile a whole file. But that’s not all, read on.

      -
        -
      • Paredit - Paredit is a -classic. It defines the must-have commands (move, kill, split, join -a sexp,…). -(visual tutorial)
      • -
      • Smartparens - Smartparens -not only deals with parens but with everything that comes in pairs -(html tags,…) and thus has features for non-lispy languages.
      • -
      • Lispy - Lispy reimagines Paredit -with the goal to have the shortest bindings (mostly one key) that -only act depending on the point position.
      • -
      • Paxedit - Paxedit adds -commands based on the context (in a symbol, a sexp,… ) and puts -efforts on whitespace cleanup and context refactoring.
      • -
      • Parinfer - Parinfer -automatically fixes the parens depending on the indentation, or the -other way round (or both !).
      • -
      +

      Note that we give function names for SLIME. They are most of the time similar with SLY.

      -

      We personally advice to try Parinfer and the famous Paredit, then to -go up the list. See explanations and even more on -Wikemacs.

      - -

      - -

      Editing

      - -

      Emacs has, of course, built-in commands to deal with s-expressions.

      - -
      Forward/Backward/Up/Down movement and selection by s-expressions
      - -

      Use C-M-f and C-M-b (forward-sexp and backward-sexp) to move -in units of s-expressions.

      - -

      Use C-M-t to swap -the first addition sexp and the second one. Put the cursor on the open -parens of “(+ x” in defun c and press

      - -

      Use C-M-@ to highlight an entire sexp. Then press C-M-u to expand -the selection “upwards” and C-M-d to move forward down one level of -parentheses.

      - -
      Deleting s-expressions
      - -

      Use C-M-k (kill-sexp) and C-M-backspace (backward-kill-sexp) (but caution: this keybinding may restart the system on GNU/Linux).

      - -

      For example, if point is before (progn (I’ll use [] as an indication where the cursor is):

      - -
      (defun d ()
      -  (if t
      -      (+ 3 3)
      -     [](progn
      -        (+ 1 1)
      -        (if t
      -            (+ 2 2)
      -            (+ 3 3)))
      -      (+ 4 4)))
      -
      - -

      and you press C-M-k, you get:

      - -
      (defun d ()
      -  (if t
      -      (+ 3 3)
      -      []
      -      (+ 4 4)))
      -
      - -
      Indenting s-expressions
      - -

      Indentation is automatic for Lisp forms.

      - -

      Pressing TAB will indent incorrectly indented code. For example, put -the point at the beginning of the (+ 3 3) form and press TAB:

      - -
      (progn
      -(+ 3 3))
      -
      - -

      you correctly get

      - -
      (progn
      -  (+ 3 3))
      -
      - -

      Use C-M-q (slime-reindent-defun) to indent the current function definition:

      - -
      ;; Put the cursor on the open parens of "(defun ..."
      -;; and press "C-M-q" to indent the code:
      -(defun e ()
      -"A badly indented function."
      -(let ((x 20))
      -(loop for i from 0 to x
      -do (loop for j from 0 below 10
      -do (print j))
      -(if (< i 10)
      -(let ((z nil) )
      -(setq z (format t "x=~d" i))
      -(print z))))))
      -
      -;; This is the result:
      -
      -(defun e ()
      -  "A badly indented function (now correctly indented)."
      -  (let ((x 20))
      -    (loop for i from 0 to x
      -       do (loop for j from 0 below 10
      -             do (print j))
      -         (if (< i 10)
      -             (let ((z nil) )
      -               (setq z (format t "x=~d" i))
      -               (print z))))))
      -
      - -

      You can also select a region and call M-x indent-region.

      - -
      Support for parenthesis
      - -

      Use M-( to insert a pair of parenthesis (()) and the same -keybinding with a prefix argument, C-u M-(, to enclose the -expression in front of the cursor with a pair of parens.

      - -

      For example, we start with the cursor before the first paren:

      - -
      CL-USER> |(- 2 2)
      -
      - -

      Press C-u M-( to enclose it with parens:

      - -
      CL-USER> (|(- 2 2))
      -;; now write anything.
      -CL-USER> (zerop (- 2 2))
      -
      - -

      With a numbered prefix argument (C-u 2 M-(), wrap around this number of s-expressions.

      - -

      Additionnaly, use M-x check-parens to spot malformed s-exps and C-c -C-] (slime-close-all-parens-in-sexp) to insert the required number -of closing parenthesis.

      - -
      Code completion
      +

      Code completion

      Use the built-in C-c TAB to complete symbols in SLIME. You can get tooltips with company-mode.

      -

      +

      -

      In the REPL, it’s simply TAB.

      +

      In the REPL, it’s simply TAB.

      -

      Use Emacs’ hippie-expand, bound to M-/, to complete any string -present in other open buffers.

      +

      Use Emacs’ hippie-expand, bound to M-/, to complete any string present in other open buffers.

      -
      Hiding/showing code
      - -

      Use C-x n n (narrow-to-region) and C-x n w to widen back.

      - -

      See also code folding.

      - -
      Comments
      - -

      Insert a comment, comment a region with M-;, adjust text with M-q.

      - -

      - -

      Evaluating and Compiling Lisp in SLIME

      +

      Evaluating and Compiling Lisp in SLIME

      Compile the entire buffer by pressing C-c C-k (slime-compile-and-load-file).

      @@ -474,13 +268,16 @@ present in other open buffers.

      Compile a defun by putting the cursor inside it and pressing C-c C-c (slime-compile-defun).

      +

      Once you compiled some code, you can try it, for example on the REPL.

      +

      To evaluate rather than compile:

      • evaluate the sexp before the point by putting the cursor after its closing paren and pressing C-x C-e (slime-eval-last-expression). The result is printed in the minibuffer.
      • -
      • similarly, use C-c C-p (slime-pprint-eval-last-expression) to eval and pretty-print the expression before point. It shows the result in a new “slime-description” window.
      • +
      • similarly, use C-c C-p (slime-pprint-eval-last-expression) to eval and pretty-print the expression before point. It shows the result in a new “slime-description” buffer.
      • +
      • use M-x slime-eval-print-last-expression (unbound by default) to print the result in the same file, under the cursor.
      • evaluate a region with C-c C-r,
      • evaluate a defun with C-M-x,
      • type C-c C-e (slime-interactive-eval) to get a prompt that asks for code to eval in the current context. It prints the result in the minibuffer. With a prefix argument, insert the result into the current buffer.
      • @@ -489,9 +286,9 @@ its closing paren and pressing C-x C-e

        See also other commands in the menu.

        -
        +

        But what’s the difference between evaluating and compiling some code?

        -

        EVALUATION VS COMPILATION

        +

        evaluation VS compilation

        There are a couple of pragmatic differences when choosing between compiling or evaluating. In general, it is better to compile top-level forms, for two reasons:

        @@ -524,89 +321,52 @@ In general, it is better to compile top-level forms, for two reasons:See also eval-in-repl to send any form to the repl.

        -
        +

        Debugging

        -

        +

        We cover debugging commands in its own debugging chapter.

        -

        Searching Lisp Code

        - -
        Standard Emacs text search (isearch forward/backward, regexp searches, search/replace)
        - -

        C-s does an incremental search forward (e.g. - as each key is -the search string is entered, the source file is searched for the -first match. This can make finding specific text much quicker as -you only need to type in the unique characters. Repeat searches -(using the same search characters) can be done by repeatedly -pressing C-s

        - -

        C-r does an incremental search backward

        - -

        C-s RET and C-r RET both do conventional string searches -(forward and backward respectively)

        - -

        C-M-s and C-M-r both do regular expression searches (forward -and backward respectively)

        - -

        M-% does a search/replace while C-M-% does a regular -expression search/replace

        - -
        Finding occurrences (occur, grep)
        - -

        Use M-x grep, rgrep, occur

        - -

        See also interactive versions with -helm-swoop, helm-occur, -ag.el.

        - -
        Go to definition
        +

        Go to definition

        Put the cursor on any symbol and press M-. (slime-edit-definition) to go to its definition. Press M-, to come back.

        -
        Go to symbol, list symbols in current source
        +

        Go to any symbol, list symbols in current source

        -

        Use C-u M-. (slime-edit-definition with a prefix argument, also available as M-- M-.) to autocomplete the symbol and navigate to it. This command always asks for a symbol even if the cursor is on one. It works with any loaded definition. Here’s a little demonstration video.

        +

        Use C-u M-. (slime-edit-definition with a prefix argument, also available as M-- M-.) to autocomplete the symbol and navigate to it.

        + +

        This command always asks for a symbol even if the cursor is on one. It works with any loaded definition. Here’s a little demonstration video.

        You can think of it as a imenu completion that always work for any Lisp symbol. Add in Slime’s fuzzy completion for maximum powerness!

        -
        Crossreferencing: find who’s calling, referencing, setting a symbol
        - -

        Slime has nice cross-referencing facilities. For example, you can ask -what calls a particular function, what expands a macro, or where a global variable is being used.

        - -

        Results are presented in a new buffer, listing the places which reference a particular entity. -From there, we can press Enter to go to the corresponding source line, -or more interestingly we can recompile the place at point by pressing C-c C-c on that -line. Likewise, C-c C-k will recompile all the references. This is useful when -modifying macros, inline functions, or constants.

        - -

        The bindings are the following (they are also shown in Slime’s menu):

        - -
          -
        • C-c C-w c (slime-who-calls) callers of a function
        • -
        • C-c C-w m (slime-who-macroexpands) places where a macro is expanded
        • -
        • C-c C-w r (slime-who-references) global variable references
        • -
        • C-c C-w b (slime-who-bind) global variable bindings
        • -
        • C-c C-w s (slime-who-sets) global variable setters
        • -
        • C-c C-w a (slime-who-specializes) methods specialized on a symbol
        • -
        - -

        And when the slime-asdf contrib is enabled, -C-c C-w d (slime-who-depends-on) lists dependent ASDF systems

        - -

        And a general binding: M-? or M-_ (slime-edit-uses) combines all -of the above, it lists every kind of references.

        - -

        - -

        Lisp Documentation in Emacs - Learning About Lisp Symbols

        - -

        Argument lists

        +

        Argument lists

        When you put the cursor on a function, SLIME will show its signature in the minibuffer.

        -

        Documentation lookup

        +

        If you want to see them better, try C-c C-s after a function name.

        + +

        For example, you forgot how to use with-open-file. Write it:

        + +
        (with-open-file
        +
        + +

        now press C-c C-s (slime-complete-form) and you’ll get:

        + +
        (with-open-file (stream filespec :direction direction
        +                                 :element-type element-type
        +                                 :if-exists if-exists
        +                                 :if-does-not-exist if-does-not-exist
        +                                 :external-format external-format
        +                                 :class class
        +                         )
        +           body...)
        +
        + +

        written in your source file (or in the REPL).

        + +

        The minibuffer will show you the default values of the arguments.

        + +

        Documentation lookup

        The main shortcut to know is:

        @@ -634,15 +394,172 @@ in the minibuffer.

        -

        Inspect

        +

        Inspector

        You can call (inspect 'symbol) from the REPL or call it with C-c I from a source file.

        -

        Macroexpand

        +

        Learn to use with its documentation: use l to come back to the previous object, * to copy the object at point… and more.

        + +

        Macroexpand

        Use C-c M-m to macroexpand a macro call

        -

        Consult the Hyper Spec (CLHS) offline

        +

        Crossreferencing: find who’s calling, referencing, setting a symbol

        + +

        Slime has nice cross-referencing facilities. For example, you can ask +what calls a particular function, what expands a macro, or where a global variable is being used.

        + +

        Results are presented in a new buffer, listing the places which reference a particular entity. +From there, we can press Enter to go to the corresponding source line, +or more interestingly we can recompile the place at point by pressing C-c C-c on that +line. Likewise, C-c C-k will recompile all the references. This is useful when +modifying macros, inline functions, or constants.

        + +

        The bindings are the following (they are also shown in Slime’s menu):

        + +
          +
        • C-c C-w c (slime-who-calls) callers of a function
        • +
        • C-c C-w m (slime-who-macroexpands) places where a macro is expanded
        • +
        • C-c C-w r (slime-who-references) global variable references
        • +
        • C-c C-w b (slime-who-bind) global variable bindings
        • +
        • C-c C-w s (slime-who-sets) global variable setters
        • +
        • C-c C-w a (slime-who-specializes) methods specialized on a symbol
        • +
        + +

        And when the slime-asdf contrib is enabled, +C-c C-w d (slime-who-depends-on) lists dependent ASDF systems

        + +

        And a general binding: M-? or M-_ (slime-edit-uses) combines all +of the above, it lists every kind of references.

        + +

        Systems interactions

        + +

        In Slime, you can use the usual C-c C-k in an .asd file to compile and load it, then ql:quickload (or asdf:load-system) to effectively load the system. SLIME offers more interactive commands to interact with Lisp systems:

        + +
          +
        • M-x slime-load-system: offers a prompt to select an ASDF system, with autocompletion of projects collected from where ASDF sees Common Lisp projects, then compile and load the system. The default system name is taken from the first file matching *.asd in the current buffer’s working directory. +
            +
          • note that the system name is inferred from the .asd file name. The real system name defined inside may be different.
          • +
          • to understand where ASDF looks for Lisp systems, read the getting started page, section “How to load an existing project”.
          • +
          +
        • +
        • M-x slime-open-system: this opens a new buffer for all source files of a given system.
        • +
        • M-x slime-browse-system: this command opens a Dired buffer to browse the files of a system.
        • +
        • M-x slime-rgrep-system: run rgrep on the base directory of a system.
        • +
        • M-x slime-isearch-system: run isearch on the files of a system.
        • +
        • M-x slime-query-replace-system: run query-replace on an ASDF system.
        • +
        • M-x slime-save-system: save all files belongign to a system.
        • +
        • M-x slime-delete-system-fasls: this deletes the cached .fasl files for this system.
        • +
        + +

        Sly users have a more featureful sly-load-system command that will search the .asd file on the current directory and in parent directories.

        + +

        REPL interactions

        + +

        From the SLIME REPL, press , to prompt for commands. There is completion +over the available systems and packages. Examples:

        + +
          +
        • ,load-system
        • +
        • ,reload-system
        • +
        • ,in-package (also C-c M-p in a .lisp file)
        • +
        • ,restart-inferior-lisp
        • +
        + +

        and many more. Usually the interactive commands given in the previous section have a REPL shortcut.

        + +

        With the slime-quicklisp contrib, we can use ,ql to +autocomplete a system to install, from all systems available for +installation.

        + +

        In addition, we can use the Quicklisp-systems Slime extension to search, browse and load Quicklisp systems from Emacs.

        + +

        Sending code to the REPL

        + +

        You can write code in the REPL, but you can also interact with code directly from the source file.

        + +

        We saw C-c C-j, that sends the expression at point to the REPL and evaluates it.

        + +

        C-c C-y (slime-call-defun): send code to the REPL.

        + +

        When the point is inside a defun and C-c C-y is pressed (below I’ll use [] as an indication where the cursor is)

        + +
        (defun foo ()
        + nil[])
        +
        + +

        then (foo []) will be inserted into the REPL, so that you can write +additional arguments and run it.

        + +

        If foo was in a different package than the package of the REPL, +(package:foo ) or (package::foo ) will be inserted.

        + +

        This feature is very useful for testing a function you just wrote.

        + +

        That works not only for a defun, but also for defgeneric, defmethod, +defmacro, and define-compiler-macro in the same fashion as for defun.

        + +

        For defvar, defparameter, defconstant: [] *foo* will be inserted +(the cursor is positioned before the symbol so that you can easily +wrap it into a function call).

        + +

        For defclass: (make-instance ‘class-name ).

        + +

        Inserting calls to frames in the debugger

        + +

        C-y in SLDB on a frame will insert a call to that frame into the REPL, e.g.,

        + +
        (/ 0) =>
        +…
        +1: (CCL::INTEGER-/-INTEGER 1 0)
        +…
        +
        + +

        C-y will insert (CCL::INTEGER-/-INTEGER 1 0).

        + +

        (thanks to Slime tips)

        + +

        Synchronizing packages

        + +

        C-c ~ (slime-sync-package-and-default-directory): When run in a +buffer with a lisp file it will change the current package of the REPL +to the package of that file and also set the current directory of the REPL +to the parent directory of the file.

        + +

        Exporting symbols

        + +

        Slime provides a shortcut to add export declarations to your package, effectively exporting one or many symbol(s), or on the contrary un-exporting it.

        + +

        C-c x (slime-export-symbol-at-point) from the slime-package-fu +contrib: takes the symbol at point and modifies the :export clause of +the corresponding defpackage form. It also exports the symbol. When +called with a negative argument (C-u C-c x) it will remove the symbol +from :export and unexport it.

        + +

        M-x slime-export-class does the same but with symbols defined +by a structure or a class, like accessors, constructors, and so on. +It works on structures only on SBCL and Clozure CL so far. +Classes should work everywhere with MOP.

        + +

        Customization

        + +

        There are different styles of how symbols are presented in +defpackage, the default is to use uninterned symbols (#:foo). +This can be changed:

        + +

        to use keywords, add this to your Emacs init file:

        + +
        (setq slime-export-symbol-representation-function
        +      (lambda (n) (format ":%s" n)))
        +
        + +

        or strings:

        + +
        (setq slime-export-symbol-representation-function
        + (lambda (n) (format "\"%s\"" (upcase n))))
        +
        + +

        (optional) Consult the Hyper Spec (CLHS) offline

        The Common Lisp Hyper Spec is the official online version of the ANSI Common Lisp standard. We can start @@ -699,106 +616,301 @@ common-lisp-hyperspec-format, bound to C-c C-d ~.

      • you can also look-up glossary terms (for example, you can look-up “function” instead of “defun”), use M-x common-lisp-hyperspec-glossary-term, bound to C-c C-d g.
      -

      Miscellaneous

      +

      Working with Emacs

      -

      Synchronizing packages

      +

      In this section we’ll learn the most useful Emacs commands to work with Lisp code in general, or to perform common actions.

      -

      C-c ~ (slime-sync-package-and-default-directory): When run in a -buffer with a lisp file it will change the current package of the REPL -to the package of that file and also set the current directory of the REPL -to the parent directory of the file.

      +

      We’ll start by how to find your way into Emacs’ built-in documentation. If there is a skill you should learn, that is the one.

      -

      Calling code

      +

      Don’t forget that Emacs both GUI and terminal interfaces have menus, they help in discovering all available commands. If you don’t see one, ensure that your emacs configuration doesn’t hide it. Display the menu with M-x menu-bar-mode.

      -

      C-c C-y (slime-call-defun): When the point is inside a defun and -C-c C-y is pressed,

      +

      Built-in documentation

      -

      (I’ll use [] as an indication where the cursor is)

      +

      Emacs comes with built-in tutorials and documentation. Moreover, it is +a self-documented and self-discoverable editor, capable of introspection to let you +know about the current keybindings, to let you search about function documentation, +available variables,source code, tutorials, etc. Whenever you ask yourself questions like +“what are the available shortcuts to do x” or “what does this +keybinding really do”, the answer is most probably a keystroke away, +right inside Emacs. You should learn a few keybindings to be able to +discover Emacs with Emacs flawlessly.

      -
      (defun foo ()
      - nil[])
      -
      - -

      then (foo []) will be inserted into the REPL, so that you can write -additional arguments and run it.

      - -

      If foo was in a different package than the package of the REPL, -(package:foo ) or (package::foo ) will be inserted.

      - -

      This feature is very useful for testing a function you just wrote.

      - -

      That works not only for defun, but also for defgeneric, defmethod, -defmacro, and define-compiler-macro in the same fashion as for defun.

      - -

      For defvar, defparameter, defconstant: [] *foo* will be inserted -(the cursor is positioned before the symbol so that you can easily -wrap it into a function call).

      - -

      For defclass: (make-instance ‘class-name ).

      - -

      Inserting calls to frames in the debugger

      - -

      C-y in SLDB on a frame will insert a call to that frame into the REPL, e.g.,

      - -
      (/ 0) =>
      -…
      -1: (CCL::INTEGER-/-INTEGER 1 0)
      -…
      -
      - -

      C-y will insert (CCL::INTEGER-/-INTEGER 1 0).

      - -

      (thanks to Slime tips)

      - -

      Exporting symbols

      - -

      C-c x (slime-export-symbol-at-point) from the slime-package-fu -contrib: takes the symbol at point and modifies the :export clause of -the corresponding defpackage form. It also exports the symbol. When -called with a negative argument (C-u C-c x) it will remove the symbol -from :export and unexport it.

      - -

      M-x slime-export-class does the same but with symbols defined -by a structure or a class, like accessors, constructors, and so on. -It works on structures only on SBCL and Clozure CL so far. -Classes should work everywhere with MOP.

      - -

      Customization

      - -

      There are different styles of how symbols are presented in -defpackage, the default is to use uninterned symbols (#:foo). -This can be changed:

      - -

      to use keywords:

      - -
      (setq slime-export-symbol-representation-function
      -      (lambda (n) (format ":%s" n)))
      -
      - -

      or strings:

      - -
      (setq slime-export-symbol-representation-function
      - (lambda (n) (format "\"%s\"" (upcase n))))
      -
      - -

      Project Management

      - -

      ASDF is the de-facto build facility. It is shipped in most Common Lisp implementations.

      +

      The help on the topic is here:

      -

      Searching Quicklisp libraries

      +

      The help keybindings start with either C-h or F1. Important ones are:

      -

      From the REPL, we can use ,ql to install a package known by name already.

      +
        +
      • C-h k <keybinding>: what function does this keybinding call?
      • +
      • C-h f <function name>: what keybinding is linked to this function?
      • +
      • C-h a <topic>: show a list of commands whose name match the given topic. It accepts a keyword, a list of keywords or a regular expression.
      • +
      • C-h i: show the Info page, a menu of major topics.
      • +
      -

      In addition, we can use the Quicklisp-systems Slime extension to search, browse and load Quicklisp systems from Emacs.

      +

      Some Emacs packages give even more help.

      -

      Questions/Answers

      +

      More help and discoverability packages

      -

      utf-8 encoding

      +

      Sometimes, you start typing a key sequence but you can’t remember it +completely. Or, you wonder what other keybindings are related. Comes +which-key-mode. This +packages will display all possible keybindings starting with the key(s) you just typed.

      + +

      For example, I know there are useful keybindings under C-x but I don’t remember which ones… I just type C-x, I wait for half a second, and which-key shows all the ones available.

      + +

      + +

      Just try it with C-h too!

      + +

      See also Helpful, an alternative to the built-in Emacs help that provides much more contextual information.

      + +

      + +

      Built-in tutorial

      + +

      Emacs ships its own tutorial. You should give it a look to learn the most important keybindings and concepts.

      + +

      Call it with M-x help-with-tutorial (where M-x is alt-x).

      + +

      Editing

      + +

      Emacs has, of course, built-in commands to deal with s-expressions.

      + +

      Forward/Backward/Up/Down movement and selection by s-expressions

      + +

      Use C-M-f and C-M-b (forward-sexp and backward-sexp) to move +in units of s-expressions.

      + +

      Use C-M-t to swap +the first addition sexp and the second one. Put the cursor on the open +parens of “(+ x” in defun c and press

      + +

      Use C-M-@ to highlight an entire sexp. Then press C-M-u to expand +the selection “upwards” and C-M-d to move forward down one level of +parentheses.

      + +

      Deleting s-expressions

      + +

      Use C-M-k (kill-sexp) and C-M-backspace (backward-kill-sexp) (but caution: this keybinding may restart the system on GNU/Linux).

      + +

      For example, if point is before (progn (I’ll use [] as an indication where the cursor is):

      + +
      (defun d ()
      +  (if t
      +      (+ 3 3)
      +     [](progn
      +        (+ 1 1)
      +        (if t
      +            (+ 2 2)
      +            (+ 3 3)))
      +      (+ 4 4)))
      +
      + +

      and you press C-M-k, you get:

      + +
      (defun d ()
      +  (if t
      +      (+ 3 3)
      +      []
      +      (+ 4 4)))
      +
      + +

      Indenting s-expressions

      + +

      Indentation is automatic for Lisp forms.

      + +

      Pressing TAB will indent incorrectly indented code. For example, put +the point at the beginning of the (+ 3 3) form and press TAB:

      + +
      (progn
      +(+ 3 3))
      +
      + +

      you correctly get

      + +
      (progn
      +  (+ 3 3))
      +
      + +

      Use C-M-q (slime-reindent-defun) to indent the current function definition:

      + +
      ;; Put the cursor on the open parens of "(defun ..."
      +;; and press "C-M-q" to indent the code:
      +(defun e ()
      +"A badly indented function."
      +(let ((x 20))
      +(loop for i from 0 to x
      +do (loop for j from 0 below 10
      +do (print j))
      +(if (< i 10)
      +(let ((z nil) )
      +(setq z (format t "x=~d" i))
      +(print z))))))
      +
      +;; This is the result:
      +
      +(defun e ()
      +  "A badly indented function (now correctly indented)."
      +  (let ((x 20))
      +    (loop for i from 0 to x
      +       do (loop for j from 0 below 10
      +             do (print j))
      +         (if (< i 10)
      +             (let ((z nil) )
      +               (setq z (format t "x=~d" i))
      +               (print z))))))
      +
      + +

      You can also select a region and call M-x indent-region.

      + +

      Open and close parenthesis

      + +

      When you are in a Slime REPL, you can use C-return or M-return +(slime-repl-closing-return) to close the remaining parenthesis and +evaluate your input string.

      + +

      In files, use M-( to insert a pair of parenthesis (()) and the same +keybinding with a prefix argument, C-u M-(, to enclose the +expression in front of the cursor with a pair of parens.

      + +

      For example, we start with the cursor before the first paren:

      + +
      CL-USER> |(- 2 2)
      +
      + +

      Press C-u M-( to enclose it with parens:

      + +
      CL-USER> (|(- 2 2))
      +;; now write anything.
      +CL-USER> (zerop (- 2 2))
      +
      + +

      With a numbered prefix argument (C-u 2 M-(), wrap around this number of s-expressions.

      + +

      Additionnaly, use M-x check-parens to spot malformed s-exps and C-c +C-] (slime-close-all-parens-in-sexp) to insert the required number +of closing parenthesis.

      + +

      There are additional packages that can make your use of parens easier:

      + +
        +
      • M-x show-paren-mode, a built-in Emacs mode: it toggles the +visualization of matching parenthesis. When enabled, place the +cursor on a paren and you’ll see the other paren it matches +with. You can initialize it in your Emacs init file with +(show-paren-mode t). It is a global minor mode (it will work for +all buffers, all languages).
      • +
      • when evil-mode (the vim layer) is enabled, you can use the % key to go to the matchin paren.
      • +
      • M-x electric-pair-mode, a built-in Emacs mode: when enabled, +typing an open parenthesis automatically inserts the corresponding +closing parenthesis, and vice versa. (Likewise for brackets, etc.). +If the region is active, the parentheses (brackets, etc.) are inserted +around the region instead.
      • +
      • you could use Paredit (animated guide) to automatically insert parentheses in pairs,
      • +
      • or lispy-mode, like Paredit, but a key triggers an action when the cursor is placed right before or right after a parentheses.
      • +
      + +

      Hiding/showing code

      + +

      Use C-x n n (narrow-to-region) and C-x n w to widen back.

      + +

      See also code folding with external packages.

      + +

      Comments

      + +

      Insert a comment or comment a region with M-;, adjust text with M-q.

      + +

      (optional) Packages for structured editing

      + +

      In addition to the built-in Emacs commands, you have several packages at your disposal +that will help to keep the parens and/or the indentation balanced. +The list below is somewhat sorted by age of the +extension, according to the +history of Lisp editing:

      + +
        +
      • Paredit - Paredit is a +classic. It defines the must-have commands (move, kill, split, join +a sexp,…). +(visual tutorial)
      • +
      • Smartparens - Smartparens +not only deals with parens but with everything that comes in pairs +(html tags,…) and thus has features for non-lispy languages.
      • +
      • Lispy - Lispy reimagines Paredit +with the goal to have the shortest bindings (mostly one key) that +only act depending on the point position.
      • +
      • Paxedit - Paxedit adds +commands based on the context (in a symbol, a sexp,… ) and puts +efforts on whitespace cleanup and context refactoring.
      • +
      • Parinfer - Parinfer +automatically fixes the parens depending on the indentation, or the +other way round (or both !).
      • +
      + +

      We personally advice to try Parinfer and the famous Paredit, then to +go up the list. See explanations and even more on +Wikemacs.

      + +

      Search and replace

      + +

      isearch forward/backward, regexp searches, search/replace

      + +

      C-s does an incremental search forward (e.g. - as each key is +the search string is entered, the source file is searched for the +first match. This can make finding specific text much quicker as +you only need to type in the unique characters. Repeat searches +(using the same search characters) can be done by repeatedly +pressing C-s

      + +

      C-r does an incremental search backward

      + +

      C-s RET and C-r RET both do conventional string searches +(forward and backward respectively)

      + +

      C-M-s and C-M-r both do regular expression searches (forward +and backward respectively)

      + +

      M-% does a search/replace while C-M-% does a regular +expression search/replace

      + +

      Finding occurrences (occur, grep)

      + +

      Use M-x grep, rgrep, occur

      + +

      See also interactive versions with +helm-swoop, helm-occur, +ag.el.

      + +

      Questions/Answers

      + +

      Emacs Lisp vs Common Lisp

      + +

      It isn’t necessary to write Emacs Lisp in order to use Emacs with Slime or Sly for Common Lisp.

      + +

      However learning Emacs Lisp can be useful and is similar (but different) from CL:

      +
        +
      • Dynamic scope is everywhere
      • +
      • There are no reader (or reader-related) functions
      • +
      • Does not support all the types that are supported in CL
      • +
      • Incomplete implementation of CLOS (with the add-on EIEIO package)
      • +
      • No numerical tower support
      • +
      + +

      Some good Emacs Lisp learning resources:

      + + +

      What about LSP (Language Server Protocol)?

      + +

      LSP server and client ports for Common Lisp exist, but we don’t need them to have a high quality IDE integration. In fact, Slime/Swank follow a client/server architecture, like LSP, but Slime predates LSP by decades, and still offers much more features for lispers than LSP.

      + +

      utf-8 encoding

      You might want to set this to your init file:

      @@ -816,7 +928,7 @@ This can be changed:

      This will avoid getting ascii stream decoding errors when you have non-ascii characters in files you evaluate with SLIME.

      -

      Default cut/copy/paste keybindings

      +

      Default cut/copy/paste keybindings

      I am so used to C-c, C-v and friends to copy and paste text that the default Emacs shortcuts don’t make any sense to me.

      @@ -827,9 +939,9 @@ the default Emacs shortcuts don’t make any sense to me.

      (require 'cua) (CUA-mode t) -

      Appendix

      +

      Appendix

      -

      All Slime REPL shortcuts

      +

      All Slime REPL shortcuts

      Here is the reference of all Slime shortcuts that work in the REPL.

      @@ -849,8 +961,8 @@ SPC slime-space (that binding is currently shadowed by another mode) , slime-handle-repl-shortcut DEL backward-delete-char-untabify -<C-down> slime-repl-forward-input <C-return> slime-repl-closing-return +<C-down> slime-repl-forward-input <C-up> slime-repl-backward-input <return> slime-repl-return @@ -887,7 +999,9 @@ C-c M-i slime-fuzzy-complete-symbol C-c M-o slime-repl-clear-buffer -

      All other Slime shortcuts

      +

      All other Slime shortcuts

      + +

      There is more to what we showed! Slime has shortcuts to disassemble the function definition of the symbol at point, learn how to navigate the inspector, toggle functions profiling, learn its indentation or completion strategies, use multiple Lisp connections, learn how to manipulate presentations

      Here are all the default keybindings defined by Slime mode.

      @@ -1053,10 +1167,28 @@ C-x 4 . slime-edit-definition-other-window C-c C-v M-o slime-clear-presentations -

      See also

      +

      See also

        -
      • Common Lisp REPL exploration guide - a concise and curated set of highlights to find one’s way in the REPL.
      • +
      • SLIME’s documentation
      • +
      • Slime video tutorial (and the author’s channel, full of great stuff)
      • +
      • Marco Baringer’s Slime tutorial
      • +
      • Common Lisp REPL exploration guide, a concise and curated set of highlights to find one’s way in the REPL.
      • +
      • Emacs4CL, a tiny DIY kit to set up vanilla Emacs for Common Lisp programming.
      • +
      • slime-star, a collection of extensions for SLIME: +
          +
        • doc contribs: richer slime-help and slime-info buffers to display documentation.
        • +
        • Quicklisp systems: autocompletion to load Quicklisp systems from the REPL.
        • +
        • quicksearch integration: search for Common Lisp repositories on Quicklisp, Github and Cliki.
        • +
        • Slime breakpoints: set breakpoints visually without code annotation, get buttons to step through code.
        • +
        • Quicklisp apropos: “apropos” across Quicklisp libraries.
        • +
        • Slime critic: get the Slime critic gently critique your code.
        • +
        • interactive print and trace buffers
        • +
        • dedicated Emacs buffers for output streams
        • +
        • access to the ANSICL specification in Emacs’ Info format.
        • +
        • Lisp system browser: a (work in progress) Smalltalk-like system browser for Common Lisp, where one can get different panes to browse available packages and their functions, variables, macros, classes, generic functions.
        • +
        +
      @@ -1089,7 +1221,7 @@ C-c C-v M-o slime-clear-presentations
      © 2002–2023 the Common Lisp Cookbook Project
      - 📹 Discover vindarel's Lisp course on Udemy + 📹 Discover our contributor's Common Lisp video course on Udemy
      diff --git a/lispcookbook.github.io/cl-cookbook/error_handling.html b/lispcookbook.github.io/cl-cookbook/error_handling.html index dff6b47..37570df 100644 --- a/lispcookbook.github.io/cl-cookbook/error_handling.html +++ b/lispcookbook.github.io/cl-cookbook/error_handling.html @@ -53,14 +53,19 @@

      The Common Lisp Cookbook – Error and exception handling

      -

      - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

      + + + + + + + + + +

      + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

      +

      📕 Get the EPUB and PDF

      @@ -657,7 +662,7 @@ anything), but our message is printed afterwards anyway.


      © 2002–2023 the Common Lisp Cookbook Project
      - 📹 Discover vindarel's Lisp course on Udemy + 📹 Discover our contributor's Common Lisp video course on Udemy
      diff --git a/lispcookbook.github.io/cl-cookbook/ffi.html b/lispcookbook.github.io/cl-cookbook/ffi.html index 49ab4d6..031d234 100644 --- a/lispcookbook.github.io/cl-cookbook/ffi.html +++ b/lispcookbook.github.io/cl-cookbook/ffi.html @@ -53,125 +53,95 @@

      The Common Lisp Cookbook – Foreign Function Interfaces

      -

      - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

      + + + + + + + + + +

      + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

      +

      📕 Get the EPUB and PDF

      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.

      +

      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 library, a portable and easy-to-use C foreign function interface.

      -

      +
      +

      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.

      +
      -

      Example: Calling ‘gethostname’ from CLISP

      +

      We’ll see an example right now.

      -

      Note: You should read the relevant chapter from the CLISP implementation notes before you proceed.

      +

      CFFI: calling a C function from the math.h header file.

      -

      int gethostname(char *name, int len) follows a typical pattern of C “out”-parameter convention - it expects a pointer to a buffer it’s going to fill. So you must view this parameter as either :OUT or :IN-OUT. Additionally, one must tell the function the size of the buffer. Here len is just an :IN parameter. Sometimes this will be an :IN-OUT parameter, returning the number of bytes actually filled in.

      +

      Let’s use defcfun to interface with the foreign ceil C function from math.h.

      -

      So name is actually a pointer to an array of up to len characters, regardless of what the poor “char *” C prototype says, to be used like a C string (0-termination). How many elements are in the array? Luckily, in our case, you can find it out without calculating the sizeof() a C structure. It’s a hostname that will be returned. The Solaris 2.x manpage says “Host names are limited to MAXHOSTNAMELEN characters, currently 256.”

      +

      defcfun is a macro in the cffi library that generates a function with the name you give it.

      -

      Also, in the present example, you can use allocation :ALLOCA, like you’d do in C: stack-allocate a temporary. Why make things worse when using Lisp than when using C?

      - -

      This yields the following useful signature for your foreign function:

      - -
      (ffi:def-c-call-out gethostname
      -     (:arguments (name (ffi:c-ptr (ffi:c-array-max ffi:char 256))
      -     :out :alloca)
      -     (len ffi:int))
      -     ;; (:return-type BOOLEAN) could have been used here
      -     ;; (Solaris says it's either 0 or -1).
      -     (:return-type ffi:int))
      -
      -     (defun myhostname ()
      -     (multiple-value-bind (success name)
      -     ;; :OUT or :IN-OUT parameters are returned via multiple values
      -     (gethostname 256)
      -     (if (zerop success)
      -     (subseq name 0 (position #\null name))
      -     (error ... ; errno may be set
      -     ...))))
      -     (defvar hostname (myhostname))
      +
      CL-USER> (cffi:defcfund ("ceil" c-ceil) :double (number :double))
       
      -

      Possibly SUBSEQ and POSITION are superfluous, thanks to C-ARRAY-MAX as opposed to C-ARRAY:

      +

      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.

      -
      (defun myhostname ()
      -     (multiple-value-bind (success name)
      -     ;; :out or :in-out parameters are returned via multiple values
      -     (gethostname 256)
      -     (if (zerop success) name
      -     (error ... ; errno may be set
      -     ...))))
      +

      Here is the above function macroexpanded with macrostep-expand:

      + +
      (progn
      +  nil
      +  (defun c-ceil (number)
      +    (let ((#:g312 number))
      +      (cffi-sys:%foreign-funcall "ceil" (:double #:g312 :double) :convention
      +				 :cdecl :library :default))))
       
      -

      +

      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.

      -

      Example: Calling ‘gethostname’ from Allegro CL

      +

      Now that we have a c-ceil function from math.h, let’s use it! We must give it double float.

      -

      This is how the same example above would be written in Allegro Common Lisp version 6 and above. ACL doesn’t explicitly distinguish between input and output arguments. The way to declare an argument as output (i.e., modifiable by C) is to use an array, since arrays are passed by reference and C therefore receives a pointer to a memory location (which is what it expects). In this case things are made even easier by the fact that gethostname() expects an array of char, and a SIMPLE-ARRAY of CHARACTER represents essentially the same thing in Lisp. The foreign function definition is therefore the following:

      - -
      (def-foreign-call (c-get-hostname "gethostname")
      -     ((name (* :char) (simple-array 'character (*)))
      -     (len :int integer))
      -     :returning :int)
      +
      CL-USER> (c-ceil 5.4d0)
      +6.0d0
       
      -

      Let’s read this line by line: this form defines a Lisp function called C-GET-HOSTNAME that calls the C function gethostname(). It takes two arguments: the first one, called NAME, is a pointer to a char (*char in C), and a SIMPLE-ARRAY of characters in Lisp; the second one is called LEN, and is an integer. The function returns an integer value.

      +

      As you can see, it works! The double gets rounded up to 6.0d0 as expected.

      -

      And now the Lisp side:

      +

      Let’s try another one! This time, we’ll use floor, and we couldn’t name it “floor” because this Common Lisp function exists.

      -
      (defun get-hostname ()
      -     (let* ((name (make-array 256 :element-type 'character))
      -     (result (c-get-hostname name 256)))
      -     (if (zerop result)
      -     (let ((pos (position #\null name)))
      -     (subseq name 0 pos))
      -     (error "gethostname() failed."))))
      +
      CL-USER> (cffi:defcfun ("floor" c-floor) :double (number :double))
      +C-FLOOR
      +CL-USER> (c-floor 5d0)
      +5.0d0
      +CL-USER> (c-floor 5.4d0)
      +5.0d0
       
      -

      This function creates the NAME array, calls C-GET-HOSTNAME to fill it and then checks the returned value. If the value is zero, then the call was successful, and we return the contents of NAME up to the first 0 character (the string terminator in C), otherwise we signal an error. Note that, unlike the previous example, we allocate the string in Lisp, and we rely on the Lisp garbage collector to get rid of it after the function terminates. Here is a usage example:

      +

      Great!

      -
      * (get-hostname)
      -     "terminus"
      +

      One more, let’s try sqrt from math.h, still with double floats:

      + +
      CL-USER> (cffi:defcfun ("sqrt" c-sqrt) :double (number :double))
      +C-SQRT
      +CL-USER> (c-sqrt 36.50d0)
      +6.041522986797286d0
       
      -

      Working with strings is, in general, easier than the previous example showed. Let’s say you want to call getenv() from Lisp to access the value of an environment variable. getenv() takes a string argument (the variable name) and returns another string (the variable value). To be more precise, the argument is a pointer to a sequence of characters that should have been allocated by the caller, and the return value is a pointer to an already-existing sequence of chars (in the environment). Here is the definition of C-GETENV:

      +

      We can do arithmetic with our new c-sqrt:

      -
      (def-foreign-call (c-getenv "getenv")
      -     ((var (* :char) string))
      -     :returning :int
      -     :strings-convert t)
      +
      CL-USER> (+ 2 (c-sqrt 3d0))
      +3.732050807568877d0
       
      -

      The argument in this case is still a pointer to char in C, but we can declare it a STRING to Lisp. The return value is a pointer, so we declare it as integer. Finally, the :STRINGS-CONVERT keyword argument specifies that ACL should automatically translate the Lisp string passed as the first argument into a C string. Here is how it’s used:

      +

      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!

      -
      * (c-getenv "SHELL")
      -     -1073742215
      -
      - -

      If you are surprised by the return value, just remember that C-GETENV returns a pointer, and we must tell Lisp how to interpret the contents of the memory location pointed to by it. Since in this case we know that it will point to a C string, we can use the FF:NATIVE-TO-STRING function to convert it to a Lisp string:

      - -
      * (native-to-string (c-getenv "SHELL"))
      -     "/bin/tcsh"
      -     9
      -     9
      -
      - -

      (The second and third values are the number of characters and bytes copied, respectively). One caveat: if you ask for the value of a non-existent variable, C-GETENV will return 0, and NATIVE-TO-STRING will fail. So a safer example would be:

      - -
      * (let ((ptr (c-getenv "NOSUCHVAR")))
      -     (unless (zerop ptr)
      -     (native-to-string ptr)))
      -     NIL
      +
      CL-USER> (mapcar #'c-sqrt '(3d0 4d0 5d0 6d0 7.5d0 12.75d0))
      +(1.7320508075688772d0 2.0d0 2.23606797749979d0 2.449489742783178d0
      + 2.7386127875258306d0 3.570714214271425d0)
       
      @@ -204,7 +174,7 @@
      © 2002–2023 the Common Lisp Cookbook Project diff --git a/lispcookbook.github.io/cl-cookbook/files.html b/lispcookbook.github.io/cl-cookbook/files.html index 6f39478..9d941cf 100644 --- a/lispcookbook.github.io/cl-cookbook/files.html +++ b/lispcookbook.github.io/cl-cookbook/files.html @@ -53,14 +53,19 @@

      The Common Lisp Cookbook – Files and Directories

      -

      - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

      + + + + + + + + + +

      + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

      +

      📕 Get the EPUB and PDF

      @@ -872,7 +877,7 @@ operations.


      © 2002–2023 the Common Lisp Cookbook Project diff --git a/lispcookbook.github.io/cl-cookbook/functions.html b/lispcookbook.github.io/cl-cookbook/functions.html index 920a2c2..4ed703d 100644 --- a/lispcookbook.github.io/cl-cookbook/functions.html +++ b/lispcookbook.github.io/cl-cookbook/functions.html @@ -53,14 +53,19 @@

      The Common Lisp Cookbook – Functions

      -

      - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

      + + + + + + + + + +

      + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

      +

      📕 Get the EPUB and PDF

      @@ -761,7 +766,7 @@ library (in Quicklisp).


      © 2002–2023 the Common Lisp Cookbook Project diff --git a/lispcookbook.github.io/cl-cookbook/getting-started.html b/lispcookbook.github.io/cl-cookbook/getting-started.html index 19fe5fe..3776fc1 100644 --- a/lispcookbook.github.io/cl-cookbook/getting-started.html +++ b/lispcookbook.github.io/cl-cookbook/getting-started.html @@ -53,23 +53,28 @@

      The Common Lisp Cookbook – Getting started with Common Lisp

      -

      - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

      + + + + + + + + + +

      + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

      +

      📕 Get the EPUB and PDF

      We’ll begin with presenting easy steps to install a development environment and to start a new Common Lisp project.

      +

      We’ll begin by presenting easy steps to install a development environment and to start a new Common Lisp project.

      -

      Want a 2-clicks install? Then get +

      Want a 2-click install? Then get Portacle, a portable and multi-platform Common Lisp environment. It ships Emacs, SBCL (the implementation), Quicklisp (package manager), SLIME (IDE) and @@ -84,9 +89,9 @@ Git. It’s the most straightforward way to get going!

      apt-get install sbcl
       
      -

      Common Lisp has been standardized via an ANSI document, so it can be -implemented in different ways. See -Wikipedia’s list of implementations.

      +

      Common Lisp is an ANSI standard but implementations can vary greatly in what +they provide in addition to the standard. See Wikipedia’s list of +implementations.

      The following implementations are packaged for Debian and most other popular Linux distributions:

      @@ -100,7 +105,7 @@ implemented in different ways. See
      • ABCL, to interface with the JVM,
      • -
      • ClozureCL, a good implementation with very fast build times (see this Debian package for Clozure CL),
      • +
      • ClozureCL, a good implementation with very fast build times,
      • CLASP, that interoperates with C++ libraries using LLVM for compilation to native code,
      • AllegroCL (proprietary)
      • LispWorks (proprietary)
      • @@ -386,7 +391,7 @@ Debian. The package names usually begin with the cl- prefix (use

        Advanced dependencies management

        -

        You can drop Common Lisp projects into any of those folders:

        +

        You can drop Common Lisp projects into any of these folders:

        • ~/quicklisp/local-projects
        • @@ -420,10 +425,10 @@ available right-away:

          (ql:quickload "system")
           
          -

          The practical different between the two is that ql:quickload first tries to +

          The practical difference between the two is that ql:quickload first tries to fetch the system from the Internet if it is not already installed.

          -

          Note that symlinks in local-projects to another location of your liking works too.

          +

          Note that symlinks in local-projects to another location of your liking work too.

          How to work with local versions of libraries

          @@ -445,15 +450,15 @@ to help us build dists.

          Now that we have Quicklisp and our editor ready, we can start writing Lisp code in a file and interacting with the REPL.

          -

          But what if we want to work with an existing project or create a new -one, how do we proceed, what’s the right sequence of defpackage, -what to put in the .asd file, how to load the project into the REPL ?

          +

          But if we want to work with an existing project or create a new +one, how do we proceed? What’s the right sequence of defpackage? +What should we put in the .asd file? How do we load the project into the REPL ?

          Creating a new project

          Some project builders help to scaffold the project structure. We like -cl-project that also sets -up a tests skeleton.

          +cl-project, which also sets +up a test skeleton.

          In short:

          @@ -461,7 +466,7 @@ up a tests skeleton.

          (cl-project:make-project #P"./path-to-project/root/")
      -

      it will create a directory structure like this:

      +

      will create a directory structure like this:

      |-- my-project.asd
       |-- my-project-test.asd
      @@ -473,7 +478,7 @@ up a tests skeleton.

      `-- my-project.lisp
      -

      Where my-project.asd resembles this:

      +

      where my-project.asd resembles this:

      (asdf:defsystem "my-project"
         :version "0.1.0"
      @@ -504,8 +509,8 @@ up a tests skeleton.

      How to load an existing project

      You have created a new project, or you have an existing one, and you -want to work with it on the REPL, but Quicklisp doesn’t know it. How -can you do ?

      +want to work with it in the REPL, but Quicklisp doesn’t know about it. What +do you do?

      Well first, if you create it or clone it into one of ~/common-lisp, ~/.local/share/common-lisp/source/ or @@ -547,14 +552,14 @@ result in the REPL.

      You can add this to your ~/.sbclrc.

      -

      If you dislike the REPL to print all symbols upcase, add this:

      +

      If you dislike the REPL printing all symbols uppercase, add this:

      (setf *print-case* :downcase)
       
      -Warning: This might break the behaviour of some packages like it happened with +Warning: This might break the behaviour of some packages like happened with Mito. Avoid doing this in production.
      @@ -603,7 +608,7 @@ Avoid doing this in production.
      © 2002–2023 the Common Lisp Cookbook Project diff --git a/lispcookbook.github.io/cl-cookbook/gui.html b/lispcookbook.github.io/cl-cookbook/gui.html index d08ed72..f850280 100644 --- a/lispcookbook.github.io/cl-cookbook/gui.html +++ b/lispcookbook.github.io/cl-cookbook/gui.html @@ -53,14 +53,19 @@

      The Common Lisp Cookbook – GUI toolkits

      -

      - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

      + + + + + + + + + +

      + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

      +

      📕 Get the EPUB and PDF

      @@ -1165,7 +1170,7 @@ be immediately applied while the application is running!


      © 2002–2023 the Common Lisp Cookbook Project diff --git a/lispcookbook.github.io/cl-cookbook/index.html b/lispcookbook.github.io/cl-cookbook/index.html index 05d90f8..d94cc24 100644 --- a/lispcookbook.github.io/cl-cookbook/index.html +++ b/lispcookbook.github.io/cl-cookbook/index.html @@ -53,14 +53,19 @@

      The Common Lisp Cookbook – Home

      -

      - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

      + + + + + + + + + +

      + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

      +

      📕 Get the EPUB and PDF

      @@ -149,6 +154,7 @@ a book containing recipes and other information about the preparation and cookin
    • Interfacing with your OS
    • Databases
    • Foreign Function Interfaces
    • +
    • NEW! ⭐ Building Dynamic Libraries
    • GUI programming
    • Sockets
    • WebSockets
    • @@ -295,7 +301,7 @@ later.


      © 2002–2023 the Common Lisp Cookbook Project diff --git a/lispcookbook.github.io/cl-cookbook/io.html b/lispcookbook.github.io/cl-cookbook/io.html index 655e82e..843dba7 100644 --- a/lispcookbook.github.io/cl-cookbook/io.html +++ b/lispcookbook.github.io/cl-cookbook/io.html @@ -53,14 +53,19 @@

      The Common Lisp Cookbook – Input/Output

      -

      - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

      + + + + + + + + + +

      + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

      +

      📕 Get the EPUB and PDF

      @@ -277,7 +282,7 @@ and
      © 2002–2023 the Common Lisp Cookbook Project diff --git a/lispcookbook.github.io/cl-cookbook/iteration.html b/lispcookbook.github.io/cl-cookbook/iteration.html index 2450fab..3258bcd 100644 --- a/lispcookbook.github.io/cl-cookbook/iteration.html +++ b/lispcookbook.github.io/cl-cookbook/iteration.html @@ -53,14 +53,19 @@

      The Common Lisp Cookbook – Loop, iteration, mapping

      -

      - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

      + + + + + + + + + +

      + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

      +

      📕 Get the EPUB and PDF

      @@ -264,8 +269,10 @@ needed for the final result are actually created.

      reducers (i.e. functions like +) to traverse data streams in any way they wish, all while being very memory efficient.

      -

      See its README or its -API for more information.

      +

      See its README, its +API, or the original +Transducers document for more +information.

      Recipes

      @@ -347,7 +354,7 @@ infinitely. Here we show how to loop on a list forever.

      We can build an infinite list by setting its last element to the list itself:

      -
      (loop with list-a = '(1 2 3)
      +
      (loop with list-a = (list 1 2 3)
             with infinite-list = (setf (cdr (last list-a)) list-a)
             for item in infinite-list
             repeat 8
      @@ -355,7 +362,7 @@ infinitely. Here we show how to loop on a list forever.

      ;; (1 2 3 1 2 3 1 2)
      -

      Illustration: (last '(1 2 3)) is (3), a list, or rather a cons cell, whose car is 3 and cdr is NIL. See the data-structures chapter for a reminder. This is the representation of (list 3):

      +

      Illustration: (last (list 1 2 3)) is (3), a list, or rather a cons cell, whose car is 3 and cdr is NIL. See the data-structures chapter for a reminder. This is the representation of (list 3):

      [o|/]
        |
      @@ -1157,7 +1164,7 @@ NIL
       ;; (X Y Z)
       
      -
      Iterating 2 by 2 over a list
      +
      Iterating over a plist or 2 by 2 over a list

      To iterate over a list, 2 items at a time we use a combination of on, by and destructuring.

      @@ -1536,7 +1543,7 @@ external-symbols fixnum float t nil of-type
      © 2002–2023 the Common Lisp Cookbook Project diff --git a/lispcookbook.github.io/cl-cookbook/license.html b/lispcookbook.github.io/cl-cookbook/license.html index f81917e..2adc9da 100644 --- a/lispcookbook.github.io/cl-cookbook/license.html +++ b/lispcookbook.github.io/cl-cookbook/license.html @@ -53,14 +53,19 @@

      The Common Lisp Cookbook – License

      -

      - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

      + + + + + + + + + +

      + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

      +

      📕 Get the EPUB and PDF

      @@ -124,7 +129,7 @@ documentation, even if advised of the possibility of such damage.


      © 2002–2023 the Common Lisp Cookbook Project diff --git a/lispcookbook.github.io/cl-cookbook/lispworks.html b/lispcookbook.github.io/cl-cookbook/lispworks.html index 2b0fe51..451f4cb 100644 --- a/lispcookbook.github.io/cl-cookbook/lispworks.html +++ b/lispcookbook.github.io/cl-cookbook/lispworks.html @@ -53,14 +53,19 @@

      The Common Lisp Cookbook – LispWorks review

      -

      - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

      + + + + + + + + + +

      + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

      +

      📕 Get the EPUB and PDF

      @@ -640,6 +645,33 @@ create a so-called console image with multiprocessing enabled:

      See LispWorks’ documentation.

      +

      Delivering applications

      + +

      LispWorks’ delivery method revolves around its delivery function. It has good documentation: https://www.lispworks.com/documentation/lw80/deliv/deliv.htm.

      + +

      Unlike other open-source Lisps, LispWorks provides a tree-shaker that +can strip-off packages from the delivered application, allowing to +build small binaries, around 7MB.

      + +

      Delivery limitations

      + +

      LispWorks’s delivery doesn’t include compile-file into the delivered +application (nor save-image, deliver and the IDE). As such, it isn’t possible to change code on the fly on a +delivered image. No Swank server, no possibility to use +ql:quickload.

      + +

      To allow remote debugging, LW however provides its own debugger client. On the backend, do:

      + +
      (require "remote-debugger-client")
      +(dbg:start-client-remote-debugging-server :announce t)
      +
      + +

      and on the IDE, do:

      + +
      (require "remote-debugger-full")
      +(dbg:ide-connect-remote-debugging "host" :open-a-listener t)
      +
      +

      See also

      @@ -679,7 +712,7 @@ create a so-called console image with multiprocessing enabled:


      © 2002–2023 the Common Lisp Cookbook Project diff --git a/lispcookbook.github.io/cl-cookbook/macros.html b/lispcookbook.github.io/cl-cookbook/macros.html index fc450a7..c09367a 100644 --- a/lispcookbook.github.io/cl-cookbook/macros.html +++ b/lispcookbook.github.io/cl-cookbook/macros.html @@ -53,14 +53,19 @@

      The Common Lisp Cookbook – Macros

      -

      - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

      + + + + + + + + + +

      + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

      +

      📕 Get the EPUB and PDF

      @@ -774,7 +779,7 @@ It also shows how to manipulate macros (and their expansion) in Emacs.


      © 2002–2023 the Common Lisp Cookbook Project diff --git a/lispcookbook.github.io/cl-cookbook/misc.html b/lispcookbook.github.io/cl-cookbook/misc.html index 77f21c4..8aa4ae1 100644 --- a/lispcookbook.github.io/cl-cookbook/misc.html +++ b/lispcookbook.github.io/cl-cookbook/misc.html @@ -53,14 +53,19 @@

      The Common Lisp Cookbook – Miscellaneous

      -

      - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

      + + + + + + + + + +

      + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

      +

      📕 Get the EPUB and PDF

      @@ -189,7 +194,7 @@ T
      © 2002–2023 the Common Lisp Cookbook Project diff --git a/lispcookbook.github.io/cl-cookbook/numbers.html b/lispcookbook.github.io/cl-cookbook/numbers.html index c599e8a..2bdb0e9 100644 --- a/lispcookbook.github.io/cl-cookbook/numbers.html +++ b/lispcookbook.github.io/cl-cookbook/numbers.html @@ -53,14 +53,19 @@

      The Common Lisp Cookbook – Numbers

      -

      - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

      + + + + + + + + + +

      + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

      +

      📕 Get the EPUB and PDF

      @@ -699,7 +704,7 @@ or other bit-wise functions.


      © 2002–2023 the Common Lisp Cookbook Project diff --git a/lispcookbook.github.io/cl-cookbook/os.html b/lispcookbook.github.io/cl-cookbook/os.html index 0a9641a..e95b6d3 100644 --- a/lispcookbook.github.io/cl-cookbook/os.html +++ b/lispcookbook.github.io/cl-cookbook/os.html @@ -53,14 +53,19 @@

      The Common Lisp Cookbook – Interfacing with your OS

      -

      - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

      + + + + + + + + + +

      + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

      +

      📕 Get the EPUB and PDF

      @@ -114,6 +119,8 @@ NIL

      You should also note that some of these implementations also provide the ability to set these variables. These include ECL (si:setenv) and AllegroCL, LispWorks, and CLISP where you can use the functions from above together with setf. This feature might be important if you want to start subprocesses from your Lisp environment.

      +

      To set an envionmental variable, you can setf with (uiop:getenv "lisp") in a implementation-independent way.

      +

      Also note that the Osicat library has the method (environment-variable "name"), on POSIX-like @@ -156,7 +163,8 @@ libraries (see next section) make it portable.

      (defun my-command-line ()
         (or
          #+SBCL *posix-argv*
      -   #+LISPWORKS system:*line-arguments-list*))
      +   #+LISPWORKS system:*line-arguments-list*)
      +   #+CLISP *args*)
       

      Now it would be handy to access them in a portable way and to parse @@ -495,7 +503,7 @@ with the returned exit-code. 0 is success.

      (format t "error output is: ~a" error-output)))
      -

      Running visual commands (htop)

      +

      Running interactive and visual commands (htop)

      Use uiop:run-program and set both :input and :output to :interactive:

      @@ -506,8 +514,7 @@ with the returned exit-code. 0 is success.

      This will spawn htop in full screen, as it should.

      -

      It works for more commands (sudo, vim…), however not for all interactive -programs, such as less or fzf.

      +

      It works for more commands (sudo, vim, less…).

      Piping

      @@ -585,7 +592,7 @@ SB-POSIX:WAITPID (fbound)
      © 2002–2023 the Common Lisp Cookbook Project diff --git a/lispcookbook.github.io/cl-cookbook/packages.html b/lispcookbook.github.io/cl-cookbook/packages.html index ce903c7..d4b02c9 100644 --- a/lispcookbook.github.io/cl-cookbook/packages.html +++ b/lispcookbook.github.io/cl-cookbook/packages.html @@ -53,14 +53,19 @@

      The Common Lisp Cookbook – Packages

      -

      - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

      + + + + + + + + + +

      + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

      +

      📕 Get the EPUB and PDF

      @@ -430,7 +435,7 @@ example:


      © 2002–2023 the Common Lisp Cookbook Project diff --git a/lispcookbook.github.io/cl-cookbook/pattern_matching.html b/lispcookbook.github.io/cl-cookbook/pattern_matching.html index d4e16e0..b2c18f7 100644 --- a/lispcookbook.github.io/cl-cookbook/pattern_matching.html +++ b/lispcookbook.github.io/cl-cookbook/pattern_matching.html @@ -53,14 +53,19 @@

      The Common Lisp Cookbook – Pattern Matching

      -

      - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

      + + + + + + + + + +

      + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

      +

      📕 Get the EPUB and PDF

      @@ -294,7 +299,7 @@ true it is matched against subpattern1.


      © 2002–2023 the Common Lisp Cookbook Project diff --git a/lispcookbook.github.io/cl-cookbook/performance.html b/lispcookbook.github.io/cl-cookbook/performance.html index c0f9d44..109fe8d 100644 --- a/lispcookbook.github.io/cl-cookbook/performance.html +++ b/lispcookbook.github.io/cl-cookbook/performance.html @@ -53,14 +53,19 @@

      The Common Lisp Cookbook – Performance Tuning and Tips

      -

      - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

      + + + + + + + + + +

      + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

      +

      📕 Get the EPUB and PDF

      @@ -749,7 +754,7 @@ unless it is declared notinline.


      © 2002–2023 the Common Lisp Cookbook Project diff --git a/lispcookbook.github.io/cl-cookbook/process.html b/lispcookbook.github.io/cl-cookbook/process.html index b67cbdf..25113c4 100644 --- a/lispcookbook.github.io/cl-cookbook/process.html +++ b/lispcookbook.github.io/cl-cookbook/process.html @@ -53,14 +53,19 @@

      The Common Lisp Cookbook – Threads, concurrency, parallelism

      -

      - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

      + + + + + + + + + +

      + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

      +

      📕 Get the EPUB and PDF

      @@ -2373,7 +2378,7 @@ manual. For SBCL, here is a link to the of
      © 2002–2023 the Common Lisp Cookbook Project
      diff --git a/lispcookbook.github.io/cl-cookbook/regexp.html b/lispcookbook.github.io/cl-cookbook/regexp.html index 1fb3891..17a63e4 100644 --- a/lispcookbook.github.io/cl-cookbook/regexp.html +++ b/lispcookbook.github.io/cl-cookbook/regexp.html @@ -53,14 +53,19 @@

      The Common Lisp Cookbook – Regular Expressions

      -

      - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

      + + + + + + + + + +

      + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

      +

      📕 Get the EPUB and PDF

      @@ -164,6 +169,17 @@ CL-USER> (mapcar #'parse-integer *)

      If SHAREDP is true, the substrings may share structure with TARGET-STRING.

      +

      count-matches (new in 2.1.2, April 2024)

      + +

      (count-matches regex target-string) returns a count of all matches of regex against target-string:

      + +
      CL-USER> (ppcre:count-matches "a" "foo bar baz")
      +2
      +
      +CL-USER> (ppcre:count-matches "\\w*" "foo bar baz")
      +6
      +
      +

      scan-to-strings, register-groups-bind

      The scan-to-strings function is similar to scan but returns @@ -238,7 +254,7 @@ assigning the matching fragment to the variable:


      © 2002–2023 the Common Lisp Cookbook Project diff --git a/lispcookbook.github.io/cl-cookbook/scripting.html b/lispcookbook.github.io/cl-cookbook/scripting.html index ad8a47b..c67990f 100644 --- a/lispcookbook.github.io/cl-cookbook/scripting.html +++ b/lispcookbook.github.io/cl-cookbook/scripting.html @@ -53,14 +53,19 @@

      The Common Lisp Cookbook – Scripting. Command line arguments. Executables.

      -

      - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

      + + + + + + + + + +

      + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

      +

      📕 Get the EPUB and PDF

      @@ -888,7 +893,7 @@ whichever other policy.


      © 2002–2023 the Common Lisp Cookbook Project diff --git a/lispcookbook.github.io/cl-cookbook/sockets.html b/lispcookbook.github.io/cl-cookbook/sockets.html index 577cf7c..1ebae1c 100644 --- a/lispcookbook.github.io/cl-cookbook/sockets.html +++ b/lispcookbook.github.io/cl-cookbook/sockets.html @@ -53,14 +53,19 @@

      The Common Lisp Cookbook – TCP/UDP programming with sockets

      -

      - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

      + + + + + + + + + +

      + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

      +

      📕 Get the EPUB and PDF

      @@ -253,7 +258,7 @@ and #(8 7 6 5 4 3 2 1) on the second one.


      © 2002–2023 the Common Lisp Cookbook Project diff --git a/lispcookbook.github.io/cl-cookbook/strings.html b/lispcookbook.github.io/cl-cookbook/strings.html index 22b51e8..c7b69a8 100644 --- a/lispcookbook.github.io/cl-cookbook/strings.html +++ b/lispcookbook.github.io/cl-cookbook/strings.html @@ -53,14 +53,19 @@

      The Common Lisp Cookbook – Strings

      -

      - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

      + + + + + + + + + +

      + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

      +

      📕 Get the EPUB and PDF

      @@ -801,7 +806,7 @@ in this case.

      ;; (42 41 1)
      -

      To any number: read-from-string

      +

      To any number: read-from-string

      Be aware that the full reader is in effect if you’re using this function. This can lead to vulnerability issues. You should use a @@ -834,6 +839,23 @@ SYMBOL "gotcha"

      +

      Protecting read-from-string

      + +

      At the very least, if you are reading data coming from the outside, use this:

      + +
      (let ((cl:*read-eval* nil))
      +  (read-from-string "…"))
      +
      + +

      This prevents code to be evaluated at read-time. That way our last example, using the #. reader macro, would not work. You’ll get the error “can’t read #. while *READ-EVAL* is NIL”.

      + +

      And better yet, for more protection from a possibly custom readtable that would introduce another reader macro:

      + +
      (with-standard-io-syntax
      +  (let ((cl:*read-eval* nil))
      +    (read-from-string "…")))
      +
      +

      To a float: the parse-float library

      There is no built-in function similar to parse-integer to parse @@ -1275,7 +1297,7 @@ _ - Conditional Newline


      © 2002–2023 the Common Lisp Cookbook Project diff --git a/lispcookbook.github.io/cl-cookbook/systems.html b/lispcookbook.github.io/cl-cookbook/systems.html index d666bc8..3e14a3b 100644 --- a/lispcookbook.github.io/cl-cookbook/systems.html +++ b/lispcookbook.github.io/cl-cookbook/systems.html @@ -53,14 +53,19 @@

      The Common Lisp Cookbook – Defining Systems

      -

      - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

      + + + + + + + + + +

      + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

      +

      📕 Get the EPUB and PDF

      @@ -298,7 +303,7 @@ generate a project skeleton. It will create a default ASDF definition,
      © 2002–2023 the Common Lisp Cookbook Project diff --git a/lispcookbook.github.io/cl-cookbook/testing.html b/lispcookbook.github.io/cl-cookbook/testing.html index 1c04871..a9d3c3a 100644 --- a/lispcookbook.github.io/cl-cookbook/testing.html +++ b/lispcookbook.github.io/cl-cookbook/testing.html @@ -53,14 +53,19 @@

      The Common Lisp Cookbook – Testing the code

      -

      - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

      + + + + + + + + + +

      + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

      +

      📕 Get the EPUB and PDF

      @@ -932,7 +937,7 @@ a problem.


      © 2002–2023 the Common Lisp Cookbook Project diff --git a/lispcookbook.github.io/cl-cookbook/trace-dialog.png b/lispcookbook.github.io/cl-cookbook/trace-dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..805bca8f7b4e7b2de1ed76fbeb005f7cd0b1b7d6 GIT binary patch literal 65573 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 Common Lisp Cookbook – Type System -

      - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

      + + + + + + + + + +

      + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

      +

      📕 Get the EPUB and PDF

      @@ -646,7 +651,7 @@ means “any Lisp datum”.  © 2002–2023 the Common Lisp Cookbook Project diff --git a/lispcookbook.github.io/cl-cookbook/vscode-alive.html b/lispcookbook.github.io/cl-cookbook/vscode-alive.html index 10dde45..9f88e1a 100644 --- a/lispcookbook.github.io/cl-cookbook/vscode-alive.html +++ b/lispcookbook.github.io/cl-cookbook/vscode-alive.html @@ -53,14 +53,19 @@

      The Common Lisp Cookbook – Using VSCode with Alive

      -

      - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

      + + + + + + + + + +

      + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

      +

      📕 Get the EPUB and PDF

      @@ -649,7 +654,7 @@ on an SBCL image in a docker container.
      © 2002–2023 the Common Lisp Cookbook Project diff --git a/lispcookbook.github.io/cl-cookbook/web-scraping.html b/lispcookbook.github.io/cl-cookbook/web-scraping.html index 8efe2bb..e3090bf 100644 --- a/lispcookbook.github.io/cl-cookbook/web-scraping.html +++ b/lispcookbook.github.io/cl-cookbook/web-scraping.html @@ -53,14 +53,19 @@

      The Common Lisp Cookbook – Web Scraping

      -

      - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

      + + + + + + + + + +

      + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

      +

      📕 Get the EPUB and PDF

      @@ -423,7 +428,7 @@ network, parallelism and concurrency libraries to see on the
      © 2002–2023 the Common Lisp Cookbook Project diff --git a/lispcookbook.github.io/cl-cookbook/web.html b/lispcookbook.github.io/cl-cookbook/web.html index d5caf83..bcfcfe7 100644 --- a/lispcookbook.github.io/cl-cookbook/web.html +++ b/lispcookbook.github.io/cl-cookbook/web.html @@ -53,14 +53,19 @@

      The Common Lisp Cookbook – Web development

      -

      - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

      + + + + + + + + + +

      + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

      +

      📕 Get the EPUB and PDF

      @@ -566,9 +571,7 @@ the interactive debugger (for development only, of course):

      See also the generic function maybe-invoke-debugger if you want to fine-tune this behaviour. You might want to specialize it on specific -condition classes (see below) for debugging purposes. The default method invokes -the debugger -if *catch-errors-p* is nil.

      +condition classes (see below) for debugging purposes.

      • *show-lisp-errors-p*: set to t if you want to see errors in HTML output in the browser.
      • @@ -706,7 +709,7 @@ the most downloaded libraries of Quicklisp.

        Djula filters

        Filters allow to modify how a variable is displayed. Djula comes with -a good set of built-in filters and they are well documented. They are not to be confused with tags.

        +a good set of built-in filters and they are well documented. They are not to be confused with tags.

        They look like this: ``, where lower is an existing filter, which renders the text into lowercase.

        @@ -987,7 +990,9 @@ Listening on localhost:9003.

        Multi-platform delivery with Electron

        -

        Ceramic makes all the work for us.

        +

        Once you built a binary of your web application, you can point an Electron window to it.

        + +

        Ceramic is a collection of tools that make all the work for us.

        It is as simple as this:

        @@ -1206,6 +1211,8 @@ and DB migrations. Uses Qlot, Buildapp, SystemD for deployment. a simple project template with Hunchentoot, Easy-Routes, Djula and Bulma CSS.
      • lisp-web-live-reload-example - a toy project to show how to interact with a running web app.
      • +
      • lisp-journey: enrich your stacktrace with session and user data
      • +
      • video: how to build a web app in Lisp · part 1 featuring Hunchentoot, easy-routes, Djula templates, error handling, common traps.

      Credits

      @@ -1245,7 +1252,7 @@ a toy project to show how to interact with a running web app.
      © 2002–2023 the Common Lisp Cookbook Project diff --git a/lispcookbook.github.io/cl-cookbook/websockets.html b/lispcookbook.github.io/cl-cookbook/websockets.html index 639d45d..2fefdef 100644 --- a/lispcookbook.github.io/cl-cookbook/websockets.html +++ b/lispcookbook.github.io/cl-cookbook/websockets.html @@ -53,14 +53,19 @@

      The Common Lisp Cookbook – WebSockets

      -

      - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

      + + + + + + + + + +

      + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

      +

      📕 Get the EPUB and PDF

      @@ -397,7 +402,7 @@ should see your chat app!


      © 2002–2023 the Common Lisp Cookbook Project diff --git a/lispcookbook.github.io/cl-cookbook/win32.html b/lispcookbook.github.io/cl-cookbook/win32.html index 2d73e5d..2af9adf 100644 --- a/lispcookbook.github.io/cl-cookbook/win32.html +++ b/lispcookbook.github.io/cl-cookbook/win32.html @@ -53,14 +53,19 @@

      The Common Lisp Cookbook – Using the Win32 API

      -

      - 📢 🤶 ⭐ - Discover our contributor's Lisp course with this Christmas coupon. - - Recently added: 18 videos on MACROS. - - Learn more. -

      + + + + + + + + + +

      + 📢 New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo +

      +

      📕 Get the EPUB and PDF

      @@ -1290,7 +1295,7 @@ handle
      © 2002–2023 the Common Lisp Cookbook Project