Index: library/lib/doc-tools.xotcl =================================================================== diff -u -rdf07993bf4e3486dbfaa090b56291767deea6696 -r9ce60e39bd1960823ae6f2c2e1d7836a86c90ba5 --- library/lib/doc-tools.xotcl (.../doc-tools.xotcl) (revision df07993bf4e3486dbfaa090b56291767deea6696) +++ library/lib/doc-tools.xotcl (.../doc-tools.xotcl) (revision 9ce60e39bd1960823ae6f2c2e1d7836a86c90ba5) @@ -41,19 +41,25 @@ return $result } - Class create EntityFactory -superclass Class { + Class create EntityClass -superclass Class { # - # EntityFactory is a meta-class for named doc entities + # EntityClass is a meta-class for named doc entities # + # TODO: currently, explicitly specifying "substdefault" + # e.g. tag:substdefault leads to issues in the toParameterSyntax + # machinery. Leave it unspecified and have the machinery fall back + # to substdefault by detecting the squared braches + :attribute {tag {[string tolower [namespace tail [self]]]}} + :attribute tagged_entity:optional,object,type=[self] - :attribute tag:required - namespace eval ::nx::doc::entities {} set :root_namespace ::nx::doc::entities :method init {} { next - [:info class] object forward @${:tag} [self] new -name %1 + if {![info exists :tagged_entity]} { + [:info class] object forward ${:tag} [self] new -name %1 + } } :method createOrConfigure {id arguments} { @@ -67,7 +73,81 @@ } } + # @object PartAttribute + # + # This special-purpose Attribute variant realises (1) a cumulative + # value management and (2) support for distinguishing between + # literal parts (e.g., @author, @see) and object parts (e.g., + # @param). + # + # The cumulative value management adds the append() operation which + # translates into an add(...,end) operation. PartAttribute slots + # default to append() as their default setter operation. To draw a + # line between object and literal parts, PartAttribute slots either + # refer to a part_class (a subclass of Part) or they do not. If a + # part_class is given, the values will be transformed accordingly + # before being pushed into the internal storage. + + ::nx::MetaSlot create PartAttribute -superclass ::nx::Attribute { + # @attribute part_class + # + # The attribute slot refers to a concrete subclass of Part which + # describes the parts being managed by the attribute slot. + :attribute part_class:optional,class + + :method init args { + :defaultmethods [list get append] + :multivalued true + set :incremental true + # TODO: setting a default value leads to erratic behaviour; + # needs to be verified -> @author returns "" + # :default "" + next + } + + :method get_part {domain prop value} { + if {[info exists :part_class]} { + if {[::nx::core::is $value object] && \ + [::nx::core::is $value type ${:part_class}]} { + return $value + } + set part [${:part_class} new \ + [self] \ + $domain \ + -name [lindex $value 0] \ + -partof [$domain name] \ + -doc [lrange $value 1 end] + ] + return $part + + } + return $value + } + :method append {domain prop value} { + :add $domain $prop $value end + } + :method assign {domain prop value} { + set parts [list] + foreach v $value { + lappend parts [:get_part $domain $prop $v] + } + next $domain $prop $parts + } + :method add {domain prop value {pos 0}} { + set p [:get_part $domain $prop $value] + if {![$domain exists $prop] || $p ni [$domain $prop]} { + next $domain $prop $p $pos + } + return $p + } + :method delete {domain prop value} { + next $domain $prop [:get_part $prop $value] + } + } + + + Class create Entity { # # Entity is the base class for the documentation classes @@ -78,19 +158,18 @@ :method objectparameter args {next {doc:optional __initcmd:initcmd,optional}} :attribute doc:multivalued {set :incremental 1} + :attribute name:required - #the following two cases (incremental multivalued) could be nicer - :attribute {variants:multivalued ""} {set :incremental 1} - :attribute {params:multivalued ""} {set :incremental 1} - :attribute {@see:multivalued ""} {set :incremental 1} + :attribute @see -slotclass ::nx::doc::PartAttribute + # @method _doc # # The method _doc can be use to obtain the value of the documentation # from another doc entity. This should avoid redundant documentation pieces. :method _doc {doc use what value} { if {$doc ne ""} {return $doc} if {$use ne ""} { - foreach thing {NextCommand NextClass} { + foreach thing {@command @object} { set docobj [$thing id $use] if {[::nx::core::objectproperty $docobj object]} break } @@ -112,94 +191,113 @@ # # This is an abstract hook method to be refined by the subclasses # of Entity - :method process {comment_block} { - puts stderr "EntityFactory process -context [self] $comment_block" - EntityFactory process -context [self] $comment_block + :method process { + {-initial_section:optional "context"} + -entity:optional + comment_block + } { + EntityClass process \ + -partof_entity [self] \ + -initial_section $initial_section \ + {*}[expr {[info exists entity]?"-entity $entity":""}] \ + $comment_block } - - # @method param - # - # The method param is currently used for documenting parameters of - # tcl-commands and xotcl methods. Most probably, it should cover - # object parameters as well. The parameters are identified by a - # name and ar part of another documentation entitiy - # - :method param {param doc {-use ""}} { - set flags [list -param $param] - if {[llength $param]>1} { - lappend flags -default [lindex $param 1] - set param [lindex $param 0] - } - set name $param - if {[regexp {^(.*):(.*)$} $param _ name spec]} { - lappend flags -spec $spec - } - lappend flags -fullname param - @ NextCommand::Parameter $name -partof [self] {*}$flags [:_doc $doc $use params $name] - } - # @method variant - # - # variants are used in cases, where depending on a parameter, the - # semantics of a command (and therefore its documentation) is - # completely different. A typical case are subcommands in Tcl. - # - :method variant {name doc {-use ""}} { - @ NextCommand::Variant $name -partof [self] [:_doc $doc $use variants $name] - } - # @method text # # text is used to access the content of doc of an Entity, and # performs substitution on it. The substitution is not essential, # but looks for now convenient. # :method text {} {subst [join ${:doc} " "]} + } # - # Now, define some kinds of docEntities. The toplevel docEntities - # are named objects in the ::nx::doc::entities namespace to ease access to it. + # Now, define some kinds of documentation entities. The toplevel + # docEntities are named objects in the ::nx::doc::entities namespace + # to ease access to it. # - # We define here the following toplevel docEntities (e.g. xotclObject will follow): - # - NextCommand - # - NextObject + # For now, we define here the following toplevel docEntities: + # - @command + # - @object # - # The xotcl methods are defined as Parts. - # - NextMethod + # These can contain multiple parts. + # - @method + # - @attribute + # - ... # - EntityFactory create NextCommand \ - -tag "command" \ - -superclass Entity { - :attribute name - :attribute arguments - :attribute {returns ""} - :object method id {name} {return [[:info class] eval {set :root_namespace}]::cmd::[string trimleft $name :]} - :object method new args { - foreach {att value} $args {if {$att eq "-name"} {set name $value}} - :createOrConfigure [:id $name] $args - } - } + EntityClass create @command -superclass Entity { + :attribute arguments + :attribute {returns ""} + :object method id {name} {return [[:info class] eval {set :root_namespace}]::cmd::[string trimleft $name :]} + :object method new args { + foreach {att value} $args {if {$att eq "-name"} {set name $value}} + :createOrConfigure [:id $name] $args + } + } - EntityFactory create NextClass \ - -tag "class" \ + EntityClass create @object \ -superclass Entity { - :attribute name - :attribute {@author:multivalued ""} { - # TODO: incremental does not produced effects apart from - # deactivating the optimizer, shouldn't set the attribute's - # default methods to {get add}, to obtain the increment - # effect? - set :incremental 1 - } - :attribute {methods:multivalued ""} {set :incremental 1} - :object method id {name} {puts stderr ""; return [[:info class] eval {set :root_namespace}]::class::[string trimleft $name :]} + :attribute @author -slotclass ::nx::doc::PartAttribute + :attribute @method -slotclass ::nx::doc::PartAttribute { + set :part_class @method + } + :attribute @object-method -slotclass ::nx::doc::PartAttribute { + set :part_class @method + } + :attribute @attribute -slotclass ::nx::doc::PartAttribute { + set :part_class @attribute + } + :object method id {name} { + return [[:info class] eval { + # TODO: Why not simply use the @-prefixed object names here? + set :root_namespace}]::object::[string trimleft $name :] + } :object method new args { foreach {att value} $args {if {$att eq "-name"} {set name $value}} :createOrConfigure [:id $name] $args } + + :method process { + {-initial_section:optional "context"} + -entity:optional + comment_block + } { + next; + + foreach methodName [${:name} info methods -methodtype scripted] { + set blocks [doc comment_blocks [${:name} info method \ + body $methodName]] + foreach {line_offset block} $blocks { + if {$line_offset > 1} break; + set id [@method new "" "" \ + -name $methodName \ + -partof ${:name} \ + -scope class] + $id process -initial_section description $block + } + } + + foreach methodName [${:name} object info methods\ + -methodtype scripted] { + + set blocks [doc comment_blocks [${:name} object info method \ + body $methodName]] + foreach {line_offset block} $blocks { + if {$line_offset > 1} break; + set id [@method new "" "" \ + -name $methodName \ + -partof ${:name} \ + -scope object] + $id process -initial_section description $block + } + } + + } } + # @class Part # @@ -209,75 +307,134 @@ # Class create Part -superclass Entity { #:method objectparameter args {next {doc -use}} - :attribute name:required :attribute partof:required :attribute use } # - # variant and param are Parts: - # - Class create NextCommand::Variant -superclass Part { - :method init {} {${:partof} variants add [self] end} - } - Class create NextCommand::Parameter -superclass Part { - :attribute param - :attribute fullname - :attribute spec - :attribute default - :method init {} {${:partof} params add [self] end} - } - - - # - # xotclMethod is a named entity, which is part of some other + # @method is a named entity, which is part of some other # docEntity (a class or an object). We might be able to use the # "use" parameter for registered aliases to be able to refer to the # documentation of the original method. # - EntityFactory create NextMethod \ - -tag "method" \ + EntityClass create @method \ + -tagged_entity "@object" \ -superclass Part { - :attribute {scope class} - :attribute {modifier public} - :attribute arguments - :attribute {returns ""} - :object method id {partof scope name} { - return [[:info class] eval {set :root_namespace}]::method::[string trimleft $partof :]::${scope}::${name} - } + :attribute {scope class} + :attribute {modifier public} + :attribute @param -slotclass ::nx::doc::PartAttribute { + set :part_class @command::Parameter + } + :attribute @return -slotclass ::nx::doc::PartAttribute { + set :part_class @command::Parameter + } + :object method id {partof scope name} { + return [[:info class] eval {set :root_namespace}]::method::[string trimleft $partof :]::${scope}::${name} + } + + :object method new {tag domain args} { - :object method new args { - foreach {att value} $args { - if {$att eq "-partof"} {set partof $value} - if {$att eq "-name"} {set name $value} - if {$att eq "-scope"} {set scope $value} - } - if {![info exists scope]} { - if {[::nx::core::objectproperty $partof class]} { - set scope class - } elseif {[::nx::core::objectproperty $partof object]} { - set scope object - } else { - set scope class + # TODO: originally, the scope value was not modified in the + # args vector (provided, that the scope is derived somehow); + # this left the entity object with a scope different to + # its id! We fixed it here by feeding the args vector into an + # array structure and by updating this array structure + # accordingly. However, this is hacky and needs to be revised + # all over the place, along with refactoring the new() + # methods as such. + + array set "" $args + + if {![info exists (-scope)]} { + regexp -- {@(object|class)-.*} $tag _ (-scope) + } + + if {![info exists (-scope)]} { + if {[::nx::core::objectproperty $(-partof) class]} { + set (-scope) class + } elseif {[::nx::core::objectproperty $(-partof) object]} { + set (-scope) object + } else { + set (-scope) class + } + } + :createOrConfigure [:id $(-partof) $(-scope) $(-name)] [array get ""] } - } - :createOrConfigure [:id $partof $scope $name] $args - } + + :method signature {} { + if {[info exists :arguments]} { + set arguments ${:arguments} + } else { + set arguments [list] + foreach p [:params] {lappend arguments [$p param]} + } + set result "obj ${:name} $arguments" + } + :method process { + {-initial_section:optional "context"} + comment_block + } { + next \ + -initial_section $initial_section \ + -entity [self] $comment_block + } - :method init {} {[NextClass id ${:partof}] methods add [self] end} + }; # @method + + EntityClass create @attribute \ + -tagged_entity @object \ + -superclass Part { + :attribute {scope class} + :attribute {modifier public} + :object method id {partof scope name} { + return [[:info class] eval {set :root_namespace}]::[string trimleft ${:tag} @]::[string trimleft $partof :]::${scope}::${name} + } + + :object method new {tag domain args} { + foreach {att value} $args { + if {$att eq "-partof"} {set partof $value} + if {$att eq "-name"} {set name $value} + if {$att eq "-scope"} {set scope $value} + } + if {![info exists scope]} { + if {[::nx::core::objectproperty $partof class]} { + set scope class + } elseif {[::nx::core::objectproperty $partof object]} { + set scope object + } else { + set scope class + } + } + :createOrConfigure [:id $partof $scope $name] $args + } - :method signature {} { - if {[info exists :arguments]} { - set arguments ${:arguments} - } else { - set arguments [list] - foreach p [:params] {lappend arguments [$p param]} + }; # @attribute + + Class create @command::Variant -superclass Part + + EntityClass create @command::Parameter \ + -tag "param" \ + -tagged_entity "@method" \ + -superclass Part { + :attribute param + :attribute fullname + :attribute spec + :attribute default + + :object method id {domain name} { + return [[:info class] eval {set :root_namespace}]::${:tag}::[string trimleft [$domain partof]::[$domain scope]::[$domain name] :]::${name} + } + + :object method new {tag domain args} { + foreach {att value} $args { + if {$att eq "-partof"} {set partof $value} + if {$att eq "-name"} {set name $value} + } + :createOrConfigure [:id $domain $name] $args + } } - set result "obj ${:name} $arguments" - } - }; # NextMethod - namespace export EntityFactory NextCommand NextClass NextMethod @ + namespace export EntityClass @command @object @method @attribute @ } @@ -316,7 +473,7 @@ } # - # render xotcl classes + # render next classes # :method renderClass {} { puts "
  • [:cmd ${:name}]
    \n[:text]" @@ -333,7 +490,7 @@ } # - # render xotcl methods + # render next methods # :method renderMethod {} { puts "
  • [:cmd [:signature]]
    \n[:text]" @@ -358,7 +515,7 @@ # namespace eval ::nx { namespace import -force ::nx::doc::* - Object create doc { + ::nx::Object create doc { :method log {msg} { puts stderr "[self]->[uplevel 1 [list ::nx::core::current proc]]: $msg" @@ -377,13 +534,10 @@ :method process {thing} { # TODO: tcl packages as an option? # 1) in-situ processing: a class object - if {[::nx::core::objectproperty $thing class]} { + if {[::nx::core::objectproperty $thing object]} { if {[$thing exists __initcmd]} { - :analyze_initcmd NextClass $thing [$thing eval {set :__initcmd}] + :analyze_initcmd @object $thing [$thing eval {set :__initcmd}] } - } elseif {[::nx::core::objectproperty $thing object]} { - # 2) in-situ processing: a non-class object - :log "can't postprocess objects currently" } elseif {[file isfile $thing]} { # 3) alien script file if {[file isreadable $thing]} { @@ -396,233 +550,136 @@ } } else { # 4) we assume a string block, e.g., to be fed into eval - :analyze $thing + set i [interp create] + set cmd [subst { + package req next::doc + namespace import -force ::nx::doc::* + doc analyze [list $thing] + }] + interp eval $i $cmd + #interp delete $i + return $i } } :method analyze {script} { + # NOTE: This method is to be executed in a child/ slave + # interpreter. + set pre_commands [:list_commands] + uplevel #0 [list eval $script] + set post_commands [:list_commands] + set additions [dict keys [dict remove [dict create {*}"[join $post_commands " _ "] _"] {*}$pre_commands]] + set blocks [:comment_blocks $script] - :log "blocks: '$blocks'" - foreach block $blocks { - :log "block: '$block'" - set analyzed_block [:analyze_comment_block $block] - set cb [dict create {*}$analyzed_block] - :log ">>>> $cb" - # - # 1) resolve the entity by tag; e.g.: class -> NextClass - # - set entity [EntityFactory eval set :tags([dict get $cb entity])] - # - # 2) provide an object rep of the entity - # - set entity_instance [@ $entity [dict get $cb [$entity tag]] [dict get $cb text]] - # - # 3) process entity-specific parts, according to their tags - # - $entity_instance process $analyzed_block + # :log "blocks: '$blocks'" + # 1) eval the script in a dedicated interp; provide for + # recording script-specific object additions. + set failed_blocks [list] + foreach {line_offset block} $blocks { + # 2) process the comment blocks, however, fail gracefully here + # (most blocks, especially in initcmd and method blocks, are + # not qualified, so they are set to fail. however, record the + # failing ones for the time being + if {[catch {::nx::doc::EntityClass process $block} msg]} { + lappend failed_blocks $block + } } - } - - - - :method analyze_line {line} { - if {[regexp {^\s*$} $line]} { - return 1 - } elseif {[regexp {^\s*#} $line]} { - return 2 - } else { - return 3 + # 3) process the recorded object additions, i.e., the stored + # initcmds and method bodies. + foreach addition $additions { + # TODO: for now, we skip over pure Tcl commands and procs + if {![::nx::core::is $addition object]} continue; + :process $addition } } - :method analyze_line {line} { - # - # 1 ... empty line - # - if {[regexp {^\s*$} $line]} { - return 1 - } elseif {[regexp {^\s*#\s*@[^[:space:]@]} $line]} { - # - # 2 ... tagged comment line - # - return 2 - } elseif {[regexp {^\s*#\s*[^[:space:]]\s*} $line]} { - # - # 3 ... untagged, non-emtpy comment line - # - - return 3 - } elseif {[regexp {^\s*#} $line]} { - # - # 4 ... untagged, empty comment line - # - return 4 - } else { - # - # 5 ... code line - # - return 5 + :method list_commands {{parent ::}} { + set cmds [info commands ${parent}::*] + foreach nsp [namespace children $parent] { + lappend cmds {*}[:list_commands ${nsp}] } + return $cmds } :method analyze_line {line} { - if {[regexp -- {^\s*#+[#\s]*(.*)$} $line --> comment]} { + set regex {^\s*#+[#\s]*(.*)$} + if {[regexp -- $regex $line --> comment]} { return [list 1 [string trim $comment]] } else { return [list 0 $line] } } - - - :method append_tag {line} { - set line [:remove_comment_markup $line] - set tag [string trimleft [lindex $line 0] @] - return [list $tag [lrange $line 1 end]] - } :method comment_blocks {script} { set lines [split $script \n] set comment_blocks [list] set was_comment 0 set spec { - 0,1 {set comment_block [list]; lappend comment_block $text} - 1,0 {lappend comment_blocks $comment_block} + 0,1 { + set line_offset $line_counter; + set comment_block [list]; + lappend comment_block $text} + 1,0 {lappend comment_blocks $line_offset $comment_block} 1,1 {lappend comment_block $text} 0,0 {} } array set do $spec + set line_counter -1 foreach line $lines { - foreach {is_comment text} [:analyze_line $line] break; + incr line_counter + # foreach {is_comment text} [:analyze_line $line] break; + lassign [:analyze_line $line] is_comment text; eval $do($was_comment,$is_comment) set was_comment $is_comment } return $comment_blocks } - - - :method remove_comment_markup {comment} { - regsub -all -line {^\s*#} $comment "" comment - return $comment - } - - :method analyze_comment_block {comment} { - set result [list] - set text "" - foreach line [split $comment \n] { - if {[regexp {^ *@(class|attribute|param|returns|method|object-method) (.*)$} $line _ kind value]} { - if {$kind eq "param"} { - if {[regexp {^\s*(\S+)\s+(.*)$} $value _ name desc]} { - set value [list $name $desc] - } else { - puts stderr "invalid param specification $value" - } - } - lappend result entity $kind - lappend result $kind $value - } else { - append text $line - } - } - lappend result text $text - #puts result=$result - return $result - } - - :method analyze_method_block {-methodName -partof -scope -arguments analyzed_block} { - array set cb $analyzed_block - - @ NextMethod $methodName -partof $partof -scope $scope $cb(text) - set m [NextMethod id $partof $scope $methodName] - set docparams [list] - foreach {att value} $analyzed_block { - # we do not handle "use" yet - if {$att eq "param"} { - $m param [lindex $value 0] [lindex $value 1] - lappend docparams [lindex $value 0] - } elseif {$att eq "returns"} { - $m returns $value - } - } - if {$arguments eq ""} { - set arguments $docparams - } - $m arguments $arguments - } - :method analyze_body {-partof -methodName -scope arguments body} { - set blocks [:comment_blocks -mode first $body] - if {[llength $blocks] > 0} { - :analyze_method_block -methodName $methodName -partof $partof -scope $scope \ - -arguments $arguments \ - [:analyze_comment_block [lindex $blocks 0]] - } - } - :method analyze_initcmd {docKind name initcmd} { set first_block 1 - foreach block [:comment_blocks $initcmd] { + set failed_blocks [list] + foreach {line_offset block} [:comment_blocks $initcmd] { + set arguments [list] if {$first_block} { set id [@ $docKind $name] - if {[catch {$id process $block} msg]} { - puts stderr $msg + # + # Note: To distinguish between intial comments blocks + # in initcmds and method bodies which refer to the + # surrounding entity (e.g., the object or the method) + # we use the line_offset recorded by the + # comment_blocks() scanner. Later, we plan to use the + # line_offset to compute line pointers for error + # messages. Also, we can use the line offsets of each + # comment block to identify faulty comment blocks. + # + # A acceptance level of <= 1 means that a script + # block must contain the first line of this + # special-purpose comment block either in the very + # first or second script line. + # + if {$line_offset <= 1} { + lappend arguments -initial_section description + lappend arguments -entity $id } - } - set first_block 0 + set first_block 0 + } else { + set initial_section context + } + lappend arguments $block + if {[catch {$id process {*}$arguments} msg]} { + lappend failed_blocks $line_offset + } } - - }; # analyze_initcmd method - - # :method analyze_initcmd {docKind name initcmd} { - # set first_block 1 - # foreach block [:comment_blocks $initcmd] { - # set analyzed_block [:analyze_comment_block $block] - # array unset cb - # array set cb $analyzed_block - # if {$first_block} { - # set first_block 0 - # if {[array size cb] == 1} { - # # we got a comment for the doc kind - # @ $docKind $name $cb(text) - # continue - # } - # } - - # if {[info exists cb(method)] || [info exists cb(object-method)]} { - # set arguments "" - - # if {[info exists cb(method)]} { - # set methodName $cb(method) - # set scope class - # catch {set arguments [$name info method args $methodName]} - # } else { - # set methodName $cb(object-method) - # set scope object - # catch {set arguments [$name object info method args $methodName]} - # } - - # :analyze_method_block -methodName $methodName -partof $name -scope $scope \ - # -arguments $arguments $analyzed_block - # } - # } - - # foreach methodName [$name info methods -methodtype scripted] { - # :analyze_body -partof $name -methodName $methodName -scope class \ - # [$name info method args $methodName] \ - # [$name info method body $methodName] - # } - - # foreach methodName [$name object info methods -methodtype scripted] { - # :analyze_body -partof $name -methodName $methodName -scope object \ - # [$name object info method args $methodName] \ - # [$name object info method body $methodName] - # } - # }; # analyze_initcmd method - - # activate the recoding of initcmads - ::nx::core::configure keepinitcmd true + }; # analyze_initcmd method + + + # activate the recoding of initcmds + ::nx::core::configure keepinitcmd true + } } + # # toplevel interface @@ -632,44 +689,44 @@ namespace eval ::nx::doc { Object create make { - + :method all {{-verbose:switch} {-class ::nx::Class}} { foreach c [$class info instances -closure] { - if {$verbose} {puts "postprocess $c"} - ::nx::doc::postprocessor process $c + if {$verbose} {puts "postprocess $c"} + ::nx::doc::postprocessor process $c } } - + :method doc {{-renderer ::nx::doc::HTMLrenderer}} { - + # register the HTML renderer for all docEntities. Entity mixin add $renderer - + puts "

    Primitive XOTcl framework commands

    \n\n\n" puts "

    XOTcl Classes

    \n\n\n" - + Entity mixin delete $renderer } - } - + } + # # modal comment block parsing # - + # # contexts are entities # - EntityFactory eval { + EntityClass eval { :object forward has_next expr {${:idx} < [llength ${:comment_block}]} :object method dequeue {} { set r [lindex ${:comment_block} ${:idx}] @@ -678,20 +735,28 @@ } :object forward rewind incr :idx -1 :object forward fastforward set :idx {% expr {[llength ${:comment_block}] - 1} } - :object method process {-context:optional block} { + :object method process { + {-partof_entity:optional,substdefault "[self]"} + {-initial_section:optional context} + -entity:optional + block + } { set :comment_block $block - # the defaults - set :processed_section context - set :current_entity [self] - - if {[info exists context]} { - set :current_entity $context - set :processed_section description + # initialise the context object + set :processed_section $initial_section + set :partof_entity $partof_entity + + if {[info exists :current_entity]} { + unset :current_entity } - + + if {[info exists entity]} { + set :current_entity $entity + } + set :is_not_completed 1 - + ${:processed_section} eval [list set :context [self]] set is_first_iteration 1 set :idx 0 @@ -703,6 +768,7 @@ set is_first_iteration 0 } if {[catch {${:processed_section} transition $line} failure]} { + #puts stderr FAILURE=$failure set :is_not_completed 0 # # TODO: For now, the fast-forward mechanism jumps to the end @@ -720,13 +786,59 @@ } if {$failure ne ""} { - error $failure + error "$failure: $::errorInfo" } return ${:current_entity} } + + :object method resolve_partof_entity {tag name} { + # a) unqualified: attr1 + # b) qualified: Bar#attr1 + if {[regexp -- {([^\s#]*)#([^\s#]*)} $name _ qualifier nq_name]} { + # TODO: Currently, I only foresee @object as possible + # qualifier; however, this should be fixed asap, as soon as + # the variety of entities has been decided upon! + set partof_entity [@object id $qualifier] + # TODO: Also, we expect the qualifier to resolve against an + # already existing entity object? Is this intended? + if {[::nx::core::is $partof_entity object]} { + return [list $nq_name $partof_entity] + } else { + return [list $nq_name ${:partof_entity}] + } + } else { + return [list $name ${:partof_entity}] + } + } + + :object method dispatch args { + if {![info exists :current_entity]} { + # 1) the current (or context) entity has NOT been resolved + # + # for named entities, the provided identifier can be either + # qualified or unqualified: + # + # a) unqualified: @attribute attr1 + # b) qualified: @Bar#attribute attr1 + # + # For qualified ones, we must resolve the qualifier to serve + # as the partof_entity; see resolve + set tag [lindex $args 0] + set name [lindex $args 1] + set args [lrange $args 2 end] + # foreach {nq_name partof_entity} \ + # [:resolve_partof_entity $tag $name] break; + lassign [:resolve_partof_entity $tag $name] nq_name partof_entity; + set :current_entity [${partof_entity} $tag $nq_name {*}$args] + } else { + # 2) current (or context) entity has been resolved + # TODO: Should we explicitly disallow qualified names in parts? + ${:current_entity} {*}$args + } + } } - + # # Infrastructure for state objects: # @@ -735,21 +847,17 @@ # abstracted class, providing a refinement protocol for concrete # state subclasses # - + Class create CommentState { :attribute context; # points to the context object, i.e., an entity :method on_enter {line} {;} - - :method on_exit {line} { - #puts -nonewline stderr "EXIT -> [namespace tail [:info class]]#[namespace tail [self]]" - } - + :method signal {event line} {;} - + # # activity/event interface # - + :method event=process {line} {;} :method event=close {line} {;} :method event=next {line} {;} @@ -758,23 +866,20 @@ } :method event=rewind {line} {;} } - + # 2. CommentLines represent atomic states in the parsing state # machinery: tag, text, space - + Class create CommentLine -superclass CommentState { :attribute comment_section; # points to the super-state objects :attribute processed_line; # stores the processed text line :forward signal {% ${:comment_section} } %proc :forward context {% ${:comment_section} } %proc :forward current_entity {% :context } eval set :current_entity - + :method on_enter {line} {;} :method on_exit {line} {;} - -# :method event=next {line} { -# } - + :method match {line} {;} :method is? {line} { foreach cline [lsort [[:info class] info instances]] { @@ -785,24 +890,19 @@ } } - + CommentLine create tag { :method match {line} { set tag [lindex $line 0] return [expr {[string first @ $tag] == 0}] } :method event=process {line} { set tag [lindex $line 0] - set entity [[:current_entity] {*}$line] - #puts stderr ENTITY=$entity,line=$line - # TODO: Fix the forward-setting of the current_entity. a) place - # it when exiting from the super-state? b) or, refactor it into the - # shadowed event=process method()? c) further options? - if {[::nx::core::is $entity object]} { :current_entity $entity } + [:context] dispatch $tag [lrange $line 1 end] } } - + CommentLine create text { :method match {line} { return [regexp -- {\s*[^[:space:]@]+} $line] @@ -811,18 +911,18 @@ # # TODO: revise when incremental support is operative # - [:current_entity] doc add $line end + [:context] dispatch doc add $line end } - + } - + CommentLine create space { :method match {line} { return [expr {$line eq {}}] } } - + # # 3. CommentSections represent orthogonal super-states over # CommentLines: context, description, part @@ -833,22 +933,22 @@ :attribute current_comment_line :attribute comment_line_transitions :attribute next_comment_section; # implements a STATE-OWNED TRANSITION scheme - + :method init {} { ${:entry_comment_line} comment_section [self] } - + :method transition {line} { array set transitions ${:comment_line_transitions} - + if {![info exists :current_comment_line]} { set src "" set tgt [${:entry_comment_line} is? $line] } else { set src ${:current_comment_line} set tgt [$src is? $line] } - + # # TODO: realise the initial state nodes as NULL OBJECTs, this # helps avoid conditional branching all over the place! @@ -868,55 +968,62 @@ ${:current_comment_line} processed_line $line ${:current_comment_line} on_enter $line - foreach {event activities} $transitions(${src}->${tgt}) break; + #foreach {event activities} $transitions(${src}->${tgt}) break; + lassign $transitions(${src}->${tgt}) event activities; :signal $event $line foreach activity $activities { :signal $activity $line } } - + :method on_enter {line} {;} - + :method on_exit {line} { # TODO: move this behaviour into a more decent place if {![${:context} has_next]} { ${:current_comment_line} on_exit $line } - unset :current_comment_line + # Note: Act passive here, because e.g. upon invalid entry + # state transition requests, there is no current_comment_line + # set here. Yet, we want to exit from the comment section! + if {[info exists :current_comment_line]} { + unset :current_comment_line + } next; } - + :method signal {event line} { ${:current_comment_line} event=$event $line :event=$event $line } - + # # handled events # :method event=next {line} { set next_section [:next_comment_section] ${:current_comment_line} on_exit $line :on_exit $line - $next_section on_enter $line $next_section eval [list set :context ${:context}] + $next_section on_enter $line ${:context} eval [list set :processed_section [:next_comment_section]] + } - + :method event=rewind {line} { ${:context} rewind next } - + }; # CommentSection - + # # the OWNER-DRIVEN TRANSITIONS read as follows: # (current_state)->(next_state) {event {activity1 activty2 ...}} # - + # # context # @@ -929,13 +1036,13 @@ space->text {close {rewind next}} space->tag {close {rewind next}} } -entry_comment_line tag - + # NOTE: add these transitions for supporting multiple text lines for # the context element # tag->text {process ""} # text->text {process ""} # text->space {process ""} - + # # description # @@ -948,7 +1055,14 @@ text->space {process ""} space->space {process ""} space->tag {close {rewind next}} - } -entry_comment_line text + } -entry_comment_line text { + :method on_enter {line} { + ${:context} dispatch eval { + unset -nocomplain :doc + } + next; + } + } # # part