Lisp: ECL_HANDLER_CASE and Friends


ECL provides a facility called ECL_HANDLER_CASE that enables C code to handle conditions that are signalled from Lisp. The facility consists of three C preprocessor macro-definitions: ECL_HANDLER_CASE_BEGIN, ECL_HANDLER_CASE, and ECL_HANDLER_CASE_END. Unfortunately, as of ECL version 16.2.1, the definition of ECL_HANDLER_CASE (which can be found in <ecl/stacks.h>) is incorrect and the corresponding section of the ECL manual provides a misleading example. This memo explains how to fix and use this facility.

Fixing ECL_HANDLER_CASE

The macro ECL_HANDLER_CASE_BEGIN works by binding internal handlers to signals specified by the user. ECL_HANDLER_CASE_BEGIN tags these handlers using Lisp fixnums in the range [1,n] where n is the number of elements in the list of names of conditions. Whenever a condition is signalled, the ECL runtime calls the established internal handler and returns two values: an object that describes this condition and the tag.

Every expansion of the ECL_HANDLER_CASE macro compares the value of the tag with the first argument of the macro and executes the enclosed C code if they are equal. The object that represents the condition is copied from the ECL environment (which the user sets using ECL_HANDLER_CASE_BEGIN) to the variable specified by the user as the second argument of ECL_HANDLER_CASE.

The manual explains that ECL runtime stores multiple values returned by a Lisp function in the current environment (which is a per-thread C struct) in the field called values (which, in turn, is an array of cl_objects). In the case of tagged signal handlers, the first value is the tag and the second is the condition object. Apparently, the author of ECL_HANDLER_CASE forgot this order—the current version of the macro compares the address of the object with the first argument of the macro and copies the fixnum to the variable named as the second argument of the macro.

Errare humanum est; to fix ECL_HANDLER_CASE, one needs to exchange the indices used to address __the_env->values in the definition of the macro as follows:

#undef ECL_HANDLER_CASE
#define ECL_HANDLER_CASE(code, args)					\
	} else if (__the_env->values[0] == ecl_make_fixnum(code)) {     \
		const cl_object args = __the_env->values[1];
#endif	

Using ECL_HANDLER_CASE

Let us consider the following code:

 1  #include <stdio.h>
 2  #include <ecl/ecl.h>
 3
 4  #undef ECL_HANDLER_CASE
 5  #define ECL_HANDLER_CASE(code, args)                                \
 6          } else if (__the_env->values[0] == ecl_make_fixnum(code)) { \
 7                  const cl_object args = __the_env->values[1];
 8
 9  static void
10  test_handling ( void )
11  {
12          cl_env_ptr const environment = ecl_process_env ();
13          const cl_object const division_by_zero =
14                  ecl_make_symbol ( "DIVISION-BY-ZERO", "CL" );
15          const cl_object const end_of_file =
16                  ecl_make_symbol ( "END-OF-FILE", "CL" );
17          const cl_object const simple_type_error =
18                  ecl_make_symbol ( "SIMPLE-TYPE-ERROR", "CL" );
19          const cl_object const conditions =
20                  ecl_cons ( division_by_zero,
21                          ecl_cons ( end_of_file,
22                                  ecl_cons ( simple_type_error, Cnil ) ) );
23	    
24          ECL_HANDLER_CASE_BEGIN ( environment, conditions ) {
25                  ecl_divide ( ecl_make_fixnum ( 1 ), ecl_make_fixnum ( 0 ) );
26                  ecl_read_from_cstring ( "" );
27                  ecl_read_byte ( cl_core.standard_output );
28                  cl_funcall ( 2,
29                          ecl_make_symbol ( "READ-FROM-STRING", "CL" ),
30                          make_constant_base_string ( "" ) );
31          } ECL_HANDLER_CASE ( 1, condition ) {
32                  puts ( "Division by zero." );
33          } ECL_HANDLER_CASE ( 2, condition ) {
34                  puts ( "End of file." );
35          } ECL_HANDLER_CASE ( 3, condition ) {
36                  puts ( "Simple type error." );
37          } ECL_HANDLER_CASE_END;
38  }
39
40  int
41  main ( const int argc, const char * const argv [] )
42  {
43          cl_boot ( argc, (char **) argv );
44
45          test_handling ();
46
47          return 0;
48  }
Every logical operation in the part of the code that is protected with handlers causes a condition to be signalled:
  1. Line 25 is an example of division by zero.
  2. Line 26 is an example of reading past the end of a stream.
  3. Line 27 is an example of reading from an output stream.
  4. Lines 28–30 provide a different example of reading past the end of a stream.
The reader may experiment by commenting out these logical operations in any order to see how the behaviour of the program changes.

Caveats

A number of C API functions and macros that ECL runtime provides call the safe-eval routine internally, in a way that instructs it to evaluate the Lisp form in a null environment and to invoke the Lisp debugger if a condition is signalled. The macro ecl_read_from_cstring which is used in the second case above is an example of such behaviour. Compare it to the fourth case which uses pure Lisp functions.

Vadim Penzin, May 24th, 2016


I hereby place this article and the accompanying source code into the public domain.
You are welcome to contact me by writing to howto at this domain.
I publish this information in the hope that it will be useful, but without ANY WARRANTY.
You are responsible for any and all consequences that may arise as the result of using this information.