diff options
author | dkf <donal.k.fellows@manchester.ac.uk> | 2019-05-25 08:17:42 (GMT) |
---|---|---|
committer | dkf <donal.k.fellows@manchester.ac.uk> | 2019-05-25 08:17:42 (GMT) |
commit | 815f8822018df40f65d4dc2ce7029a9223da2ab6 (patch) | |
tree | e1b6ac62c11e8c4b03a5255b3ce9aec0641ba2d2 /generic | |
parent | e09e63e37e690f36ffc3b95ea85e966809c4b305 (diff) | |
parent | 9290e6c1ace5bd8e3074900948ea3ad677f73832 (diff) | |
download | tcl-815f8822018df40f65d4dc2ce7029a9223da2ab6.zip tcl-815f8822018df40f65d4dc2ce7029a9223da2ab6.tar.gz tcl-815f8822018df40f65d4dc2ce7029a9223da2ab6.tar.bz2 |
Merge 8.7
Diffstat (limited to 'generic')
-rw-r--r-- | generic/tclBasic.c | 286 | ||||
-rw-r--r-- | generic/tclCmdAH.c | 1 | ||||
-rw-r--r-- | generic/tclFCmd.c | 147 | ||||
-rw-r--r-- | generic/tclInt.decls | 6 | ||||
-rw-r--r-- | generic/tclInt.h | 1 | ||||
-rw-r--r-- | generic/tclIntDecls.h | 6 | ||||
-rw-r--r-- | generic/tclStubInit.c | 1 |
7 files changed, 434 insertions, 14 deletions
diff --git a/generic/tclBasic.c b/generic/tclBasic.c index ac32293..bb7819a 100644 --- a/generic/tclBasic.c +++ b/generic/tclBasic.c @@ -155,9 +155,13 @@ static Tcl_NRPostProc TEOV_RunLeaveTraces; static Tcl_NRPostProc EvalObjvCore; static Tcl_NRPostProc Dispatch; -static Tcl_ObjCmdProc NRCoroInjectObjCmd; +static Tcl_ObjCmdProc NRInjectObjCmd; static Tcl_NRPostProc NRPostInvoke; static Tcl_ObjCmdProc CoroTypeObjCmd; +static Tcl_ObjCmdProc TclNRCoroInjectObjCmd; +static Tcl_ObjCmdProc TclNRCoroProbeObjCmd; +static Tcl_NRPostProc InjectHandler; +static Tcl_NRPostProc InjectHandlerPostCall; MODULE_SCOPE const TclStubs tclStubs; @@ -224,6 +228,8 @@ static const CmdInfo builtInCmds[] = { {"catch", Tcl_CatchObjCmd, TclCompileCatchCmd, TclNRCatchObjCmd, CMD_IS_SAFE}, {"concat", Tcl_ConcatObjCmd, TclCompileConcatCmd, NULL, CMD_IS_SAFE}, {"continue", Tcl_ContinueObjCmd, TclCompileContinueCmd, NULL, CMD_IS_SAFE}, + {"coroinject", NULL, NULL, TclNRCoroInjectObjCmd, CMD_IS_SAFE}, + {"coroprobe", NULL, NULL, TclNRCoroProbeObjCmd, CMD_IS_SAFE}, {"coroutine", NULL, NULL, TclNRCoroutineObjCmd, CMD_IS_SAFE}, {"error", Tcl_ErrorObjCmd, TclCompileErrorCmd, NULL, CMD_IS_SAFE}, {"eval", Tcl_EvalObjCmd, NULL, TclNREvalObjCmd, CMD_IS_SAFE}, @@ -347,6 +353,7 @@ static const UnsafeEnsembleInfo unsafeEnsembleCommands[] = { {"file", "size"}, {"file", "stat"}, {"file", "tail"}, + {"file", "tempdir"}, {"file", "tempfile"}, {"file", "type"}, {"file", "volumes"}, @@ -940,7 +947,7 @@ Tcl_CreateInterp(void) /* Coroutine monkeybusiness */ Tcl_NRCreateCommand(interp, "::tcl::unsupported::inject", NULL, - NRCoroInjectObjCmd, NULL, NULL); + NRInjectObjCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "::tcl::unsupported::corotype", CoroTypeObjCmd, NULL, NULL); @@ -8527,27 +8534,47 @@ CoroTypeObjCmd( /* *---------------------------------------------------------------------- * - * NRCoroInjectObjCmd -- + * TclNRCoroInjectObjCmd, TclNRCoroProbeObjCmd -- * - * Implementation of [::tcl::unsupported::inject] command. + * Implementation of [coroinject] and [coroprobe] commands. * *---------------------------------------------------------------------- */ +static inline CoroutineData * +GetCoroutineFromObj( + Tcl_Interp *interp, + Tcl_Obj *objPtr, + const char *errMsg) +{ + /* + * How to get a coroutine from its handle. + */ + + Command *cmdPtr = (Command *) Tcl_GetCommandFromObj(interp, objPtr); + + if ((!cmdPtr) || (cmdPtr->nreProc != TclNRInterpCoroutine)) { + Tcl_SetObjResult(interp, Tcl_NewStringObj(errMsg, -1)); + Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "COROUTINE", + TclGetString(objPtr), NULL); + return NULL; + } + return cmdPtr->objClientData; +} + static int -NRCoroInjectObjCmd( +TclNRCoroInjectObjCmd( ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { - Command *cmdPtr; CoroutineData *corPtr; ExecEnv *savedEEPtr = iPtr->execEnvPtr; /* * Usage more or less like tailcall: - * inject coroName cmd ?arg1 arg2 ...? + * coroinject coroName cmd ?arg1 arg2 ...? */ if (objc < 3) { @@ -8555,16 +8582,249 @@ NRCoroInjectObjCmd( return TCL_ERROR; } - cmdPtr = (Command *) Tcl_GetCommandFromObj(interp, objv[1]); - if ((!cmdPtr) || (cmdPtr->nreProc != TclNRInterpCoroutine)) { + corPtr = GetCoroutineFromObj(interp, objv[1], + "can only inject a command into a coroutine"); + if (!corPtr) { + return TCL_ERROR; + } + if (!COR_IS_SUSPENDED(corPtr)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( - "can only inject a command into a coroutine", -1)); - Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "COROUTINE", - TclGetString(objv[1]), NULL); + "can only inject a command into a suspended coroutine", -1)); + Tcl_SetErrorCode(interp, "TCL", "COROUTINE", "ACTIVE", NULL); return TCL_ERROR; } - corPtr = cmdPtr->objClientData; + /* + * Add the callback to the coro's execEnv, so that it is the first thing + * to happen when the coro is resumed. + */ + + iPtr->execEnvPtr = corPtr->eePtr; + TclNRAddCallback(interp, InjectHandler, corPtr, + Tcl_NewListObj(objc - 2, objv + 2), INT2PTR(corPtr->nargs), NULL); + iPtr->execEnvPtr = savedEEPtr; + + return TCL_OK; +} + +static int +TclNRCoroProbeObjCmd( + ClientData clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *const objv[]) +{ + CoroutineData *corPtr; + ExecEnv *savedEEPtr = iPtr->execEnvPtr; + int numLevels, unused; + int *stackLevel = &unused; + + /* + * Usage more or less like tailcall: + * coroprobe coroName cmd ?arg1 arg2 ...? + */ + + if (objc < 3) { + Tcl_WrongNumArgs(interp, 1, objv, "coroName cmd ?arg1 arg2 ...?"); + return TCL_ERROR; + } + + corPtr = GetCoroutineFromObj(interp, objv[1], + "can only inject a probe command into a coroutine"); + if (!corPtr) { + return TCL_ERROR; + } + if (!COR_IS_SUSPENDED(corPtr)) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "can only inject a probe command into a suspended coroutine", + -1)); + Tcl_SetErrorCode(interp, "TCL", "COROUTINE", "ACTIVE", NULL); + return TCL_ERROR; + } + + /* + * Add the callback to the coro's execEnv, so that it is the first thing + * to happen when the coro is resumed. + */ + + iPtr->execEnvPtr = corPtr->eePtr; + TclNRAddCallback(interp, InjectHandler, corPtr, + Tcl_NewListObj(objc - 2, objv + 2), INT2PTR(corPtr->nargs), corPtr); + iPtr->execEnvPtr = savedEEPtr; + + /* + * Now we immediately transfer control to the coroutine to run our probe. + * TRICKY STUFF copied from the [yield] implementation. + * + * Push the callback to restore the caller's context on yield back. + */ + + TclNRAddCallback(interp, NRCoroutineCallerCallback, corPtr, + NULL, NULL, NULL); + + /* + * Record the stackLevel at which the resume is happening, then swap + * the interp's environment to make it suitable to run this coroutine. + */ + + corPtr->stackLevel = stackLevel; + numLevels = corPtr->auxNumLevels; + corPtr->auxNumLevels = iPtr->numLevels; + + /* + * Do the actual stack swap. + */ + + SAVE_CONTEXT(corPtr->caller); + corPtr->callerEEPtr = iPtr->execEnvPtr; + RESTORE_CONTEXT(corPtr->running); + iPtr->execEnvPtr = corPtr->eePtr; + iPtr->numLevels += numLevels; + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * InjectHandler, InjectHandlerPostProc -- + * + * Part of the implementation of [coroinject] and [coroprobe]. These are + * run inside the context of the coroutine being injected/probed into. + * + * InjectHandler runs a script (possibly adding arguments) in the context + * of the coroutine. The script is specified as a one-shot list (with + * reference count equal to 1) in data[1]. This function also arranges + * for InjectHandlerPostProc to be the part that runs after the script + * completes. + * + * InjectHandlerPostProc cleans up after InjectHandler (deleting the + * list) and, for the [coroprobe] command *only*, yields back to the + * caller context (i.e., where [coroprobe] was run). + *s + *---------------------------------------------------------------------- + */ + +static int +InjectHandler( + ClientData data[], + Tcl_Interp *interp, + int result) +{ + CoroutineData *corPtr = data[0]; + Tcl_Obj *listPtr = data[1]; + int nargs = PTR2INT(data[2]); + ClientData isProbe = data[3]; + int objc; + Tcl_Obj **objv; + + if (!isProbe) { + /* + * If this is [coroinject], add the extra arguments now. + */ + + if (nargs == COROUTINE_ARGUMENTS_SINGLE_OPTIONAL) { + Tcl_ListObjAppendElement(NULL, listPtr, + Tcl_NewStringObj("yield", -1)); + } else if (nargs == COROUTINE_ARGUMENTS_ARBITRARY) { + Tcl_ListObjAppendElement(NULL, listPtr, + Tcl_NewStringObj("yieldto", -1)); + } else { + /* + * I don't think this is reachable... + */ + + Tcl_ListObjAppendElement(NULL, listPtr, Tcl_NewIntObj(nargs)); + } + Tcl_ListObjAppendElement(NULL, listPtr, Tcl_GetObjResult(interp)); + } + + /* + * Call the user's script; we're in the right place. + */ + + Tcl_IncrRefCount(listPtr); + TclMarkTailcall(interp); + TclNRAddCallback(interp, InjectHandlerPostCall, corPtr, listPtr, + INT2PTR(nargs), isProbe); + TclListObjGetElements(NULL, listPtr, &objc, &objv); + return TclNREvalObjv(interp, objc, objv, 0, NULL); +} + +static int +InjectHandlerPostCall( + ClientData data[], + Tcl_Interp *interp, + int result) +{ + CoroutineData *corPtr = data[0]; + Tcl_Obj *listPtr = data[1]; + int nargs = PTR2INT(data[2]); + ClientData isProbe = data[3]; + int numLevels; + + /* + * Delete the command words for what we just executed. + */ + + Tcl_DecrRefCount(listPtr); + + /* + * If we were doing a probe, splice ourselves back out of the stack + * cleanly here. General injection should instead just look after itself. + * + * Code from guts of [yield] implementation. + */ + + if (isProbe) { + if (result == TCL_ERROR) { + Tcl_AddErrorInfo(interp, + "\n (injected coroutine probe command)"); + } + corPtr->nargs = nargs; + corPtr->stackLevel = NULL; + numLevels = iPtr->numLevels; + iPtr->numLevels = corPtr->auxNumLevels; + corPtr->auxNumLevels = numLevels - corPtr->auxNumLevels; + iPtr->execEnvPtr = corPtr->callerEEPtr; + } + return result; +} + +/* + *---------------------------------------------------------------------- + * + * NRInjectObjCmd -- + * + * Implementation of [::tcl::unsupported::inject] command. + * + *---------------------------------------------------------------------- + */ + +static int +NRInjectObjCmd( + ClientData clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *const objv[]) +{ + CoroutineData *corPtr; + ExecEnv *savedEEPtr = iPtr->execEnvPtr; + + /* + * Usage more or less like tailcall: + * inject coroName cmd ?arg1 arg2 ...? + */ + + if (objc < 3) { + Tcl_WrongNumArgs(interp, 1, objv, "coroName cmd ?arg1 arg2 ...?"); + return TCL_ERROR; + } + + corPtr = GetCoroutineFromObj(interp, objv[1], + "can only inject a command into a coroutine"); + if (!corPtr) { + return TCL_ERROR; + } if (!COR_IS_SUSPENDED(corPtr)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "can only inject a command into a suspended coroutine", -1)); diff --git a/generic/tclCmdAH.c b/generic/tclCmdAH.c index 0893ba4..68c0eb4 100644 --- a/generic/tclCmdAH.c +++ b/generic/tclCmdAH.c @@ -956,6 +956,7 @@ TclInitFileCmd( {"stat", FileAttrStatCmd, TclCompileBasic2ArgCmd, NULL, NULL, 1}, {"system", PathFilesystemCmd, TclCompileBasic0Or1ArgCmd, NULL, NULL, 0}, {"tail", PathTailCmd, TclCompileBasic1ArgCmd, NULL, NULL, 1}, + {"tempdir", TclFileTempDirCmd, TclCompileBasic0Or1ArgCmd, NULL, NULL, 1}, {"tempfile", TclFileTemporaryCmd, TclCompileBasic0To2ArgCmd, NULL, NULL, 1}, {"type", FileAttrTypeCmd, TclCompileBasic1ArgCmd, NULL, NULL, 1}, {"volumes", FilesystemVolumesCmd, TclCompileBasic0ArgCmd, NULL, NULL, 1}, diff --git a/generic/tclFCmd.c b/generic/tclFCmd.c index 5c6883d..264711d 100644 --- a/generic/tclFCmd.c +++ b/generic/tclFCmd.c @@ -1345,7 +1345,7 @@ TclFileReadLinkCmd( /* *--------------------------------------------------------------------------- * - * TclFileTemporaryCmd + * TclFileTemporaryCmd -- * * This function implements the "tempfile" subcommand of the "file" * command. @@ -1505,6 +1505,151 @@ TclFileTemporaryCmd( } /* + *--------------------------------------------------------------------------- + * + * TclFileTempDirCmd -- + * + * This function implements the "tempdir" subcommand of the "file" + * command. + * + * Results: + * Returns a standard Tcl result. + * + * Side effects: + * Creates a temporary directory. + * + *--------------------------------------------------------------------------- + */ + +int +TclFileTempDirCmd( + ClientData clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *const objv[]) +{ + Tcl_Obj *dirNameObj; /* Object that will contain the directory + * name. */ + Tcl_Obj *baseDirObj = NULL, *nameBaseObj = NULL; + /* Pieces of template. Each piece is NULL if + * it is omitted. The platform temporary file + * engine might ignore some pieces. */ + + if (objc < 1 || objc > 2) { + Tcl_WrongNumArgs(interp, 1, objv, "?template?"); + return TCL_ERROR; + } + + if (objc > 1) { + int length; + Tcl_Obj *templateObj = objv[1]; + const char *string = TclGetStringFromObj(templateObj, &length); + const int onWindows = (tclPlatform == TCL_PLATFORM_WINDOWS); + + /* + * Treat an empty string as if it wasn't there. + */ + + if (length == 0) { + goto makeTemporary; + } + + /* + * The template only gives a directory if there is a directory + * separator in it, and only gives a base name if there's at least one + * character after the last directory separator. + */ + + if (strchr(string, '/') == NULL + && (!onWindows || strchr(string, '\\') == NULL)) { + /* + * No directory separator, so just assume we have a file name. + * This is a bit wrong on Windows where we could have problems + * with disk name prefixes... but those are much less common in + * naked form so we just pass through and let the OS figure it out + * instead. + */ + + nameBaseObj = templateObj; + Tcl_IncrRefCount(nameBaseObj); + } else if (string[length-1] != '/' + && (!onWindows || string[length-1] != '\\')) { + /* + * If the template has a non-terminal directory separator, split + * into dirname and tail. + */ + + baseDirObj = TclPathPart(interp, templateObj, TCL_PATH_DIRNAME); + nameBaseObj = TclPathPart(interp, templateObj, TCL_PATH_TAIL); + } else { + /* + * Otherwise, there must be a terminal directory separator, so + * just the directory is given. + */ + + baseDirObj = templateObj; + Tcl_IncrRefCount(baseDirObj); + } + + /* + * Only allow creation of temporary directories in the native + * filesystem since they are frequently used for integration with + * external tools or system libraries. + */ + + if (baseDirObj != NULL && Tcl_FSGetFileSystemForPath(baseDirObj) + != &tclNativeFilesystem) { + TclDecrRefCount(baseDirObj); + baseDirObj = NULL; + } + } + + /* + * Convert empty parts of the template into unspecified parts. + */ + + if (baseDirObj && !TclGetString(baseDirObj)[0]) { + TclDecrRefCount(baseDirObj); + baseDirObj = NULL; + } + if (nameBaseObj && !TclGetString(nameBaseObj)[0]) { + TclDecrRefCount(nameBaseObj); + nameBaseObj = NULL; + } + + /* + * Create and open the temporary file. + */ + + makeTemporary: + dirNameObj = TclpCreateTemporaryDirectory(baseDirObj, nameBaseObj); + + /* + * If we created pieces of template, get rid of them now. + */ + + if (baseDirObj) { + TclDecrRefCount(baseDirObj); + } + if (nameBaseObj) { + TclDecrRefCount(nameBaseObj); + } + + /* + * Deal with results. + */ + + if (dirNameObj == NULL) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "can't create temporary directory: %s", + Tcl_PosixError(interp))); + return TCL_ERROR; + } + Tcl_SetObjResult(interp, dirNameObj); + return TCL_OK; +} + +/* * Local Variables: * mode: c * c-basic-offset: 4 diff --git a/generic/tclInt.decls b/generic/tclInt.decls index c0d7696..6dc748b 100644 --- a/generic/tclInt.decls +++ b/generic/tclInt.decls @@ -1057,6 +1057,12 @@ declare 257 { void TclStaticPackage(Tcl_Interp *interp, const char *pkgName, Tcl_PackageInitProc *initProc, Tcl_PackageInitProc *safeInitProc) } + +# TIP 431: temporary directory creation function +declare 258 { + Tcl_Obj *TclpCreateTemporaryDirectory(Tcl_Obj *dirObj, + Tcl_Obj *basenameObj) +} ############################################################################## diff --git a/generic/tclInt.h b/generic/tclInt.h index 0c884b3..389b5da 100644 --- a/generic/tclInt.h +++ b/generic/tclInt.h @@ -2896,6 +2896,7 @@ MODULE_SCOPE Tcl_ObjCmdProc TclFileLinkCmd; MODULE_SCOPE Tcl_ObjCmdProc TclFileMakeDirsCmd; MODULE_SCOPE Tcl_ObjCmdProc TclFileReadLinkCmd; MODULE_SCOPE Tcl_ObjCmdProc TclFileRenameCmd; +MODULE_SCOPE Tcl_ObjCmdProc TclFileTempDirCmd; MODULE_SCOPE Tcl_ObjCmdProc TclFileTemporaryCmd; MODULE_SCOPE void TclCreateLateExitHandler(Tcl_ExitProc *proc, void *clientData); diff --git a/generic/tclIntDecls.h b/generic/tclIntDecls.h index 61249c0..ec86071 100644 --- a/generic/tclIntDecls.h +++ b/generic/tclIntDecls.h @@ -583,6 +583,9 @@ EXTERN void TclStaticPackage(Tcl_Interp *interp, const char *pkgName, Tcl_PackageInitProc *initProc, Tcl_PackageInitProc *safeInitProc); +/* 258 */ +EXTERN Tcl_Obj * TclpCreateTemporaryDirectory(Tcl_Obj *dirObj, + Tcl_Obj *basenameObj); typedef struct TclIntStubs { int magic; @@ -846,6 +849,7 @@ typedef struct TclIntStubs { int (*tclPtrObjMakeUpvar) (Tcl_Interp *interp, Tcl_Var otherPtr, Tcl_Obj *myNamePtr, int myFlags); /* 255 */ int (*tclPtrUnsetVar) (Tcl_Interp *interp, Tcl_Var varPtr, Tcl_Var arrayPtr, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, const int flags); /* 256 */ void (*tclStaticPackage) (Tcl_Interp *interp, const char *pkgName, Tcl_PackageInitProc *initProc, Tcl_PackageInitProc *safeInitProc); /* 257 */ + Tcl_Obj * (*tclpCreateTemporaryDirectory) (Tcl_Obj *dirObj, Tcl_Obj *basenameObj); /* 258 */ } TclIntStubs; extern const TclIntStubs *tclIntStubsPtr; @@ -1263,6 +1267,8 @@ extern const TclIntStubs *tclIntStubsPtr; (tclIntStubsPtr->tclPtrUnsetVar) /* 256 */ #define TclStaticPackage \ (tclIntStubsPtr->tclStaticPackage) /* 257 */ +#define TclpCreateTemporaryDirectory \ + (tclIntStubsPtr->tclpCreateTemporaryDirectory) /* 258 */ #endif /* defined(USE_TCL_STUBS) */ diff --git a/generic/tclStubInit.c b/generic/tclStubInit.c index 1b3214d..a0973fc 100644 --- a/generic/tclStubInit.c +++ b/generic/tclStubInit.c @@ -458,6 +458,7 @@ static const TclIntStubs tclIntStubs = { TclPtrObjMakeUpvar, /* 255 */ TclPtrUnsetVar, /* 256 */ TclStaticPackage, /* 257 */ + TclpCreateTemporaryDirectory, /* 258 */ }; static const TclIntPlatStubs tclIntPlatStubs = { |