Search code examples
c++common-lispecl

Getting ECL symbol name as string in C++


I'm working on embedding Embeddable Common Lisp into a library and I've been writing utility functions to convert ECL's cl_object to various C/C++ types - e.g. to convert a cl_object representing a string to a std::string.

My question is this - why am I failing to retrieve a string containing the name of a symbol in ECL?

I'm having trouble getting the following function ecl_symbol_to_string to work, which should take an ECL symbol and return a std::string with it's name:

string ecl_symbol_to_string(cl_object sym) {
    return ecl_string_to_string(sym->symbol.name);
}

string ecl_string_to_string(cl_object echar) {
    string res("");
    int j = echar->string.dim; //get dimension   
    ecl_character* selv = echar->string.self; //get pointer   

    //do simple pointer addition
    for(int i=0;i<j;i++){
        res += (*(selv+i));
    }
    return res;
};

Note that ecl_string_to_string works for lisp strings.

A simple unit test illustrates the failure:

TEST_CASE( "ecl_symbol_to_string returns a string for symbol",
           "[ecl_string_to_string]" ) {

  LispRuntime *rt = new LispRuntime("()");
  std::string eval_script;
  cl_object   eval_result;
  std::string subject_result;

  eval_script = "'mysymbol";
  eval_result = rt->evaluate(eval_script);
  REQUIRE( ECL_SYMBOLP(eval_result) );
  subject_result = ecl_symbol_to_string(eval_result);
  REQUIRE ( ECL_STRINGP(cl_symbol_name(eval_result)) );
  std::cout << subject_result.c_str() << std::endl;
  REQUIRE( subject_result.compare("mysymbol") == 0 );

  delete rt;

}

This test case prints out MM for the call to cout. I have also tried comparing to "MYSYMBOL", which fails, and "M" which passes.

LispRuntime::eval_script simply converts and evaluates the form:

cl_object LispRuntime::evaluate(std::string &code) {
  cl_object form = c_string_to_object(code.c_str());
  cl_object result = cl_eval(form);

  return result;
}

I compiled ECL version 16.1.3 locally with the C++ option enabled, with debugging symbols and with all other settings default. Any assistance is greatly appreciated.


Solution

  • I believe this is a unicode/non-unicode mix-up: ECL defines two string types in object.h. One is ecl_base_string, where the member self ultimately typedefs to an unsigned char* and the other is ecl_string where the member self usually (depending on compile time arguments, I think) typedefs to a int*. You're accessing it as an ecl_string.

    If you trace through the workings of ecl_make_symbol you find it ends up calling the function make_constant_base_string which returns a base string. Thus your ecl_string_to_string accesses it through the wrong type.

    I suspect the easiest solution is to build a type-check/conversion into ecl_string_to_string:

    string ecl_string_to_string(cl_object echar) {
        switch (ecl_t_of(echar)) {
        #ifdef ECL_UNICODE
          case t_string:
            if (!ecl_fits_in_base_string(echar)) {
              echar = cl_copy_seq(echar);
            } else {
              echar = si_copy_to_simple_base_string(echar);
            }
            break;
        #endif
          case t_base_string:
            // OK
            break;
          default:
            // PRINT SOME ERROR
            return string(); // or raise an exception
        }
    
        string res("");
        int j = echar->base_string.dim; //get dimension   
        ecl_base_char* selv = echar->base_string.self; //get pointer   
    
        //do simple pointer addition
        for(int i=0;i<j;i++){
            res += (*(selv+i));
        }
        return res;
    };
    

    The extra code I've added was heavily copied from the ECL function cl_make_symbol. I decided to convert to ecl_base_string rather than ecl_string since the C++ string won't accept unicode characters anyway. You could probably do it the other way round if you had good reason to.