Bimo
Bimo

Reputation: 6647

tcl library: how to use tcl_eval() to set return result for c-code tcl command extension?

Let's suppose I implemented a new tcl command written in c-code that I registered using Tcl_CreateObjCommand, and inside of this c-code I call Tcl_Eval to eval a string containing code to create an Associative array and store it in a variable tmp. How can I set this tmp variable created with Tcl_eval() as the return result object from the c-function?

Example:

int MyCommand(
    ClientData   clientData,
    Tcl_Interp*  interp,
    int          argc,
    char*        argv[])
{

     int rc = Tcl_Eval(interp, 
       "array set tmp [list {key1} {value1} {key2} {value2}]");
     if (rc != TCL_OK) {
         return rc;
     }

     //???
     Tcl_SetObjResult(interp, ?? tmp variable from eval??);
     return TCL_OK;
}

When I run the Tcl interpreter with the above C-extension, I would expect to see this result:

TCL> set x [MyCommand]

TCL> puts "$x(key1)"
value1   # Currently an Error and not set

TCL> puts "$x(key2)"
value2   # Currently and Error and not set

In a way the above works. Just not the way I want it to. For Example, if I type:

TCL> set x [MyCommand]

TCL> puts "$tmp(key1)"
value1   # Its Works!  Except, I didn't want to set a global variable tmp

TCL> puts "$tmp(key2)"
value2  # Its Works!  Except, I didn't want to set a global variable tmp

(Maybe its a "feature" to set tmp instead??) Anyways, I still want it to work the correct way by returning the value using the proc "return" mechanism.

It should be legal to call Tcl_Eval() from inside of Tcl_Eval of c-command-extension because the documentation for the "Tcl Library" States that for tcl_eval, it is legal to make nested calls to evaluate other commands. I just don't know how to copy the object result from Tcl_Eval to "return" object for c-extension procedure.

Upvotes: 0

Views: 1182

Answers (2)

Donal Fellows
Donal Fellows

Reputation: 137787

The recommended way to set an array (given you're working with char* values in the first place) is using calls to Tcl_SetVar2 (so named because it takes variable names as two parts).

Tcl_SetVar2(interp, "tmp", "key1", "value1", 0);
Tcl_SetVar2(interp, "tmp", "key2", "value2", 0);

Idiomatically, you'd use a name passed in as an argument to your C command implementation as an argument, so that the caller can tell you what variable to write into, and you'd want to check the results too:

int MyCommand(
    ClientData   clientData,
    Tcl_Interp*  interp,
    int          argc,
    char*        argv[])
{
    // Missing: check # of arguments

    if (Tcl_SetVar2(interp, argv[1], "key1", "value1", 0) == NULL)
        return TCL_ERROR;
    if (Tcl_SetVar2(interp, argv[1], "key2", "value2", 0) == NULL)
        return TCL_ERROR;
    return TCL_OK;
}

You'd then call that like this:

MyCommand x
# It has no meaningful result.

puts $x(key1)
puts $x(key2)

Upvotes: 2

andy mango
andy mango

Reputation: 1551

I see two problems here. You can't set the return value of a command to be the value of an array because arrays are not values. Arrays are collections of variables indexed by a string. It's a common misunderstanding. You could return the value of an element of an array. If you want a key / value map that is a proper Tcl value, consider a dictionary. Dictionaries are values and can be returned as the value of a command.

The second problem why are you using Tcl_Eval() to create an array. It is much simpler to use Tcl_SetVar() or one of its several variations to build an array.

Upvotes: 2

Related Questions