Compare commits

...
Author SHA1 Message Date
fvogel d8cae66217 Fix typo in set-old.test 2017-10-25 17:31:09 +00:00
fvogel 40ce2eccbc 'array for' implementation (TIP #421) from Brad Lanam 2017-10-24 21:24:07 +00:00
4 changed files with 527 additions and 32 deletions
+7
View File
@@ -47,6 +47,13 @@ been the return value from a previous invocation of
Returns 1 if \fIarrayName\fR is an array variable, 0 if there
is no variable by that name or if it is a scalar variable.
.TP
\fBarray for {\fIkeyVariable ?valueVariable?\fB} \fIarrayName body\fR
The first argument is a one or two element list of variable names for the
key and value of each entry in the array. The second argument is the
array name to iterate over. The third argument is the body to execute
for each key and value returned.
The ordering of the returned keys is undefined.
.TP
\fBarray get \fIarrayName\fR ?\fIpattern\fR?
Returns a list containing pairs of elements. The first
element in each pair is the name of an element in \fIarrayName\fR
+365 -23
View File
@@ -165,6 +165,7 @@ typedef struct ArraySearch {
struct ArraySearch *nextPtr;/* Next in list of all active searches for
* this variable, or NULL if this is the last
* one. */
Tcl_Obj *arrayNameObj; /* name of the array object */
} ArraySearch;
/*
@@ -173,6 +174,8 @@ typedef struct ArraySearch {
static void AppendLocals(Tcl_Interp *interp, Tcl_Obj *listPtr,
Tcl_Obj *patternPtr, int includeLinks);
static void ArrayDoneSearch (Interp *iPtr, Var *varPtr, ArraySearch *searchPtr);
static Tcl_NRPostProc ArrayForLoopCallback;
static void DeleteSearches(Interp *iPtr, Var *arrayVarPtr);
static void DeleteArray(Interp *iPtr, Tcl_Obj *arrayNamePtr,
Var *varPtr, int flags, int index);
@@ -3095,6 +3098,321 @@ TclArraySet(
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* ArrayForNRCmd
* ArrayForLoopCallback
* ArrayObjFirst
* ArrayObjNext
*
* These functions implement the "array for" Tcl command.
* array for {k v} a {}
* The array for command iterates over the array, setting the
* the specified loop variables, and executing the body each iteration.
*
* ArrayForNRCmd() sets up the ArraySearch structure, sets arrayNamePtr
* inside the structure and calls VarHashFirstEntry to start the hash
* iteration.
*
* ArrayForNRCmd() does not execute the body or set the loop variables,
* it only initializes the iterator.
*
* ArrayForLoopCallback() iterates over the entire array, executing
* the body each time.
*
* ArrayObjFirst() Does not execute the body or set the key/value variables.
*
*----------------------------------------------------------------------
*/
void
ArrayObjFirst(
Tcl_Interp *interp,
Tcl_Obj *arrayNameObj,
Var *varPtr,
ArraySearch *searchPtr)
{
Interp *iPtr = (Interp *) interp;
Tcl_HashEntry *hPtr;
int isNew;
searchPtr->varPtr = varPtr;
searchPtr->arrayNameObj = arrayNameObj;
/* add the search to the search table */
hPtr = Tcl_CreateHashEntry(&iPtr->varSearches, varPtr, &isNew);
if (isNew) {
searchPtr->id = 1;
varPtr->flags |= VAR_SEARCH_ACTIVE;
searchPtr->nextPtr = NULL;
} else {
searchPtr->id = ((ArraySearch *) Tcl_GetHashValue(hPtr))->id + 1;
searchPtr->nextPtr = Tcl_GetHashValue(hPtr);
}
searchPtr->nextEntry = VarHashFirstEntry(varPtr->value.tablePtr,
&searchPtr->search);
Tcl_SetHashValue(hPtr, searchPtr);
}
int
ArrayObjNext(
Tcl_Interp *interp,
Var *varPtr, /* array */
ArraySearch *searchPtr,
Tcl_Obj **keyPtrPtr, /* Pointer to a variable to have the key
* written into, or NULL. */
Tcl_Obj **valuePtrPtr /* Pointer to a variable to have the
* value written into, or NULL.*/
)
{
Tcl_Obj *keyObj;
Tcl_Obj *valueObj = NULL;
int gotValue;
int donerc;
donerc = TCL_BREAK;
if ((varPtr->flags & VAR_SEARCH_ACTIVE) != VAR_SEARCH_ACTIVE) {
donerc = TCL_ERROR;
return donerc;
}
gotValue = 0;
while (1) {
Tcl_HashEntry *hPtr = searchPtr->nextEntry;
if (hPtr != NULL) {
searchPtr->nextEntry = NULL;
} else {
hPtr = Tcl_NextHashEntry(&searchPtr->search);
if (hPtr == NULL) {
gotValue = 0;
break;
}
}
varPtr = VarHashGetValue(hPtr);
if (!TclIsVarUndefined(varPtr)) {
gotValue = 1;
break;
}
}
if (! gotValue) {
return donerc;
}
donerc = TCL_CONTINUE;
keyObj = VarHashGetKey(varPtr);
*keyPtrPtr = keyObj;
valueObj = Tcl_ObjGetVar2(interp, searchPtr->arrayNameObj,
keyObj, TCL_LEAVE_ERR_MSG);
*valuePtrPtr = valueObj;
return donerc;
}
static int
ArrayForNRCmd(
ClientData dummy,
Tcl_Interp *interp,
int objc,
Tcl_Obj *const *objv)
{
Interp *iPtr = (Interp *) interp;
Tcl_Obj *scriptObj, *keyVarObj, *valueVarObj;
Tcl_Obj **varv;
Tcl_Obj *arrayNameObj;
ArraySearch *searchPtr = NULL;
Var *varPtr;
Var *arrayPtr;
int varc;
/*
* array for {k v} a body
*/
if (objc != 4) {
Tcl_WrongNumArgs(interp, 1, objv,
"{key value} arrayName script");
return TCL_ERROR;
}
/*
* Parse arguments.
*/
if (TclListObjGetElements(interp, objv[1], &varc, &varv) != TCL_OK) {
return TCL_ERROR;
}
if (varc != 2) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"must have two variable names", -1));
Tcl_SetErrorCode(interp, "TCL", "SYNTAX", "array", "for", NULL);
return TCL_ERROR;
}
arrayNameObj = objv[2];
keyVarObj = varv[0];
valueVarObj = varv[1];
scriptObj = objv[3];
/*
* Locate the array variable.
*/
varPtr = TclObjLookupVarEx(interp, arrayNameObj, NULL, /*flags*/ 0,
/*msg*/ 0, /*createPart1*/ 0, /*createPart2*/ 0, &arrayPtr);
/*
* Special array trace used to keep the env array in sync for array names,
* array get, etc.
*/
if (varPtr && (varPtr->flags & VAR_TRACED_ARRAY)
&& (TclIsVarArray(varPtr) || TclIsVarUndefined(varPtr))) {
if (TclObjCallVarTraces(iPtr, arrayPtr, varPtr, arrayNameObj, NULL,
(TCL_LEAVE_ERR_MSG|TCL_NAMESPACE_ONLY|TCL_GLOBAL_ONLY|
TCL_TRACE_ARRAY), /* leaveErrMsg */ 1, -1) == TCL_ERROR) {
return TCL_ERROR;
}
}
/*
* Verify that it is indeed an array variable. This test comes after the
* traces; the variable may actually become an array as an effect of said
* traces.
*/
if ((varPtr == NULL) || !TclIsVarArray(varPtr)
|| TclIsVarUndefined(varPtr)) {
const char *varName = Tcl_GetString(arrayNameObj);
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"\"%s\" isn't an array", varName));
Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "ARRAY", varName, NULL);
return TCL_ERROR;
}
/*
* Make a new array search, put it on the stack.
*/
searchPtr = ckalloc(sizeof(ArraySearch));
searchPtr->arrayNameObj = NULL;
ArrayObjFirst(interp, arrayNameObj, varPtr, searchPtr);
/*
* Make sure that these objects (which we need throughout the body of the
* loop) don't vanish.
*/
Tcl_IncrRefCount(keyVarObj);
Tcl_IncrRefCount(valueVarObj);
Tcl_IncrRefCount(scriptObj);
/*
* Run the script.
*/
TclNRAddCallback(interp, ArrayForLoopCallback, searchPtr, keyVarObj,
valueVarObj, scriptObj);
return TCL_OK;
}
static int
ArrayForLoopCallback(
ClientData data[],
Tcl_Interp *interp,
int result)
{
Interp *iPtr = (Interp *) interp;
ArraySearch *searchPtr = data[0];
Tcl_Obj *keyVarObj = data[1];
Tcl_Obj *valueVarObj = data[2];
Tcl_Obj *scriptObj = data[3];
Tcl_Obj *keyObj, *valueObj;
Var *varPtr;
Var *arrayPtr;
int done;
/*
* Process the result from the previous execution of the script body.
*/
done = TCL_ERROR;
varPtr = TclObjLookupVarEx(interp, searchPtr->arrayNameObj, NULL, /*flags*/ 0,
/*msg*/ 0, /*createPart1*/ 0, /*createPart2*/ 0, &arrayPtr);
if (result == TCL_CONTINUE) {
result = TCL_OK;
} else if (result != TCL_OK) {
if (result == TCL_BREAK) {
Tcl_ResetResult(interp);
result = TCL_OK;
} else if (result == TCL_ERROR) {
Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf(
"\n (\"array for\" body line %d)",
Tcl_GetErrorLine(interp)));
}
goto arrayfordone;
}
/*
* Get the next mapping from the array.
*/
keyObj = NULL;
valueObj = NULL;
done = ArrayObjNext (interp, varPtr, searchPtr, &keyObj, &valueObj);
result = TCL_OK;
if (done != TCL_CONTINUE) {
Tcl_ResetResult(interp);
if (done == TCL_ERROR) {
varPtr->flags |= TCL_LEAVE_ERR_MSG;
Tcl_AddErrorInfo(interp, "array changed during iteration");
result = done;
}
goto arrayfordone;
}
if (Tcl_ObjSetVar2(interp, keyVarObj, NULL, keyObj, TCL_LEAVE_ERR_MSG) == NULL) {
result = TCL_ERROR;
goto arrayfordone;
}
if (valueObj != NULL) {
if (Tcl_ObjSetVar2(interp, valueVarObj, NULL, valueObj, TCL_LEAVE_ERR_MSG) == NULL) {
result = TCL_ERROR;
goto arrayfordone;
}
}
/*
* Run the script.
*/
TclNRAddCallback(interp, ArrayForLoopCallback, searchPtr, keyVarObj,
valueVarObj, scriptObj);
return TclNREvalObjEx(interp, scriptObj, 0, iPtr->cmdFramePtr, 3);
/*
* For unwinding everything once the iterating is done.
*/
arrayfordone:
/* if the search was terminated by an array change, the
* VAR_SEARCH_ACTIVE flag will no longer be set
*/
if (done != TCL_ERROR) {
ArrayDoneSearch (iPtr, varPtr, searchPtr);
ckfree(searchPtr);
}
TclDecrRefCount(keyVarObj);
TclDecrRefCount(valueVarObj);
TclDecrRefCount(scriptObj);
return result;
}
/*
*----------------------------------------------------------------------
*
@@ -3194,6 +3512,50 @@ ArrayStartSearchCmd(
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* ArrayDoneSearch --
*
* Removes the search from the hash of active searches.
*
*----------------------------------------------------------------------
*/
static void
ArrayDoneSearch (
Interp *iPtr,
Var *varPtr,
ArraySearch *searchPtr)
{
Tcl_HashEntry *hPtr;
ArraySearch *prevPtr;
/*
* Unhook the search from the list of searches associated with the
* variable.
*/
hPtr = Tcl_FindHashEntry(&iPtr->varSearches, varPtr);
if (hPtr == NULL) {
return;
}
if (searchPtr == Tcl_GetHashValue(hPtr)) {
if (searchPtr->nextPtr) {
Tcl_SetHashValue(hPtr, searchPtr->nextPtr);
} else {
varPtr->flags &= ~VAR_SEARCH_ACTIVE;
Tcl_DeleteHashEntry(hPtr);
}
} else {
for (prevPtr=Tcl_GetHashValue(hPtr) ;; prevPtr=prevPtr->nextPtr) {
if (prevPtr->nextPtr == searchPtr) {
prevPtr->nextPtr = searchPtr->nextPtr;
break;
}
}
}
}
/*
*----------------------------------------------------------------------
*
@@ -3437,9 +3799,8 @@ ArrayDoneSearchCmd(
{
Interp *iPtr = (Interp *) interp;
Var *varPtr, *arrayPtr;
Tcl_HashEntry *hPtr;
Tcl_Obj *varNameObj, *searchObj;
ArraySearch *searchPtr, *prevPtr;
ArraySearch *searchPtr;
if (objc != 3) {
Tcl_WrongNumArgs(interp, 1, objv, "arrayName searchId");
@@ -3493,27 +3854,7 @@ ArrayDoneSearchCmd(
return TCL_ERROR;
}
/*
* Unhook the search from the list of searches associated with the
* variable.
*/
hPtr = Tcl_FindHashEntry(&iPtr->varSearches, varPtr);
if (searchPtr == Tcl_GetHashValue(hPtr)) {
if (searchPtr->nextPtr) {
Tcl_SetHashValue(hPtr, searchPtr->nextPtr);
} else {
varPtr->flags &= ~VAR_SEARCH_ACTIVE;
Tcl_DeleteHashEntry(hPtr);
}
} else {
for (prevPtr=Tcl_GetHashValue(hPtr) ;; prevPtr=prevPtr->nextPtr) {
if (prevPtr->nextPtr == searchPtr) {
prevPtr->nextPtr = searchPtr->nextPtr;
break;
}
}
}
ArrayDoneSearch (iPtr, varPtr, searchPtr);
ckfree(searchPtr);
return TCL_OK;
}
@@ -4372,6 +4713,7 @@ TclInitArrayCmd(
{"anymore", ArrayAnyMoreCmd, TclCompileBasic2ArgCmd, NULL, NULL, 0},
{"donesearch", ArrayDoneSearchCmd, TclCompileBasic2ArgCmd, NULL, NULL, 0},
{"exists", ArrayExistsCmd, TclCompileArrayExistsCmd, NULL, NULL, 0},
{"for", NULL, TclCompileBasic3ArgCmd, ArrayForNRCmd, NULL, 0},
{"get", ArrayGetCmd, TclCompileBasic1Or2ArgCmd, NULL, NULL, 0},
{"names", ArrayNamesCmd, TclCompileBasic1To3ArgCmd, NULL, NULL, 0},
{"nextelement", ArrayNextElementCmd, TclCompileBasic2ArgCmd, NULL, NULL, 0},
+2 -2
View File
@@ -340,7 +340,7 @@ test set-old-8.6 {array command} {
catch {unset a}
set a(22) 3
list [catch {array gorp a} msg] $msg
} {1 {unknown or ambiguous subcommand "gorp": must be anymore, donesearch, exists, get, names, nextelement, set, size, startsearch, statistics, or unset}}
} {1 {unknown or ambiguous subcommand "gorp": must be anymore, donesearch, exists, for, get, names, nextelement, set, size, startsearch, statistics, or unset}}
test set-old-8.7 {array command, anymore option} {
catch {unset a}
list [catch {array anymore a x} msg] $msg
@@ -940,7 +940,7 @@ catch {rename foo {}}
# cleanup
::tcltest::cleanupTests
return
return
# Local Variables:
# mode: tcl
+153 -7
View File
@@ -53,7 +53,7 @@ catch {unset arr}
test var-1.1 {TclLookupVar, Array handling} -setup {
catch {unset a}
} -body {
set x "incr" ;# force no compilation and runtime call to Tcl_IncrCmd
set x "incr" ;# force no compilation and runtime call to Tcl_IncrCmd
set i 10
set arr(foo) 37
list [$x i] $i [$x arr(foo)] $arr(foo)
@@ -234,7 +234,7 @@ test var-3.3 {MakeUpvar, my var has TCL_GLOBAL_ONLY specified} -setup {
set a 123321
proc p {} {
# create global xx linked to global a
testupvar 1 a {} xx global
testupvar 1 a {} xx global
}
list [p] $xx [set xx 789] $a
} -result {{} 123321 789 789}
@@ -246,7 +246,7 @@ test var-3.4 {MakeUpvar, my var has TCL_NAMESPACE_ONLY specified} -setup {
catch {unset ::test_ns_var::vv}
proc p {} {
# create namespace var vv linked to global a
testupvar 1 a {} vv namespace
testupvar 1 a {} vv namespace
}
p
}
@@ -548,11 +548,11 @@ test var-7.14 {Tcl_VariableObjCmd, array element parameter} -body {
namespace eval test_ns_var { variable arrayvar(1) }
} -returnCodes error -result "can't define \"arrayvar(1)\": name refers to an element in an array"
test var-7.15 {Tcl_VariableObjCmd, array element parameter} -body {
namespace eval test_ns_var {
namespace eval test_ns_var {
variable arrayvar
set arrayvar(1) x
variable arrayvar(1) y
}
}
} -returnCodes error -result "can't define \"arrayvar(1)\": name refers to an element in an array"
test var-7.16 {Tcl_VariableObjCmd, no args (TIP 323)} {
variable
@@ -790,7 +790,7 @@ test var-15.1 {segfault in [unset], [Bug 735335]} {
set var $name
}
#
# Note that the variable name has to be
# Note that the variable name has to be
# unused previously for the segfault to
# be triggered.
#
@@ -997,7 +997,153 @@ test var-22.1 {leak in localVarName intrep: Bug 80304238ac} -setup {
rename getbytes {}
rename doit {}
} -result 0
unset -nocomplain a k v
test var-23.1 {array command, for loop, too many args} -returnCodes error -body {
array for {k v} c d e {}
} -result {wrong # args: should be "array for {key value} arrayName script"}
test var-23.2 {array command, for loop, not enough args} -returnCodes error -body {
array for {k v} {}
} -result {wrong # args: should be "array for {key value} arrayName script"}
test var-23.3 {array command, for loop, too many list args} -setup {
unset -nocomplain a
} -returnCodes error -body {
array for {k v w} a {}
} -result {must have two variable names}
test var-23.4 {array command, for loop, not enough list args} -setup {
unset -nocomplain a
} -returnCodes error -body {
array for {k} a {}
} -result {must have two variable names}
test var-23.5 {array command, for loop, no array} -setup {
unset -nocomplain a
} -returnCodes error -body {
array for {k v} a {}
} -result {"a" isn't an array}
test var-23.6 {array command, for loop, array doesn't exist yet but has compiler-allocated procedure slot} -setup {
catch {rename p ""}
} -returnCodes error -body {
apply {{x} {
if {$x==1} {
return [array for {k v} a {}]
}
set a(x) 123
}} 1
} -result {"a" isn't an array}
test var-23.7 {array enumeration} -setup {
unset -nocomplain a
set reslist [list]
} -body {
array set a {a 1 b 2 c 3}
array for {k v} a {
lappend reslist $k $v
}
lsort -stride 2 -index 0 $reslist
} -cleanup {
unset -nocomplain a
unset -nocomplain reslist
} -result {a 1 b 2 c 3}
test var-23.9 {array enumeration, nested} -setup {
unset -nocomplain a
set reslist [list]
} -body {
array set a {a 1 b 2 c 3}
array for {k1 v1} a {
lappend reslist $k1 $v1
set r2 {}
array for {k2 v2} a {
lappend r2 $k2 $v2
}
lappend reslist [lsort -stride 2 -index 0 $r2]
}
# there is no guarantee in which order the array contents will be
# returned.
lsort -stride 3 -index 0 $reslist
} -cleanup {
unset -nocomplain a
unset -nocomplain reslist
} -result {a 1 {a 1 b 2 c 3} b 2 {a 1 b 2 c 3} c 3 {a 1 b 2 c 3}}
test var-23.10 {array enumeration, delete key} -match glob -setup {
unset -nocomplain a
set reslist [list]
} -body {
set retval {}
try {
array set a {a 1 b 2 c 3 d 4}
array for {k v} a {
lappend reslist $k $v
if { $k eq "a" } {
unset a(c)
}
}
lsort -stride 2 -index 0 $reslist
} on error {err res} {
set retval [dict get $res -errorinfo]
}
set retval
} -cleanup {
unset -nocomplain a
unset -nocomplain reslist
unset -nocomplain retval
} -result {array changed during iteration*}
test var-23.11 {array enumeration, insert key} -match glob -setup {
unset -nocomplain a
set reslist [list]
} -body {
set retval {}
try {
array set a {a 1 b 2 c 3 d 4}
array for {k v} a {
lappend reslist $k $v
if { $k eq "a" } {
set a(e) 5
}
}
lsort -stride 2 -index 0 $reslist
} on error {err res} {
set retval [dict get $res -errorinfo]
}
} -cleanup {
unset -nocomplain a
unset -nocomplain reslist
} -result {array changed during iteration*}
test var-23.12 {array enumeration, change value} -setup {
unset -nocomplain a
set reslist [list]
} -body {
array set a {a 1 b 2 c 3}
array for {k v} a {
lappend reslist $k $v
if { $k eq "a" } {
set a(c) 9
}
}
lsort -stride 2 -index 0 $reslist
} -cleanup {
unset -nocomplain a
unset -nocomplain reslist
} -result {a 1 b 2 c 9}
test var-23.13 {array enumeration, number of traces} -setup {
set ::countarrayfor 0
proc ::tracearrayfor { args } {
incr ::countarrayfor
}
unset -nocomplain ::a
set reslist [list]
} -body {
array set ::a {a 1 b 2 c 3}
foreach {k} [array names a] {
trace add variable ::a($k) read ::tracearrayfor
}
array for {k v} ::a {
lappend reslist $k $v
}
set ::countarrayfor
} -cleanup {
unset -nocomplain ::countarrayfor
unset -nocomplain ::a
unset -nocomplain reslist
} -result 3
catch {namespace delete ns}
catch {unset arr}