TIP 353: NR-enabled Expressions for Extensions

Author:		Don Porter <[email protected]>
State:		Final
Type:		Project
Vote:		Done
Created:	29-Jul-2009
Tcl-Version:	8.6
Tcl-Ticket:		2823282


This TIP proposes the new public routine Tcl_NRExprObj to provide extension commands that evaluate Tcl expressions the ability to do so in a non-recursive manner.


In a few contexts, expressions that contain yield raise the error "cannot yield: C stack busy"; see Tcl Bugs 2823282 https://sourceforge.net/support/tracker.php?aid=2823282 and 2823276 https://sourceforge.net/support/tracker.php?aid=2823276 . This is because a few little-visited corners of Tcl's implementation call the routine Tcl_ExprObj and that routine is not NR-enabled.

For extensions wishing to evaluate Tcl expressions, Tcl_ExprObj is not little-visited. It is the public, supported, recommended tool for the job. Just as [322] provided a routine Tcl_NREvalObj as an NR-enabled replacement for Tcl_EvalObj, extensions wishing to NR-enable their commands need an analogous replacement for Tcl_ExprObj.


Tcl has a long history of providing extensions access to the same capabilities available to the built-in command set so that extension commands are on an equal footing, not in a second class status. Keeping with that, we want extensions to be able to create NR-enabled commands, so we need to provide an interface for extensions to evaluate expressions in an NR-enabled manner. This TIP can be seen as filling up a hole in [322].

Scope Limitations

The Tcl public C interface provides a whole family of variants of Tcl_ExprObj: Tcl_ExprLongObj, Tcl_ExprDoubleObj, Tcl_ExprBooleanObj, Tcl_ExprLong, Tcl_ExprDouble, Tcl_ExprBoolean, Tcl_ExprString. NR-enabled counterparts to these routines are not proposed. Extensions rewriting their command procedures to use the proposed Tcl_NRExprObj for sake of NR-enabling can at the same time be expected to convert from these convenience wrappers to more direct use of a single NR-enabled primitive.


Add the following routine to Tcl's public interface:

int Tcl_NRExprObj(Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Obj *resultPtr)

This routine places on the NR stack a request that the Tcl non-recursive trampoline evaluate the objPtr value as a Tcl expression in interpreter interp. This routine returns the value TCL_OK, since there is (currently) no way this request operation can fail. The proposed interface still provides for an int return value so that future revisions to Tcl's internals have the freedom to change that without need to change the public interface.

The resultPtr argument must be an unshared Tcl value. When expression evaluation succeeds, the result of the expression is written to resultPtr in the same way that Tcl_SetStringObj would write a string value to an unshared Tcl value. If expression evaluation produces any return code other than TCL_OK, the value of resultPtr is left untouched.

Callers of Tcl_NRExprObj will also need to call Tcl_NRAddCallback to request a Tcl_NRPostProc callback routine be placed on the NR stack which can take care of managing resultPtr as appropriate depending on the result value.


The patch attached to Tcl Bug 2823282 https://sourceforge.net/support/tracker.php?aid=2823282 implements this proposal.


There should be no compatibility issues, since at the interface level this is just the addition of a new routine. Revisions to the internal implementations of existing routines should be harmless.


As an example for extensions to follow, consider this template for a Tcl_ObjCmdProc currently calling Tcl_ExprObj.

int ObjCmd(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
{   int code;
    Tcl_Obj *resultPtr;
    /* determine expression, objPtr */
    code = Tcl_ExprObj(interp, objPtr, &resultPtr);
    if (code != TCL_OK) {return code}
    /* resultPtr holds expression result; continue */

Tcl_CreateObjCommand(interp, name, ObjCmd, /* ... */);

To use Tcl_NRExprObj to NR-enable this command, rewrite along these lines:

int ObjCmd(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
{   return Tcl_NRCallObjProc(interp, NRObjCmd, cd, objc, objv);  }

int NRObjCmd(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
{   Tcl_Obj *resultPtr = Tcl_NewObj();
    /* determine expression, objPtr */
    Tcl_NRAddCallback(interp, Callback, resultPtr, /*...*/);
    return Tcl_NRExprObj(interp, objPtr, resultPtr);

int Callback(ClientData data[], Tcl_Interp *interp, int code)
{   Tcl_Obj *resultPtr = data[0];
    if (code != TCL_OK) {Tcl_DecrRefCount(resultPtr); return code;}
    /* resultPtr holds expression result; continue */

Tcl_NRCreateCommand(interp, name, ObjCmd, NRObjCmd, /*...*/);


This document has been placed in the public domain.