Tcl Library Source Code

Changes On Branch add-defer
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Changes In Branch add-defer Excluding Merge-Ins

This is equivalent to a diff from c84e50e6e8 to aaf5773163

2017-08-16
04:57
Merged `defer` into main line. Regenerated documentation. check-in: baac0e818b user: aku tags: trunk
04:49
Tweaks for file recognition in emacs. Skip index for pre-8.6 core. Move doc version info into a variable (single place of change) Closed-Leaf check-in: aaf5773163 user: aku tags: add-defer
2017-08-15
18:43
Add cleanup of testsuite, like pki.test check-in: f8037daead user: rkeene tags: add-defer
16:26
Added start of "defer" package v1 check-in: ca495cc9f9 user: rkeene tags: add-defer
2017-08-10
18:54
Added tests for areaPolygon (ticket cb043ecc70e0e90bff93535d1d371a78b94f5d44). Updated the ChangeLog check-in: c84e50e6e8 user: arjenmarkus tags: trunk
2017-08-08
20:59
Correct one more UTF-8 issue check-in: 9afa83afac user: tdc7675 tags: trunk

Added modules/defer/defer.man.













































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
[vset VERSION 1]
[comment {-*- tcl -*- doctools manpage}]
[manpage_begin defer n [vset VERSION]]
[keywords golang]
[keywords cleanup]
[copyright {2017, Roy Keene}]
[moddesc {Defered execution ala Go}]
[titledesc {Defered execution}]
[category  {Utility}]
[require Tcl 8.6]
[require defer [opt [vset VERSION]]]
[description]

The [cmd defer] commands allow a developer to schedule actions to happen
as part of the current variable scope terminating.  This is most useful
for dealing with cleanup activities.  Since the defered actions always
execute, and always execute in the reverse order from which the defer
statements themselves execute, the programmer can schedule the cleanup
of a resource (for example, a channel) as soon as that resource is
acquired.  Then, later if the procedure or lambda ends, either due to
an error, or an explicit return, the cleanup of that resource will
always occur.

[para]

[section {COMMANDS}]

[list_begin definitions]

[call [cmd "::defer::defer"] \
        [opt [arg command]] \
        [opt [arg arg1]] \
        [opt [arg arg2]] \
        [opt [arg argN...]]]

Defers execution of some code until the current variable scope
ends.  Each argument is concatencated together to form the script
to execute at deferal time.

Multiple defer statements may be used, they are executed in the order
of last-in, first-out.

[comment {
	Just like Go !
}]

The return value is an identifier which can be used later with 
[cmd defer::cancel]

[call [cmd "::defer::with"] \
	[arg variableList] [arg script]]

Defers execution of a script while copying the current value of some
variables, whose names specified in [arg variableList], into the script.
The script acts like a lambda but executes at the same level as the
[cmd defer::with]
call.

The return value is the same as
[cmd ::defer::defer]

[call [cmd ::defer::autowith] [arg script]]

The same as
[cmd ::defer::with] but uses all local variables in the variable list.

[call [cmd ::defer::cancel] \
    [opt [arg id...]]]

Cancels the execution of a defered action.  The [arg id] argument is the
identifier returned by
[cmd ::defer::defer],
[cmd ::defer::with], or
[cmd ::defer::autowith].

Any number of arguments may be supplied, and all of the IDs supplied
will be cancelled.

[list_end]

[section "EXAMPLES"]

[example {
	package require defer 1
	apply {{} {
		set fd [open /dev/null]
		defer::defer close $fd
	}}
}]

[section "REFERENCES"]

[list_begin enumerated]
[enum]
[list_end]

[section AUTHORS]
Roy Keene

[vset CATEGORY defer]
[include ../doctools2base/include/feedback.inc]
[manpage_end]

Added modules/defer/defer.tcl.

















































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
#! /usr/bin/env tclsh

# Copyright (c) 2017 Roy Keene
# 
# Permission is hereby granted, free of charge, to any person obtaining a 
# copy of this software and associated documentation files (the "Software"), 
# to deal in the Software without restriction, including without limitation 
# the rights to use, copy, modify, merge, publish, distribute, sublicense, 
# and/or sell copies of the Software, and to permit persons to whom the 
# Software is furnished to do so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in 
# all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
# DEALINGS IN THE SOFTWARE.

package require Tcl 8.6

namespace eval ::defer {
	namespace export defer

	variable idVar "<defer>\n<trace variable>"
}

proc ::defer::with {args} {
	if {[llength $args] == 1} {
		set varlist [list]
		set code [lindex $args 0]
	} elseif {[llength $args] == 2} {
		set varlist [lindex $args 0]
		set code [lindex $args 1]
	} else {
		return -code error "wrong # args: defer::with ?varlist? script"
	}

	if {[info level] == 1} {
		set global true
	} else {
		set global false
	}

	# We can't reliably handle cleanup from the global scope, don't let people
	# register ineffective handlers for now
	if {$global} {
		return -code error "defer may not be used from the global scope"
	}

	# Generate an ID to un-defer if requested
	set id [clock clicks]
	for {set i 0} {$i < 5} {incr i} {
		append id [expr rand()]
	}

	# If a list of variable names has been supplied, slurp up their values
	# and add the appropriate script to set those variables in the lambda
	## Generate a list of commands to create the variables
	foreach var $varlist {
		if {![uplevel 1 [list info exists $var]]} {
			continue
		}

		if {[uplevel 1 [list array exists $var]]} {
			set val [uplevel 1 [list array get $var]]
			lappend codeSetVars [list unset -nocomplain $var]
			lappend codeSetVars [list array set $var $val]
		} else {
			set val [uplevel 1 [list set $var]]
			lappend codeSetVars [list set $var $val]
		}
	}

	## Format the above commands in the structure of a Tcl command
	if {[info exists codeSetVars]} {
		set codeSetVars [join $codeSetVars "; "]
		set code "${codeSetVars}; ${code}"
	}

	## Unset the "args" variable, which is just an artifact of the lambda
	set code "# ${id}\nunset args; ${code}"

	# Register our interest in a variable to monitor for it to disappear

	uplevel 1 [list trace add variable $::defer::idVar unset [list apply [list args $code]]]

	return $id
}

proc ::defer::defer {args} {
	set code $args
	tailcall ::defer::with $code
}

proc ::defer::autowith {script} {
	tailcall ::defer::with [uplevel 1 {info vars}] $script
}

proc ::defer::cancel {args} {
	set idList $args

	set traces [uplevel 1 [list trace info variable $::defer::idVar]]

	foreach trace $traces {
		set action [lindex $trace 0]
		set code   [lindex $trace 1]

		foreach id $idList {
			if {[string match "*# $id*" $code]} {
				uplevel 1 [list trace remove variable $::defer::idVar $action $code]
			}
		}
	}
}

package provide defer 1

Added modules/defer/defer.test.





































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# defer.test - Copyright (c) 2017 Roy Keene
# -*- tcl -*-

# -------------------------------------------------------------------------

source [file join \
	[file dirname [file dirname [file join [pwd] [info script]]]] \
	devtools testutilities.tcl]

testsNeedTcl     8.6
testsNeedTcltest 2

testing {
	useLocal defer.tcl defer
}

# -------------------------------------------------------------------------

# Series 1: defer::defer
test defer-1.0 {defer::defer simple} -setup {
	set deferTest FAIL
} -body {
	apply {{} {
		defer::defer apply {{} {
			uplevel 2 {set deferTest PASS}
		}}
	}}

	set deferTest
} -cleanup {
	unset -nocomplain deferTest
} -result {PASS}

test defer-1.1 {defer::defer fd} -setup {
	set fd [file tempfile]
} -body {
	apply {{fd} {
		defer::defer close $fd
	}} $fd

	lsearch -exact [chan names] $fd
} -cleanup {
	catch {
		close $fd
	}
	unset fd
} -result {-1}

# Series 2: defer::with
test defer-2.0 {defer::with simple} -setup {
	set deferTest FAIL
} -body {
	apply {{} {
		set withCheck true
		defer::with withCheck {
			if {$withCheck} {
				uplevel 1 {set deferTest PASS}
			}
		}
	}}

	set deferTest
} -cleanup {
	unset -nocomplain deferTest
} -result {PASS}

test defer-2.1 {defer::with fd} -setup {
	set fd [file tempfile]
} -body {
	apply {{fd} {
		defer::with fd {
			close $fd
		}
	}} $fd

	lsearch -exact [chan names] $fd
} -cleanup {
	catch {
		close $fd
	}
	unset fd
} -result {-1}

# Series 3: defer::autowith
test defer-3.0 {defer::autowith simple} -setup {
	set deferTest FAIL
} -body {
	apply {{} {
		set autoWithCheck true

		defer::autowith {
			if {$autoWithCheck} {
				uplevel 1 {set deferTest PASS}
			}
		}
	}}

	set deferTest
} -cleanup {
	unset -nocomplain deferTest
} -result {PASS}

test defer-3.1 {defer::autowith fd} -setup {
	set fd [file tempfile]
} -body {
	apply {{fd} {
		defer::autowith {
			close $fd
		}
	}} $fd

	lsearch -exact [chan names] $fd
} -cleanup {
	catch {
		close $fd
	}
	unset fd
} -result {-1}

# Series 4: defer::cancel
test defer-4.0 {defer::cancel simple} -setup {
	set deferTest FAIL-1
} -body {
	apply {{} {
		set defId [defer::with "" {
			uplevel 1 {set deferTest FAIL-2}
		}]

		defer::with "" {
			uplevel 1 {set deferTest PASS}
		}

		defer::cancel $defId
	}}

	set deferTest
} -cleanup {
	unset -nocomplain deferTest
} -result {PASS}

# Series 5: Order is LIFO
test defer-5.0 {defer is LIFO} -setup {
	set deferTest "INVALID"
} -body {
	apply {{} {
		for {set i 0} {$i < 10} {incr i} {
			defer::defer uplevel 1 [list set deferTest "RESULT:$i"]
		}
	}}

	set deferTest
} -cleanup {
	unset -nocomplain deferTest
} -result {RESULT:0}

# Series 6: Usage checks
test defer-6.0 {defer::defer global fails} -body {
	defer::defer info patchlevel
} -returnCodes ERROR -result {defer may not be used from the global scope}

test defer-6.1 {defer::defer with no args works} -body {
	apply {{} {
		defer::defer

		return "PASS"
	}}
} -result {PASS}

test defer-6.2 {defer::with syntax too few args} -body {
	apply {{} {
		defer::with
	}}
} -returnCodes ERROR -match glob -result {wrong # args: *}

test defer-6.3 {defer::with syntax too many args} -body {
	apply {{} {
		defer::with [list] error BADARG
	}}
} -returnCodes ERROR -match glob -result {wrong # args: *}

test defer-6.4 {defer::autowith syntax too few args} -body {
	apply {{} {
		defer::autowith
	}}
} -returnCodes ERROR -match glob -result {wrong # args: *}

test defer-6.5 {defer::autowith syntax too many args} -body {
	apply {{} {
		defer::autowith error BADARG
	}}
} -returnCodes ERROR -match glob -result {wrong # args: *}

test defer-6.6 {defer::cancel syntax too few args} -body {
	apply {{} {
		defer::cancel

		return "PASS"
	}}
} -result {PASS}

test defer-6.7 {defer::cancel syntax too many args} -body {
	apply {{} {
		defer::cancel A B

		return "PASS"
	}}
} -result {PASS}

# -------------------------------------------------------------------------
testsuiteCleanup

Added modules/defer/pkgIndex.tcl.











>
>
>
>
>
1
2
3
4
5
if {![package vsatisfies [package provide Tcl] 8.6]} {
    # PRAGMA: returnok
    return
}
package ifneeded defer 1 [list source [file join $dir defer.tcl]]

Changes to support/installation/modules.tcl.

51
52
53
54
55
56
57

58
59
60
61
62
63
64
Module  control      _tci _man  _null
Module  coroutine   _tcl _null  _null
Module  counter     _tcl  _man  _null
Module  crc         _tcl  _man  _null
Module  cron        _tcl  _man  _null
Module  csv         _tcl  _man _exa
Module  debug       _tcl _null  _null

Module  des         _tcl  _man  _null
Module  dicttool    _tcl  _man  _null
Module  dns          _msg _man _exa
Module  docstrip    _tcl  _man  _null
Module  doctools     _doc _man _exa
Module  doctools2base _tcl _man _null
Module  doctools2idx  _tcl _man _null







>







51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
Module  control      _tci _man  _null
Module  coroutine   _tcl _null  _null
Module  counter     _tcl  _man  _null
Module  crc         _tcl  _man  _null
Module  cron        _tcl  _man  _null
Module  csv         _tcl  _man _exa
Module  debug       _tcl _null  _null
Module  defer       _tcl  _man  _null
Module  des         _tcl  _man  _null
Module  dicttool    _tcl  _man  _null
Module  dns          _msg _man _exa
Module  docstrip    _tcl  _man  _null
Module  doctools     _doc _man _exa
Module  doctools2base _tcl _man _null
Module  doctools2idx  _tcl _man _null