Index: doc/lreplace.n ================================================================== --- doc/lreplace.n +++ doc/lreplace.n @@ -28,17 +28,30 @@ 0 refers to the first element of the list, and \fBend\fR refers to the last element of the list. If \fIlist\fR is empty, then \fIfirst\fR and \fIlast\fR are ignored. .PP If \fIfirst\fR is less than zero, it is considered to refer to before the -first element of the list. For non-empty lists, the element indicated -by \fIfirst\fR must exist or \fIfirst\fR must indicate before the +first element of the list. +.VS TIP505 +If \fIfirst\fR indicates a position greater than the index of the last element +of the list, it is treated as if it is an index one greater than the last +element. This allows this command to append elements to the list. +.VE TIP505 +For non-empty lists, the element indicated +by \fIfirst\fR must exist, or \fIfirst\fR must indicate before the start of the list. .PP If \fIlast\fR is less than \fIfirst\fR, then any specified elements will be inserted into the list before the point specified by \fIfirst\fR with no elements being deleted. +.VS TIP505 +If \fIlast\fR is greater than the index of the last item of the list, it is +treated as if it is an index one greater than the last element. This means +that if it is also greater than than \fIfirst\fR, all elements from +\fIfirst\fR to the end of the list will be replaced, and otherwise the +elements will be appended. +.VE TIP505 .PP The \fIelement\fR arguments specify zero or more new arguments to be added to the list in place of those that were deleted. Each \fIelement\fR argument will become a separate element of the list. If no \fIelement\fR arguments are specified, then the elements @@ -76,11 +89,28 @@ upvar 1 $listVariable var set idx [lsearch -exact $var $value] set var [\fBlreplace\fR $var $idx $idx] } .CE +.PP +.VS TIP505 +Adding elements to the end of the list; note that \fBend+2\fR will initially +be treated as if it is \fB6\fR here, but both that and \fB12345\fR are greater +than the index of the final item so they behave identically: +.PP +.CS +% set var {a b c d e} +a b c d e +% set var [\fBlreplace\fR $var 12345 end+2 f g h i] +a b c d e f g h i +.CE +.VE TIP505 .SH "SEE ALSO" list(n), lappend(n), lindex(n), linsert(n), llength(n), lsearch(n), lset(n), lrange(n), lsort(n), string(n) .SH KEYWORDS element, list, replace +.\" Local variables: +.\" mode: nroff +.\" fill-column: 78 +.\" End: Index: generic/tclCmdIL.c ================================================================== --- generic/tclCmdIL.c +++ generic/tclCmdIL.c @@ -2745,25 +2745,14 @@ } if (first < 0) { first = 0; } - - /* - * Complain if the user asked for a start element that is greater than the - * list length. This won't ever trigger for the "end-*" case as that will - * be properly constrained by TclGetIntForIndex because we use listLen-1 - * (to allow for replacing the last elem). - */ - - if ((first >= listLen) && (listLen > 0)) { - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "list doesn't contain element %s", TclGetString(objv[2]))); - Tcl_SetErrorCode(interp, "TCL", "OPERATION", "LREPLACE", "BADIDX", - NULL); - return TCL_ERROR; - } + if (first > listLen) { + first = listLen; + } + if (last >= listLen) { last = listLen - 1; } if (first <= last) { numToDelete = last - first + 1; Index: generic/tclCompCmdsGR.c ================================================================== --- generic/tclCompCmdsGR.c +++ generic/tclCompCmdsGR.c @@ -1470,11 +1470,11 @@ * compiled. */ CompileEnv *envPtr) /* Holds the resulting instructions. */ { Tcl_Token *tokenPtr, *listTokenPtr; DefineLineInformation; /* TIP #280 */ - int idx1, idx2, i, offset, offset2; + int idx1, idx2, i; int emptyPrefix=1, suffixStart = 0; if (parsePtr->numWords < 4) { return TCL_ERROR; } @@ -1490,27 +1490,10 @@ if (TclGetIndexFromToken(tokenPtr, TCL_INDEX_BEFORE, TCL_INDEX_END, &idx2) != TCL_OK) { return TCL_ERROR; } - /* - * idx1, idx2 are the conventional encoded forms of the tokens parsed - * as all forms of index values. Values of idx1 that come before the - * list are treated the same as if they were the start of the list. - * Values of idx2 that come after the list are treated the same as if - * they were the end of the list. - */ - - if (idx1 == TCL_INDEX_AFTER) { - /* - * [lreplace] treats idx1 value end+1 differently from end+2, etc. - * The operand encoding cannot distinguish them, so we must bail - * out to direct evaluation. - */ - return TCL_ERROR; - } - /* * General structure of the [lreplace] result is * prefix replacement suffix * In a few cases we can predict various parts will be empty and * take advantage. @@ -1518,11 +1501,13 @@ * The proper suffix begins with the greater of indices idx1 or * idx2 + 1. If we cannot tell at compile time which is greater, * we must defer to direct evaluation. */ - if (idx2 == TCL_INDEX_BEFORE) { + if (idx1 == TCL_INDEX_AFTER) { + suffixStart = idx1; + } else if (idx2 == TCL_INDEX_BEFORE) { suffixStart = idx1; } else if (idx2 == TCL_INDEX_END) { suffixStart = TCL_INDEX_AFTER; } else if (((idx2 < TCL_INDEX_END) && (idx1 <= TCL_INDEX_END)) || ((idx2 >= TCL_INDEX_START) && (idx1 >= TCL_INDEX_START))) { @@ -1550,46 +1535,10 @@ TclEmitInstInt4( INST_LIST, i - 4, envPtr); emptyPrefix = 0; } - /* - * [lreplace] raises an error when idx1 points after the list, but - * only when the list is not empty. This is maximum stupidity. - * - * TODO: TIP this nonsense away! - */ - if (idx1 >= TCL_INDEX_START) { - if (emptyPrefix) { - TclEmitOpcode( INST_DUP, envPtr); - } else { - TclEmitInstInt4( INST_OVER, 1, envPtr); - } - TclEmitOpcode( INST_LIST_LENGTH, envPtr); - TclEmitOpcode( INST_DUP, envPtr); - offset = CurrentOffset(envPtr); - TclEmitInstInt1( INST_JUMP_FALSE1, 0, envPtr); - - /* List is not empty */ - TclEmitPush(TclAddLiteralObj(envPtr, Tcl_NewIntObj(idx1), - NULL), envPtr); - TclEmitOpcode( INST_GT, envPtr); - offset2 = CurrentOffset(envPtr); - TclEmitInstInt1( INST_JUMP_TRUE1, 0, envPtr); - - /* Idx1 >= list length ===> raise an error */ - TclEmitPush(TclAddLiteralObj(envPtr, Tcl_ObjPrintf( - "list doesn't contain element %d", idx1), NULL), envPtr); - CompileReturnInternal(envPtr, INST_RETURN_IMM, TCL_ERROR, 0, - Tcl_ObjPrintf("-errorcode {TCL OPERATION LREPLACE BADIDX}")); - TclStoreInt1AtPtr(CurrentOffset(envPtr) - offset, - envPtr->codeStart + offset + 1); - TclEmitOpcode( INST_POP, envPtr); - TclStoreInt1AtPtr(CurrentOffset(envPtr) - offset2, - envPtr->codeStart + offset2 + 1); - } - if ((idx1 == suffixStart) && (parsePtr->numWords == 4)) { /* * This is a "no-op". Example: [lreplace {a b c} 2 0] * We still do a list operation to get list-verification * and canonicalization side effects. Index: tests/lreplace.test ================================================================== --- tests/lreplace.test +++ tests/lreplace.test @@ -98,14 +98,14 @@ [set foo [lreplace $foo end end]] \ [set foo [lreplace $foo end end]] } {a {} {}} test lreplace-1.27 {lreplace command} -body { lreplace x 1 1 -} -returnCodes 1 -result {list doesn't contain element 1} +} -result x test lreplace-1.28 {lreplace command} -body { lreplace x 1 1 y -} -returnCodes 1 -result {list doesn't contain element 1} +} -result {x y} test lreplace-1.29 {lreplace command} -body { lreplace x 1 1 [error foo] } -returnCodes 1 -result {foo} test lreplace-1.30 {lreplace command} -body { lreplace {not {}alist} 0 0 [error foo] @@ -126,14 +126,14 @@ test lreplace-2.5 {lreplace errors} { list [catch {lreplace x 10 1x} msg] $msg } {1 {bad index "1x": must be integer?[+-]integer? or end?[+-]integer?}} test lreplace-2.6 {lreplace errors} { list [catch {lreplace x 3 2} msg] $msg -} {1 {list doesn't contain element 3}} +} {0 x} test lreplace-2.7 {lreplace errors} { list [catch {lreplace x 2 2} msg] $msg -} {1 {list doesn't contain element 2}} +} {0 x} test lreplace-3.1 {lreplace won't modify shared argument objects} { proc p {} { lreplace "a b c" 1 1 "x y" return "a b c"