diff options
author | pspjuth <peter.spjuth@gmail.com> | 2018-10-22 18:43:47 (GMT) |
---|---|---|
committer | pspjuth <peter.spjuth@gmail.com> | 2018-10-22 18:43:47 (GMT) |
commit | 36f2cb4d7dbd7c8008161479f97985026e060e9e (patch) | |
tree | 7500f1c3c6308ae92d8fc5bfcfb6de3936eac164 | |
parent | 86013715f6a114cd89394f52ed82e18d58813c77 (diff) | |
download | tcl-36f2cb4d7dbd7c8008161479f97985026e060e9e.zip tcl-36f2cb4d7dbd7c8008161479f97985026e060e9e.tar.gz tcl-36f2cb4d7dbd7c8008161479f97985026e060e9e.tar.bz2 |
Implement TIP 523, New lpop command
-rw-r--r-- | generic/tclBasic.c | 1 | ||||
-rw-r--r-- | generic/tclCmdIL.c | 97 | ||||
-rw-r--r-- | generic/tclInt.h | 3 | ||||
-rw-r--r-- | generic/tclListObj.c | 26 | ||||
-rw-r--r-- | tests/lpop.test | 136 |
5 files changed, 258 insertions, 5 deletions
diff --git a/generic/tclBasic.c b/generic/tclBasic.c index 179306d..3a133d3 100644 --- a/generic/tclBasic.c +++ b/generic/tclBasic.c @@ -260,6 +260,7 @@ static const CmdInfo builtInCmds[] = { {"list", Tcl_ListObjCmd, TclCompileListCmd, NULL, CMD_IS_SAFE|CMD_COMPILES_EXPANDED}, {"llength", Tcl_LlengthObjCmd, TclCompileLlengthCmd, NULL, CMD_IS_SAFE}, {"lmap", Tcl_LmapObjCmd, TclCompileLmapCmd, TclNRLmapCmd, CMD_IS_SAFE}, + {"lpop", Tcl_LpopObjCmd, NULL, NULL, CMD_IS_SAFE}, {"lrange", Tcl_LrangeObjCmd, TclCompileLrangeCmd, NULL, CMD_IS_SAFE}, {"lrepeat", Tcl_LrepeatObjCmd, NULL, NULL, CMD_IS_SAFE}, {"lreplace", Tcl_LreplaceObjCmd, TclCompileLreplaceCmd, NULL, CMD_IS_SAFE}, diff --git a/generic/tclCmdIL.c b/generic/tclCmdIL.c index 434840e..fb19a3f 100644 --- a/generic/tclCmdIL.c +++ b/generic/tclCmdIL.c @@ -2571,6 +2571,103 @@ Tcl_LlengthObjCmd( /* *---------------------------------------------------------------------- * + * Tcl_LpopObjCmd -- + * + * This procedure is invoked to process the "lpop" Tcl command. See the + * user documentation for details on what it does. + * + * Results: + * A standard Tcl object result. + * + * Side effects: + * See the user documentation. + * + *---------------------------------------------------------------------- + */ + +int +Tcl_LpopObjCmd( + ClientData notUsed, /* Not used. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + register Tcl_Obj *const objv[]) + /* Argument objects. */ +{ + int listLen, result; + Tcl_Obj *elemPtr; + Tcl_Obj *listPtr, **elemPtrs; + + if (objc < 2) { + Tcl_WrongNumArgs(interp, 1, objv, "listvar ?index?"); + return TCL_ERROR; + } + + listPtr = Tcl_ObjGetVar2(interp, objv[1], NULL, TCL_LEAVE_ERR_MSG); + if (listPtr == NULL) { + return TCL_ERROR; + } + + result = TclListObjGetElements(interp, listPtr, &listLen, &elemPtrs); + if (result != TCL_OK) { + return result; + } + + /* + * First, extract the element to be returned. + * TclLindex* adds a ref count which is handled. + */ + + if (objc == 2) { + elemPtr = elemPtrs[listLen - 1]; + Tcl_IncrRefCount(elemPtr); + } else { + if (objc == 3) { + elemPtr = TclLindexList(interp, listPtr, objv[2]); + } else { + elemPtr = TclLindexFlat(interp, listPtr, objc-2, objv+2); + } + + if (elemPtr == NULL) { + return TCL_ERROR; + } + } + Tcl_SetObjResult(interp, elemPtr); + Tcl_DecrRefCount(elemPtr); + + /* + * Second, remove the element. + */ + + if (objc == 2) { + if (Tcl_IsShared(listPtr)) { + listPtr = TclListObjCopy(NULL, listPtr); + } + result = Tcl_ListObjReplace(interp, listPtr, listLen - 1, 1, 0, NULL); + if (result != TCL_OK) { + return result; + } + } else { + if (objc == 3) { + listPtr = TclLsetList(interp, listPtr, objv[2], NULL); + } else { + listPtr = TclLsetFlat(interp, listPtr, objc-2, objv+2, NULL); + } + if (listPtr == NULL) { + return TCL_ERROR; + } + } + + listPtr = Tcl_ObjSetVar2(interp, objv[1], NULL, listPtr, TCL_LEAVE_ERR_MSG); + if (listPtr == NULL) { + return TCL_ERROR; + } + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * * Tcl_LrangeObjCmd -- * * This procedure is invoked to process the "lrange" Tcl command. See the diff --git a/generic/tclInt.h b/generic/tclInt.h index abf6c83..04ed7af 100644 --- a/generic/tclInt.h +++ b/generic/tclInt.h @@ -3452,6 +3452,9 @@ MODULE_SCOPE int Tcl_LmapObjCmd(ClientData clientData, MODULE_SCOPE int Tcl_LoadObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); +MODULE_SCOPE int Tcl_LpopObjCmd(ClientData clientData, + Tcl_Interp *interp, int objc, + Tcl_Obj *const objv[]); MODULE_SCOPE int Tcl_LrangeObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); diff --git a/generic/tclListObj.c b/generic/tclListObj.c index 8314306..be05873 100644 --- a/generic/tclListObj.c +++ b/generic/tclListObj.c @@ -1356,6 +1356,7 @@ TclLindexFlat( * * Core of the 'lset' command when objc == 4. Objv[2] may be either a * scalar index or a list of indices. + * It also handles 'lpop' when given a NULL value. * * Results: * Returns the new value of the list variable, or NULL if there was an @@ -1380,7 +1381,7 @@ TclLsetList( Tcl_Interp *interp, /* Tcl interpreter. */ Tcl_Obj *listPtr, /* Pointer to the list being modified. */ Tcl_Obj *indexArgPtr, /* Index or index-list arg to 'lset'. */ - Tcl_Obj *valuePtr) /* Value arg to 'lset'. */ + Tcl_Obj *valuePtr) /* Value arg to 'lset' or NULL to 'lpop'. */ { int indexCount = 0; /* Number of indices in the index list. */ Tcl_Obj **indices = NULL; /* Vector of indices in the index list. */ @@ -1431,6 +1432,7 @@ TclLsetList( * TclLsetFlat -- * * Core engine of the 'lset' command. + * It also handles 'lpop' when given a NULL value. * * Results: * Returns the new value of the list variable, or NULL if an error @@ -1475,7 +1477,7 @@ TclLsetFlat( int indexCount, /* Number of index args. */ Tcl_Obj *const indexArray[], /* Index args. */ - Tcl_Obj *valuePtr) /* Value arg to 'lset'. */ + Tcl_Obj *valuePtr) /* Value arg to 'lset' or NULL to 'lpop'. */ { int index, result, len; Tcl_Obj *subListPtr, *retValuePtr, *chainPtr; @@ -1483,9 +1485,19 @@ TclLsetFlat( /* * If there are no indices, simply return the new value. (Without * indices, [lset] is a synonym for [set]. + * This is an error for [lpop]. */ if (indexCount == 0) { + if (valuePtr == NULL) { + if (interp != NULL) { + Tcl_SetObjResult(interp, + Tcl_NewStringObj("list index out of range", -1)); + Tcl_SetErrorCode(interp, "TCL", "OPERATION", "LPOP", + "BADINDEX", NULL); + } + return NULL; + } Tcl_IncrRefCount(valuePtr); return valuePtr; } @@ -1546,12 +1558,14 @@ TclLsetFlat( } indexArray++; - if (index < 0 || index > elemCount) { + if (index < 0 || index > elemCount + || (valuePtr == NULL && index >= elemCount)) { /* ...the index points outside the sublist. */ if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("list index out of range", -1)); - Tcl_SetErrorCode(interp, "TCL", "OPERATION", "LSET", + Tcl_SetErrorCode(interp, "TCL", "OPERATION", + valuePtr == NULL ? "LPOP" : "LSET", "BADINDEX", NULL); } result = TCL_ERROR; @@ -1661,7 +1675,9 @@ TclLsetFlat( len = -1; TclListObjLength(NULL, subListPtr, &len); - if (index == len) { + if (valuePtr == NULL) { + Tcl_ListObjReplace(NULL, subListPtr, index, 1, 0, NULL); + } else if (index == len) { Tcl_ListObjAppendElement(NULL, subListPtr, valuePtr); } else { TclListObjSetElement(NULL, subListPtr, index, valuePtr); diff --git a/tests/lpop.test b/tests/lpop.test new file mode 100644 index 0000000..7f0c6a8 --- /dev/null +++ b/tests/lpop.test @@ -0,0 +1,136 @@ +# Commands covered: lpop +# +# This file contains a collection of tests for one or more of the Tcl +# built-in commands. Sourcing this file into Tcl runs the tests and +# generates output for errors. No output means no errors were found. +# +# Copyright (c) 1991-1993 The Regents of the University of California. +# Copyright (c) 1994 Sun Microsystems, Inc. +# Copyright (c) 1998-1999 by Scriptics Corporation. +# +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. + +if {[lsearch [namespace children] ::tcltest] == -1} { + package require tcltest + namespace import -force ::tcltest::* +} + +test lpop-1.1 {error conditions} -returnCodes error -body { + lpop no +} -result {can't read "no": no such variable} +test lpop-1.2 {error conditions} -returnCodes error -body { + lpop no 0 +} -result {can't read "no": no such variable} +test lpop-1.3 {error conditions} -returnCodes error -body { + set no "x {}x" + lpop no +} -result {list element in braces followed by "x" instead of space} +test lpop-1.4 {error conditions} -returnCodes error -body { + set no "x y" + lpop no -1 +} -result {list index out of range} +test lpop-1.5 {error conditions} -returnCodes error -body { + set no "x y z" + lpop no 3 +} -result {list index out of range} ;#-errorCode {TCL OPERATION LPOP BADINDEX} +test lpop-1.6 {error conditions} -returnCodes error -body { + set no "x y" + lpop no end+1 +} -result {list index out of range} +test lpop-1.7 {error conditions} -returnCodes error -body { + set no "x y" + lpop no {} +} -result {list index out of range} +test lpop-1.8 {error conditions} -returnCodes error -body { + set no "x y" + lpop no {0 0 0 0 1} +} -result {list index out of range} + +test lpop-2.1 {basic functionality} -body { + set l "x y z" + list [lpop l 0] $l +} -result {x {y z}} +test lpop-2.2 {basic functionality} -body { + set l "x y z" + list [lpop l 1] $l +} -result {y {x z}} +test lpop-2.3 {basic functionality} -body { + set l "x y z" + list [lpop l] $l +} -result {z {x y}} +test lpop-2.4 {basic functionality} -body { + set l "x y z" + set l2 $l + list [lpop l] $l $l2 +} -result {z {x y} {x y z}} + +test lpop-3.1 {nested} -body { + set l "x y" + set l2 $l + list [lpop l {0 0 0 0}] $l $l2 +} -result {x {{{{}}} y} {x y}} +test lpop-3.2 {nested} -body { + set l "{x y} {a b}" + list [lpop l {0 1}] $l +} -result {y {x {a b}}} +test lpop-3.3 {nested} -body { + set l "{x y} {a b}" + list [lpop l {1 0}] $l +} -result {a {{x y} b}} + + + + + +test lpop-99.1 {performance} -constraints perf -body { + set l [lrepeat 10000 x] + set l2 $l + set t1 [time { + while {[llength $l] >= 2} { + lpop l end + } + }] + set l [lrepeat 30000 x] + set l2 $l + set t2 [time { + while {[llength $l] >= 2} { + lpop l end + } + }] + regexp {\d+} $t1 ms1 + regexp {\d+} $t2 ms2 + set ratio [expr {double($ms2)/$ms1}] + # Deleting from end should have linear performance + expr {$ratio > 4 ? $ratio : 4} +} -result {4} + +test lpop-99.2 {performance} -constraints perf -body { + set l [lrepeat 10000 x] + set l2 $l + set t1 [time { + while {[llength $l] >= 2} { + lpop l 1 + } + }] + set l [lrepeat 30000 x] + set l2 $l + set t2 [time { + while {[llength $l] >= 2} { + lpop l 1 + } + }] + regexp {\d+} $t1 ms1 + regexp {\d+} $t2 ms2 + set ratio [expr {double($ms2)/$ms1}] + expr {$ratio > 10 ? $ratio : 10} +} -result {10} + + +# cleanup +::tcltest::cleanupTests +return + +# Local Variables: +# mode: tcl +# End: |