Version 2 of string forward compatibility

Updated 2002-07-13 02:16:28
 #
 # This code implements the following Tcl 8.1.1 commands, allowing you
 # to use them in earlier versions of Tcl:
 #     String: 
 #         equal - the '-nocase' and '-length' options are not implemented
 #         is boolean - the '-failindex' option is not implemented
 #         is false   -  "       "         "        "       "
 #         is integer -  "       "         "        "       "
 #         is true    -  "       "         "        "       "
 #         map  - the '-nocase' option is not implemented.  See the notes
 #                in the source for other limitations.
 #         repeat
 #
 #####################################################################
 package require Tcl
 if {[package vcompare [package provide Tcl] 8.1] < 0} {
    rename string Tcl8.1_string
    proc string {cmd args} {
        switch -exact -- $cmd {

            #
            # "string equal"
            #
            eq    -
            equ   -
            equa  -
            equal {
                if {[llength $args] != 2} {
                    return -code error \
                          "wrong # args, should be string equal string1 string2"
                }
                foreach {str1 str2} $args break
                return [StringEqual $str1 $str2]
            }

            #
            # "string is (boolean or false or int or true)"
            #
            is {
                if {([llength $args] < 2) || ([llength $args] > 3)} {
                    return -code error \
                          "wrong # args, should be string is class ?-strict? string"
                }
                set class [lindex $args 0]
                set args [lrange $args 1 end]
                switch -exact -- $class {
                    b       -
                    bo      -
                    boo     -
                    bool    -
                    boole   -
                    boolea  -
                    boolean {
                        return [eval StringIsBoolean $args]
                    }
                    f     -
                    fa    -
                    fal   -
                    fals  -
                    false {
                        return [eval StringIsFalse $args]
                    }
                    i       -
                    in      -
                    int     -
                    inte    -
                    integ   -
                    intege  -
                    integer { 
                        return [eval StringIsInt $args]
                    }
                    t    -
                    tr   -
                    tru  -
                    true {
                        return [eval StringIsTrue $args]
                    }
                    default {
                        return -code error \
                              "bad class \"$class\": must be boolean, false, integer or true"
                    }
                } 
            }

            #
            # "string map"
            #
            map {
                if {[llength $args] != 2} {
                    return -code error \
                          "wrong # args, should be string map charMap string"
                }
                foreach {charMap str} $args break
                return [StringMap $charMap $str]
            }

            #
            # "string repeat"
            #
            repe   -
            repea  -
            repeat {
                if {[llength $args] != 2} {
                    return -code error \
                          "wrong # args, should be string repeat string count"
                }
                foreach {str n} $args break
                return [StringRepeat $str $n]
            }
        }
        uplevel [list Tcl8.1_string $cmd] $args
    }


    #
    # This procedure implements the "string equal" command
    #
    proc StringEqual {str1 str2} {
        if {[string compare $str1 $str2] == 0} {
            return 1
        } else {
            return 0
        }
    }


    #
    # This procedure implements the "string is boolean" command
    #
    proc StringIsBoolean {args} {
        if {[eval StringIsFalse $args] || [eval StringIsTrue $args]} {
            return 1
        } else {
            return 0
        }
    }


    #
    # This procedure implements the "string is false" command
    #
    proc StringIsFalse {args} {
        if {[llength $args] == 2} {
            #
            # There are two arguments; the first one must be "-strict"
            #
            if {![eval ValidStrict $args]} {
                return 0
            } else {
                #
                # Reduce the arguments to a single string
                #
                set args [lindex $args 1]
            }
        }
        set str [string toupper [lindex $args 0]]
        switch -exact -- $str {
            ""    -
            F     -
            FA    -
            FAL   -
            FALS  -
            FALSE -
            OF    -
            OFF   -
            N     -
            NO    -
            0     {
                return 1
            } 
            default {
                return 0
            }
        } 
    }


    #
    # This procedure implements the "string is true" command
    #
    proc StringIsTrue {args} {
        if {[llength $args] == 2} {
            #
            # There are two arguments; the first one must be "-strict"
            #
            if {![eval ValidStrict $args]} {
                return 0
            } else {
                #
                # Reduce the arguments to a single string
                #
                set args [lindex $args 1]
            }
        }
        set str [string toupper [lindex $args 0]]
        switch -exact -- $str {
            ""    -
            T     -
            TR    -
            TRU   - 
            TRUE  -
            Y     -
            YE    -
            YES   -
            ON    -
            1     {
                return 1
            } 
            default {
                return 0
            }
        } 
    }


    #
    # This procedure implements the "string is int" command
    #
    proc StringIsInt {args} {
        if {[llength $args] == 1} {
            #
            # There is only one argument, so a null string is a valid
            # integer.
            #
            if {[lindex $args 0] == ""} {
                return 1
            }
        } else {
            #
            # There are two arguments; the first one must be "-strict"
            #
            if {![eval ValidStrict $args]} {
                return 0
            } else {
                #
                # Reduce the arguments to a single string
                #
                set args [lindex $args 1]
            }
        }
        #
        # Args now consists of a single string which is the presumptive
        # integer.  We will try to convert it to one.
        #
        if {[catch {format %d [lindex $args 0]} temp] == 0} {
            return 1
        } else {
            return 0
        }
    }


    #
    # This procedure processes the '-strict' option for
    # 'string is (boolean, false, int, or true)'.  It returns:
    #   0 - '-strict' is specified and the argument is an empty string;
    #       therefore it is not a valid (boolean, false, int, or true).
    #   1 - '-strict' is specified but the argument is not an empty string;
    #       therefore the calling routine must evaluate it.
    #  If the first argument is not '-strict' this procedure raises an error.
    #
    proc ValidStrict {args} {
        if {[lindex $args 0] != "-strict"} {
            return -code error \
              "bad option \"[lindex $args 0]\": must be -strict"
        } else {
            if {[lindex $args 1] == ""} {
                return 0
            } else {
                return 1
            }
        }
    }


    #
    # This procedure implements a close approximation of the 
    # "string map" command.
    #
    # This implementation of "String Map" is not identical to the 
    # Tcl standard. It does not support the -nocase option, and 
    # it iterates over the string once for each Key-Value pair
    # (rather than just once as Tcl does).
    #
    # This produces different results if a Value is followed by a
    # matching Key.  For example:
    #    "string map {ab 12 cd 34}" - produces the same results as Tcl
    #    "string map {ab cd cd 34}" - will produce different results if
    #        the Key "ab" occurs in the target string.  Tcl will take a
    #        target string of "abcd" and produce "cd34"; this proc will
    #        produce "3434".
    #
    proc StringMap {charMap str} {

        foreach {old_char new_char} $charMap {
            set index [string first $old_char $str]
            if {$index >= 0} {
                set str1 [string range $str 0 [expr $index-1]]
                set str2 [string range $str [expr $index+[string length $old_char]] end]
                set str3 [StringMap [list $old_char $new_char] $str2]
                set str $str1$new_char$str3
            }
        }
        return $str
    }

    #
    # This procedure implements the "string repeat" command
    #
    proc StringRepeat {text n} {
        if {$n <= 0} {
            return ""
        } elseif {$n == 1} {
            return $text
        } elseif {$n == 2} {
            return $text$text
        } elseif {0 == ($n % 2)} {
            set result [StringRepeat $text [expr {$n / 2}]]
            return "$result$result"
        }
        return "$text[StringRepeat $text [incr n -1]]"
    }

 }

Category Porting