4

I'm trying to get into the fascinating world of Common Lisp embedded in C++. My problem is that I can't manage to read and print from c++ a string returned by a lisp function defined in ECL.

In C++ I have this function to run arbitrary Lisp expressions:

cl_object lisp(const std::string & call) {
    return cl_safe_eval(c_string_to_object(call.c_str()), Cnil, Cnil);
}

I can do it with a number in this way:

ECL:

(defun return-a-number () 5.2)

read and print in C++:

auto x = ecl_to_float(lisp("(return-a-number)"));
std::cout << "The number is " << x << std::endl;

Everything is set and works fine, but I don't know to do it with a string instead of a number. This is what I have tried:

ECL:

(defun return-a-string () "Hello")

C++:

 cl_object y = lisp("(return-a-string)");
 std::cout << "A string: " << y << std::endl;

And the result of printing the string is this:

A string: 0x3188b00

that I guess is the address of the string.

Here it is a capture of the debugger and the contents of the y cl_object. y->string.self type is an ecl_character.

Debug

4 Answers4

6

(Starting from @coredump's answer that the string.self field provides the result.)

The string.self field is defined as type ecl_character* (ecl/object.h), which appears to be given in ecl/config.h as type int (although I suspect this is slightly platform dependent). Therefore, you will not be able to just print it as if it was a character array.

The way I found worked for me was to reinterpret it as a wchar_t (i.e. a unicode character). Unfortunately, I'm reasonably sure this isn't portable and depends both on how ecl is configured and the C++ compiler.

// basic check that this should work
static_assert(sizeof(ecl_character)==sizeof(wchar_t),"sizes must be the same");

std::wcout << "A string: " << reinterpret_cast<wchar_t*>(y->string.self) << std::endl;
// prints hello, as required
// note the use of wcout

The alternative is to use the lisp type base-string which does use char (base-char in lisp) as its character type. The lisp code then reads

(defun return-a-base-string ()
    (coerce "Hello" 'base-string))

(there may be more elegant ways to do the conversion to base-string but I don't know them).

To print in C++

cl_object y2 = lisp("(return-a-base-string)");
std::cout << "Another: " << y2->base_string.self << std::endl;

(note that you can't mix wcout and cout in the same program)

DavidW
  • 29,336
  • 6
  • 55
  • 86
3

According to section 2.6 Strings of The ECL Manual, I think that the actual character array is found by accessing the string.self field of the returned object. Can you try the following?

std::cout << y->string.self << std::endl;
coredump
  • 37,664
  • 5
  • 43
  • 77
  • Yes, I have tried that too and a memory address is printed. – user6034704 Mar 15 '16 at 22:09
  • @user6034704 I can't test right now, but I'll check this later. I think you can use `cl_print` to print lisp object, if necessary, but that's more a workaround for you I guess. – coredump Mar 15 '16 at 22:37
  • 1
    @coredump does C++ know that `y->string.self` is `char*`? I can think of two ways: 1) coerce it to `char*` or 2) construct `std::string` from it. – mobiuseng Mar 16 '16 at 06:06
  • Yes, I can print it with cl_print but I want to have it in a variable, since I need to show it in a GUI instead of the console. – user6034704 Mar 16 '16 at 06:22
2
std::string str {""};    
cl_object y2 = lisp("(return-a-base-string)");
//get dimension
int j = y2->string.dim;
//get pointer
ecl_character* selv = y2->string.self;
//do simple pointer addition
for(int i=0;i<j;i++){
    str += (*(selv+i));
}
//do whatever you want to str

this code works when the string is build from ecl_characters

from the documentation:

"ECL defines two C types to hold its characters: ecl_base_char and ecl_character.

When ECL is built without Unicode, they both coincide and typically match unsigned char, to cover the 256 codes that are needed. When ECL is built with Unicode, the two types are no longer equivalent, with ecl_character being larger. For your code to be portable and future proof, use both types to really express what you intend to do."

1

On my system the return-a-base-string is not needed, but I think it could be good to add for compatibility. I use the (ecl) embedded CLISP 16.1.2 version. The following piece of code reads a string from lisp and converts to C++ strings types - std::string and c-string- and store them on C++ variables:

// strings initializations: string and c-string
std::string str2 {""};     
char str_c[99] = " ";
// text read from clisp, whatever clisp function that returns string type
cl_object cl_text = lisp("(coerce (text-from-lisp X) 'base-string)");
//cl_object cl_text = lisp("(text-from-lisp X)"); // no base string conversions
// catch dimension
int cl_text_dim = cl_text->string.dim;
// complete c-string char by char
for(int ind=0;i<cl_text_dim;i++){
str_c[i] = ecl_char(cl_text,i); // ecl function to get char from cl_object
   }
str_c[cl_text_dim] ='\0'; // end of the c-string
str2 = str_c; // get the string on the other string type
std::cout <<  "Dim: " << cl_ text_dim << " C-String var: " << str_c() << " String var << str2 << std::endl;   

It is a slow process as passing char by char but it is the only way by the moment I know. Hope it helps. Greetings!

L. Jacob
  • 141
  • 3