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
|
# -*- Tcl -*-
package prefer latest
package req nx
package req nx::test
#
# export | unexport
#
# TclOO provides a bulk declarator to export (i.e., make
# visible and accessible) and to unexport (i.e., make invisible and
# inaccessible) method features of an object, a class, or a class
# hierarchy. Note that export and unexport go beyond applying mere
# visibility/accessibility modifiers; it is also about extending or
# reducing the public method interface of an object along the
# linearization path (or of derived, intermediary classes in an
# inheritance hierarchy). This export|unexport can be realized by
# assembling some NSF building blocks: method call protection,
# selective next forwarding, ...
#
# Internally, exporting a TclOO method means adding to its C-level
# rep's flags PUBLIC_METHOD; unexporting consists of withdrawing it
# (again). An unexported, non-public TclOO method can only me invoked
# upon through a self send (i.e., the my command). This corresponds
# somewhat to NSF's call protection property. In addition, [export] or
# [unexport] extends the method record of an object (or class) type in
# case the method to be exported or to be unexported has not yet been
# defined (on the exporting or unexporting object or class). These
# "extension methods", however, are mere method stubs, they do not
# contain a method implementation (a proc). Without any invokable
# method impl, they are skipped during method dispatch (as in an
# implicit next call). Using the method stubs, the public interface
# (i.e., the interface dispatchable through an object's Tcl_command)
# can be extended or shrunk by selectively enabling or disabling
# shadowed (inherited) method implementations along the instande-of or
# the inheritance relationships. Exported or unexported, yet
# unimplemented methods are treated as unknowns.
#
# Below is a prototype implementation of the export|unexport feature
# for NSF/Nx. The realization is complete as testable through the
# respective TclOO test cases in oo.test, test cases 4.1-4.6. The
# export|unexport stub methods are fully reported by NSF/Nx method
# introspection, as they are in TclOO.
nsf::proc methodExport {current {-perObject:switch false} {-callProtected:switch false} args} {
set scope [expr {$perObject?"object":"class"}]
foreach m $args {
set methodHandle [::nsf::dispatch $current \
::nsf::methods::${scope}::info::method registrationhandle $m]
if {$methodHandle eq ""} {
set methodHandle [::nsf::method::create $current {*}[expr {$perObject?"-per-object":""}] $m args {
if {[::nsf::current nextmethod] eq ""} {
return -code error "[::nsf::current]: unable to dispatch method '[::nsf::current method]'"
}
::nsf::next
}]
}
::nsf::method::property $current $methodHandle call-protected $callProtected
}
return
}
nx::Object public method export args {
methodExport [::nsf::current] -perObject {*}$args
}
nx::Class public method export args {
methodExport [::nsf::current] {*}$args
}
nx::Object public method unexport args {
methodExport [::nsf::current] -perObject -callProtected {*}$args
}
nx::Class public method unexport args {
methodExport [::nsf::current] -callProtected {*}$args
}
nx::Class create ExportUnexportUtil {
:public method class {what args} {
if {$what in {export unexport}} {
return [::nsf::dispatch [current] ::nsf::classes::nx::Object::$what {*}$args]
}
::nsf::next
}
}
nx::Class mixins add ExportUnexportUtil
nx::test case export {
#
# Exporting existing, non-inherited method (see TclOO tests,
# oo.test, oo-4.1)
#
set o [nx::Object new]
$o object method Foo {} { return [::nsf::current method]}
? [list $o Foo] "$o: unable to dispatch method 'Foo'"
? [list $o eval {:Foo}] Foo
$o export Foo
? [list $o Foo] "Foo"
? [list $o eval {:Foo}] Foo
#
# A solitary, preemptive [export]: In TclOO, [::oo::define export]
# creates a method record entry which does not have any
# implementation (body) attached and which is deprived of its property of
# a PUBLIC_METHOD. This non-implemented, body-less method (if not
# succeeded by an implemented one) will be reported as unknown
# method (see e.g. TclOO tests, oo.test, oo-4.3)
#
# As we actually simulate the TclOO non-implemented method record
# entries by full-fledged NSF methods, with a specific body (a next
# call), we need to handle the solitary case, i.e., the case when
# there is no method implementation available. We do so by
# inspecting whether there is a next method to be called; if not, we
# throw an unknown error.
#
? [list $o bar] "$o: unable to dispatch method 'bar'" "bar is neither defined, nor declared exported"
$o export bar
? [list $o bar] "$o: unable to dispatch method 'bar'" "bar is exported, yet not defined anywhere"
? [list $o eval {:bar}] "$o: unable to dispatch method 'bar'" "bar is exported, yet not defined anywhere (self send)"
#
# Exporting a per-class method from one of the class' instances (see
# TclOO tests, oo.test, oo-4.4)
#
Class create testClass {
# protected (non-exported) by default
:method Good {} { return ok }
:method Fine {} { return OK }
:method Finest {} {return ko }
:create testObject
}
? {testObject Good} "::testObject: unable to dispatch method 'Good'"
? {testObject eval {:Good}} ok
testObject export Good
? {testObject Good} ok
#
# Exporting a per-class method from within the class
#
? {testObject Fine} "::testObject: unable to dispatch method 'Fine'"
? {testObject eval {:Fine}} OK
testClass export Fine
? {testObject Fine} OK
? {testObject eval {:Fine}} OK
#
# Exporting a per-class method by a subclass
#
Class create anotherTestClass -superclass testClass {
:create anotherTestObject
}
? {anotherTestObject Finest} "::anotherTestObject: unable to dispatch method 'Finest'"
anotherTestClass export Finest
? {anotherTestObject Finest} ko
#
# export creates ordinary methods, to be replaced by subsequent
# once, see TclOO tests, oo.test, oo-4.5
#
nx::Object create bran {
:export foo
:public object method foo {} {return ok}
}
? {bran foo} ok
bran eval {
:unexport foo
}
? {bran foo} "::bran: unable to dispatch method 'foo'"
}
nx::test case unexport {
# A solitary, preemptive [unexport]: see description for the
# corresponding [export] case
set p [Object new]
? [list $p bar] "$p: unable to dispatch method 'bar'"
$p unexport bar
? [list $p bar] "$p: unable to dispatch method 'bar'"
? [list $p eval {:bar}] "$p: unable to dispatch method 'bar'"
#
# unexport existing, non-inherited method (see TclOO tests:
# oo.test/oo-4.2)
#
set o [nx::Object new]
$o public object method foo {} { return [::nsf::current method]}
? [list $o foo] foo
? [list $o eval {:foo}] foo
$o unexport foo
? [list $o foo] "$o: unable to dispatch method 'foo'" "foo was made 'protected'"
? [list $o eval {:foo}] foo "foo is still available for self sends"
#
# unexport any (e.g., inherited) methods
#
Class create C {
:public method foo {} {return ok}
}
set c [C new]
? [list $c foo] ok
? [list $c eval {:foo}] ok
$c unexport foo
? [list $c foo] "$c: unable to dispatch method 'foo'" "created a protected dummy"
? [list $c eval {:foo}] ok "foo is still available for self sends (through a next send in the dummy)"
#
# unexport existing method at the class level
#
C eval {
:public method bar {} {return OK}
:public method baz {} {return ko}
}
? [list $c bar] OK
? [list $c eval {:bar}] OK
C unexport bar
? [list $c bar] "$c: unable to dispatch method 'bar'" "created a protected dummy"
? [list $c eval {:bar}] OK "bar is still available for self sends (through a next send in the dummy)"
#
# unexport any (e.g., an inherited) method at the class level
#
nx::Class create D -superclass C
set d [D new]
? [list $d bar] "$d: unable to dispatch method 'bar'" "shielded by protected dummy at the level of class C"
? [list $d eval {:bar}] OK
? [list $d baz] ko
D unexport baz
? [list $d baz] "$d: unable to dispatch method 'baz'"
? [list $d eval {:baz}] ko
#
# unexport creates ordinary methods, to be fully replaced by subsequent
# method declarations, see TclOO tests, oo.test, oo-4.6
#
Class create testClass2 {
:unexport foo
:public method foo {} {return ok}
}
? {[testClass2 new] foo} ok
#
# https://rosettacode.org/wiki/Abstract_type
#
nx::Class create AbstractQueue {
:method enqueue item {
error "not implemented"
}
:method dequeue {} {
error "not implemented"
}
:class unexport create new
}
? {AbstractQueue new} {method 'new' unknown for ::AbstractQueue; in order to create an instance of class ::AbstractQueue, consider using '::AbstractQueue create new ?...?'}
? {AbstractQueue create aQueue} {method 'create' unknown for ::AbstractQueue; in order to create an instance of class ::AbstractQueue, consider using '::AbstractQueue create create ?...?'}
}
#
# Local variables:
# mode: tcl
# tcl-indent-level: 2
# indent-tabs-mode: nil
# End:
|