This is an annotation package for TclOO. See the example code at the end for how to use and a bit above that for how to derive new annotations.
Still to do:
package require Tcl 8.6 package require TclOO package provide TclOO::annotate namespace eval ::oo::Annotations { variable classInfo array set classInfo {} variable objInfo array set objInfo {} proc ::oo::InfoClass::annotations {className {annotationName {}} args} { variable ::oo::Annotations::classInfo set className [uplevel 1 [list namespace which $className]] if {[llength [info level 0]] == 2} { if {![info exists classInfo($className)]} { return {} } return [dict keys $classInfo($className)] } if { ![info exists classInfo($className)] || ![dict exists $classInfo($className) $annotationName] } then { return -code error "unknown annotation \"$annotationName\"" } set result {} foreach handlerObj [dict get $classInfo($className) $annotationName] { try { $handlerObj describe result {*}$args } on error msg { return -code error $msg } } return $result } proc ::oo::InfoObject::annotations {objectName {annotationName {}} args} { variable ::oo::Annotations::objInfo set objectName [uplevel 1 [list namespace which $objectName]] if {$annotationName eq ""} { if {![info exists objInfo($objectName)]} { return {} } return [dict keys $objInfo($objectName)] } if { ![info exists objInfo($objectName)] || ![dict exists $objInfo($objectName) $annotationName] } then { return -code error "unknown annotation \"$annotationName\"" } set result {} foreach handlerObj [dict get $objInfo($objectName) $annotationName] { try { $handlerObj describe result {*}$args } on error msg { return -code error $msg } } return $result } variable globalAnnotations {} proc ::unknown {args} [concat { if {[string match @* [lindex $args 0]]} { lappend ::oo::Annotations::globalAnnotations $args return }; } [info body ::unknown]] ::oo::define ::oo::class constructor {{definitionScript {}}} {\ set script [list ::oo::define [self] $definitionScript];\ ::oo::Annotations::AttachGlobalAnnotations [self];\ lassign [::oo::UpCatch $script] msg opts if {[dict get $opts -code] == 1} { dict set opts -errorline 0xDeadBeef } return -options $opts $msg } proc AttachGlobalAnnotations cls { variable globalAnnotations variable classInfo set ga $globalAnnotations set globalAnnotations {} set clsAnnotations {} try { foreach annotationSpec $ga { set annotationType \ [string range [lindex $annotationSpec 0] 1 end] set args [lrange $annotationSpec 1 end] lappend clsAnnotations \ [Annotation.$annotationType new class {*}$args] } } on error {msg opt} { foreach a $clsAnnotations { $a destroy } return -options $opt $msg } foreach a $clsAnnotations { set name [$a name] $a register class {} if { ![info exists classInfo($cls)] || ![dict exists $classInfo($cls) $name] } then { dict set classInfo($cls) $name {} } dict lappend classInfo($cls) $name $a } } proc DefineUnknown {cmd args} { if {[string match @* $cmd]} { try { AnnotateClass [lindex [info level -1] 1] \ [string range $cmd 1 end] {*}$args return } on error msg { return -code error $msg } } tailcall ::oo::UnknownDefinition $cmd {*}$args } proc ObjdefineUnknown {cmd args} { if {[string match @* $cmd]} { if {[lindex [info level -1] 0] eq "self"} { set obj [lindex [info level -2] 1] } else { set obj [lindex [info level -1] 1] } try { AnnotateObject $obj [string range $cmd 1 end] {*}$args return } on error msg { return -code error $msg } } tailcall ::oo::UnknownDefinition $cmd {*}$args } namespace eval ::oo::define [list \ namespace unknown [namespace which DefineUnknown]] namespace eval ::oo::objdefine [list \ namespace unknown [namespace which ObjdefineUnknown]] namespace eval RealDefines {} namespace eval RealObjDefines {} apply [list {} { foreach cmd [info commands ::oo::define::*] { set tail [namespace tail $cmd] set target ::oo::Annotations::RealDefines::$tail rename $cmd $target proc $cmd args " ::oo::Annotations::ClassDefinition $tail {*}\$args tailcall [list $target] {*}\$args " } foreach cmd [info commands ::oo::objdefine::*] { set tail [namespace tail $cmd] set target ::oo::Annotations::RealObjDefines::$tail rename $cmd $target proc $cmd args " ::oo::Annotations::ObjectDefinition $tail {*}\$args tailcall [list $target] {*}\$args " } } [namespace current]] variable subject variable currentAnnotators proc AnnotateClass {subjectClass annotationType args} { variable subject $subjectClass variable currentAnnotators lappend currentAnnotators \ [Annotation.$annotationType new class {*}$args] } proc AnnotateObject {subjectObject annotationType args} { variable subject $subjectObject variable currentAnnotators lappend currentAnnotators \ [Annotation.$annotationType new object {*}$args] } proc ClassDefinition {operation args} { variable currentAnnotators if {![info exists currentAnnotators]} { return } variable subject variable classInfo try { foreach a $currentAnnotators { set name [$a name] $a register $operation {*}$args if { ![info exists classInfo($subject)] || ![dict exists $classInfo($subject) $name] } then { dict set classInfo($subject) $name {} } dict lappend classInfo($subject) $name $a set currentAnnotators [lrange $currentAnnotators 1 end] } } on error msg { foreach a $currentAnnotators { $a destroy } return -level 2 $msg } finally { unset currentAnnotators } } proc ObjectDefinition {operation args} { variable currentAnnotators if {![info exists currentAnnotators]} { return } variable subject variable objInfo try { foreach a $currentAnnotators { set name [$a name] $a register $operation {*}$args if { ![info exists objInfo($subject)] || ![dict exists $objInfo($subject) $name] } then { dict set objInfo($subject) $name {} } dict lappend objInfo($subject) $name $a set currentAnnotators [lrange $currentAnnotators 1 end] } } on error msg { foreach a $currentAnnotators { $a destroy } return -level 2 $msg } finally { unset currentAnnotators } } ::oo::class create annotation { unexport create variable annotation Type Operation constructor {type args} { set Type $type my MayApplyToType $type my RememberAnnotationArguments $args } method MayApplyToType type { throw ANNOTATION {may not apply this annotation to that type} } method MayApplyToOperation operation { throw ANNOTATION {may not apply that annotation to this operation} } method RememberAnnotationArguments values { set annotation $values } method QualifyAnnotation args { # Do nothing by default } method name {} { set name [namespace tail [info object class [self]]] return [regsub {^Annotation.} $name @] } method register {operation args} { set Operation $operation my MayApplyToOperation $operation my QualifyAnnotation {*}$args } method describe {varName} { upvar 1 $varName v lappend v $annotation } } ::oo::class create classannotation { superclass annotation method MayApplyToType type { if {$type ne "class"} {next $type} } } ::oo::class create objectannotation { superclass annotation method MayApplyToType type { if {$type ne "object"} {next $type} } } } namespace eval ::oo::Annotations { oo::class create Annotation.Describe { superclass oo::Annotations::classannotation \ oo::Annotations::objectannotation variable annotation Operation method method MayApplyToOperation operation { if {$operation ni {method forward constructor class}} { next $operation } } method QualifyAnnotation {name args} { if {$Operation eq "constructor"} { set method <<constructor>> } else { set method $name } } method describe {varName {name {}}} { upvar 1 $varName v if {[llength [info level 0]] == 3} { dict set v $method [join $annotation] } elseif {$method eq $name} { set v [join $annotation] return -code break } } } oo::class create Annotation.Result { superclass Annotation.Describe method MayApplyToOperation operation { if {$operation eq "constructor"} { throw ANNOTATE "not on a constructor" } next $operation } } oo::class create Annotation.SideEffects { superclass Annotation.Describe } oo::class create Annotation.Argument { superclass oo::Annotations::classannotation \ oo::Annotations::objectannotation variable annotation Operation method argument constructor {type argName args} { set argument $argName next $type {*}$args } method MayApplyToOperation operation { if {$operation ni {method forward constructor}} {next $operation} } method QualifyAnnotation {name args} { if {$Operation eq "constructor"} { set method <<constructor>> } else { set method $name } } method describe {varName {name {}} {argname {}}} { upvar 1 $varName v if {[llength [info level 0]] == 3} { lappend v $method } elseif {$method eq $name} { if {[llength [info level 0]] == 4} { lappend v $argument } elseif {$argument eq $argname} { set v [join $annotation] return -code break } } } } }
@Describe This is a whole class annotation! oo::class create example { @Describe This is the foo method. It has multiple annotations \ attached to it. @Argument x The first argument to the method. @Argument args The list of remaining arguments to the method. @Result None. @SideEffects Prints to stdout. method foo {x args} { puts foo } @Describe This is the bar method. method bar args { puts bar } } puts annotations:\t[info class annotation example] puts example-desc:\t[info class annotation example @Describe {}] puts desc-foo:\t[info class annotation example @Describe] puts desc-foo:\t[info class annotation example @Describe foo] puts args-foo:\t[info class annotation example @Argument foo] foreach a [info class annotation example @Argument foo] { puts args-foo-${a}:\t[info class annotation example @Argument foo $a] } puts result-foo:\t[info class annotation example @Result foo] puts effects-foo:\t[info class annotation example @SideEffects foo]
The output from this is:
annotations: @Describe @Argument @Result @SideEffects example-desc: This is a whole class annotation! desc-foo: {} {This is a whole class annotation!} foo {This is the foo method. It has multiple annotations attached to it.} bar {This is the bar method.} desc-foo: This is the foo method. It has multiple annotations attached to it. args-foo: x args args-foo-x: The first argument to the method. args-foo-args: The list of remaining arguments to the method. result-foo: None. effects-foo: Prints to stdout.