Posted to tcl by aspect at Mon Mar 23 08:55:06 GMT 2015view pretty

# this really wants to use [cmdsplit] so $arr(spacey key) and [string is digit] can work as patterns without quoting

# switch -glob matching literal []{}*:  a special king of quoting hell

source debug.tcl
namespace eval lmatch {

    proc lmatch args {
        options {-compare ::tcl::mathop::eq ::tcl::mathop::== glob lmatch}
        arguments {pattern list}
        if {$compare eq "glob"} {
            set compare {string match}
        } elseif {$compare eq "nocase"} {
            set compare {string compare -nocase}
        }
        set pi 0
        set li 0
        try {
            while {$pi < [llength $pattern]} {
                set pat [lindex $pattern $pi]
                set val [lindex $list $li]
                switch -glob $pat {
                    \\* {    # match any single element
                        incr li ; incr pi
                    }
                    \\*\\* - \{\\*\}\\* {     # match any N elements
                        set pi2 [expr {$pi + 1}]
                        if {$pi2 >= [llength $pattern]} {
                            return 1
                        }
                        for {set li2 $li} {$li2 < [llength $list]} {incr li2} {
                            if {[uplevel 1 [list lmatch [lrange $pattern $pi2 end] [lrange $list $li2 end]]]} {
                                return 1
                            }
                        }
                        throw {LMATCH FAIL} "Failed to unify after **: [list [lrange $pattern $pi end] [lrange $list $pi end]]"
                    }
                    $* {    # match a variable's value in the enclosing scope
                        set lit [uplevel 1 [list subst $pat]]
                        if {$lit ne $val} { # NOTE: this case disregards -compare!
                            throw {LMATCH FAIL} "{$val} does not match {$lit} (value of $pat)"
                        }
                        incr li ; incr pi
                    }
                    \\[*\\] {   # match with a command  - FIXME:  commands break list parsing, duh
                        set cmd [string range $pat 1 end-1]
                        if {![uplevel 1 $cmd [list $val]]} {
                            throw {LMATCH FAIL} "{$val} is not \[$cmd\]"
                        }
                        incr li ; incr pi
                    }
                    default {   # match a literal value
                        puts "Comparing $pat with $val"
                        if {![$compare $pat $val]} {
                            throw {LMATCH FAIL} "{$val} ne {$pat}"
                        }
                        incr li ; incr pi
                    }
                }
            }
            if {$pi == [llength $pattern] && $li == [llength $list]} {
                return 1
            }
            return 0
        } trap {LMATCH FAIL} {e o} {
            debug log {Match failed: $e at pattern index $li ($pat), value index $li ($val)}
            return 0
        }

    }

    proc lbind args {
        options
        arguments {pattern list}
    }

    namespace export {[a-z]*}

    proc test {} {
        set c foop
        debug assert { [lmatch {a B} {a B}] }
        debug assert {![lmatch {a B} {a b}] }
        debug assert { [lmatch {a *} {a b}] }
        debug assert {![lmatch {a *} {a b c}] }
        debug assert { [lmatch {**} {}] }
        debug assert { [lmatch {**} {a b c}] }
        debug assert { [lmatch {a **} {a b c}] }
        debug assert { [lmatch {a ** c} {a b c}] }
        debug assert {![lmatch {a * $c} {a B}] }
        debug assert {![lmatch {a * $c} {a B food}] }
        debug assert { [lmatch {a * $c} {a B foop}] }
        debug assert { [catch {lmatch {a * $nonesuch} {a B foop}}] }
        debug assert { [lmatch {"[string is digit]" a *} {123 a B}] }
        return 1
    }
}

catch {rename ::lmatch {}}  ;# turns out Tclx uses this name with arguments reversed :(
namespace import ::lmatch::*
try {
    ::lmatch::test
} on error {e o} {
    puts "ERROR: $e"
    pdict $o
}