namespace class foo { common counter variable names proc __init__ {args} { common counter incr counter variable names array set names $args } # it has to look like a normal Tcl proc, so it has an arglist proc __destroy__ {} { common counter incr counter -1 } proc display {} { variable names puts "My name is $names(first) $names(last)" } proc count {} { common counter return $counter } } foo new name first Richard last Heinz name destroy set name [foo %AUTO% first Richard last Heinz] $name display set name::names(first) George $name display # generates an error, because it is an instance method #foo display $name destroyWhere common would behave like variable, with a static qualifier. I submit that a sequence of variable bar may be replaced by instance command that wraps all variable calls related to (instance) variables.
basique.tcl
namespace eval ::basique { namespace export class variable classes array set classes {} proc class {name code} { if {[string range $name 0 1] ne "::"} { set name ::$name } variable classes if {[info exists classes($name)]} { cdelete $name } set common_prelude [string map [list %NAME% $name] { # does nothing proc variable {var args} { lappend ::basique::classes(%NAME%,instvars) $var } # provide common variable statement proc common {var args} { if {[llength $args] > 1} { error "common varname ?default?" } if {[llength $args] == 0} { uplevel 1 ::variable $var return } uplevel 1 ::variable $var [lindex $args 0] return } proc alias {name args} { if {[llength $name] != 1} { error "cannot create such a composite alias" } proc $name {args} [string map [list ARGS $args] { eval [linsert $args 0 ARGS] }] } proc component {var args} { foreach {opt val} $args { switch -- $opt { -common {set common $val} -initscript {::basique::initscript %NAME% $val $var $common} -destroyscript {::basique::destroyscript %NAME% $val $var $common} default { error "option should be one of : -common -initscript -destroyscript" } } } if {![info exists common] || !$common} { return } proc $var {subcmd args} [string map [list VAR $var] { common VAR uplevel 1 [linsert $args 0 $VAR $subcmd] }] } }] namespace eval $name $common_prelude\n$code if {[info exists classes($name,compinit)]} { namespace eval $name $classes($name,compinit) } catch {${name}::__classinit__} set classes($name) $code CreateClass $name } proc initscript {class script var common} { variable classes if {$common} { append classes($class,compinit) "common $var\n $script\n" } else { append classes($class,instcompinit) "variable $var\n $script\n" } } proc destroyscript {class script var common} { variable classes if {$common} { append classes($class,compdestroy) "common $var\n $script\n" } else { append classes($class,instcompdestroy) "variable $var\n $script\n" } } proc cdelete {name} { variable classes unset classes($name) catch {${name}::__classdestroy__} catch {namespace eval $name $classes($name,compdestroy)} foreach sub {instvars compinit instcompinit compdestroy instcompdestroy} { catch {unset classes($name,$sub)} } catch {namespace delete $name} catch {rename $name ""} } # crée la commande qui instanciera les objets proc CreateClass {name} { variable classes # the 'real' constructor (__init__ is the one at user level) # le constructeur réel (le constructeur public est __init__) proc $name {args} [string map [list %NAME% $name] { if {![llength $args]} { set args {%AUTO%} } if {[lindex $args 0] eq "%AUTO%"} { set args [linsert $args 0 new] } switch -- [lindex $args 0] { new { set instance [lindex $args 1] if {$instance eq "%AUTO%"} { set instance [::basique::autoname %NAME%] } set args [lrange $args 2 end] } default { return [uplevel 1 namespace eval %NAME% $args] } } if {[string range $instance 0 1] ne "::"} { set instance ::$instance } # creates the prelude set instance_prelude [string map [list %OBJ% $instance] { proc common {var args} { if {[llength $args]>1} { error "common var ?default?" } if {[llength $args]==0} { uplevel 1 [list upvar %NAME%::$var $var] } } proc body {name arglist body} { proc %OBJ%::$name $arglist $body } proc . {varname} { return %OBJ%::$varname } proc self {} { return %OBJ% } proc -> {varname} { variable $varname set $varname } proc component {var args} { foreach {opt val} $args { switch -- $opt { -common {set common $val} -initscript - -destroyscript {} default { error "option should be one of : -common -initscript -destroyscript" } } } if {![info exists common] || !$common} { set type variable uplevel 1 variable $var } else { set type common } proc $var {subcmd args} [string map [list TYPE $type VAR $var] { TYPE VAR uplevel 1 [linsert $args 0 $VAR $subcmd] }] } proc alias {name args} { if {[llength $name] != 1} { error "cannot create such a composite alias" } proc $name {args} [string map [list ARGS $args] { uplevel 1 [linsert $args 0 ARGS] }] } }] # insert 'instance' where you want quickly to get instance variables # (instead, you would have to type lots of 'variable' statements) append instance_prelude "proc instance \{\} \{\nuplevel 1 \{\n" if {[info exists ::basique::classes(%NAME%,instvars)]} { foreach var $::basique::classes(%NAME%,instvars) { append instance_prelude "variable $var\n" } } append instance_prelude "\}\n\}\n" # %NAME% is preprocessed into ::myclass # then ::myclass body's namespace is mapped into the instance namespace set body [string map [list [string trim %NAME% :]:: \ [string trim $instance :]::] $::basique::classes(%NAME%)] namespace eval $instance $instance_prelude namespace eval $instance $body # builds the instance #puts before if {[info exists ::basique::classes(%NAME%,instcompinit)]} { # initscript at instance level namespace eval %NAME% $::basique::classes(%NAME%,instcompinit) } uplevel 1 [linsert $args 0 ${instance}::__init__] #puts after proc $instance {command args} [string map [list %OBJ% $instance] { if {$command eq "destroy"} { %OBJ%::__destroy__ if {[info exists ::basique::classes(%NAME%,instcompdestroy)]} { # destroyscript at instance level namespace eval %NAME% $::basique::classes(%NAME%,instcompdestroy) } catch {namespace delete %OBJ%} catch {rename %OBJ% ""} return } switch -- $command { __init__ - __destroy__ - __classinit__ - __classdestroy__ - common - instance { error "protected command" } default { return [uplevel 1 [linsert $args 0 %OBJ%::$command]] } } }] return $instance }] } proc lfilter {var list condition} { upvar $var x set out "" foreach x $list { if {[uplevel 1 expr $condition]} { lappend out $x } } return $out } proc autoname {name} { variable classes if {![info exists classes($name)]} { error "class $name not found" } if {![info exists classes($name,counter)]} { set classes($name,counter) 0 } while {[llength [info procs ${name}__$classes($name,counter)]]} { incr classes($name,counter) } return ${name}__$classes($name,counter) } } package provide basique 1.0
Example
basique::class foo { variable name proc __init__ {vname} { instance set name $vname } proc __destroy__ {} { instance puts "Goodbye $name !" } } foo new a Arnold set [a . name] Meyer puts [a -> name] a destroyNow let's build a singleton handler :
basique::class Foo { common instance proc __classdestroy__ {} { common instance $instance destroy } proc __init__ {} { if {[uplevel namespace current] ne "::Foo"} { error "cannot instanciate singleton class" } } proc __destroy__ {} { } proc getInstance {} { common instance if {![info exists instance]} { set instance [::Foo] } return $instance } } set inst "" foreach i {1 2 3} { lappend inst [Foo getInstance] } puts "singletons : $inst"
ANON: 2006-07-26 - Nice Work!I wonder how to distinguish those namespaces as classes or instances from any other namespace.Sarnold 2006-07-29 : You really hit the point ! Since those classes and objects are instances of namespaces, there is, in theory, no way to distinguish them from ordinary namespaces.But I can tell you that, to make them more object-like, for every classe and object, a command with the same name as the namespace is created. Indeed we can do :
MyClass cook veryhotas well as :
MyClass::cook veryhotAnd in fact, you are invited to use the "object-like" method for creation and destruction of objects.So, back to our subject, to test if it is not an ordinary namespace,
proc isobject {name} {llength [info procs MyClass]} if {[isobject MyClass]} {...} MyClass a if {[isobject $a]} {...}
2008-07-14 - Updated for Fiction!. -- Sarnold