I am using the C API to interact with ECL and I am trying to create a closure object from a native function that has some stored state.
I have tried this:
cl_object f(long nargs, ...) {
std::cout << nargs << std::endl;
std::cout << "has value?" << std::endl;
cl_print(1, cl_boundp(c_string_to_object("HI")));
std::cout << "\ndone" << std::endl;
return Cnil;
}
auto closure = ecl_make_cclosure_va(f, c_string_to_object("((HI . 2))"), Cnil);
ecl_defparameter(c_string_to_object("F"), closure);
This makes the function f
callable with (funcall f)
in lisp, but cl_boundp
always returns nil
inside the body of f
. How does one construct the env
(environment) parameter to ecl_make_cclosure_va
so that the native code can read the values in the closure? I thought it was just an alist but apparently not, and I have found no examples of building one on Google.
Edit: I've added a second, fairly clean, shorter answer first, which probably doesn't quite do what you want, but might just be close enough. The original, longer answer which says why doing exactly what you want is complicated, but suggests a possible starting point remains afterwards.
Easier approach
I've outlined this in terms of the ECL interface, but it could probably largely be done with an FFI interface for a cleaner, more portable approach.
code:
// defined here as a non-varargs function - you'll need to change it slightly
cl_object f(cl_object hi, cl_object func_param1, cl_object_fund_param2) {
// note you can pass hi back to lisp fairly easily
cl_object name = ecl_make_symbol("do-something-useful","CL-USER");
cl_funcall(2,name,hi);
// some other stuff
}
Wrap the function so it's callable by lisp (ecl_def_c_function
?)
Wrap the closure in lisp
code:
cl_object wrapped_c_function = cl_safe_eval(c_string_to_object(
"(let ((hi 2))
#'(lambda (x y) (your-c-function hi x y)))"),Cnil,Cnil);
Original answer:
This is a slightly long answer to say "not easily" but:
The easiest way to understand what ecl does is use it to compile a simple script to C (ecl -c <filename.c> -compile <filename.lisp>
). Here's a simple bit of lisp that generates a closure with a variable argument list
(defun make-closure-function (x y)
#'(lambda (&rest arguments) (apply '+ (append (list x y) arguments))))
(defun main ()
(let ((f (make-closure-function 1 2)))
(print (funcall f 3)))
(format t "~%"))
(main)
And here's the relevant parts of the C code it generates
/* function definition for MAKE-CLOSURE-FUNCTION */
/* optimize speed 3, debug 0, space 0, safety 2 */
static cl_object L2make_closure_function(cl_object v1x, cl_object v2y)
{
cl_object env0;
cl_object CLV0, CLV1;
const cl_env_ptr cl_env_copy = ecl_process_env();
cl_object value0;
ecl_cs_check(cl_env_copy,value0);
{
env0 = ECL_NIL;
CLV0 = env0 = CONS(v1x,env0); /* X */
CLV1 = env0 = CONS(v2y,env0); /* Y */
{
cl_object v3;
v3 = ecl_make_cclosure_va((cl_objectfn)LC1__g0,env0,Cblock);
value0 = v3;
cl_env_copy->nvalues = 1;
return value0;
}
}
}
/* closure G0 */
/* optimize speed 3, debug 0, space 0, safety 2 */
static cl_object LC1__g0(cl_narg narg, ...)
{
cl_object T0, T1;
cl_object CLV0, CLV1;
const cl_env_ptr cl_env_copy = ecl_process_env();
cl_object env0 = cl_env_copy->function->cclosure.env;
cl_object value0;
ecl_cs_check(cl_env_copy,value0);
/* Scanning closure data ... */
CLV1 = env0; /* Y */
CLV0 = _ecl_cdr(CLV1);
{ /* ... closure scanning finished */
{
cl_object v1arguments;
ecl_va_list args; ecl_va_start(args,narg,narg,0);
v1arguments = cl_grab_rest_args(args);
ecl_va_end(args);
T0 = cl_list(2, ECL_CONS_CAR(CLV0), ECL_CONS_CAR(CLV1));
T1 = ecl_append(T0,v1arguments);
value0 = cl_apply(2, ECL_SYM("+",14), T1);
return value0;
}
}
}
/* function definition for MAIN */
/* optimize speed 3, debug 0, space 0, safety 2 */
static cl_object L3main()
{
cl_object T0;
const cl_env_ptr cl_env_copy = ecl_process_env();
cl_object value0;
ecl_cs_check(cl_env_copy,value0);
{
TTL:
{
cl_object v1f;
v1f = L2make_closure_function(ecl_make_fixnum(1), ecl_make_fixnum(2));
T0 = ecl_function_dispatch(cl_env_copy,v1f)(1, ecl_make_fixnum(3));
ecl_print(T0,ECL_NIL);
}
value0 = cl_format(2, ECL_T, VV[1]);
return value0;
}
}
It creates env0
in make_closure_function
, and makes it list with x
and y
appended. Then it calls ecl_make_cclosure_va
. In LC1_g0
(the wrapping of the lambda) it accesses the environment with cl_object env0 = cl_env_copy->function->cclosure.env;
.
Note that the values in the environment are accessed because it knows the order, rather than being able to get them by name.
You'd basically have to replicate this mechanism to access closure variables in a c function (although you could generate an environment that stores the values in a mode convenient way (e.g. a list of (HI 2)
).
Your best option would probably to bind bits from the environment as a special variable using ecl_bds_bind (see http://ecls.sourceforge.net/new-manual/re04.html). If you did that then I imagine you could use HI
. Although beware it's dynamically rather than lexically bound.
It's a lot of work though! This really isn't a "user-facing interface".