No description, website, or topics provided.
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
LICENSE
README.org
extract-code.sh

README.org

Stupid ECL tricks

Mostly these are half baked hacks. But hopefully they stimulate the imagination of real programmers by providing a glims of what ELC is cabable of.

Running ECL in gdb.

I’ve always had a total mental block when it comes to C pointers. It makes no sense to my brain that * indicates a variable is a pointer when used in a declaration, but retrieves a value when used as an operator. And an array of pointers to a character string makes total sense to me in words but char** str[] causes my mind to go blank. As a consequence any C code I write, or even look at too intently immediately blows up when compiled and run. A big inconvenience when embedding Lisp. Replacing the usual,

(setq inferior-lisp-program "ecl")

with,

(setq inferior-lisp-program
      "gdb --eval-command=run --eval-command=quit --args ecl")

Will run ecl under gdb, which will provide you the normal gdb environment with c runtime errors, while throwing you into the lisp debugger for Lisp errors. Note that gdb by default breaks on SIGPWR and SIGXCPU which ecl uses for internal processing. So, you’ll also want to add the following to your .gdbinit file.

handle SIGPWR nostop noprint
handle SIGXCPU nostop noprint

Embedding Swank in a c application.

Swank is a Lisp program that provides remote access to a Lisp instance. It started as client/server application layer in CMUCL and the Hemlock editor it ran. It’s since been ported to most Lisps. Slime is the Emacs front-end client to Swank. Together the two tools provide a powerful Lisp development environment in Emacs. The easiest way to install Swank and Slime is simply to get it from quicklisp. See:

https://www.quicklisp.org/beta/

Swank and slime work in following way:

+----------+     launch ecl in                +--------------------+ 
| emacs    |---- process buffer, tell ------> | ecl process buffer |
+----------+     ecl to start swank           +-----+--------------+       		   
   |      	    	       		                 |
   |	    	                           start swank server:
create slime    			           (swank-loader:init)
buffer					   (swank:start-server)
   |                                                |
   |                                                |
  \/  		                                \/
+--------------+      integrated      +--------------------------------+
| repl:        +<---- lisp repl   --->| swank server listening         |
| slime buffer |      interaction     | on some arbitrary              |
+--------------+                      | TCP/IP port e.g.               |
                                      | "Swank started at port: 46493" |
                                      +--------------------------------+
                                                      /\
+--------------------------+                           |
| edit:                    +<--------------------------+
| buffer with Lisp source  |
+--------------------------+   

To embed swank in a C application we need the application to launch Swank and then for Emacs to establish the connection to the swank server using slime-connect. Below is the C code that launches Swank.

Note, the following example is for a GNU/Linux type system. ecl needs to explicitly load load a shared library in order to access binary symbols such as C functions or C variables in the process, this is a hackish way of handling it since the library was already loaded when the applicaiton started, and could cause problems on platforms that put different constraints on loading shared libraries.

/* -*- mode: c;  -*-
   file: main.c
*/

#include "app_main.h"
/* a.out wrapper for call into a shared library. */
int main() {
  return app_main();
}
/* -*- mode: c;  -*-
   file: app_main.h
*/

#ifndef __APP_MAIN_H__
#define __APP_MAIN_H__

#include <ecl/ecl.h>

int app_main();

#endif /* APP_MAIN_H */

The following creates the shared library app_main used by both the C program and ECL for symbols. The embedded ECL code initializes the ECL environment and calls the Common Lisp load function to load a local Lisp file with the code to run swank.

/* -*- mode: c;  -*-
   file: app_main.c
*/

#include <stdlib.h>
#include <math.h>
#include "app_main.h"

void run_swank();

/* TODO: Are embedded quotes really needed? */
char start_swank[] =
  "\"/mnt/pixel-512/dev/stupid-ecl-tricks-1/start-swank-server.lisp\"";

char* argv;
char** pargv;

int app_main() {
  argv = "app";
  pargv = &argv;

  cl_boot(1, pargv);
  atexit(cl_shutdown);

  /* Set up handler for Lisp errors to prevent buggy Lisp (an */
  /* imposibility, I know!) from killing the app. */
  const cl_env_ptr l_env = ecl_process_env();
  CL_CATCH_ALL_BEGIN(l_env) {
    CL_UNWIND_PROTECT_BEGIN(l_env) {
      run_swank();
    }
    CL_UNWIND_PROTECT_EXIT {}
    CL_UNWIND_PROTECT_END;
  }
  CL_CATCH_ALL_END;

  return 0;

}

void run_swank() {
  cl_object cl_start_swank_path = c_string_to_object(start_swank);
  cl_object cl_load =  ecl_make_symbol("LOAD","CL");
  cl_funcall(2, cl_load, cl_start_swank_path);
  return;
}

The following Lisp file, loaded by app_main, contains a couple of snippets of code I copied from the Emacs Slime client that launches the Swank server. When Swank launches it will print out the socket you can use to connect to it, e.g.

;; Swank started at port: 58252.

you can then connect to it in Emacs using Slime:

M-x slime-connect

;;; -*- mode: lisp ; syntax: ansi-common-lisp -*-

;; standard quicklisp init file, since with be launching ecl without ~/.eclrc
(let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp"
                                       (user-homedir-pathname))))
  (when (probe-file quicklisp-init)
    (load quicklisp-init)))

(when (probe-file  "/tmp/slime.2565")
  (delete-file "/tmp/slime.2565"))

(load
 "~/quicklisp/dists/quicklisp/software/slime-2.14/swank-loader.lisp"
 :verbose t)

(funcall (read-from-string "swank-loader:init"))
(funcall (read-from-string "swank:start-server")
         "/tmp/slime.2565"))

A quick and dirty script file to build a shared library.

# -*- mode: bash;  -*-


rm -f *.o *.so app

export libs="-lm"

# Note, the -Wl,-R flags will make our shared library available to the
# executable app from the location that it was compiled, rather than
# having to be installed globably or adding the build path to
# LD_LIBRARY_PATH.

export ldflags="-L. -Wl,-R -Wl,."
export cflags="-DGC_LINUX_THREADS -D_REENTRANT -fPIC  -g -pipe -Wall"

gcc $cflags -c app_main.c
gcc -shared -Wl,-soname,libapp_main.so $ldflags -lecl -o libapp_main.so *o $libs
gcc main.c $cflags $ldflags -lapp_main -lecl -o app

To build and run

$ ./build_app.sh
$ ./app

Troubleshooting compilation problems with ffi:c-inline

ECL provide a facility for embedding C code directly in Lisp code like the following:

(defun c-sin (x)
  (ffi:clines "#include \"ecl/ecl.h\"")
  ;; Whoops!  mathh.h should be math.h
  (ffi:clines "#include <mathh.h>")
  (ffi:clines  "#include \"app_main.h\"")
  (ffi:c-inline (x) (:double) :double "{
@(return 0)= sin(#0);
}" :one-liner nil))

To use this function you need to compile the defun. When you issue the explicit compile,

(compile 'c-sin)

ECL will invoke your underlying C compiler. However, C syntax and header include errors, like we included in the above example, will cause compilation to fail. Unfortunately, ECL doesn’t pass along the compilers output. You’ll get something like the following:

;;; OPTIMIZE levels: Safety=2, Space=0, Speed=3, Debug=3
;;;
;;; End of Pass 1.
;;; Internal error:
;;;   ** Error code 1 when executing
;;; (RUN-PROGRAM "gcc" ("-I." "-I/usr/local/include/" "-D_GNU_SOURCE" "-D_FILE_OFFSET_BITS=64" "-g" "-O2" "-fPIC" "-D_THREAD_SAFE" "-Dlinux" "-O2" "-c" "/tmp/ecl001QoKf80.c" "-o" "/tmp/ecl001QoKf80.o"))

if you try to recreate the error by invoking the implied shell command:

$ gcc -I. -I/usr/local/include/ -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 \
    -g -O2 -fPIC -D_THREAD_SAFE -Dlinux -O2 -c /tmp/ecl001QoKf8.c \
    -o /tmp/ecl001QoKf80.o

You’ll get the error:

gcc: error: /tmp/ecl001QoKf80.c: No such file or directory
gcc: fatal error: no input files
compilation terminated.

Because ECL has already cleaned it from /tmp.

But, ECL has a special variable, compiler::*delete-files* that controls cleaning up c output files. By setting it to nil, (setf compiler::*delete-files* nil) you can troubleshoot compilation errors. Re-running above gcc command on from the Unix shell gives us the following:

In file included from /tmp/ecl001QoKf80.c:6:0:
/tmp/ecl001QoKf80.eclh:8:19: fatal error: mathh.h: No such file or directory
#include <mathh.h>
                ^
compilation terminated.

Cache Files

Swank and ECL’s embedded C in Lisp facility seem to have some issues with caching where compiled C snippets and a Swank images don’t get refreshed when they should (at least on GNU/Linux). If you start noticing strange issues with changes to ffi:c-inline not taking effect or Swank having the wrong image, try deleting the following cache files:

rm -rf ~/.cache/common-lisp/ecl-15.2.21-ee989b97-linux-x64
rm -rf ~/.slime