-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathParam.tcl
420 lines (351 loc) · 10.9 KB
/
Param.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
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
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
if { [namespace exists ::Param] } return
source [file join [file dirname [info script]] .. tcl-Utils Debug.tcl]
source [file join [file dirname [info script]] .. tcl-Utils ProcAccess.tcl]
namespace eval ::Param {
variable basetypes_ {}
variable typedefs_ {}
variable cnt_ 0
variable rangeErrorCmd_ {}
namespace import ::Debug::vputs ::Debug::verboseDo ::Debug::dumpDict
public proc basetype { name {vtorNamespace {}} {replace 0} } {
if { [isBasetype $name] && !$replace} {
return -code error "Duplicate basetype name '$name'"
}
if { "$vtorNamespace" == "" } {
# Assume BUILTIN vtorNamespace
set vtorNamespace "::Param::VTOR::$name"
}
if { ![namespace exists $vtorNamespace] } {
return -code error "Validator namespace undefined '$vtorNamespace'"
}
variable basetypes_
dict set basetypes_ $name $vtorNamespace
set doTypedef 1
if { "" != "[info vars ${vtorNamespace}::createTypedef_]" } {
set doTypedef [set ${vtorNamespace}::createTypedef_]
}
if { $doTypedef } {
# create no-range typedef with same name as basetype
typedef $name $name
}
if { "" != "[info procs ${vtorNamespace}::registerAliases]" } {
# rename so proc cannot be called again if vtorNamespace is used by a
# basetype alias!
rename ${vtorNamespace}::registerAliases ${vtorNamespace}::registerAliasesImpl
# let basetype create aliases
${vtorNamespace}::registerAliasesImpl
}
}
public proc typedef { basetype name {range {}} {replace 0} } {
if { ![isBasetype $basetype] } {
return -code error "Invalid typedef basetype '$name'"
}
if { [isTypedef $name] && !$replace} {
return -code error "Duplicate typedef name '$name'"
}
if { ![isBasetype $basetype] } {
return -code error "Invalid basetype '$basetype'"
}
variable basetypes_
variable typedefs_
set vtorNamespace [dict get $basetypes_ $basetype]
dict set typedefs_ $name BaseType $basetype
dict set typedefs_ $name Limits [${vtorNamespace}::parseRange $range]
dict set typedefs_ $name Range $range
if { [namespace exists ::Param::$name] } {
return -code error "Typedef namespace collision '$name'"
}
# build typedef's ensemble
variable typedefProto_
namespace eval ::Param::$name $typedefProto_
set ::Param::${name}::self_ $name
if { "" != "[info vars ${vtorNamespace}::defaultValue_]" } {
# validator wants to modify the typedef default value
set ::Param::${name}::defaultValue_ [set ${vtorNamespace}::defaultValue_]
}
if { "" != "[info vars ${vtorNamespace}::staticProto_]" } {
# validator wants to modify the typedef ensemble
namespace eval ::Param::$name [set ${vtorNamespace}::staticProto_]
}
namespace eval ::Param::$name {
namespace ensemble create
}
return $name
}
public proc new { type args } {
variable basetypes_
variable typedefs_
if { ![isTypedef $type] } {
return -code error "Unknown Param type '$type' must be one of [dict keys $typedefs_]"
}
variable cnt_
variable paramProto_
set ret "::Param::param_[incr cnt_]"
namespace eval $ret $paramProto_
set ${ret}::self_ $ret
set ${ret}::type_ $type
set vtorNamespace [getValidator $type]
if { "" != "[info vars ${vtorNamespace}::objectProto_]" } {
#vputs "${vtorNamespace}::objectProto_ =\n[set ${vtorNamespace}::objectProto_]"
namespace eval $ret [set ${vtorNamespace}::objectProto_]
}
# now we can create the ensemble
namespace eval $ret {
namespace ensemble create
}
# assign ctor value
if { 0 == [llength $args] } {
$ret = [::Param::$type getDefaultValue]
} else {
$ret = {*}$args
}
return $ret
}
public proc isBasetype { name } {
variable basetypes_
return [dict exists $basetypes_ $name]
}
public proc getBasetype { typedefName } {
variable typedefs_
return [dict get $typedefs_ $typedefName BaseType]
}
public proc getBasetypes { } {
variable basetypes_
return [dict keys $basetypes_]
}
public proc getTypedefs { } {
variable typedefs_
return [dict keys $typedefs_]
}
public proc getValidator { type } {
if { [isTypedef $type] } {
set type [getBasetype $type]
}
variable basetypes_
return [dict get $basetypes_ $type]
}
public proc getLimits { type } {
if { ![isTypedef $type] } {
return -code error "Unknown Param type '$type' must be one of [dict keys $typedefs_]"
}
variable typedefs_
return [dict get $typedefs_ $type Limits]
}
public proc getRange { type } {
if { ![isTypedef $type] } {
return -code error "Unknown Param type '$type' must be one of [dict keys $typedefs_]"
}
variable typedefs_
return [dict get $typedefs_ $type Range]
}
public proc getRangeSignature { type } {
return [set [getValidator $type]::rangeSignature_]
}
public proc setRangeErrorCmd { cb } {
variable rangeErrorCmd_
set oldCB $rangeErrorCmd_
set rangeErrorCmd_ $cb
return $oldCB
}
public proc getRangeErrorCmd { } {
variable rangeErrorCmd_
return $rangeErrorCmd_
}
public proc isTypedef { name } {
variable typedefs_
return [dict exists $typedefs_ $name]
}
# namespace for BUILTIN validators
namespace eval VTOR {
}
# ================================= PRIVATE =================================
private proc init {} {
#basetype enum
#basetype boolean
set scriptDir [file dirname [info script]]
set basetypesDir [file join $scriptDir basetypes]
foreach basetypeFile [glob -directory $basetypesDir -type f *.basetype.tcl] {
# Capture "name?-namespace?" from "/path/to/name?-namespace?.basetype.tcl"
lassign [split [file tail $basetypeFile] "."] name
# Capture "name" and "namespace" from "name?-namespace?"
lassign [split $name "-"] name nspace
if { "" != "$nspace" } {
# Make validator namespace a child of the ::Param::VTOR namespace
set nspace "::Param::VTOR::$nspace"
}
if { [namespace exists $nspace] } {
return -code error "Duplicate validator '$nspace' in '$basetypeFile'."
}
# load validator
namespace eval VTOR [list source "$basetypeFile"]
# register new basetype with ::Param
basetype $name $nspace
}
verboseDo {
Param::dump "[namespace current]::init"
}
}
private proc dump { title } {
variable basetypes_
variable typedefs_
puts {}
puts "$title \{"
dumpDict "::Param::basetypes_|Base Type|Validator Namespace" $basetypes_ 1
dumpDict "::Param::typedefs_|Type Name|Limits" $typedefs_ 1
puts "\}"
}
private proc notifyRangeError { obj valVar } {
upvar $valVar val
variable rangeErrorCmd_
# Give Param first dibs
set ret [invokeRangeErrorCmd $rangeErrorCmd_ $obj val]
if { "fatal" == "$ret" } {
# Not handled by Param. Give the $obj's typedef namespace a chance
set ret [::Param::[$obj getType]::notifyRangeError $obj val]
}
if { "fatal" == "$ret" } {
# Not handled by Param or typedef. Give $obj a chance
set ret [${obj}::notifyRangeError val]
}
return $ret ;# 0 means not handled
}
private proc invokeRangeErrorCmd { cb obj valVar } {
upvar $valVar val
set ret fatal
if { "" != "$cb" } {
#puts "### invokeRangeErrorCmd '$cb' '$obj' '$val'"
set ret [{*}$cb $obj val]
}
return $ret
}
# standard typedef static commands
variable typedefProto_ {
variable self_ {}
variable rangeErrorCmd_ {}
variable defaultValue_ {}
public proc setRangeErrorCmd { cmd } {
variable rangeErrorCmd_
set oldCmd $rangeErrorCmd_
set rangeErrorCmd_ $cmd
return $oldCmd
}
public proc getRangeErrorCmd { } {
variable rangeErrorCmd_
return $rangeErrorCmd_
}
public proc setDefaultValue { val } {
variable defaultValue_
set ret $defaultValue_
set defaultValue_ $val
return $ret
}
public proc getDefaultValue { } {
variable defaultValue_
return $defaultValue_
}
private proc notifyRangeError { obj valVar } {
upvar $valVar val
variable rangeErrorCmd_
return [::Param::invokeRangeErrorCmd $rangeErrorCmd_ $obj val]
}
}
# standard param object commands
variable paramProto_ {
variable self_ {}
variable type_ {}
variable val_ {}
variable rangeErrorCmd_ {}
public proc = { args } {
variable self_
variable type_
variable val_
if { 1 == [llength $args] } {
set val [lindex $args 0]
} else {
set val "$args"
}
if { [[::Param getValidator $type_]::validate val [::Param getLimits $type_]] } {
# val is good - use it
return [set val_ $val]
}
# give any range error handlers a chance
switch [set nre [::Param::notifyRangeError $self_ val]] {
fatal {
# trigger an error
}
again {
# value modified - validate it again
if { [[::Param getValidator $type_]::validate val [::Param getLimits $type_]] } {
# A range error handler fixed val - use it
return [set val_ $val]
}
# validation failed again - trigger an error
}
ignore {
# Ignore error and leave val_ alone.
return $val_
}
force {
# Force assignment of val to val_. Note that val MAY have been modified
# by notifyRangeError and could still be an invalid value.
return [set val_ $val]
}
default {
return -code error "Invalid return from notifyRangeError '$nre'"
} }
# invalid val
return -code error "Value [list $val] not in range [list [::Param getRange $type_]]"
}
public proc setValue { args } {
= {*}$args
}
public proc getValue { } {
variable val_
return $val_
}
public proc getType { } {
variable type_
return $type_
}
public proc getLimits { } {
variable type_
return [::Param getLimits $type_]
}
public proc getRange { } {
variable type_
return [::Param getRange $type_]
}
public proc toString {} {
variable self_
variable type_
variable val_
return "${self_}: type($type_) value($val_)"
}
public proc dump {} {
variable self_
puts [$self_ toString]
}
public proc setRangeErrorCmd { cb } {
variable rangeErrorCmd_
set oldCB $rangeErrorCmd_
set rangeErrorCmd_ $cb
return $oldCB
}
public proc getRangeErrorCmd { } {
variable rangeErrorCmd_
return $rangeErrorCmd_
}
public proc delete {} {
variable self_
namespace delete $self_
}
private proc notifyRangeError { valVar } {
upvar $valVar val
variable self_
variable rangeErrorCmd_
return [::Param::invokeRangeErrorCmd $rangeErrorCmd_ $self_ val]
}
}
namespace ensemble create
}
Param::init