1 /*
2  * itcl2TclOO.c --
3  *
4  *	This file contains code to create and manage methods.
5  *
6  * Copyright (c) 2007 by Arnulf P. Wiedemann
7  *
8  * See the file "license.terms" for information on usage and redistribution of
9  * this file, and for a DISCLAIMER OF ALL WARRANTIES.
10  */
11 
12 #include <tclInt.h>
13 #include <tclOOInt.h>
14 #undef FOREACH_HASH_DECLS
15 #undef FOREACH_HASH
16 #undef FOREACH_HASH_VALUE
17 #include "itclInt.h"
18 
19 void *
Itcl_GetCurrentCallbackPtr(Tcl_Interp * interp)20 Itcl_GetCurrentCallbackPtr(
21     Tcl_Interp *interp)
22 {
23     return TOP_CB(interp);
24 }
25 
26 int
Itcl_NRRunCallbacks(Tcl_Interp * interp,void * rootPtr)27 Itcl_NRRunCallbacks(
28     Tcl_Interp *interp,
29     void *rootPtr)
30 {
31     return TclNRRunCallbacks(interp, TCL_OK, (NRE_callback*)rootPtr);
32 }
33 
34 static int
CallFinalizePMCall(void * data[],Tcl_Interp * interp,int result)35 CallFinalizePMCall(
36     void *data[],
37     Tcl_Interp *interp,
38     int result)
39 {
40     Tcl_Namespace *nsPtr = (Tcl_Namespace *)data[0];
41     TclOO_PostCallProc *postCallProc = (TclOO_PostCallProc *)data[1];
42     void *clientData = data[2];
43 
44     /*
45      * Give the post-call callback a chance to do some cleanup. Note that at
46      * this point the call frame itself is invalid; it's already been popped.
47      */
48 
49     return postCallProc(clientData, interp, NULL, nsPtr, result);
50 }
51 
52 static int
FreeCommand(void * data[],Tcl_Interp * dummy,int result)53 FreeCommand(
54     void *data[],
55     Tcl_Interp *dummy,
56     int result)
57 {
58     Command *cmdPtr = (Command *)data[0];
59     Proc *procPtr = (Proc *)data[1];
60     (void)dummy;
61 
62     ckfree(cmdPtr);
63     procPtr->cmdPtr = NULL;
64 
65     return result;
66 }
67 
68 static int
Tcl_InvokeClassProcedureMethod(Tcl_Interp * interp,Tcl_Obj * namePtr,Tcl_Namespace * nsPtr,ProcedureMethod * pmPtr,int objc,Tcl_Obj * const * objv)69 Tcl_InvokeClassProcedureMethod(
70     Tcl_Interp *interp,
71     Tcl_Obj *namePtr,           /* name of the method */
72     Tcl_Namespace *nsPtr,       /* namespace for calling method */
73     ProcedureMethod *pmPtr,     /* method type specific data */
74     int objc,			/* Number of arguments. */
75     Tcl_Obj *const *objv)	/* Arguments as actually seen. */
76 {
77     Proc *procPtr = pmPtr->procPtr;
78     CallFrame *framePtr = NULL;
79     CallFrame **framePtrPtr1 = &framePtr;
80     Tcl_CallFrame **framePtrPtr = (Tcl_CallFrame **)framePtrPtr1;
81     int result;
82 
83     if (procPtr->cmdPtr == NULL) {
84 	Command *cmdPtr = (Command *)ckalloc(sizeof(Command));
85 
86 	memset(cmdPtr, 0, sizeof(Command));
87 	cmdPtr->nsPtr = (Namespace *) nsPtr;
88 	cmdPtr->clientData = NULL;
89 	procPtr->cmdPtr = cmdPtr;
90 	Tcl_NRAddCallback(interp, FreeCommand, cmdPtr, procPtr, NULL, NULL);
91     }
92 
93     result = TclProcCompileProc(interp, procPtr, procPtr->bodyPtr,
94 	    (Namespace *) nsPtr, "body of method", Tcl_GetString(namePtr));
95     if (result != TCL_OK) {
96 	return result;
97     }
98     /*
99      * Make the stack frame and fill it out with information about this call.
100      * This operation may fail.
101      */
102 
103 
104     result = TclPushStackFrame(interp, framePtrPtr, nsPtr, FRAME_IS_PROC);
105     if (result != TCL_OK) {
106 	return result;
107     }
108 
109     framePtr->clientData = NULL;
110     framePtr->objc = objc;
111     framePtr->objv = objv;
112     framePtr->procPtr = procPtr;
113 
114     /*
115      * Give the pre-call callback a chance to do some setup and, possibly,
116      * veto the call.
117      */
118 
119     if (pmPtr->preCallProc != NULL) {
120 	int isFinished;
121 
122 	result = pmPtr->preCallProc(pmPtr->clientData, interp, NULL,
123 		(Tcl_CallFrame *) framePtr, &isFinished);
124 	if (isFinished || result != TCL_OK) {
125 	    Tcl_PopCallFrame(interp);
126 	    TclStackFree(interp, framePtr);
127 	    goto done;
128 	}
129     }
130 
131     /*
132      * Now invoke the body of the method. Note that we need to take special
133      * action when doing unknown processing to ensure that the missing method
134      * name is passed as an argument.
135      */
136 
137     if (pmPtr->postCallProc) {
138 	Tcl_NRAddCallback(interp, CallFinalizePMCall, nsPtr,
139 		(void *)pmPtr->postCallProc, pmPtr->clientData, NULL);
140     }
141     return TclNRInterpProcCore(interp, namePtr, 1, pmPtr->errProc);
142 
143 done:
144     return result;
145 }
146 
147 int
Itcl_InvokeProcedureMethod(void * clientData,Tcl_Interp * interp,int objc,Tcl_Obj * const * objv)148 Itcl_InvokeProcedureMethod(
149     void *clientData,	/* Pointer to some per-method context. */
150     Tcl_Interp *interp,
151     int objc,			/* Number of arguments. */
152     Tcl_Obj *const *objv)	/* Arguments as actually seen. */
153 {
154     Tcl_Namespace *nsPtr;
155     Method *mPtr;
156 
157     mPtr = (Method *)clientData;
158     if (mPtr->declaringClassPtr == NULL) {
159 	/* that is the case for typemethods */
160         nsPtr = mPtr->declaringObjectPtr->namespacePtr;
161     } else {
162         nsPtr = mPtr->declaringClassPtr->thisPtr->namespacePtr;
163     }
164 
165     return Tcl_InvokeClassProcedureMethod(interp, mPtr->namePtr, nsPtr,
166             (ProcedureMethod *)mPtr->clientData, objc, objv);
167 }
168 
169 static int
FreeProcedureMethod(void * data[],Tcl_Interp * dummy,int result)170 FreeProcedureMethod(
171     void *data[],
172     Tcl_Interp *dummy,
173     int result)
174 {
175     ProcedureMethod *pmPtr = (ProcedureMethod *)data[0];
176     (void)dummy;
177 
178     ckfree(pmPtr);
179     return result;
180 }
181 
182 static void
EnsembleErrorProc(Tcl_Interp * interp,Tcl_Obj * procNameObj)183 EnsembleErrorProc(
184     Tcl_Interp *interp,
185     Tcl_Obj *procNameObj)
186 {
187     int overflow, limit = 60, nameLen;
188     const char *procName = Tcl_GetStringFromObj(procNameObj, &nameLen);
189 
190     overflow = (nameLen > limit);
191     Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf(
192             "\n    (itcl ensemble part \"%.*s%s\" line %d)",
193             (overflow ? limit : nameLen), procName,
194             (overflow ? "..." : ""), Tcl_GetErrorLine(interp)));
195 }
196 
197 int
Itcl_InvokeEnsembleMethod(Tcl_Interp * interp,Tcl_Namespace * nsPtr,Tcl_Obj * namePtr,Tcl_Proc * procPtr,int objc,Tcl_Obj * const * objv)198 Itcl_InvokeEnsembleMethod(
199     Tcl_Interp *interp,
200     Tcl_Namespace *nsPtr,       /* namespace to call the method in */
201     Tcl_Obj *namePtr,           /* name of the method */
202     Tcl_Proc *procPtr,
203     int objc,			/* Number of arguments. */
204     Tcl_Obj *const *objv)	/* Arguments as actually seen. */
205 {
206     ProcedureMethod *pmPtr = (ProcedureMethod *)ckalloc(sizeof(ProcedureMethod));
207 
208     memset(pmPtr, 0, sizeof(ProcedureMethod));
209     pmPtr->version = TCLOO_PROCEDURE_METHOD_VERSION;
210     pmPtr->procPtr = (Proc *)procPtr;
211     pmPtr->flags = USE_DECLARER_NS;
212     pmPtr->errProc = EnsembleErrorProc;
213 
214     Tcl_NRAddCallback(interp, FreeProcedureMethod, pmPtr, NULL, NULL, NULL);
215     return Tcl_InvokeClassProcedureMethod(interp, namePtr, nsPtr,
216             pmPtr, objc, objv);
217 }
218 
219 
220 /*
221  * ----------------------------------------------------------------------
222  *
223  * Itcl_PublicObjectCmd, Itcl_PrivateObjectCmd --
224  *
225  *	Main entry point for object invokations. The Public* and Private*
226  *	wrapper functions are just thin wrappers around the main ObjectCmd
227  *	function that does call chain creation, management and invokation.
228  *
229  * ----------------------------------------------------------------------
230  */
231 
232 int
Itcl_PublicObjectCmd(void * clientData,Tcl_Interp * interp,Tcl_Class clsPtr,int objc,Tcl_Obj * const * objv)233 Itcl_PublicObjectCmd(
234     void *clientData,
235     Tcl_Interp *interp,
236     Tcl_Class clsPtr,
237     int objc,
238     Tcl_Obj *const *objv)
239 {
240     Tcl_Object oPtr = (Tcl_Object)clientData;
241     int result;
242 
243     if (oPtr) {
244 	result = TclOOInvokeObject(interp, oPtr, clsPtr, PUBLIC_METHOD,
245             objc, objv);
246     } else {
247 	Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
248 	    "cannot access object-specific info without an object context",
249 	    NULL);
250 	return TCL_ERROR;
251     }
252     return result;
253 }
254 
255 /*
256  * ----------------------------------------------------------------------
257  *
258  * Itcl_NewProcClassMethod --
259  *
260  *	Create a new procedure-like method for a class for Itcl.
261  *
262  * ----------------------------------------------------------------------
263  */
264 
265 Tcl_Method
Itcl_NewProcClassMethod(Tcl_Interp * interp,Tcl_Class clsPtr,TclOO_PreCallProc * preCallPtr,TclOO_PostCallProc * postCallPtr,ProcErrorProc * errProc,void * clientData,Tcl_Obj * nameObj,Tcl_Obj * argsObj,Tcl_Obj * bodyObj,void ** clientData2)266 Itcl_NewProcClassMethod(
267     Tcl_Interp *interp,		/* The interpreter containing the class. */
268     Tcl_Class clsPtr,		/* The class to modify. */
269     TclOO_PreCallProc *preCallPtr,
270     TclOO_PostCallProc *postCallPtr,
271     ProcErrorProc *errProc,
272     void *clientData,
273     Tcl_Obj *nameObj,		/* The name of the method, which may be NULL;
274 				 * if so, up to caller to manage storage
275 				 * (e.g., because it is a constructor or
276 				 * destructor). */
277     Tcl_Obj *argsObj,		/* The formal argument list for the method,
278 				 * which may be NULL; if so, it is equivalent
279 				 * to an empty list. */
280     Tcl_Obj *bodyObj,		/* The body of the method, which must not be
281 				 * NULL. */
282     void **clientData2)
283 {
284     Tcl_Method result;
285 
286     result = TclOONewProcMethodEx(interp, clsPtr, preCallPtr, postCallPtr,
287            errProc, clientData, nameObj, argsObj, bodyObj,
288            PUBLIC_METHOD | USE_DECLARER_NS, clientData2);
289     return result;
290 }
291 
292 /*
293  * ----------------------------------------------------------------------
294  *
295  * Itcl_NewProcMethod --
296  *
297  *	Create a new procedure-like method for an object for Itcl.
298  *
299  * ----------------------------------------------------------------------
300  */
301 
302 Tcl_Method
Itcl_NewProcMethod(Tcl_Interp * interp,Tcl_Object oPtr,TclOO_PreCallProc * preCallPtr,TclOO_PostCallProc * postCallPtr,ProcErrorProc * errProc,void * clientData,Tcl_Obj * nameObj,Tcl_Obj * argsObj,Tcl_Obj * bodyObj,void ** clientData2)303 Itcl_NewProcMethod(
304     Tcl_Interp *interp,		/* The interpreter containing the object. */
305     Tcl_Object oPtr,		/* The object to modify. */
306     TclOO_PreCallProc *preCallPtr,
307     TclOO_PostCallProc *postCallPtr,
308     ProcErrorProc *errProc,
309     void *clientData,
310     Tcl_Obj *nameObj,		/* The name of the method, which must not be
311 				 * NULL. */
312     Tcl_Obj *argsObj,		/* The formal argument list for the method,
313 				 * which must not be NULL. */
314     Tcl_Obj *bodyObj,		/* The body of the method, which must not be
315 				 * NULL. */
316     void **clientData2)
317 {
318     return TclOONewProcInstanceMethodEx(interp, oPtr, preCallPtr, postCallPtr,
319            errProc, clientData, nameObj, argsObj, bodyObj,
320            PUBLIC_METHOD | USE_DECLARER_NS, clientData2);
321 }
322 
323 /*
324  * ----------------------------------------------------------------------
325  *
326  * Itcl_NewForwardClassMethod --
327  *
328  *	Create a new forwarded method for a class for Itcl.
329  *
330  * ----------------------------------------------------------------------
331  */
332 
333 Tcl_Method
Itcl_NewForwardClassMethod(Tcl_Interp * interp,Tcl_Class clsPtr,int flags,Tcl_Obj * nameObj,Tcl_Obj * prefixObj)334 Itcl_NewForwardClassMethod(
335     Tcl_Interp *interp,
336     Tcl_Class clsPtr,
337     int flags,
338     Tcl_Obj *nameObj,
339     Tcl_Obj *prefixObj)
340 {
341     return (Tcl_Method)TclOONewForwardMethod(interp, (Class *)clsPtr,
342             flags, nameObj, prefixObj);
343 }
344 
345 
346 static Tcl_Obj *
Itcl_TclOOObjectName(Tcl_Interp * interp,Object * oPtr)347 Itcl_TclOOObjectName(
348     Tcl_Interp *interp,
349     Object *oPtr)
350 {
351     Tcl_Obj *namePtr;
352 
353     if (oPtr->cachedNameObj) {
354         return oPtr->cachedNameObj;
355     }
356     namePtr = Tcl_NewObj();
357     Tcl_GetCommandFullName(interp, oPtr->command, namePtr);
358     Tcl_IncrRefCount(namePtr);
359     oPtr->cachedNameObj = namePtr;
360     return namePtr;
361 }
362 
363 int
Itcl_SelfCmd(void * dummy,Tcl_Interp * interp,int objc,Tcl_Obj * const * objv)364 Itcl_SelfCmd(
365     void *dummy,
366     Tcl_Interp *interp,
367     int objc,
368     Tcl_Obj *const *objv)
369 {
370     Interp *iPtr = (Interp *) interp;
371     CallFrame *framePtr = iPtr->varFramePtr;
372     CallContext *contextPtr;
373     (void)dummy;
374 
375     if (!Itcl_IsMethodCallFrame(interp)) {
376         Tcl_AppendResult(interp, TclGetString(objv[0]),
377                 " may only be called from inside a method", NULL);
378         return TCL_ERROR;
379     }
380 
381     contextPtr = (CallContext *)framePtr->clientData;
382 
383     if (objc == 1) {
384         Tcl_SetObjResult(interp, Itcl_TclOOObjectName(interp, contextPtr->oPtr));
385         return TCL_OK;
386     }
387     return TCL_ERROR;
388 }
389 
390 int
Itcl_IsMethodCallFrame(Tcl_Interp * interp)391 Itcl_IsMethodCallFrame(
392     Tcl_Interp *interp)
393 {
394     Interp *iPtr = (Interp *) interp;
395     CallFrame *framePtr = iPtr->varFramePtr;
396     if (framePtr == NULL || !(framePtr->isProcCallFrame & FRAME_IS_METHOD)) {
397         return 0;
398     }
399     return 1;
400 }
401