Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Tcl 8.5 & Vim's Tcl interface

61 views
Skip to first unread message

Dave

unread,
Jan 31, 2012, 2:46:08 PM1/31/12
to
[Pardon the leading period, but I'm hoping the indenting is
preserved to make reading easier]

. I wanted Vim on windows (x64) that was compiled with Vim's
. Tcl interface. All went fairly well, I was able to build
. Vim using window's SDK command line compiler for x64, and I
. linked against ActiveState's Tcl 8.5.11.0 for x64 using
. tclstub85.lib to allow dynamic loading of the Tcl DLL.
.
. [As an aside, I'm hoping to convince Vim's maintainer to
. build the release version of Vim containing support for all
. the language interfaces since only an error message would
. occur if the respective DLL was not found. It appears that
. the Vim maintainer last checked building against 8.4]
.
. The Tcl interface works for a simple ":tcl puts Hello" but
. when I tried anything more I get the error:
.
. wrong # args: should be "catch command ?varName?"
.
. This is because Vim creates a replacement "catch" command
. (I've attached the relevant code below. As the comments
. state, Vim needs to prevent exit() being called by Tcl. Tcl
. 8.5 has introduced a new "catch" command that takes an
. additional argument, and somewhere -- either the Tcl
. library or tcl85.dll -- uses catch with four arguments. I
. don't have 8.5's source but I see that auto.tcl, clock.tcl,
. init.tcl, (I stopped looking) all use the four-argument catch.
.
. So here's my question: what's the best way this could be fixed?
.
. I see several possibilities:
.
. 1) Compile and link Vim against Tcl 8.5 (as I've done) and
. update Vim's catch replacement.
. Con: Breaks run-time compatibility with Tcl versions
. prior to 8.5 (is this correct?) If true, this would
. be a BIG problem since a binary Vim installer
. containing the Tcl interface would not work against
. Tcl 8.4 or earlier.
. Pro: Should work (I don't see any problems here)
.
. 2) Compile and link Vim against Tcl 8.3
. Con: I don't have a 64-bit tclstub83.lib (I've got a
. copy of tcl-8.3.5 source but no 64-bit ActiveState installer)
. Wouldn't the same problem occur? 8.5's library uses
. the four-argument catch and Vim's catch would fail
.
. 3) Compile and link Vim against Tcl 8.3/4 but use a
. run-time test after calling Tcl_InitStubs() to find
. out what version of Tcl and then replace catch with a
. version specific routine.
. Con: Can one determine the version of Tcl after
. Tcl_InitStubs() has been called?
. Compiling Vim against 8.3/4 but trying to back-port a
. four-argument catch would be pretty difficult
. since the dict stuff had not yet been invented,
. the Tcl header files would not contain any dict
. declarations, and tclstub83.lib would not have
. hooks to 8.5's dict routines.
.
. So, is anything but option #1 viable or even possible?
.
. ===
. === Vim's replacement for exit and catch from if_tcl.c (note that
. === catch checks for exit having been called; this is used by Vim
. === to determine if the current Tcl interpreter should be deleted
. === and a new one created):
. ===
.
. /*
. * Replace standard "exit" and "catch" commands.
. *
. * This is a design flaw in Tcl - the standard "exit" command just calls
. * exit() and kills the application. It should return TCL_EXIT to the
. * app, which then decides if it wants to terminate or not. In our case,
. * we just delete the Tcl interpreter (and create a new one with the next
. * :tcl command).
. */
. #define TCL_EXIT 5
.
. static int
. exitcmd(dummy, interp, objc, objv)
. ClientData dummy UNUSED;
. Tcl_Interp *interp;
. int objc;
. Tcl_Obj *CONST objv[];
. {
. int value = 0;
.
. switch (objc)
. {
. case 2:
. if (Tcl_GetIntFromObj(interp, objv[1], &value) != TCL_OK)
. break;
. /* FALLTHROUGH */
. case 1:
. Tcl_SetObjResult(interp, Tcl_NewIntObj(value));
. return TCL_EXIT;
. default:
. Tcl_WrongNumArgs(interp, 1, objv, "?returnCode?");
. }
. return TCL_ERROR;
. }
.
. static int
. catchcmd(dummy, interp, objc, objv)
. ClientData dummy UNUSED;
. Tcl_Interp *interp;
. int objc;
. Tcl_Obj *CONST objv[];
. {
. char *varname = NULL;
. int result;
.
. switch (objc)
. {
. case 3:
. varname = Tcl_GetStringFromObj(objv[2], NULL);
. /* fallthrough */
. case 2:
. Tcl_ResetResult(interp);
. Tcl_AllowExceptions(interp);
. result = Tcl_EvalObj(interp, objv[1]);
. if (result == TCL_EXIT)
. return result;
. if (varname)
. {
. if (Tcl_SetVar(interp, varname, Tcl_GetStringResult(interp), 0) == NULL)
. {
. Tcl_SetResult(interp, "couldn't save command result in
variable", TCL_STATIC);
. return TCL_ERROR;
. }
. }
. Tcl_SetObjResult(interp, Tcl_NewIntObj(result));
. return TCL_OK;
. default:
. Tcl_WrongNumArgs(interp, 1, objv, "command ?varName?");
. }
. return TCL_ERROR;
. }

Don Porter

unread,
Jan 31, 2012, 2:59:30 PM1/31/12
to
Dave wrote:
> . So, is anything but option #1 viable or even possible?

Best answer would be to change the question.

> . /*
> . * Replace standard "exit" and "catch" commands.
> . *
> . * This is a design flaw in Tcl - the standard "exit" command just calls
> . * exit() and kills the application.

That's correct, at least in this context.

It should return TCL_EXIT to the
> . * app, which then decides if it wants to terminate or not. In our case,
> . * we just delete the Tcl interpreter (and create a new one with the next
> . * :tcl command).

But that's not really a good solution.

Better solution is to replace [exit] with a command that deletes
the interp. No use of a custom return code, and thus, no need to
replace [catch] to enable special treatment of the custom return
code. Note that with the addition of [try] in later Tcl releases,
modifying [catch] is no longer sufficient anyway, to say nothing
of the other places where a custom return code might be encountered.

> . */
> . #define TCL_EXIT 5
> .
> . static int
> . exitcmd(dummy, interp, objc, objv)
> . ClientData dummy UNUSED;
> . Tcl_Interp *interp;
> . int objc;
> . Tcl_Obj *CONST objv[];
> . {
> . int value = 0;
> .
> . switch (objc)
> . {
> . case 2:
> . if (Tcl_GetIntFromObj(interp, objv[1], &value) != TCL_OK)
> . break;
> . /* FALLTHROUGH */
> . case 1:
> . Tcl_SetObjResult(interp, Tcl_NewIntObj(value));

Tcl_DeleteInterp(interp);
break;

> . default:
> . Tcl_WrongNumArgs(interp, 1, objv, "?returnCode?");
> . }
> . return TCL_ERROR;


--
| Don Porter Applied and Computational Mathematics Division |
| donald...@nist.gov Information Technology Laboratory |
| http://math.nist.gov/~DPorter/ NIST |
|______________________________________________________________________|

Dave

unread,
Jan 31, 2012, 3:51:45 PM1/31/12
to
On 1/31/2012 1:59 PM, Don Porter wrote:

>> . static int
>> . exitcmd(dummy, interp, objc, objv)
>> . ClientData dummy UNUSED;
>> . Tcl_Interp *interp;
>> . int objc;
>> . Tcl_Obj *CONST objv[];
>> . {
>> . int value = 0;
>> .
>> . switch (objc)
>> . {
>> . case 2:
>> . if (Tcl_GetIntFromObj(interp, objv[1], &value) != TCL_OK)
>> . break;
>> . /* FALLTHROUGH */
>> . case 1:
>> . Tcl_SetObjResult(interp, Tcl_NewIntObj(value));
>
> Tcl_DeleteInterp(interp);
> break;
>

Wow, thanks for the quick reply.

Vim's if_tcl.c uses Tcl_Eval() and Tcl_EvalFile() to run a script. My
first thought was to setjmp/longjmp these calls with the replacement
exit() doing the longjmp. However, from your suggestion above, I'm
thinking that a call to Tcl_InterpDeleted() after the calls to Tcl_Eval
or Tcl_EvalFile would be the way to go. Am I on the right track?

Don Porter

unread,
Jan 31, 2012, 4:09:05 PM1/31/12
to

> Vim's if_tcl.c uses Tcl_Eval() and Tcl_EvalFile() to run a script. My
> first thought was to setjmp/longjmp these calls with the replacement
> exit() doing the longjmp. However, from your suggestion above, I'm
> thinking that a call to Tcl_InterpDeleted() after the calls to Tcl_Eval
> or Tcl_EvalFile would be the way to go. Am I on the right track?

Sounds like it. You'll also want to make use of Tcl_Preserve() and
Tcl_Release(). I suggest you look at the sources for Tcl_Main() for
some guidance.

Dave

unread,
Jan 31, 2012, 11:00:15 PM1/31/12
to
On 1/31/2012 3:09 PM, Don Porter wrote:
>
>> Vim's if_tcl.c uses Tcl_Eval() and Tcl_EvalFile() to run a script. My
>> first thought was to setjmp/longjmp these calls with the replacement
>> exit() doing the longjmp. However, from your suggestion above, I'm
>> thinking that a call to Tcl_InterpDeleted() after the calls to
>> Tcl_Eval or Tcl_EvalFile would be the way to go. Am I on the right track?
>
> Sounds like it. You'll also want to make use of Tcl_Preserve() and
> Tcl_Release(). I suggest you look at the sources for Tcl_Main() for
> some guidance.
>
Using your suggestion it took me all of ten minutes to make the changes.
As far as I can tell Vim is now working with Tcl 8.5
I'll submit the patch to vim.org

Thanks much for the help.
0 new messages