summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordkf <donal.k.fellows@manchester.ac.uk>2019-04-23 14:24:53 (GMT)
committerdkf <donal.k.fellows@manchester.ac.uk>2019-04-23 14:24:53 (GMT)
commite42fbc68b9ea8a6b6209a348830ae0743a020c5c (patch)
tree1fedb7b68df7f8e35d04d54b09b3ce7bc39fedd1
parentba3eca6d7e1ad5c6a643052f7cc496d25272e3a5 (diff)
downloadtcl-e42fbc68b9ea8a6b6209a348830ae0743a020c5c.zip
tcl-e42fbc68b9ea8a6b6209a348830ae0743a020c5c.tar.gz
tcl-e42fbc68b9ea8a6b6209a348830ae0743a020c5c.tar.bz2
Added primitive to allow working coroutine deep introspection
-rw-r--r--generic/tclBasic.c73
-rw-r--r--tests/coroutine.test75
2 files changed, 148 insertions, 0 deletions
diff --git a/generic/tclBasic.c b/generic/tclBasic.c
index d252f00..1a48f44 100644
--- a/generic/tclBasic.c
+++ b/generic/tclBasic.c
@@ -158,6 +158,7 @@ static Tcl_NRPostProc Dispatch;
static Tcl_ObjCmdProc NRCoroInjectObjCmd;
static Tcl_NRPostProc NRPostInvoke;
+static Tcl_ObjCmdProc CoroTypeObjCmd;
MODULE_SCOPE const TclStubs tclStubs;
@@ -845,8 +846,11 @@ Tcl_CreateInterp(void)
TclNRAssembleObjCmd, NULL, NULL);
cmdPtr->compileProc = &TclCompileAssembleCmd;
+ /* Coroutine monkeybusiness */
Tcl_NRCreateCommand(interp, "::tcl::unsupported::inject", NULL,
NRCoroInjectObjCmd, NULL, NULL);
+ Tcl_CreateObjCommand(interp, "::tcl::unsupported::corotype",
+ CoroTypeObjCmd, NULL, NULL);
/* Create an unsupported command for timerate */
Tcl_CreateObjCommand(interp, "::tcl::unsupported::timerate",
@@ -8902,6 +8906,75 @@ TclNREvalList(
/*
*----------------------------------------------------------------------
*
+ * CoroTypeObjCmd --
+ *
+ * Implementation of [::tcl::unsupported::corotype] command.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+CoroTypeObjCmd(
+ ClientData clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *const objv[])
+{
+ Command *cmdPtr;
+ CoroutineData *corPtr;
+
+ if (objc != 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "coroName");
+ return TCL_ERROR;
+ }
+
+ /*
+ * Look up the coroutine.
+ */
+
+ cmdPtr = (Command *) Tcl_GetCommandFromObj(interp, objv[1]);
+ if ((!cmdPtr) || (cmdPtr->nreProc != TclNRInterpCoroutine)) {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(
+ "can only get coroutine type of a coroutine", -1));
+ Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "COROUTINE",
+ TclGetString(objv[1]), NULL);
+ return TCL_ERROR;
+ }
+
+ /*
+ * An active coroutine is "active". Can't tell what it might do in the
+ * future.
+ */
+
+ corPtr = cmdPtr->objClientData;
+ if (!COR_IS_SUSPENDED(corPtr)) {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj("active", -1));
+ return TCL_OK;
+ }
+
+ /*
+ * Inactive coroutines are classified by the (effective) command used to
+ * suspend them, which matters when you're injecting a probe.
+ */
+
+ switch (corPtr->nargs) {
+ case COROUTINE_ARGUMENTS_SINGLE_OPTIONAL:
+ Tcl_SetObjResult(interp, Tcl_NewStringObj("yield", -1));
+ return TCL_OK;
+ case COROUTINE_ARGUMENTS_ARBITRARY:
+ Tcl_SetObjResult(interp, Tcl_NewStringObj("yieldto", -1));
+ return TCL_OK;
+ default:
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(
+ "unknown coroutine type", -1));
+ Tcl_SetErrorCode(interp, "TCL", "COROUTINE", "BAD_TYPE", NULL);
+ return TCL_ERROR;
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* NRCoroInjectObjCmd --
*
* Implementation of [::tcl::unsupported::inject] command.
diff --git a/tests/coroutine.test b/tests/coroutine.test
index be2b624..df545f5 100644
--- a/tests/coroutine.test
+++ b/tests/coroutine.test
@@ -792,6 +792,81 @@ test coroutine-8.1.2 {coro inject with result, ticket 42202ba1e5ff566e} -body {
interp delete slave
set result
} -result {inject-executed}
+
+test coroutine-9.1 {coro type} {
+ coroutine demo eval {
+ yield
+ yield "PHASE 1"
+ yieldto string cat "PHASE 2"
+ ::tcl::unsupported::corotype [info coroutine]
+ }
+ list [demo] [::tcl::unsupported::corotype demo] \
+ [demo] [::tcl::unsupported::corotype demo] [demo]
+} {{PHASE 1} yield {PHASE 2} yieldto active}
+test coroutine-9.2 {coro type} -setup {
+ catch {rename nosuchcommand ""}
+} -returnCodes error -body {
+ ::tcl::unsupported::corotype nosuchcommand
+} -result {can only get coroutine type of a coroutine}
+test coroutine-9.3 {coro type} -returnCodes error -body {
+ proc notacoroutine {} {}
+ ::tcl::unsupported::corotype notacoroutine
+} -returnCodes error -cleanup {
+ rename notacoroutine {}
+} -result {can only get coroutine type of a coroutine}
+
+test coroutine-10.1 {coroutine general introspection} -setup {
+ set i [interp create]
+} -body {
+ $i eval {
+ # Make the introspection code
+ namespace path tcl::unsupported
+ proc probe {type var} {
+ upvar 1 $var v
+ set f [info frame]
+ incr f -1
+ set result [list $v [dict get [info frame $f] proc]]
+ if {$type eq "yield"} {
+ tailcall yield $result
+ } else {
+ tailcall yieldto string cat $result
+ }
+ }
+ proc pokecoro {c var} {
+ inject $c probe [corotype $c] $var
+ $c
+ }
+
+ # Coroutine implementations
+ proc cbody1 {} {
+ set val [info coroutine]
+ set accum {}
+ while {[set val [yield $val]] ne ""} {
+ lappend accum $val
+ set val ok
+ }
+ return $accum
+ }
+ proc cbody2 {} {
+ set val [info coroutine]
+ set accum {}
+ while {[llength [set val [yieldto string cat $val]]]} {
+ lappend accum {*}$val
+ set val ok
+ }
+ return $accum
+ }
+
+ # Make the coroutines
+ coroutine c1 cbody1
+ coroutine c2 cbody2
+ list [c1 abc] [c2 1 2 3] [pokecoro c1 accum] [pokecoro c2 accum] \
+ [c1 def] [c2 4 5 6] [pokecoro c1 accum] [pokecoro c2 accum] \
+ [c1] [c2]
+ }
+} -cleanup {
+ interp delete $i
+} -result {ok ok {abc ::cbody1} {{1 2 3} ::cbody2} ok ok {{abc def} ::cbody1} {{1 2 3 4 5 6} ::cbody2} {abc def} {1 2 3 4 5 6}}
# cleanup
unset lambda