summaryrefslogtreecommitdiff
path: root/lib/class.tcl
blob: f08506f3834a1ec821390190b920146d83078997 (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
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
# git-gui simple class/object fake-alike
# Copyright (C) 2007 Shawn Pearce

proc class {class body} {
	if {[namespace exists $class]} {
		error "class $class already declared"
	}
	namespace eval $class "
		variable __nextid     0
		variable __sealed     0
		variable __field_list {}
		variable __field_array

		proc cb {name args} {
			upvar this this
			concat \[list ${class}::\$name \$this\] \$args
		}
	"
	namespace eval $class $body
}

proc field {name args} {
	set class [uplevel {namespace current}]
	variable ${class}::__sealed
	variable ${class}::__field_array

	switch [llength $args] {
	0 { set new [list $name] }
	1 { set new [list $name [lindex $args 0]] }
	default { error "wrong # args: field name value?" }
	}

	if {$__sealed} {
		error "class $class is sealed (cannot add new fields)"
	}

	if {[catch {set old $__field_array($name)}]} {
		variable ${class}::__field_list
		lappend __field_list $new
		set __field_array($name) 1
	} else {
		error "field $name already declared"
	}
}

proc constructor {name params body} {
	set class [uplevel {namespace current}]
	set ${class}::__sealed 1
	variable ${class}::__field_list
	set mbodyc {}

	append mbodyc {set this } $class
	append mbodyc {::__o[incr } $class {::__nextid]::__d} \;
	append mbodyc {create_this } $class \;
	append mbodyc {set __this [namespace qualifiers $this]} \;

	if {$__field_list ne {}} {
		append mbodyc {upvar #0}
		foreach n $__field_list {
			set n [lindex $n 0]
			append mbodyc { ${__this}::} $n { } $n
			regsub -all @$n\\M $body "\${__this}::$n" body
		}
		append mbodyc \;
		foreach n $__field_list {
			if {[llength $n] == 2} {
				append mbodyc \
				{set } [lindex $n 0] { } [list [lindex $n 1]] \;
			}
		}
	}
	append mbodyc $body
	namespace eval $class [list proc $name $params $mbodyc]
}

proc method {name params body {deleted {}} {del_body {}}} {
	set class [uplevel {namespace current}]
	set ${class}::__sealed 1
	variable ${class}::__field_list
	set params [linsert $params 0 this]
	set mbodyc {}

	append mbodyc {set __this [namespace qualifiers $this]} \;

	switch $deleted {
	{} {}
	ifdeleted {
		append mbodyc {if {![namespace exists $__this]} }
		append mbodyc \{ $del_body \; return \} \;
	}
	default {
		error "wrong # args: method name args body (ifdeleted body)?"
	}
	}

	set decl {}
	foreach n $__field_list {
		set n [lindex $n 0]
		if {[regexp -- $n\\M $body]} {
			if {   [regexp -all -- $n\\M $body] == 1
				&& [regexp -all -- \\\$$n\\M $body] == 1
				&& [regexp -all -- \\\$$n\\( $body] == 0} {
				regsub -all \
					\\\$$n\\M $body \
					"\[set \${__this}::$n\]" body
			} else {
				append decl { ${__this}::} $n { } $n
				regsub -all @$n\\M $body "\${__this}::$n" body
			}
		}
	}
	if {$decl ne {}} {
		append mbodyc {upvar #0} $decl \;
	}
	append mbodyc $body
	namespace eval $class [list proc $name $params $mbodyc]
}

proc create_this {class} {
	upvar this this
	namespace eval [namespace qualifiers $this] [list proc \
		[namespace tail $this] \
		[list name args] \
		"eval \[list ${class}::\$name $this\] \$args" \
	]
}

proc delete_this {{t {}}} {
	if {$t eq {}} {
		upvar this this
		set t $this
	}
	set t [namespace qualifiers $t]
	if {[namespace exists $t]} {namespace delete $t}
}

proc make_dialog {t w args} {
	upvar $t top $w pfx this this
	global use_ttk
	uplevel [linsert $args 0 make_toplevel $t $w]
	catch {wm attributes $top -type dialog}
	pave_toplevel $pfx
}

proc make_toplevel {t w args} {
	upvar $t top $w pfx this this

	if {[llength $args] % 2} {
		error "make_toplevel topvar winvar {options}"
	}
	set autodelete 1
	foreach {name value} $args {
		switch -exact -- $name {
		-autodelete {set autodelete $value}
		default     {error "unsupported option $name"}
		}
	}

	if {$::root_exists || [winfo ismapped .]} {
		regsub -all {::} $this {__} w
		set top .$w
		set pfx $top
		toplevel $top
		set ::root_exists 1
	} else {
		set top .
		set pfx {}
	}

	if {$autodelete} {
		wm protocol $top WM_DELETE_WINDOW "
			[list delete_this $this]
			[list destroy $top]
		"
	}
}


## auto_mkindex support for class/constructor/method
##
auto_mkindex_parser::command class {name body} {
	variable parser
	variable contextStack
	set contextStack [linsert $contextStack 0 $name]
	$parser eval [list _%@namespace eval $name] $body
	set contextStack [lrange $contextStack 1 end]
}
auto_mkindex_parser::command constructor {name args} {
	variable index
	variable scriptFile
	append index [list set auto_index([fullname $name])] \
		[format { [list source [file join $dir %s]]} \
		[file split $scriptFile]] "\n"
}