Synopsis edit
- incr variable ?increment?
Documentation edit
Description edit
Before Tcl 8.5, the variable had to exist before the incr call. variable must have value that can be interpreted as a an integer. increment must also be an integer and defaults to 1 if not specified.incr iIf $i didn't previously exist, 1 will now be its value.If $i is a non-integral real number, [incr] could not be used, but [set] could:
set i [expr {$i + 1}]increment can be a negative number, making it possible to increment downwards.RS: increment may be zero. This sounds useless, but has the side effect that the variable is type-checked to be integer, which avoids endless loops (the infinity trick) like
for {set i 0} {$i < $max} {incr i} {...}when max is a non-numeric string whose first character sorts higher than "9". If max comes in as user-supplied parameter, this may well happen... A simple incr max 0 safeguards that it must be an integer.
Post-increment edit
RS 2013-12-07 - In C, integer variables can be pre-incremented (++i: first do increment, then retrieve value, like incr does), or post-incremented (i++: first retrieve value, then do increment). Here is how to do the latter in Tcl:proc pincr {_var {amount 1}} { upvar 1 $_var var if ![info exists var] {set var 0} set res $var incr var $amount return $res } # -------------- and now testing: % set x 42 42 % foreach i {a b c} {puts [pincr x]} 42 43 44AMG: This can also be done with [K].
proc K {x y} {set x} proc pincr {varName {amount 1}} { upvar 1 $varName var K $var [incr var] } % set x 42; lmap i {a b c} {pincr x} 42 43 44 % set x 42; lmap i {a b c} {K $x [incr x]} 42 43 44Here's another way of going about it:
% set x 42 42 % lindex $x[incr x; list] 42 % set x 43The single-argument [lindex] is just there to return its one argument. Other commands can fulfill the same role.Here's the 8.6.1 disassembly for the above:
tcl::unsupported::disassemble lambda {{x} {lindex $x[incr x; list]}} ByteCode 0x000000000253A9B0, refCt 1, epoch 16, interp 0x00000000025765B0 (epoch 16) Source "lindex $x[incr x; list]" Cmds 3, src 23, inst 34, litObjs 1, aux 0, stkDepth 2, code/src 0.00 Proc 0x0000000002533810, refCt 1, args 1, compiled locals 1 slot 0, scalar, arg, "x" Commands 3: 1: pc 0-32, src 0-22 2: pc 2-14, src 10-15 3: pc 15-25, src 18-21 Command 1: "lindex $x[incr x; list]" (0) loadScalar1 %v0 # var "x" Command 2: "incr x" (2) startCommand +12 1 # next cmd at pc 14 (11) incrScalar1Imm %v0 +1 # var "x" (14) pop Command 3: "list" (15) startCommand +11 1 # next cmd at pc 26 (24) push1 0 # "" (26) concat1 2 (28) lindexMulti 1 (33) doneI see things can be optimized further by making the bytecode generation engine aware that the sequence "push1 0; concat1 2" is a no-op when literal #0 is empty string. Also it can be taught that "lindexMulti 1" basically doesn't do anything either.
Incrementing Doubles edit
Incrementing doubles as well as ints can be done like this:proc += {varName {amount 1}} { upvar 1 $varName var set var [expr {$var+$amount}] } ;# RSAMG: Why doesn't [incr] do this natively?
Incrementing Hexadecimal edit
Csan: An enhanced incr proc which adds hexadecimal string incrementing (delta could be e.g. 0xF):proc incr {varName {delta 1}} { upvar 1 $varName var if {[regexp -- ^0x.*$ $var]} { set var 0x[format %X [expr [scan $var %x]+$delta]] } { if {[string equal [format %d [scan $var %d]] $var]} { set var [expr {$var+$delta}] } } }Trying it out:
% set n 1 ; incr n 2 % set n 0xA ; incr n 0xB % set n 0xFE ; incr n 0xFF % set n 0xFF ; incr n 0x100 % set n 0xF0 ; incr n 0xE 0xFE % set n 123H ; incr n %My thanks go to Lars H for pointing out the weakness of the previous code ;) (do you know of a better way to tell if a number is a decimal besides checking if a scan-format produces the same string?)
% set n 0xF 0xF % incr n 0x10 % incr n 0xA 0x1ALars H: Only numbers which cannot be parsed as decimal integers are incremented as hexadecimals, but a lot of hexadecimals can be parsed as decimals (with a completely different value). The result is that what starts out hexadecimal probably doesn't stay so for very long.
A Non-Incrementing [incr] edit
Sarnold: String and list indices are difficult to handle with additions and substractions. Replace the following code which mimics [split]:while {[set idx [string first ( $str]] >= 0} { lappend picked [string range $str 0 [expr {$idx-1}]] set str [string range $str [expr {$idx+1}]] }by:
while {[set idx [string first ( $str]] >= 0} { lappend picked [string range $str 0 [incr idx -1]] set str [string range $str [incr idx 2]] }But this is not as readable and error-prone. Here the problem is that line 2
...[incr idx -1]modifies idx, then we have to increment later by 2 instead of 1. Lots of confusion...So we need a [noincr] proc that returns the result of the increment, without modifying the variable.
while {[set idx [string first ( $str]]>=0} { lappend picked [string range $str 0 [noincr idx -1]] set str [string range $str [noincr idx]] }It is then more readable, and of course more maintainable.Here is [noincr]:
proc noincr {var {i 1}} { upvar $var v set x $v incr x $i }PYK 2013-10-15: I would just stick with the [expr] variant.
Not Sure What This is Good For - Delete This Section? edit
avi: This might be an alternate implementation in the lines of C/C++ style ++ opsproc incrWrap {pChar} { upvar 1 $pChar lVar if { ![info exists lVar] || [string is double -strict $lVar] } { incr lVar } else { scan $lVar %c lVal incr lVal set lVar [format %c $lVal] } }AMG: I fixed a few bugs (inability to handle variables that don't exist yet, using the variable name instead of its value), but I still don't understand what this code is useful for. It does ordinary incr on strings which appear to be numbers (even though [incr] doesn't support floating-point numbers, only integers), and it increments the ASCII/Unicode value of the first character of anything else (beware empty strings and strings longer than one character!). But since it's really only guessing whether a string is intended to be a number or a character, it can guess wrong:
set x . ;# returns . incrWrap x ;# returns / incrWrap x ;# returns 0 incrWrap x ;# returns 1 # ... keep going ..]. incrWrap x ;# returns 8 incrWrap x ;# returns 9 incrWrap x ;# returns 10 even though I wanted :Here's my recommendation. Use [incr] directly when you need to increment and decrement integers, and use [scan] and [format] when you need to convert between characters and numeric character codes. Don't create a wrapper for something until you find a demonstrable need, or else you will make your code more confusing, not less confusing.avi: incrWrap (with the amendments you made) can be used as a drop-in substitute for incr in a Tcl for loop which needs to handle both character and ints. As far as "need" is concerned, it is the same as why a procedure is written instead of a bunch of lines of code.
Historical: Platform-Specific Caveats edit
In versions of Tcl prior to 8.5, [incr] was limited by the size of int in the on the platform Tcl was compiled for. This section only applies to those historical versions of Tcl.Twylite 2005-01-04: In 8.4, [incr] works according to the underlying type of the integer, which is determined by its size. so:% set i [expr 2147483647] 2147483647 % incr i -2147483648 % set i [expr 21474836470] 21474836470 % incr i 21474836471A number larger than 32 bits can be formed as a string. To use 32-bit numbers in a calculation that will have a 64-bit result you can use the wide type (see expr problems with int)The only solution I am aware of for a "64-bit incr" is:
set i [expr wide($i) + 1]
Historical: Tolerant Incrementer edit
This section is historical, since modern [incr] automatically creates the variable, if necessary.This tolerant incrementer creates the variable if necessary, and initializes it with 0:proc inc {varName {amount 1}} { upvar 1 $varName var if {![info exists var]} {set var 0} incr var $amount }KPV: You can avoid [info exists] altogether by doing:
set var [expr [append var + $amount]]Unfortunately, while this is tighter, it is slower due to shimmering.'Lars H: The need to parse the expression generated probably also contributes to that being slower. Another idiom is
if {[catch {incr var $amount}]} then {set var $amount}If the variable exists most of the time then this is faster than the [info exists] approach.
Half-Bakery: Using [incr] with Strings edit
RS had a half-baked idea these days - extend incr to also work on characters (like in C), so we could write:for {set letter "A"} {$letter<="Z"} {incr letter} {...}rmax pointed rightly out that ambiguities come up as soon as the character is a digit: set i 9; incr i would make it 10, not ":" as expected - so maybe with a -char switch which still looks better than
set letter [format %c [expr {[scan $letter %c]+1}]]Is there anything peculiar that happens with this in regards to encoding? Will the incrementing be of the utf8 byte value? Are there multi-byte character issues with regards to incrementing? What happens at wrap around time if only 8 bits are being used? Wrap to 0? Is this a 7 or 8 bit value?RS: To Tcl (since 8.1), all characters are Unicodes and potentially 16 bits wide (this may have to change to 32), which mostly are stored as UTF characters, 1..3 bytes wide. I would propose that incr -char reacts like the format..expr..scan code above, just faster, and might throw an error if crossing 0xFFFF. Incrementing strings of more than a char wide seems of little use to me, could raise an error as well.
- Are all 16 bit bytes valid unicode characters? (No. Some codes (0xFFFE, 0xFEFF, others?) are explicitly not characters, and others are non-spacing diacriticals that shouldn't be used on their own. -SCT)
- When I was talking about multi-byte characters, I was referring to the 2 and 3 byte long sequences for a unicode character. However, now that you mention it, I would like to be able to say
set name "/tmp/filename0" incr nameand have the file name go from filename0 to filename9, then filenamea-z, etc.RS: Hm. A long way from "9" to "a" in ASCII or Unicode.. How about using the self-incrementing proc
proc intgen {{seed 0}} { set self [lindex [info level 0] 0] proc $self "{seed [incr seed]}" [info body $self] set seed } ;# RSfrom Braintwisters, and then write (where after filename9 will come filename10):
set name "/tmp/filename[intgen]"
Here is a corresponding decrement function:
proc decr { int { n 1 } } { if { [ catch { uplevel incr $int -$n } err ] } { return -code error "decr: $err" } return [ uplevel set $int ] }RS wonders what the advantages against
proc decr {var {n 1}} { uplevel 1 [list incr $var -$n] }are (or against just writing incr i -1)?Martin Lemburg: I love readable code and using `decr var 1 is more readable than incr var -1', or am I wrong?Isn't it worth to discuss readability of code?DKF: Good bonus to readability, not so good for speed (the incr version will be bytecoded much more efficiently.) Tricky trade-off that...