summaryrefslogtreecommitdiffstats
path: root/lib/language.tcl
blob: 2977933d8d449e0e89140862926e534c14cb0b53 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# 'Language' utilities that extend and customize base Tcl.

proc fn {name argNames body} {
    lassign [uplevel Evaluator::serializeEnvironment] envArgNames envArgValues
    set argNames [linsert $argNames 0 {*}$envArgNames]
    uplevel [list set ^$name [list apply [list $argNames $body] {*}$envArgValues]]
}
rename unknown _original_unknown
# Trap resolution of commands so that they can call the lambda in
# lexical scope created by `fn`.
proc unknown {name args} {
    set err [catch {set fnVar ^$name; upvar $fnVar fn}]
    if {$err == 0 && [info exists fn]} {
        uplevel [list {*}$fn {*}$args]
    } else {
        uplevel [list _original_unknown $name {*}$args]
    }
}

namespace eval dictset {
    namespace export create add union difference entries size
    proc create {args} {
        set kvs [list]
        foreach k $args { lappend kvs $k true }
        dict create {*}$kvs
    }
    proc add {sv entry} { upvar $sv s; dict set s $entry true }

    proc union {s t} { dict merge $s $t }
    proc difference {s t} {
        dict filter $s script {k v} {expr {![dict exists $t $k]}}
    }

    proc size {s} { dict size $s }
    proc entries {s} { dict keys $s }
    namespace ensemble create
}

# Trim indentation in multiline quoted text.
proc undent {msg {whitespaceChars " "}} {
    set msgLines [split $msg "\n"]
    set maxLength [string length $msg]

    set regExp [subst -nocommands {([$whitespaceChars]*)[^$whitespaceChars]}]

    set indent [
        tcl::mathfunc::min {*}[
            lmap x $msgLines {
                if {[regexp $regExp $x match whitespace]} {
                    string length $whitespace
                } else {
                    lindex $maxLength
                }
            }
        ]
    ]

    join [ltrim [lmap x $msgLines {string range $x $indent end}]] "\n"
}
# Remove empty items at the beginning and the end of a list.
proc ltrim {list} {
    set first [lsearch -not -exact $list {}]
    set last [lsearch -not -exact [lreverse $list] {}]
    return [
        if {$first == -1} {
            list
        } else {
            lrange $list $first end-$last
        }
    ]
}
proc lenumerate {l} {
    set ret [list]
    for {set i 0} {$i < [llength $l]} {incr i} {
        lappend ret $i [lindex $l $i]
    }
    set ret
}

# Create `dict getdef` / `dict getwithdefault`
# Backported from https://core.tcl-lang.org/tips/doc/trunk/tip/342.md
proc dict_getdef {D args} {
    if {[dict exists $D {*}[lrange $args 0 end-1]]} then {
        dict get $D {*}[lrange $args 0 end-1]
    } else {
        lindex $args end
    }
}

proc python3 {args} {
    exec python3 << [undent [join $args " "]]
}

proc assert condition {
    set s "{$condition}"
    if {![uplevel 1 expr $s]} {
        set errmsg "assertion failed: $condition"
        try {
            if {[lindex $condition 1] eq "eq" && [string index [lindex $condition 0] 0] eq "$"} {
                set errmsg "$errmsg\n[uplevel 1 [list set [string range [lindex $condition 0] 1 end]]] is not equal to [lindex $condition 2]"
            }
        } on error e {}
        return -code error $errmsg
    }
}

proc baretime body { string map {" microseconds per iteration" ""} [uplevel [list time $body]] }

# forever { ... } is sort of like while true { ... }, but it yields to
# the event loop after each iteration.
proc forever {body} {
    while true {
        uplevel $body
        update
    }
}

namespace import ::tcl::mathop::*
namespace import ::tcl::mathfunc::*