#! /usr/bin/perl -w # # Class name: ParseTree # Synopsis: Used by headerdoc2html to hold parse trees # Last Updated: $Date: 2014/03/06 11:20:09 $ # # Copyright (c) 1999-2004 Apple Computer, Inc. All rights reserved. # # @APPLE_LICENSE_HEADER_START@ # # This file contains Original Code and/or Modifications of Original Code # as defined in and that are subject to the Apple Public Source License # Version 2.0 (the 'License'). You may not use this file except in # compliance with the License. Please obtain a copy of the License at # http://www.opensource.apple.com/apsl/ and read it before using this # file. # # The Original Code and all software distributed under the License are # distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER # EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, # INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. # Please see the License for the specific language governing rights and # limitations under the License. # # @APPLE_LICENSE_HEADER_END@ # ###################################################################### # /*! # @header # @abstract # ParseTree class package file. # @discussion # This module contains the ParseTree class, a data # structure for holding the parse tree as generated by # {@link //apple_ref/perl/instm/HeaderDoc::BlockParse/blockParse//() blockParse}. # # See the class documentation below for more details. # @indexgroup HeaderDoc Parser Pieces # */ # /*! # @abstract # Parse tree (or token therein) generated by parser. # @discussion # The ParseTree class is a data structure # for holding the parse tree as generated by # {@link //apple_ref/perl/instm/HeaderDoc::BlockParse/blockParse//() blockParse}. # # In addition, this class provides a series of # utility functions for manipulating the # parse tree, generating output from the # parse tree in HTML, XML, and plain text # formats, and extracting Objective-C # method parameter lists. # # @vargroup Node data and tree pointers # @var TOKEN # The token itself. # @var NEXT # The next (sibling) node in the parse tree. # @var FIRSTCHILD # The first child (descendant) node of this node. # @var PARENT # The parent node in the parse tree. # @var lastDisplayNode # The last node in the parse tree rooted at this node that # should be displayed. Used only in AppleScript, to hide # content nested inside functions while still parsing them # fully. This is used for functions that are not inside # scripts. For functions that are inside a script statement, # the lastDisplayNode in the parser state object # is used instead. # # @vargroup File information # @var FILENAME # The filename component of the header or source file that contains # the API content that this parse tree describes. Does not include # leading path parts. # @var FULLPATH # The full (possibly relative) path to the header or source file that # contains the the API content that this parse tree describes. # @var INPUTCOUNTER # The line number where this tree object appeared relative to the # start of the {@link //apple_ref/perl/cl/HeaderDoc::LineRange LineRange} # object. # @var BLOCKOFFSET # The line number of the first line of the # {@link //apple_ref/perl/cl/HeaderDoc::LineRange LineRange} object. # @var LINENUM # The actual line number in the header ($self->{INPUTCOUNTER} + # $self->{BLOCKOFFSET}). # # @vargroup Administrative information # @var REFCOUNT # The number of API objects that reference this parse tree. The # parse tree cannot be purged until this count goes to zero. # @var APIOWNERS # An array of API objects that reference the parse tree rooted in # this node. # @var RAWPARSEDPARAMETERS # An array of parsed parameters associated with this node. # Each entry in this array is a string containing the # text of a parameter. This is currently unused. # @var PARSEDPARAMS # An array of parsed parameter objects associated with this node. # Each entry in this array should be a MinorAPIElement object, # but right now, it's a mixture of objects and strings. # This is not currrently used for anything, but may be used in # the future once support is fully integrated in the parser. # @var PETDONE # Set to 1 by {@link processEmbeddedTags} to indicate that this # parse tree has already been processed. This prevents duplicate # insertion of symbols nested within classes. # # @vargroup Token attributes # @var HIDEMACROLASTTOKEN # Set to 1 on the space or 2 on the curly brace after a # macro name. Used to hide the contents of a macro for # output purposes, depending on flags. # @var ACCESSCONTROLSTATE # The access control state in force at the time this node was created. # @var PARSERSTATE # A parser state object stored at this node in the parse tree. # used for reparsing class contents. # @var HIDDEN # Indicates that this node should not be emitted in HTML output. # @var ISAVAILABILITYMACRO # Indicates that this node is an availability macro. This # is used for wrapping content in both HTML and XML. # @var RE_STATE # Indicates that this node is part of a regular expression. # This value contains one of the following values: # # RE_PREFIX (the optional "command" letters before # the expression) # # RE_START (the opening slash or other delimiter) # # RE_PARTSEP (the intermediate delimiter or delimiters) # # RE_END (the trailing slash or other delimiter). # # Note that the trailing flags are not tagged in any special way # because you can trivially distinguish the parse tree node that # contains them by inspecting the node after RE_END. # # @vargroup Caches # # @var HTMLTREE # A cached copy of the HTML tree for the parse tree rooted in this node, # as generated by {@link htmlTree}. # @var XMLTREE # A cached copy of the XML tree for the parse tree rooted in this node, # as generated by {@link xmlTree}. # @var CPNC # A cached copy of the results of {@link childpeeknc} for this node. # @var NTNC # A cached copy of the results of {@link nextTokenNoComments} for this # node. # */ package HeaderDoc::ParseTree; use strict; use vars qw($VERSION @ISA); use HeaderDoc::Utilities qw(isKeyword parseTokens stringToFields doxyTagFilter casecmp emptyHDok complexAvailabilityToArray printHash validTag); use HeaderDoc::BlockParse qw(blockParse nspaces); use Carp qw(cluck); # use WeakRef; # /*! # @abstract # The revision control revision number for this module. # @discussion # In the git repository, contains the number of seconds since # January 1, 1970. # */ $HeaderDoc::ParseTree::VERSION = '$Revision: 1394133609 $'; ################ General Constants ################################### my $debugging = 0; my $apioDebug = 0; # 0 = normal; 1 = debug; 2 = debug + backtraces my $treeDebug = 0; my %defaults = ( # TOKEN => undef, # NEXT => undef, # FIRSTCHILD => undef, # APIOWNERS => undef, # PARSEDPARAMS => undef, PETDONE => 0, REFCOUNT => 0, # XMLTREE => undef, # HTMLTREE => undef, # CPNC => undef, # NTNC => undef, # RAWPARSEDPARAMETERS => undef, # PARSERSTATE => undef, # ACCESSCONTROLSTATE => undef, # FILENAME => undef, # LINENUM => undef, # HIDDEN => undef ); # /*! # @abstract # Creates a new ParseTree object. # @param param # A reference to the relevant package object (e.g. # HeaderDoc::ParseTree->new() to allocate # a new instance of this class). # */ sub new { my($param) = shift; my($class) = ref($param) || $param; my %selfhash = %defaults; my $self = \%selfhash; # cluck("Parse tree $self created\n"); bless($self, $class); $self->_initialize(); # Now grab any key => value pairs passed in my (%attributeHash) = @_; foreach my $key (keys(%attributeHash)) { $self->{$key} = $attributeHash{$key}; } return ($self); } # /*! # @abstract # Initializes an instance of a ParseTree object. # @param self # The object to initialize. # */ sub _initialize { my($self) = shift; $self->{ACCESSCONTROLSTATE} = $HeaderDoc::AccessControlState; $self->{FILENAME} = $HeaderDoc::headerObject->filename(); $self->{FULLPATH} = $HeaderDoc::headerObject->fullpath(); $self->{LINENUM} = $HeaderDoc::CurLine; $self->{HIDDEN} = $HeaderDoc::hidetokens; $self->{APIOWNERS} = (); $self->{PARSEDPARAMS} = (); $self->{RAWPARSEDPARAMETERS} = (); return; my($self) = shift; # $self->{TOKEN} = undef; # $self->{NEXT} = undef; # $self->{FIRSTCHILD} = undef; $self->{APIOWNERS} = (); $self->{ACCESSCONTROLSTATE} = $HeaderDoc::AccessControlState; $self->{PARSEDPARAMS} = (); $self->{FILENAME} = $HeaderDoc::headerObject->filename(); $self->{FULLPATH} = $HeaderDoc::headerObject->fullpath(); $self->{LINENUM} = $HeaderDoc::CurLine; # $HeaderDoc::headerObject->linenum(); # $self->{PETDONE} = 0; $self->{HIDDEN} = $HeaderDoc::hidetokens; $self->{REFCOUNT} = 0; # $self->{XMLTREE} = undef; # $self->{HTMLTREE} = undef; # $self->{CPNC} = undef; # # $self->{NPCACHE} = undef; # $self->{NTNC} = undef; # # $self->{CTSUB} = undef; # # $self->{CTSTRING} = undef; $self->{RAWPARSEDPARAMETERS} = (); $self->{PARSERSTATE} = undef; # $HeaderDoc::curParserState; } my $colorDebug = 0; # /*! # @abstract # Duplicates this ParseTree object into another one. # @param self # The object to clone. # @param clone # The victim object. # */ sub clone { my $self = shift; my $clone = undef; if (@_) { $clone = shift; } else { $clone = HeaderDoc::ParseTree->new(); } # $self->SUPER::clone($clone); # now clone stuff specific to ParseTree $clone->{TOKEN} = $self->{TOKEN}; # Note: apiOwner is no longer recursive, so there is no need # to recursively clone parse trees. Only the top node will # ever be modified legitimately (except when pruning headerdoc # comments, but that needs to occur for all instances). $clone->{FIRSTCHILD} = $self->{FIRSTCHILD}; $clone->{NEXT} = $self->{NEXT}; # $clone->{FIRSTCHILD} = undef; # if ($self->{FIRSTCHILD}) { # my $firstchild = $self->{FIRSTCHILD}; # $clone->{FIRSTCHILD} = $firstchild->clone(); # } # $clone->{NEXT} = undef; # if ($self->{NEXT}) { # my $next = $self->{NEXT}; # $clone->{NEXT} = $next->clone(); # } $clone->{APIOWNERS} = $self->{APIOWNERS}; $clone->{PARSEDPARAMS} = $self->{PARSEDPARAMS}; $clone->{PETDONE} = 0; return $clone; } # /*! # @abstract # Adds a sibling to this node in the parse tree. # @param self # The current parse tree node. # @param name # The token value for the new node. # @param hide # Pass 1 if this node (and its children) should be # hidden when emitting the tree as HTML, else 0. # @result # Returns the new sibling node. # */ sub addSibling { my $self = shift; my $name = shift; my $hide = shift; my $newnode = HeaderDoc::ParseTree->new(); my $localDebug = 0; # print STDERR "addSibling $self\n"; print STDERR "addSibling $self \"$name\" HIDDEN: $hide\n" if ($treeDebug || $localDebug); if ($treeDebug == 2) { cluck("backtrace"); } # Always hide siblings of descendants of elements that the parser # tells us to hide. Also spaces if they are the immediate successor # to the element right after a hidden element, regardless. if ($self->hidden() == 2) { $hide = 2; } elsif ($name =~ /^\s+$/ && $self->hidden()) { $hide = 1; } print STDERR "HIDE NOW $hide\n" if ($treeDebug || $localDebug); my $parent = $self->parent; my $pos = $self; # if ($parent) { # $pos = $parent->lastchild(); # bless($pos, "HeaderDoc::ParseTree"); # } else { # warn "NOPARENTA!\nNOPARENTB!\nNOPARENTC!\n"; bless($pos, "HeaderDoc::ParseTree"); # print STDERR "POS: $pos\n"; # while ($pos && $pos->next()) { # $pos = $pos->next(); # bless($pos, "HeaderDoc::ParseTree"); # # print STDERR "POS: $pos: ".$pos->token()."\n"; # } # bless($pos, "HeaderDoc::ParseTree"); # } $newnode->token($name); if ($hide) { $newnode->hidden($hide); } $newnode->parent($parent); # if ($parent) { $parent->lastchild($newnode); } my $noderef = $newnode; my $retval = $pos->next($noderef); print STDERR "New sibling: ".$retval if ($treeDebug); return $retval; # print STDERR "$self (".$self->token().") RET $ret (".$ret->token().")\n"; # return $ret; } # /*! # @abstract # Adds a child to this node in the parse tree. # @param self # The current parse tree node. # @param name # The token value for the new node. # @param hide # Pass 1 if this node (and its children) should be # hidden when emitting the tree as HTML, else 0. # @result # Returns the new child node. # */ sub addChild { my $self = shift; my $name = shift; my $hide = shift; # print STDERR "addChild\n"; print STDERR "addChild to $self \"$name\"\n" if ($treeDebug); if ($treeDebug == 2) { cluck("backtrace"); } # If the parser wants a node hidden, any children of such a node # should be hidden, as should any siblings of those children or their # descendants. Siblings of the original node should not, however. # # The block parser passes in 3 for $hide to hide the node and its # descendants. Its descendants have their hidden value set to 2 so # that their siblings will also be hidden, but the top level node # still retains the original hidden value of 3. if ($self->hidden() == 2 || $self->hidden() == 3) { $hide = 2; } if (!$self->firstchild()) { my $newnode = HeaderDoc::ParseTree->new(); if ($hide) { $newnode->hidden($hide); } $newnode->token($name); my $noderef = $newnode; $newnode->parent($self); # $self->lastchild($noderef); my $retval = $self->firstchild($noderef); print STDERR "New child: ".$retval if ($treeDebug); return $retval; # $self->firstchild($noderef); } else { warn "addChild called when firstchild exists. Dropping.\n"; # my $node = $self->firstchild(); # bless($node, "HeaderDoc::ParseTree"); # return $node->addSibling($name, $hide); } } # /*! @abstract # Checks to see if this node is at the same level of the parse tree # and occurs after the node specified (or is the node specified). # @param self # The current node. # @param node # The specified node for comparison. # */ sub isAfter { my $self = shift; my $node = shift; my $ptr = $node; while ($ptr) { if ($ptr == $self) { return 1; } $ptr = $ptr->next(); } return 0; } # /*! @abstract # Adds an additional API owner for a tree. # @param self # This node. # @param newapio # The API owner to add. # */ sub addAPIOwner { my $self = shift; my $newapio = shift; print STDERR "addAPIOwner: SELF WAS $self\n" if ($apioDebug); print STDERR "addAPIOwner: APIO WAS $newapio\n" if ($apioDebug); if (!$newapio) { warn("apiOwner called with empty APIO!\n"); return undef; } else { $self->{REFCOUNT}++; cluck("REFCOUNT $self -> ".$self->{REFCOUNT}." because of $newapio (add)\n") if ($HeaderDoc::debugAllocations); # weaken($newapio); push(@{$self->{APIOWNERS}}, $newapio); } return $newapio; } # /*! # @abstract # Replaces an API owner for the tree. # @param self # This tree node. # @param old # The previous API owner to delete. # @param new # The new API owner to insert. # */ sub apiOwnerSub { my $self = shift; my $old = shift; my $new = shift; my $quiet = shift; my $localDebug = 0; cluck("apiOwnerSub called with SELF=$self OLD=$old NEW=$new\n") if ($localDebug); my @arr = (); my $found = 0; foreach my $possowner (@{$self->{APIOWNERS}}) { print "IN apiOwnerSub: $possowner\n" if ($HeaderDoc::debugAllocations); if ($possowner == $new) { $new = undef; # lower refcount later. } if ($possowner != $old) { push(@arr, $possowner); } else { $found++; } } if (!$found && !$quiet) { warn("OLD API OWNER NOT FOUND IN apiOwnerSub(). Please file a bug.\n"); } if ($new) { push(@arr, $new); } elsif ($found) { $self->{REFCOUNT} -= $found; print STDERR "REFCOUNT $self -> ".$self->{REFCOUNT}." because of $old going away\n" if ($HeaderDoc::debugAllocations); if (!$self->{REFCOUNT}) { cluck("Ref count went to zero in apiOwnerSub($self)\n") if ($HeaderDoc::debugAllocations); } } $self->{APIOWNERS} = \@arr; } # /*! # @abstract # Creates a new state object for use by colorTreeSub. # @param withref # # @param morevalues # Additional values to add. # # @discussion # This greatly reduces the number of arguments to # {@link colorTreeSub}. It has no other purpose beyond making # the code more readable by treating the current # state as an object. # # Keys and legal values are described as local variables for # {@link colorTreeSub}. # */ sub newCTState { my %statedata = (); if (@_) { my $withref = shift; if ($withref) { my %withvalues = %{$withref}; foreach my $key (keys %withvalues) { $statedata{$key} = $withvalues{$key}; } } if (@_) { my (%morevalues) = @_; foreach my $key (keys(%morevalues)) { $statedata{$key} = $morevalues{$key}; } } } return \%statedata; } # /*! # @abstract # Gests/sets the API owner for the tree. # @param self # This tree node. # @param newapio # The new API owner value. (Optional.) # */ sub apiOwner { my $self = shift; my $localDebug = 0; if (@_) { my $newapio = shift; if (!$newapio) { warn("apiOwner called with empty APIO!\n"); } print STDERR "apiOwner: SETTING TO $newapio (".$newapio->rawname().")\n" if ($apioDebug || $localDebug); $self->{APIOWNERS} = (); push(@{$self->{APIOWNERS}}, $newapio); $self->{REFCOUNT} = 1; print STDERR "REFCOUNT $self -> 1 because of $newapio (set)\n" if ($HeaderDoc::debugAllocations); } my $apio = undef; foreach my $possowner (@{$self->{APIOWNERS}}) { print STDERR "TESTING $possowner\n" if ($localDebug); if ($possowner !~ /HeaderDoc::HeaderElement/) { if ($possowner !~ /HeaderDoc::APIOwner/) { if ($possowner) { $apio = $possowner; print STDERR "CHOSE $apio\n" if ($localDebug); } } } } if (!$apio) { $apio = pop(@{$self->{APIOWNERS}}); push(@{$self->{APIOWNERS}}, $apio); print STDERR "GUESSING $apio\n" if ($localDebug); } # Try this if you run into trouble, but.... # if (!$apio && $self->{PARENT}) { # return $self->{PARENT}->apiOwner(); # } return $apio; } # /*! # @abstract # Returns all of the API owners for the tree. # @param self # This tree node. # */ sub apiOwners { my $self = shift; # foreach my $apio (@{$self->{APIOWNERS}} ) { # print STDERR "APIOWNERS LIST INCLUDES $apio\n"; # } return $self->{APIOWNERS}; } # /*! # @abstract # Returns the rightmost sibling of this tree node. # @param self # This tree node. # */ sub lastSibling { my $self = shift; while ($self && $self->next()) { $self = $self->next(); } return $self; } # /*! # @abstract # Gets/sets the access control state (public, private, # etc.) for this tree node. # @param self # This tree node. # @param ACCESSCONTROLSTATE # The new access control value to set. (Optional.) # */ sub acs { my $self = shift; if (@_) { $self->{ACCESSCONTROLSTATE} = shift; } return $self->{ACCESSCONTROLSTATE}; } # /*! # @abstract # Gets/sets the token for this tree node. # @param self # This tree node. # @param TOKEN # The new token value to set. (Optional.) # */ sub token { my $self = shift; if (@_) { $self->{TOKEN} = shift; } return $self->{TOKEN}; } # /*! # @abstract # Gets/sets whether this node and its children # should be hidden when emitting HTML. # @discussion # If a new value is specified, this function # recursively modifies its descendants. # @param self # This tree node. # @param value # The new token value to set. (Optional.) # */ sub hidden { my $self = shift; if (@_) { my $value = shift; $self->{HIDDEN} = $value; my $fc = $self->firstchild(); if ($fc) { $fc->hiddenrec($value); } } return $self->{HIDDEN}; } # /*! # @abstract # Sets whether this node, its children, and its # siblings should be hidden when emitting HTML. # @discussion # This function recursively modifies its # descendants and siblings. # @param self # This tree node. # @param value # The new token value to set. # */ sub hiddenrec { my $self = shift; my $value = shift; # print STDERR "SETTING HIDDEN VALUE OF TOKEN ".$self->token()." to $value\n"; # $self->hidden($value); $self->{HIDDEN} = $value; my $fc = $self->firstchild(); if ($fc) { $fc->hiddenrec($value); } my $nx = $self->next(); if ($nx) { $nx->hiddenrec($value); } } # /*! # @abstract # Scrapes the parameters for an Objective-C method from # the parse tree and returns an array. # @param self # The parse tree for the method. # */ sub objCparsedParams() { my $self = shift; my $lang = shift; my $sublang = shift; my @parsedParams = (); my $objCParmDebug = 0; my $inType = 0; my $inName = 0; my $position = 0; my $curType = ""; my $curName = ""; my $cur = $self; my @stack = (); my $eoDec = 0; my $lastTag = ""; my $tagName = ""; my $noParse = 1; my $noTag = 1; while ($cur || scalar(@stack)) { while (!$cur && !$eoDec) { if (!($cur = pop(@stack))) { $eoDec = 1; } else { $cur = $cur->next(); } } if ($eoDec) { last; } # process this element my $token = $cur->token(); if ($token eq ":") { if (!$noTag) { $tagName = $lastTag; } $noParse = 0; } elsif ($noParse) { # drop token on the floor. It's part of the name. } elsif ($token eq "(") { $inType++; $curType .= $token; } elsif ($token eq ")") { if (!(--$inType)) { $inName = 1; $noTag = 0; } $curType .= $token; } elsif ($token =~ /^[\s\W]/o && !$inType) { # drop white space and symbols on the floor (except # for pointer types) if ($inName && ($curName ne "")) { $inName = 0; my $param = HeaderDoc::MinorAPIElement->new("LANG" => $lang, "SUBLANG" => $sublang); $param->linenuminblock($self->apiOwner()->linenuminblock()); $param->blockoffset($self->apiOwner()->blockoffset()); # $param->linenum($self->apiOwner()->linenum()); $param->outputformat($self->apiOwner()->outputformat()); $param->tagname($tagName); $param->name($curName); $param->type($curType); $param->position($position++); print STDERR "ADDED $curType $curName [$tagName]\n" if ($objCParmDebug); $curName = ""; $curType = ""; push(@parsedParams, $param); $noParse = 1; } } elsif ($inType) { $curType .= $token; } elsif ($inName) { $curName .= $token; } my $fc = $cur->firstchild(); if ($fc) { push(@stack, $cur); $cur = $fc; } else { $cur = $cur->next(); } if ($token =~ /\w/) { $lastTag = $token; } } if ($objCParmDebug) { foreach my $parm (@parsedParams) { print STDERR "OCCPARSEDPARM: ".$parm->type()." ".$parm->name()."\n"; } } return @parsedParams; } # /*! # @abstract # Sets the parsed parameter list for this node or # searches the parse tree for parsed parameter lists and # reurns the result. # @param self # This object. # @param pplref # A reference to a parsed parameter list. (Optional.) # @discussion # This subroutine is for future transition. The end goal is to # move the parsed parameter support from the HeaderElement level # entirely into the parse tree. # */ sub parsedParams($) { my $self = shift; my $lang = shift; my $sublang = shift; my @array = (); # print STDERR "parsedParams called\n"; if (@_) { if ($self->apiOwner() eq "HeaderDoc::Method") { @{$self->{PARSEDPARAMS}} = $self->objCparsedParams($lang, $sublang); } else { my $pplref = shift; # @{$self->{PARSEDPARAMS}} = @_; @{$self->{PARSEDPARAMS}} = @{$pplref}; # for my $parm (@{$pplref}) { # print STDERR "ADDING PARM $parm\n"; # } } } if (!($self->{PARSEDPARAMS})) { # print STDERR "PARSEDPARAMS PROBLEM: TOKEN WAS ".$self->token()."\n"; # print STDERR "PRINTTREE:\n"; # $self->printTree(); # print STDERR "ENDOFTREE\n"; my $next = $self->next(); if ($next) { return $next->parsedParams($lang, $sublang); } else { return undef; } # else { die("Can't find parsed params\n"); } } # print STDERR "HERE: $self : ". $self->token." : ".$self->{PARSEDPARAMS}."\n"; # foreach my $parm (@{$self->{PARSEDPARAMS}}) { # print STDERR "FOUND PARM $parm\n"; # } return @{$self->{PARSEDPARAMS}}; } # /*! # @abstract # Finds the previous node by laborious tree walking of # the parent node's children. # @discussion # This function should be avoided if at all possible. # It is currently unused, though it was used in the # distant past. It is retained for completeness. # @param self # This tree node. # */ sub slowprev() { my $self = shift; my $parent = $self->parent; if (!$parent) { return undef; } my $fc = $parent->firstchild; if ($self == $fc) { return undef; } while ($fc && $fc->next && ($fc->next != $self)) { $fc = $fc->next; } return $fc; } # /*! # @abstract # Copies a parsed parameter list and sets the parsed parameter # list for this tree node to be a reference to the resulting # array. # @param self # This tree node. # @param pplref # A reference to the parsed parameter list array to copy. # */ sub parsedParamCopy { my $self = shift; my $pplref = shift; my $lang = shift; my $sublang = shift; my $localDebug = 0; my @parms = @{$pplref}; my @newparms = (); foreach my $parm (@parms) { push(@newparms, $parm); } $self->parsedParams($lang, $sublang, \@newparms); print STDERR "PARSEDPARAMCOPY -> $self\n" if ($localDebug); print STDERR "TOKEN WAS ".$self->token()."\n" if ($localDebug); } # /*! # @abstract # Handles embedded HeaderDoc markup, returning a list # of parameters, constants, etc. # @param self # The top of the parse tree/subtree to scan. # @param xmlmode # Set to 1 if output mode is XML. # @param apiOwner # If this parse tree is owned by an API owner, this # variable indicates the primary API owner to which # the resulting objects should be added. If this # parse tree is owned by a non-API-oowner object (e.g. # a Struct object), this value is unused. # */ sub processEmbeddedTags { my $self = shift; my $xmlmode = shift; my $apiOwner = shift; # Always subclass of APIOwner, used as "true" apio value for calling APIOprocessEmbeddedTagsRec my $apiolist = $self->apiOwners(); my $apio = $self->apiOwner(); # $self->printTree(); # $self->dbprint(); my $localDebug = 0; # if ($apio->isAPIOwner()) { $localDebug = 1; } print STDERR "IN PET\n" if ($localDebug); print STDERR "API IS $apio\n" if ($localDebug); print STDERR $apio->name()."\n" if ($localDebug); print STDERR "APIOLIST IS $apiolist\n" if ($localDebug);; if ($localDebug && $apiolist) { for my $tempapio (@{$apiolist}) { print STDERR "RETURNED APIOLIST INCLUDES $tempapio (".($tempapio->rawname()).")\n"; } } ### Make the APIO owner list unique. ### # print STDERR "APIOLIST:\n"; my %newapiohash = (); foreach my $tempapio (@{$apiolist}) { # print $tempapio."\n"; $newapiohash{$tempapio} = $tempapio; } # print STDERR "END APIOLIST\n"; my @newapiolist = (); foreach my $tempapio (keys %newapiohash) { push(@newapiolist, $newapiohash{$tempapio}); } $apiolist = \@newapiolist; if ($self->{PETDONE}) { print STDERR "SHORTCUT\n" if ($localDebug); return; } $self->{PETDONE} = 1; if (!$apio) { return; } my $apioclass = ref($apio) || $apio; my $old_enable_cpp = $HeaderDoc::enable_cpp; if ($apioclass =~ /HeaderDoc::PDefine/ && $apio->parseOnly()) { if ($HeaderDoc::enable_cpp) { print STDERR "CPP Enabled. Not processing comments embedded in #define macros marked as 'parse only'.\n" if ($localDebug); return; } } elsif ($apioclass =~ /HeaderDoc::PDefine/) { if ($HeaderDoc::enable_cpp) { print STDERR "Temporarily disabling CPP.\n" if ($localDebug); $HeaderDoc::enable_cpp = 0; } } # my ($sotemplate, $eotemplate, $operator, $parseTokens{soc}, $parseTokens{eoc}, $parseTokens{ilc}, $parseTokens{ilc_b}, $sofunction, # $parseTokens{soprocedure}, $parseTokens{sopreproc}, $parseTokens{lbrace}, $parseTokens{rbrace}, $unionname, $structname, # $enumname, # $typedefname, $varname, $constname, $structisbrace, $macronamesref, # $classregexp, $classbraceregexp, $classclosebraceregexp, $accessregexp, # $requiredregexp, $propname, $objcdynamicname, $objcsynthesizename, $moduleregexp, $definename, # $functionisbrace, $classisbrace, $parseTokens{lbraceconditionalre}, $parseTokens{lbraceunconditionalre}, $assignmentwithcolon, # $labelregexp, $parmswithcurlybraces, $superclasseswithcurlybraces, $soconstructor) = parseTokens($apio->lang(), $apio->sublang()); # @_; my %parseTokens = %{parseTokens($apio->lang(), $apio->sublang())}; my $eoDeclaration = 1; my $lastDeclaration = ""; my $curDeclaration = ""; my $sodec = $self; my $pendingHDcomment = ""; my ($case_sensitive, $keywordhashref) = $apio->keywords(); my $lastnode = undef; my $parserState = $self->parserState(); if ($parserState) { print STDERR "PARSERSTATE\n" if ($localDebug); $lastnode = $parserState->{lastTreeNode}; print STDERR "LASTNODE: $lastnode\n" if ($localDebug); if ($lastnode && $localDebug) { print STDERR "LASTNODE TEXT: \"".$lastnode->token()."\"\n"; } } my $enable_javadoc_comments = $HeaderDoc::parse_javadoc || ($apio->lang() eq "java"); # print STDERR "EJC: $enable_javadoc_comments\n"; if ($apio->isAPIOwner()) { print STDERR "Owner is APIOwner. Using APIOprocessEmbeddedTagsRec for parse tree $self.\n" if ($localDebug); my $hashtreecur = undef; my $hashtreeroot = undef; print STDERR "BEFORE APIOprocessEmbeddedTagsRec for $apio (".$apio->name().")\n" if ($localDebug > 1); print STDERR "PS: $parserState\n" if ($localDebug > 1); cluck("bt\n") if ($localDebug > 1); $self->dbprint() if ($localDebug > 1); # $self->printTree() if ($localDebug > 1); $self->APIOprocessEmbeddedTagsRec($apiOwner, $case_sensitive, \%parseTokens, $lastnode, 0, 1, $enable_javadoc_comments, $xmlmode, $apio->lang(), $apio->sublang(), $hashtreecur, $hashtreeroot); } else { print STDERR "calling processEmbeddedTagsRec for $apio (".$apio->name().") for parse tree $self.\n" if ($localDebug); $self->processEmbeddedTagsRec($xmlmode, $eoDeclaration, \%parseTokens, $case_sensitive, $keywordhashref, $lastDeclaration, $curDeclaration, $pendingHDcomment, $apio, $apiolist, $sodec, $lastnode, $enable_javadoc_comments, undef); } print STDERR "PETDONE\n" if ($localDebug); $HeaderDoc::enable_cpp = $old_enable_cpp; return; } # /*! # @abstract # Gets the name and the corresponding comment # field type (e.g. \@struct) for a given # declaration # @discussion # This helper reduces the amount of code needed # to use the block parser in several places in # the parse tree code by consolidating this # logic in one place. # @param self # This parse tree object. # @param string # The declaration to parse. # @param apio # The object (may be API owner or not) to which # the resulting objects should be added. # @param typedefname # The keyword typedef or equivalent. # @param case_sensitive # Set to 0/1 depending on whether the current # programming language uses case-sensitive # token matching. # @param keywordhashref # A reference to the keyword hash for this # language (obtained with a call to the # {@link //apple_ref/perl/instm/HeaderDoc::HeaderElement/keywords//() keywords} # function). # */ sub getNameAndFieldTypeFromDeclaration { my $self = shift; my $string = shift; my $apio = shift; my $typedefname = shift; my $case_sensitive = shift; my $keywordhashref = shift; my $localDebug = 0; my $inputCounter = 0; my $fullpath = $apio->fullpath(); my $linenum = $apio->linenum(); my $lang = $apio->lang(); my $sublang = $apio->sublang(); my $blockoffset = $linenum; my $argparse = 2; if ($string !~ /\S/) { return ("", ""); } # This never hurts just to make sure the parse terminates. # Be sure to add a newline before the semicolon in case # there's an inline comment at the end. $string .= "\n;\n"; print STDERR "STRING WAS $string\n" if ($localDebug); cluck("getNameAndFieldTypeFromDeclaration backtrace\n") if ($localDebug); my @lines = split(/\n/, $string); # my ($sotemplate, $eotemplate, $operator, $parseTokens{soc}, $parseTokens{eoc}, $parseTokens{ilc}, $parseTokens{ilc_b}, $sofunction, # $parseTokens{soprocedure}, $parseTokens{sopreproc}, $parseTokens{lbrace}, $parseTokens{rbrace}, $unionname, $structname, # $enumname, # $typedefname, $varname, $constname, $structisbrace, $macronameref, # $classregexp, $classbraceregexp, $classclosebraceregexp, $accessregexp, # $requiredregexp, $propname, $objcdynamicname, $objcsynthesizename, $moduleregexp, $definename, # $functionisbrace, $classisbrace, $parseTokens{lbraceconditionalre}, $parseTokens{lbraceunconditionalre}, $assignmentwithcolon, # $labelregexp, $parmswithcurlybraces, $superclasseswithcurlybraces, $soconstructor) = parseTokens($lang, $sublang); # my @newlines = (); foreach my $line (@lines) { $line .= "\n"; # push(@newlines, $line); # print STDERR "LINE: $line\n" if ($localDebug); } # @lines = @newlines; # my ($case_sensitive, $keywordhashref) = $apio->keywords(); my $lastlang = $HeaderDoc::lang; my $lastsublang = $HeaderDoc::sublang; $HeaderDoc::lang = $apio->lang; $HeaderDoc::sublang = $apio->sublang; my ($inputCounter, $declaration, $typelist, $namelist, $posstypes, $value, $pplStackRef, $returntype, $privateDeclaration, $treeTop, $simpleTDcontents, $availability, $fileoffset, $conformsToList) = blockParse($fullpath, $blockoffset, \@lines, $inputCounter, $argparse, \%HeaderDoc::ignorePrefixes, \%HeaderDoc::perHeaderIgnorePrefixes, \%HeaderDoc::perHeaderIgnoreFuncMacros, $keywordhashref, $case_sensitive, $apio->lang, $apio->sublang); # print "DEC WAS $string\n"; # print "DEC RETURNED WAS $declaration\n"; $HeaderDoc::lang = $lastlang; $HeaderDoc::sublang = $lastsublang; # @@@ FIX UP TYPES HERE print STDERR "IC:$inputCounter DEC:$declaration TL:$typelist NL:$namelist PT:$posstypes VAL:$value PSR:$pplStackRef RT:$returntype PD:$privateDeclaration TT:$treeTop STC:$simpleTDcontents AV:$availability\n" if ($localDebug); $self->parsedParamCopy($pplStackRef, $lang, $sublang); my $name = $namelist; $name =~ s/^\s*//so; # ditch leading spaces $name =~ s/\s.*$//so; # ditch any additional names. (There shouldn't be any) # print STDERR "NAME WAS $name\n"; my $typestring = $typelist . $posstypes; print STDERR "TS: $typestring\n" if ($localDebug); my $type = "\@constant"; if ($typestring =~ /^(function|method|ftmplt|operator|callback)/o) { $type = "\@$1"; if ($typestring =~ /(ftmplt|operator)/) { $type = "\@function"; } # $type = "\@callback"; } elsif ($typestring =~ /^(class|interface|module|category|protocol)/o) { $typestring =~ s/^ *//; $type = "\@$typestring"; $type =~ s/ .*$//; } elsif ($typestring =~ /^(struct|union|record|enum|typedef)/o || (($typedefname ne "") && $typestring =~ /^$typedefname/)) { $type = "\@field"; } elsif ($typestring =~ /(MACRO|#define)/o) { $type = "\@field"; if ($apio eq "HeaderDoc::PDefine") { # The @defineblock case $type = "\@define"; } } elsif ($typestring =~ /(constant)/o) { $type = "\@constant"; print STDERR "VALUE: \"$value\"\n" if ($localDebug); if (($value eq "")) { # It's just a variable. if ($apio =~ /HeaderDoc::Enum/) { $type = "\@constant"; # Still a constant, albeit with an unknown value. } else { $type = "\@field"; } } } else { warn "getNameAndFieldTypeFromDeclaration: UNKNOWN TYPE ($typestring) RETURNED BY BLOCKPARSE\n"; print STDERR "STRING WAS $string\n" if ($localDebug); } if (!$name || ($name eq "")) { warn "COULD NOT GET NAME FROM DECLARATION. DECLARATION WAS:\n$string\n"; return ("", ""); } print STDERR "TYPE $type, NAME $name\n" if ($localDebug); return ($name, $type); } # /*! # @abstract # Returns whether to process comments in the children of a node # with a given token. # @discussion # The tree walking code explicitly avoid things like strings # and regular expressions without the need to search for them by # disallowing children of any non-word, non-whitespace characters # other than parentheses, curly braces, and colons. # # @param token # The token to check. # @param soc # The start-of-comment token for the current programming language. # Obtained by a call to # {@link //apple_ref/perl/instm/HeaderDoc::Utilities/parseTokens//() parseTokens}. # @param eoc # The start-of-comment token for the current programming language. # Obtained by a call to # {@link //apple_ref/perl/instm/HeaderDoc::Utilities/parseTokens//() parseTokens}. # @param ilc # The start-of-comment token for the current programming language. # Obtained by a call to # {@link //apple_ref/perl/instm/HeaderDoc::Utilities/parseTokens//() parseTokens}. # @param ilc_b # The start-of-comment token for the current programming language. # Obtained by a call to # {@link //apple_ref/perl/instm/HeaderDoc::Utilities/parseTokens//() parseTokens}. # @param lbrace # The start-of-comment token for the current programming language. # Obtained by a call to # {@link //apple_ref/perl/instm/HeaderDoc::Utilities/parseTokens//() parseTokens}. # @param case_sensitive # The start-of-comment token for the current programming language. # Obtained by a call to # {@link //apple_ref/perl/instm/HeaderDoc::HeaderElement/keywords//() keywords}. # */ sub commentsNestedIn { my $token = shift; my $soc = shift; my $eoc = shift; my $ilc = shift; my $ilc_b = shift; my $lbrace = shift; my $case_sensitive = shift; # if ($token eq $soc || $token eq $eoc || $token eq $ilc || $token eq $ilc_b) { return 1; } if ($token =~ /\W/o) { if ($token =~ /[{(}):]/o) { return 1; } if ($token =~ /^#/o) { return 2; } if (casecmp($token, $lbrace, $case_sensitive)) { return 1; } if ($token =~ /\s/o) { return 1; } return 0; } # if (casecmp($token, $lbrace, $case_sensitive)) { return 1; } return 1; } # /*! # @abstract # Processes embedded tags within an API owner (class). # @discussion # Unlike {@link processEmbeddedTagsRec}, this function # does significantly less work because the code in APIOwner.pm # handles most of the effort, thanks in part to the pots of # parser gold hidden in the parse tree's hollow. # # This function also works differently because instead of # adding fields to a struct or whatever, it is adding # functions, data types, variables, and even classes # that are part of another class. # */ sub APIOprocessEmbeddedTagsRec($$$$$$$$) { my $self = shift; my $apiOwner = shift; # my $apiolist = shift; my $case_sensitive = shift; my $parseTokensRef = shift; my $lastTreeNode = shift; my $skipchildren = shift; my $isroot = shift; # Don't add parse tree to the parent object twice. my $enable_javadoc_comments = shift; my $xmlmode = shift; my $lang = shift; my $sublang = shift; my $hashtreecur = shift; my $hashtreeroot = shift; my %parseTokens = %{$parseTokensRef}; # print STDERR "EJC: $enable_javadoc_comments\n"; my $localDebug = 0; if (1 && $localDebug) { my $apio = $self->apiOwner(); if ($apio) { # if ($apio->name() eq "OSObject") { print STDERR "DUMPING TREE.\n"; $self->dbprint(); # } } } my $continue = 1; if ($self == $lastTreeNode) { $continue = 0; print STDERR "CONTINUE -> 0\n" if ($localDebug); # return $continue; # Added this to try to fix a bug, but wasn't needed. } my $token = $self->token(); my $firstchild = $self->firstchild(); my $next = $self->next(); print STDERR "APIOprocessEmbeddedTagsRec: TOKEN IS \"".$self->token()."\"\n" if ($localDebug); my $handled = 0; print STDERR "SOC: \"$parseTokens{soc}\" ILC: \"$parseTokens{ilc}\" ILC_B: \"$parseTokens{ilc_b}\"\n" if ($localDebug); if ((length($parseTokens{soc}) && ($token eq $parseTokens{soc})) || (length($parseTokens{ilc}) && ($token eq $parseTokens{ilc})) || (length($parseTokens{ilc_b}) && ($token eq $parseTokens{ilc_b}))) { if ((($token eq $parseTokens{soc} && $parseTokens{soc}) || ($token eq $parseTokens{ilc} && $parseTokens{ilc})) && $firstchild) { print STDERR "COMMENT CHECK\n" if ($localDebug); my $ntoken = $firstchild->token(); my $nntoken = ""; if ($firstchild->next()) { $nntoken = $firstchild->next()->token(); } # The parser changed behavior. The folowing is probably the only relevant behaviour, # but the previous behavior is preserved for partial inter-module backwards compatibility. if ($ntoken eq "" && $firstchild->next()) { $ntoken = $firstchild->next()->token(); if ($firstchild->next()->next()) { $nntoken = $firstchild->next()->next()->token(); } else { $nntoken = ""; } } # print STDERR "FC: ".$firstchild->token()."\n"; # print STDERR "FCN: ".$firstchild->next()->token()."\n"; # print STDERR "FCNN: ".$firstchild->next()->next()->token()."\n"; if ($lang eq "ruby" || $lang eq "python") { my $node = $firstchild; while ($node && ($node->token() !~ /\S/)) { $node = $node->next(); } if ($node) { $nntoken = $node->token(); } # print STDERR "HERE: N = \"$ntoken\" NN = \"$nntoken\"\n"; # if ($ntoken eq "\n" && $nntoken eq "!" && $firstchild->next()->next()->next() && # $firstchild->next()->next()->next()->next() && # $firstchild->next()->next()->next()->next()->next()) { # my $nnntoken = $firstchild->next()->next()->next()->token(); # my $nnnntoken = $firstchild->next()->next()->next()->next()->token(); # print STDERR "HERE: NNN = \"$nnntoken\" NNNN = \"$nnnntoken\"\n"; # if ($nnntoken =~ /headerdoc/i && $nnnntoken eq "!") { # $ntoken = "!"; # } else { # $ntoken = ""; # } # } else { # $ntoken = ""; # } if ($nntoken =~ /\s*!headerdoc!/i) { $ntoken = "!"; } else { $ntoken = ""; } } print STDERR "NTOKEN: $ntoken\n" if ($localDebug); print STDERR "NNTOKEN: $nntoken\n" if ($localDebug); if ((($lang ne "tcl" && ($ntoken eq "!" || ($enable_javadoc_comments && $ntoken eq "*" && $nntoken !~ /^\*/))) || ($lang eq "tcl" && ($ntoken eq "/*" && $nntoken eq "!" && $firstchild->next()->next()->next()))) && ($lang eq "applescript" || (!$self->hidden()))) { print STDERR "NODECHECK: $self\n" if ($localDebug); # HDCOMMENT $self->dbprint() if ($localDebug); my $string = $token.$firstchild->textTree(); # print STDERR "STRING IS $string\n"; if ($lang eq "ruby" || $lang eq "python") { $string = $firstchild->next()->next()->textTree(); $string =~ s/^\s*!headerdoc!\s*//si; $string = "/*!".$string; $token = $parseTokens{soc}; # print STDERR "STRING IS $string\n"; } elsif ($lang eq "tcl") { # Scripting languages work differently. $string = "/*!".$firstchild->next()->next()->next()->textTree(); # $self->dbprint(); # warn("PreString: $string\n"); my $foundend = 0; if ($string =~ /\*\//) { $foundend = 1; } my $pos = $self->next(); # warn("NEXT TEXT: ".$pos->textTree()."\n"); while ((!$foundend) && $pos) { print "MOVING TO NEXT. POS IS $pos TOKEN IS ".$pos->token()."\n" if ($localDebug); my $temp = $pos->firstchild(); if ($temp) { my $tempstr = $temp->textTree(); if ($pos->token() eq "#") { $string .= $tempstr; } elsif ($tempstr =~ /\S/) { $foundend; } else { $string .= "\n"; } } else { $string .= "\n"; } if ($string =~ /\*\//) { $foundend = 1; } $pos = $pos->next(); } # die("PostString: $string\n"); } elsif ($lang eq "applescript") { $string =~ s/^\s*\(\*/\/\*/s; } if ($ntoken eq "*") { $string =~ s/^\s*\/\*\*/\/\*\!/s; } print STDERR "FOUND HDCOMMENT:\n$string\nEND HDCOMMENT\n" if ($localDebug); # $string =~ s/^\/[\*\/]\!//s; # $string =~ s/^\s*//s; if ($token eq $parseTokens{soc} && $parseTokens{soc}) { my $eoc = $parseTokens{eoc}; $string =~ s/\Q$eoc\E\s*$//s; } print STDERR "STRING IS $string\n" if ($localDebug); # @@@ DAG CHECK ME # $string =~ s/^\s*\*\s*//mg; my $fieldref = stringToFields(doxyTagFilter($string), $self->fullpath, $self->linenum, $xmlmode, $lang, $sublang); # print STDERR "APIOLIST AT INSERT IS $apiolist\n" if ($localDebug); # foreach my $owner (@{$apiolist}) { # print STDERR "X POSSOWNER of $self: $owner\n" if ($localDebug); # } # foreach my $owner (@{$apiolist}) { print STDERR "POSSOWNER of $self: $apiOwner\n" if ($localDebug); if ($apiOwner && $apiOwner->isAPIOwner()) { print STDERR "ADDING[1] TO $apiOwner.\n" if ($localDebug); my $ncurly; ($ncurly, $hashtreecur, $hashtreeroot) = $apiOwner->processComment($fieldref, 1, $self->nextTokenNoComments($parseTokens{soc}, $parseTokens{ilc}, $parseTokens{ilc_b}, 1, $enable_javadoc_comments), $parseTokens{soc}, $parseTokens{ilc}, $parseTokens{ilc_b}, $self, $hashtreecur, $hashtreeroot); # We have found the correct level. Anything # nested deeper than this is bogus (unless we hit a curly brace). print STDERR "skipochildren -> 1 [1]" if ($localDebug); $skipchildren = 1; if ($next) { $next = $next->skipcurly($parseTokens{lbrace}, $ncurly); # nextTokenNoComments($parseTokens{soc}, $parseTokens{ilc}, $parseTokens{ilc_b}, 0, $enable_javadoc_comments); } if ($localDebug) { print STDERR "NEXT IS $next ("; if ($next) {print STDERR $next->token(); } print STDERR ")\n"; } } # } $handled = 1; } } elsif ($firstchild && $firstchild->next() && $firstchild->next()->next()) { my $pos = $firstchild->next(); my $fcntoken = $pos->token(); while ($fcntoken =~ /\s/ && $pos) { $pos = $pos->next; $fcntoken = $pos->token(); } if (($fcntoken eq $parseTokens{soc} && $parseTokens{soc}) || ($fcntoken eq $parseTokens{ilc} && $parseTokens{ilc})) { my $fcnntoken = $firstchild->next()->next()->token(); my $fcnnntoken = ""; if ($firstchild->next()->next()->next()) { $fcnnntoken = $firstchild->next()->next()->next()->token(); } if ($fcnntoken eq "!" || ($enable_javadoc_comments && $fcnntoken eq "*" && $fcnnntoken !~ /^\*/)) { # HDCOMMENT my $string = $fcntoken.$firstchild->textTree(); if ($fcnntoken eq "*") { $string =~ s/^\s*\/\*\*/\/\*\!/s; } print STDERR "FOUND HDCOMMENT:\n$string\nEND HDCOMMENT\n" if ($localDebug); # $string =~ s/^\Q$fcntoken\E//s; # $string =~ s/^\s*\/[\*\/]\!//s; # $string =~ s/^\s*//s; if ($fcntoken eq $parseTokens{soc} && $parseTokens{soc}) { my $eoc = $parseTokens{eoc}; $string =~ s/\Q$eoc\E\s*$//s; } # @@@ DAG CHECKME LEADING STARS # $string =~ s/^\s*\*\s*//mg; my $fieldref = stringToFields(doxyTagFilter($string), $self->fullpath, $self->linenum, $xmlmode, $lang, $sublang); # foreach my $owner (@{$apiolist}) { print STDERR "POSSOWNER of $self: $apiOwner\n" if ($localDebug); if ($apiOwner && $apiOwner->isAPIOwner()) { print STDERR "ADDING[2] TO $apiOwner.\n" if ($localDebug); my $ncurly; ($ncurly, $hashtreecur, $hashtreeroot) = $apiOwner->processComment($fieldref, 1, $self->nextTokenNoComments($parseTokens{soc}, $parseTokens{ilc}, $parseTokens{ilc_b}, 1, $enable_javadoc_comments), $parseTokens{soc}, $parseTokens{ilc}, $parseTokens{ilc_b}, $self, $hashtreecur, $hashtreeroot); print STDERR "skipochildren -> 1 [2]" if ($localDebug); $skipchildren = 1; # skip the current declaration before # processing anything else to avoid # bogus warnings from nested # HeaderDoc comments. $next = $next->skipcurly($parseTokens{lbrace}, $ncurly); # nextTokenNoComments($parseTokens{soc}, $parseTokens{ilc}, $parseTokens{ilc_b}, 0, $enable_javadoc_comments); if ($localDebug) { print STDERR "NEXT IS $next ("; if ($next) {print STDERR $next->token(); } print STDERR ")\n"; } } # } $handled = 1; } } } } if (!$handled && $self->parserState() && !$self->parserState()->{APIODONE} && $HeaderDoc::process_everything && !$isroot) { print STDERR "Declaration without markup\n" if ($localDebug); # foreach my $owner (@{$apiolist}) { # print STDERR "X POSSOWNER of $self: $apiOwner\n" if ($localDebug); # } # foreach my $apiOwner (@{$apiolist}) { print STDERR "POSSOWNER of $self: $apiOwner\n" if ($localDebug); # Found an embedded declaration that is not tagged. my @fields = (); if ($apiOwner && $apiOwner->isAPIOwner()) { my $ncurly; ($ncurly, $hashtreecur, $hashtreeroot) = $apiOwner->processComment(\@fields, 1, $self, $parseTokens{soc}, $parseTokens{ilc}, $parseTokens{ilc_b}, $self, $hashtreecur, $hashtreeroot); $handled = 1; } else { warn "$apiOwner is not API Owner\n"; } # } } if ($handled && $localDebug) { print STDERR "ADDED TREE TO $apiOwner (".$apiOwner->name().")\n"; $self->dbprint(); print STDERR "DUMPING API OWNER:\n"; $apiOwner->dbprint(); print STDERR "END DUMP.\n"; } # print STDERR "PS: ".$self->parserState()."\n"; # If we get here, we weren't a skipped brace, so we can start nesting again. if (length($parseTokens{lbrace}) && $token eq $parseTokens{lbrace}) { print STDERR "skipochildren -> 0 [3]" if ($localDebug); $skipchildren = 0; } if ($firstchild && !$skipchildren) { print STDERR "APIOprocessEmbeddedTagsRec: MAYBE GOING TO CHILDREN\n" if ($localDebug); my $nestallowed = commentsNestedIn($token, $parseTokens{soc}, $parseTokens{eoc}, $parseTokens{ilc}, $parseTokens{ilc_b}, $parseTokens{lbrace}, $case_sensitive); if ($nestallowed) { print STDERR "APIOprocessEmbeddedTagsRec: YUP. CHILDREN.\n" if ($localDebug); my $newcontinue; ($newcontinue, $hashtreecur, $hashtreeroot) = $firstchild->APIOprocessEmbeddedTagsRec($apiOwner, $case_sensitive, $parseTokensRef, $lastTreeNode, $skipchildren, 0, $enable_javadoc_comments, $xmlmode, $lang, $sublang, $hashtreecur, $hashtreeroot); if ($continue) { $continue = $newcontinue; } print STDERR "Back from Child\n" if ($localDebug); print STDERR "skipochildren -> $skipchildren [RECURSEOUT]" if ($localDebug); } } if ($next && $continue) { print STDERR "APIOprocessEmbeddedTagsRec: GOING TO NEXT\n" if ($localDebug); ($continue, $hashtreecur, $hashtreeroot) = $next->APIOprocessEmbeddedTagsRec($apiOwner, $case_sensitive, $parseTokensRef, $lastTreeNode, $skipchildren, 0, $enable_javadoc_comments, $xmlmode, $lang, $sublang, $hashtreecur, $hashtreeroot); print STDERR "Back from Next\n" if ($localDebug); } print STDERR "HTC: $hashtreecur, HTR: $hashtreeroot\n" if ($localDebug); print STDERR "SN: ".$self->next()." (".($self->next() ? $self->next()->token() : "").")\n" if ($localDebug); print STDERR "RECURSEOUT (CONTINUE is $continue)\n" if ($localDebug); return ($continue, $hashtreecur, $hashtreeroot); } my $extDebug = 0; # /*! # @abstract # Recursively walks the parse tree looking for HeaderDoc markup # and processes it. # @discussion # This subroutine processes the parse tree recursively looking for # (and subsequently processing) embedded headerdoc markup, then parses # the associated declarations. This is a helper function that does # the actual work for processEmbeddedTags. # */ sub processEmbeddedTagsRec { my $self = shift; my $xmlmode = shift; my $eoDeclaration = shift; my $parseTokensRef = shift; my $case_sensitive = shift; my $keywordhashref = shift; my $lastDeclaration = shift; my $curDeclaration = shift; my $pendingHDcomment = shift; my $apio = shift; my $apiolist = shift; my $sodec = shift; my $lastTreeNode = shift; my $enable_javadoc_comments = shift; my $ASterminator = shift; my %parseTokens = %{$parseTokensRef}; my $localDebug = 0 || $extDebug; my $fieldTypeDebug = 0; my $oldCurDeclaration = $curDeclaration; my $oldsodec = $sodec; my $ntoken = $self->nextpeeknc($parseTokens{soc}, $parseTokens{ilc}, $parseTokens{ilc_b}); my $skipchildren = 0; my $oldPHD = $pendingHDcomment; my $do_process = 0; my $continue = 1; my $inBlockDefine = 0; my $dropinvalid = 0; my $lastsodec = $sodec; my $nextsodec = $sodec; my $hashtreecur = undef; my $hashtreeroot = undef; print STDERR "PETREC\n" if ($localDebug); print STDERR "PENDING COMMENT: $pendingHDcomment\n" if ($localDebug); print STDERR "LAST DECLARATION: $lastDeclaration\n" if ($localDebug); if (!$self) { return ($eoDeclaration, $pendingHDcomment, $continue, $ASterminator); } my $apioclass = ref($apio) || $apio; if ($apioclass =~ /HeaderDoc::PDefine/) { # print STDERR "HDPDEF: ISBLOCK: ".$apio->isBlock()." inDefineBlock: ".$apio->inDefineBlock().".\n"; if ($apio->inDefineBlock() || $apio->isBlock()) { $inBlockDefine = 1; my $x = pop(@{$apiolist}); $do_process = $x; if ($x) { push(@{$apiolist}, $x); } } } if ($apio->lang() eq "applescript") { if (!$ASterminator) { if ($self->token eq "(") { $ASterminator = $self; } } } if ($self == $lastTreeNode) { print STDERR "CONTINUE -> 0\n" if ($localDebug); $continue = 0; } # print STDERR "lastDec: $lastDeclaration\ncurDec: $curDeclaration\neoDec: $eoDeclaration\n" if ($localDebug); # Walk the tree. my $token = $self->token(); $curDeclaration .= $token; # Used to turn on debugging at a given symbol. # if ($token eq "kDNSServiceFlagsSuppressUnusable") { $localDebug = 1; $extDebug = 1; } print STDERR "TOKEN: $token\n" if ($localDebug); # if ($token !~ /\s/o) { print STDERR "TOKEN: \"$token\" SOC: \"$parseTokens{soc}\" ILC: \"$parseTokens{ilc}\" ILC_B: \"$parseTokens{ilc_b}\".\n"; } if (($token eq $parseTokens{soc} && $parseTokens{soc}) || ($token eq $parseTokens{ilc} && $parseTokens{ilc}) || ($token eq $parseTokens{ilc_b} && $parseTokens{ilc_b})) { my $firstchild = $self->firstchild(); print STDERR "Comment start\n" if ($localDebug); if ($firstchild) { print STDERR "FCT: ".$firstchild->token()."\n" if ($localDebug); my $nextchild = $firstchild->next(); my $nntoken = ""; if ($nextchild && $nextchild->next()) { $nntoken = $nextchild->next()->token(); } if ($nextchild && ($nextchild->token eq "!" || ($enable_javadoc_comments && $nextchild->token eq "*" && $nntoken !~ /^\*/))) { print STDERR "Found embedded HeaderDoc markup\n" if ($localDebug); print STDERR "NCT: ".$nextchild->token()."\n" if ($localDebug); # print STDERR "NCT TREE:\n"; $self->printTree(); print STDERR "NCT ENDTREE\n"; print STDERR "WILL SET SODEC. SEARCHING IN:\n" if ($localDebug); $self->dbprint() if ($localDebug); $sodec = $self->nextTokenNoComments($parseTokens{soc}, $parseTokens{ilc}, $parseTokens{ilc_b}, 0, $enable_javadoc_comments); # print STDERR "NCT SODECTREE:\n"; $sodec->printTree(); print STDERR "NCT ENDTREE\n"; print STDERR "SODEC SET TO $sodec\n" if ($localDebug); if ($sodec) { $sodec->dbprint() if ($localDebug); } my $string = $firstchild->textTree(); my $fullpath = $apio->fullpath(); my $linenum = $apio->linenum(); if ($token eq $parseTokens{soc} && $parseTokens{soc}) { my $eoc = $parseTokens{eoc}; $string =~ s/\Q$eoc\E\s*$//s; } if ($string =~ /^\s*\!/o) { $string =~ s/^\s*\!//so; my $tagstring = $string; $tagstring =~ s/^\s*\@//so; print STDERR "EOD $eoDeclaration NT $ntoken STR $string\n" if ($localDebug);; if ((($eoDeclaration && $lastDeclaration =~ /[a-zA-Z]/) || !$ntoken || $ntoken =~ /[)}]/o || casecmp($ntoken, $parseTokens{rbrace}, $case_sensitive)) && ($string !~ /^\s*\@/o || !validTag($tagstring, 0))) { if (!$inBlockDefine) { $string =~ s/\@abstract/ /sg; $string =~ s/\@discussion/ /sg; } # If we're at the end of a declaration (prior to the # following newline) and the declaration starts with # a string of text (JavaDoc-style markup), we need to # figure out the name of the previous declaration and # insert it. print STDERR "Using previous declaration because:\n" if ($localDebug); print STDERR "EODEC: $eoDeclaration NTOKEN: \"$ntoken\"\n" if ($localDebug); print STDERR "RBRACE: \"$parseTokens{rbrace}\" STRING: \"$string\"\n" if ($localDebug); if (!$eoDeclaration) { print STDERR "LASTDITCH PROCESSING\n" if ($localDebug); } else { print STDERR "EOD PROCESSING\n" if ($localDebug); } # Roll back to the previous start of declaration. # This comment is at the end of a line or whatever. $nextsodec = $sodec; $sodec = $lastsodec; $string =~ s/^\s*//so; print STDERR "COMMENTSTRING WAS: $string\n" if ($localDebug); print STDERR "PRE1\n" if ($localDebug); print STDERR "LAST DECLARATION: $lastDeclaration\n" if ($localDebug); print STDERR "calling getNameAndFieldTypeFromDeclaration [1]\n" if ($localDebug || $fieldTypeDebug); my ($name, $type) = $self->getNameAndFieldTypeFromDeclaration($lastDeclaration, $apio, $parseTokens{typedefname}, $case_sensitive, $keywordhashref); if ($name) { $string = "$type $name\n$string"; print STDERR "COMMENTSTRING NOW: $string\n" if ($localDebug); } } elsif (!$eoDeclaration && (!$ntoken || $ntoken =~ /[)]/o || casecmp($ntoken, $parseTokens{rbrace}, $case_sensitive)) && $string =~ /^\s*\@/o) { # We have found an embedded headerdoc comment embedded that is # right before a close parenthesis, but which starts with an @ sign. my $nlstring = $string; $nlstring =~ s/[\n\r]/ /sg; my $emptyok = emptyHDok($nlstring); if (!$emptyok) { warn "$fullpath:$linenum: warning: Found invalid headerdoc markup: $nlstring\n"; $dropinvalid = 1; } elsif ($emptyok == 1) { warn "$fullpath:$linenum: warning Found headerdoc markup where none expected: $nlstring\n"; } else { print STDERR "Found always-empty headerdoc markup at end of block: $nlstring\n" if ($localDebug); $dropinvalid = 2; } } $string =~ s/^\s*//so; my $tagstring = $string; $tagstring =~ s/^\s*\@//so; # warn("STR: $string TAGSTR: $tagstring\n"); # warn(validTag("", 0)." != ".validTag($tagstring, 0)); if ($string =~ /^\s*\@/o && validTag($tagstring, 0)) { # $string =~ /^\s*\@/o print STDERR "COMMENTSTRING: $string\n" if ($localDebug); my $fieldref = stringToFields(doxyTagFilter($string), $fullpath, $linenum, $xmlmode, $apio->lang(), $apio->sublang()); # print STDERR "APIO: $apio\n"; foreach my $owner (@{$apiolist}) { my $copy = $fieldref; print STDERR "OWNER[1]: $owner\n" if ($localDebug); if ($owner) { if (!$inBlockDefine || $do_process == $owner) { my @copyarray = @{$copy}; # print STDERR "COPY[1]: ".$copyarray[1]."\n"; if ($inBlockDefine && !length($copyarray[0])) { $copyarray[1] =~ s/^field .*?\n/discussion /s; $copy = \@copyarray; } # print STDERR "COPY[1]: ".$copyarray[1]."\n"; if ($dropinvalid != 2) { $owner->processComment($copy, 1, $sodec, $parseTokens{soc}, $parseTokens{ilc}, $parseTokens{ilc_b}, $self, $hashtreecur, $hashtreeroot); } } } } # print STDERR "APIO: $apio\n"; $apio->{APIREFSETUPDONE} = 0; } else { if (!$dropinvalid) { if (!$inBlockDefine) { $string =~ s/\@abstract/ /sg; $string =~ s/\@discussion/ /sg; } $pendingHDcomment = $string; # } else { # warn("DROPPING $string\n"); } } if (!$HeaderDoc::dumb_as_dirt) { # Drop this comment from the output. if ($xmlmode || ($apio->lang() eq "applescript")) { # We were doing this for HTML when we needed to # be able to reparse the tree after copying # it to a cloned data type. This is no longer # needed, and the old method (above) is slightly # faster. # # However, we still have to use this approach # for AppleScript, for that reason. $self->hidden(1); $skipchildren = 1; } else { $self->{TOKEN} = ""; $self->{FIRSTCHILD} = undef; print STDERR "HIDING $self\n" if ($localDebug); } print STDERR "DROP\n" if ($localDebug); $curDeclaration = $oldCurDeclaration; } } } } } elsif ($token =~ /[;,}]/o) { print STDERR "Punctuation\n" if ($localDebug); print STDERR "SETTING LASTDEC TO $curDeclaration\n" if ($localDebug); $lastDeclaration = "$curDeclaration\n"; if ($pendingHDcomment) { # If we're at the end of a declaration (prior to the # following newline) and the declaration starts with # a string of text (JavaDoc-style markup), we need to # figure out the name of the previous declaration and # insert it. print STDERR "PRE2\n" if ($localDebug); print STDERR "calling getNameAndFieldTypeFromDeclaration [2]\n" if ($localDebug || $fieldTypeDebug); my ($name, $type) = $self->getNameAndFieldTypeFromDeclaration($lastDeclaration, $apio, $parseTokens{typedefname}, $case_sensitive, $keywordhashref); if ($name) { my $string = "$type $name\n$pendingHDcomment"; my $fullpath = $apio->fullpath(); my $linenum = $apio->linenum(); my $fieldref = stringToFields(doxyTagFilter($string), $fullpath, $linenum, $xmlmode, $apio->lang(), $apio->sublang()); print STDERR "COMMENTSTRING: $string\n" if ($localDebug); print STDERR "OWNERSTART\n" if ($localDebug); foreach my $owner (@{$apiolist}) { my $copy = $fieldref; print STDERR "OWNER[2]: $owner\n" if ($localDebug); if ($owner) { if (!$inBlockDefine || $do_process == $owner) { my @copyarray = @{$copy}; # print STDERR "COPY[1]: ".$copyarray[1]."\n"; if ($inBlockDefine && !length($copyarray[0])) { $copyarray[1] =~ s/^field .*?\n/discussion /s; $copy = \@copyarray; } # print STDERR "COPY[1]: ".$copyarray[1]."\n"; $owner->processComment($copy, 1, $sodec, $parseTokens{soc}, $parseTokens{ilc}, $parseTokens{ilc_b}, $self, $hashtreecur, $hashtreeroot); } } } print STDERR "OWNEREND\n" if ($localDebug); } # print STDERR "APIO: $apio\n"; $apio->{APIREFSETUPDONE} = 0; $pendingHDcomment = ""; } else { $eoDeclaration = 1; } $curDeclaration = ""; } elsif ($token =~ /[\r\n]/o && $lastDeclaration) { print STDERR "NLCR\n" if ($localDebug); $lastDeclaration = ""; } elsif ($token !~ /\s/o) { print STDERR "Whitespace\n" if ($localDebug); $eoDeclaration = 0; } $sodec = $nextsodec; my $firstchild = $self->firstchild(); my $next = $self->next(); if ($firstchild && !$skipchildren) { my $nestallowed = commentsNestedIn($token, $parseTokens{soc}, $parseTokens{eoc}, $parseTokens{ilc}, $parseTokens{ilc_b}, $parseTokens{lbrace}, $case_sensitive); if ($nestallowed == 1) { my $newcontinue; my $newASterminator; ($eoDeclaration, $pendingHDcomment, $newcontinue, $newASterminator) = $firstchild->processEmbeddedTagsRec($xmlmode, $eoDeclaration, $parseTokensRef, $case_sensitive, $keywordhashref, "", "", "", $apio, $apiolist, $sodec, $lastTreeNode, $enable_javadoc_comments, $ASterminator); if ($continue) { $continue = $newcontinue; } if (!$ASterminator) { $ASterminator = $newASterminator; } } else { my $newcontinue; my $newASterminator; ($eoDeclaration, $pendingHDcomment, $newcontinue, $newASterminator) = $firstchild->processEmbeddedTagsRec($xmlmode, $eoDeclaration, $parseTokensRef, $case_sensitive, $keywordhashref, "", "$curDeclaration", "", $apio, $apiolist, $sodec, $lastTreeNode, $enable_javadoc_comments, $ASterminator); if ($continue) { $continue = $newcontinue; } if (!$ASterminator) { $ASterminator = $newASterminator; } } $curDeclaration .= textTree($firstchild); } elsif ($firstchild && !$skipchildren) { $curDeclaration .= textTree($firstchild); } if ($ntoken) { print STDERR "NTOKEN: $ntoken\n" if ($localDebug); } else { print STDERR "NTOKEN: (null)\n" if ($localDebug); } if (!$ntoken || $ntoken =~ /[)]/o || casecmp($ntoken, $parseTokens{rbrace}, $case_sensitive)) { # Last-ditch chance to process pending comment. # This takes care of the edge case where some languages # do not require the last item in a struct/record to be # terminated by a semicolon or comma. if ($ntoken =~ /[)}]/o || casecmp($ntoken, $parseTokens{rbrace}, $case_sensitive)) { if ($oldCurDeclaration =~ /\S/) { # Don't use curDeclaration here. It may have been cleared already. $lastDeclaration = $oldCurDeclaration.$token; print STDERR "CLOSEBRACE LASTDITCH: SETTING LASTDEC TO $lastDeclaration\n" if ($localDebug); } } else { if ($oldCurDeclaration =~ /\S/) { print STDERR "NONCLOSEBRACE LASTDITCH: SETTING LASTDEC TO $curDeclaration\n" if ($localDebug); $lastDeclaration = $curDeclaration; } } if ($pendingHDcomment) { print STDERR "LASTDITCH\n" if ($localDebug); print STDERR "LAST DECLARATION:\n$lastDeclaration\nEND OF LAST DECLARATION\n" if ($localDebug); # If we're at the end of a declaration (prior to the # following newline) and the declaration starts with # a string of text (JavaDoc-style markup), we need to # figure out the name of the previous declaration and # insert it. print STDERR "PRE3\n" if ($localDebug); if (!$lastDeclaration) { $lastDeclaration = $curDeclaration; }; print STDERR "calling getNameAndFieldTypeFromDeclaration [3]\n" if ($localDebug || $fieldTypeDebug); my ($name, $type) = $self->getNameAndFieldTypeFromDeclaration($lastDeclaration, $apio, $parseTokens{typedefname}, $case_sensitive, $keywordhashref); if ($name) { my $string = "$type $name\n$pendingHDcomment"; my $fullpath = $apio->fullpath(); my $linenum = $apio->linenum(); my $fieldref = stringToFields(doxyTagFilter($string), $fullpath, $linenum, $xmlmode, $apio->lang(), $apio->sublang()); print STDERR "COMMENTSTRING: $string\n" if ($localDebug); foreach my $owner (@{$apiolist}) { my $copy = $fieldref; print STDERR "OWNER[3]: $owner\n" if ($localDebug); if ($owner) { if (!$inBlockDefine || $do_process == $owner) { my @copyarray = @{$copy}; # print STDERR "COPY[1]: ".$copyarray[1]."\n"; if ($inBlockDefine && !length($copyarray[0])) { $copyarray[1] =~ s/^field .*?\n/discussion /s; $copy = \@copyarray; } # print STDERR "COPY[1]: ".$copyarray[1]."\n"; $owner->processComment($copy, 1, $sodec, $parseTokens{soc}, $parseTokens{ilc}, $parseTokens{ilc_b}, $self, $hashtreecur, $hashtreeroot); } } } } # print STDERR "APIO: $apio\n"; $apio->{APIREFSETUPDONE} = 0; $pendingHDcomment = ""; } } # $sodec = $oldsodec; if ($ASterminator == $self) { $continue = 0; } if ($next && $continue) { ($eoDeclaration, $pendingHDcomment, $continue) = $next->processEmbeddedTagsRec($xmlmode, $eoDeclaration, $parseTokensRef, $case_sensitive, $keywordhashref, $lastDeclaration, $curDeclaration, $pendingHDcomment, $apio, $apiolist, $sodec, $lastTreeNode, $enable_javadoc_comments, $ASterminator); } return ($eoDeclaration, $pendingHDcomment, $continue, $ASterminator); } # THIS CODE USED TO PROCESS COMMENTS WHENEVER IT IS TIME. # my $fieldref = stringToFields(doxyTagFilter($string), $fullpath, $linenum, $xmlmode, $lang, $sublang); # $apio->processComment($fieldref, 1, $self, $parseTokens{soc}, $parseTokens{ilc}, $parseTokens{ilc_b}, $self, $hashtreecur, $hashtreeroot); # $apio->{APIREFSETUPDONE} = 0; # /*! # @abstract # Gets/sets the next parse tree node (sibling) for this one. # @param self # This parse tree object. # @param NEXT # The new next node. (Optional.) # */ sub next { my $self = shift; if (@_) { my $node = shift; $self->{NEXT} = $node; } return $self->{NEXT}; } # /*! # @abstract # Gets/sets the first child parse tree node for this one. # @param self # This parse tree object. # @param NEXT # The new child node. (Optional.) # */ sub firstchild { my $self = shift; if (@_) { my $node = shift; $self->{FIRSTCHILD} = $node; } return $self->{FIRSTCHILD}; } # /*! # @abstract # Gets/sets the parent parse tree node for this one. # @param self # This parse tree object. # @param NEXT # The new parent node. (Optional.) # */ sub parent { my $self = shift; if (@_) { my $node = shift; $self->{PARENT} = $node; # weaken($self->{PARENT}); } return $self->{PARENT}; } # /*! # @abstract # Prints the text of the parse tree to standard output (for debugging). # @param self # The top of the tree/subtree to print # @seealso //apple_ref/perl/instm/HeaderDoc::ParseTree/dbprint//() dbprint # */ sub printTree { my $self = shift; print STDERR "BEGINPRINTTREE\n"; print STDERR $self->textTree(); print STDERR "ENDPRINTTREE\n"; } # /*! # @abstract # Prints a text representation of the parse tree as a string. # @param self # The top of the tree/subtree to dump. # */ sub textTree { my $self = shift; my $nohidden = 0; if (@_) { $nohidden = shift; } my $parserState = $self->parserState(); my $lastnode = undef; my $localDebug = 0; if ($parserState) { $lastnode = $parserState->{lastTreeNode}; } print STDERR "TEXTTREE: LASTNODE: $lastnode\n" if ($localDebug); if ($lastnode && $localDebug) { print STDERR "LASTNODE TEXT: \"".$lastnode->token()."\"\n"; } my ($string, $continue) = $self->textTreeSub(0, $nohidden, "", "", "", $lastnode); return $string; } # /*! # @abstract # Prints a text representation of the parse tree as a string with # all comments omitted. # @param self # The top of the tree/subtree to dump. # @param lang # The current language. # @param sublang # The current language dialect (e.g. cpp for C++). # */ sub textTreeNC { my $self = shift; my $lang = shift; my $sublang = shift; # my ($sotemplate, $eotemplate, $operator, $parseTokens{soc}, $parseTokens{eoc}, $parseTokens{ilc}, $parseTokens{ilc_b}, $sofunction, # $parseTokens{soprocedure}, $parseTokens{sopreproc}, $parseTokens{lbrace}, $parseTokens{rbrace}, $unionname, $structname, # $enumname, # $typedefname, $varname, $constname, $structisbrace, $macronamesref, # $classregexp, $classbraceregexp, $classclosebraceregexp, $accessregexp, # $requiredregexp, $propname, $objcdynamicname, $objcsynthesizename, $moduleregexp, $definename, # $functionisbrace, $classisbrace, $parseTokens{lbraceconditionalre}, $parseTokens{lbraceunconditionalre}, $assignmentwithcolon, # $labelregexp, $parmswithcurlybraces, $superclasseswithcurlybraces, $parseTokens{soc}) = parseTokens($lang, $sublang); my $nohidden = 0; if (@_) { $nohidden = shift; } my %parseTokens = %{parseTokens($lang, $sublang)}; my ($string, $continue) = $self->textTreeSub(1, $nohidden, $parseTokens{soc}, $parseTokens{ilc}, $parseTokens{ilc_b}); return $string; } # /*! # @abstract # The recursive portion of {@link textTree} and # {@link textTreeNC}. # @param self # The top of the tree/subtree to dump. # @param nc # Set to 1 if comments should be dropped, else 0. # @param nh # Set to 1 if hidden tokens should be dropped, else 0. # @param soc # The start-of-comment token for the current programming language. # Obtained by a call to # {@link //apple_ref/perl/instm/HeaderDoc::Utilities/parseTokens//() parseTokens}. # @param ilc # The start-of-comment token for the current programming language. # Obtained by a call to # {@link //apple_ref/perl/instm/HeaderDoc::Utilities/parseTokens//() parseTokens}. # @param ilc_b # The start-of-comment token for the current programming language. # Obtained by a call to # {@link //apple_ref/perl/instm/HeaderDoc::Utilities/parseTokens//() parseTokens}. # @param lastnode # The last node to include. This node and children of this node are # included, but not siblings of this node. # */ sub textTreeSub { my $self = shift; my $nc = shift; my $nh = shift; my $soc = shift; my $ilc = shift; my $ilc_b = shift; my $lastnode = shift; my $localDebug = 0; my $continue = 1; print STDERR "TTSUB: LN: $lastnode SELF: $self TOK: ".$self->token()."\n" if ($localDebug); if ($lastnode == $self) { print STDERR "TTSUB: CONTINUE -> 0\n" if ($localDebug); $continue = 0; } my $string = ""; my $skip = 0; my $token = $self->token(); if ($nc) { if ($localDebug) { print STDERR "NC\n"; print STDERR "SOC: $soc\n"; print STDERR "ILC: $ilc\n"; print STDERR "ILC_B: $ilc_b\n"; print STDERR "TOK: $token\n"; } if (($token eq "$soc") || ($token eq "$ilc") || ($token eq "$ilc_b")) { $skip = 1; } } if ($nh) { if ($self->hidden()) { $skip = 1; } } if (!$skip) { $string .= $token; # if (!$continue) { # return ($string, $continue); # } if ($self->{FIRSTCHILD}) { my $node = $self->{FIRSTCHILD}; bless($node, "HeaderDoc::ParseTree"); my ($newstring, $newcontinue) = $node->textTreeSub($nc, $nh, $soc, $ilc, $ilc_b, $lastnode); if ($continue) { $continue = $newcontinue; } $string .= $newstring; } } if (!$continue) { return ($string, $continue); } if ($self->{NEXT}) { my $node = $self->{NEXT}; bless($node, "HeaderDoc::ParseTree"); my ($newstring, $newcontinue) = $node->textTreeSub($nc, $nh, $soc, $ilc, $ilc_b, $lastnode); $continue = $newcontinue; $string .= $newstring; } return ($string, $continue); } # /*! # @abstract # Returns an XML representation of a parse tree/subtree. # @param self # The top of the tree/subtree to print. # @param keep_whitespace # Set to 1 if whitespace should be preserved in the # output, else 0. # @param drop_pdefine_contents # Set to 1 if the contents of #define macros should be # dropped from the output, else 0. # */ sub xmlTree { my $self = shift; my $keep_whitespace = shift; my $drop_pdefine_contents = shift; # my $apiOwner = shift; # my $apio = $self->apiOwner(); if ($self->{XMLTREE}) { return $self->{XMLTREE}; } # $self->printTree(); # $self->dbprint(); my $apiOwner = undef; my $lang = undef; my $sublang = undef; my $occmethod = 0; my $localDebug = 0; my $debugName = ""; # "TypedefdStructWithCallbacksAndStructs"; if ($self->apiOwner()) { $apiOwner = $self->apiOwner(); bless($apiOwner, "HeaderDoc::HeaderElement"); bless($apiOwner, $apiOwner->class()); $lang = $apiOwner->lang(); $sublang = $apiOwner->sublang(); if (($debugName ne "") && ($apiOwner->name() eq $debugName)) { $colorDebug = 1; } else { $colorDebug = 0; print STDERR $apiOwner->name()."\n" if ($localDebug); } if ($apiOwner->class() eq "HeaderDoc::Method") { $occmethod = 1; } else { $occmethod = 0; } # print STDERR "APIOWNERS was type $apiOwner\n"; } else { warn("WARNING: Could not find API Owner for parse tree $self.\nFaking data structures. Please file a bug.\n"); $lang = "C"; # $HeaderDoc::lang; # Default to C/C? $sublang = "C"; # $HeaderDoc::sublang; $apiOwner = HeaderDoc::HeaderElement->new("LANG" => $lang, "SUBLANG" => $sublang); $apiOwner->lang($lang); $apiOwner->sublang($sublang); $occmethod = 0; # guess } # colorizer goes here # my ($sotemplate, $eotemplate, $operator, $parseTokens{soc}, $parseTokens{eoc}, $parseTokens{ilc}, $parseTokens{ilc_b}, $sofunction, # $parseTokens{soprocedure}, $parseTokens{sopreproc}, $parseTokens{lbrace}, $parseTokens{rbrace}, $unionname, $structname, # $enumname, # $typedefname, $varname, $constname, $structisbrace, $parseTokens{macronames}, # $classregexp, $classbraceregexp, $classclosebraceregexp, $accessregexp, # $requiredregexp, $propname, $objcdynamicname, $objcsynthesizename, $moduleregexp, $definename, # $functionisbrace, $classisbrace, $parseTokens{lbraceconditionalre}, $parseTokens{lbraceunconditionalre}, $assignmentwithcolon, # $labelregexp, $parmswithcurlybraces, $superclasseswithcurlybraces, $parseTokens{soc}) = parseTokens($apiOwner->lang(), $apiOwner->sublang()); my %parseTokens = %{parseTokens($apiOwner->lang(), $apiOwner->sublang())}; $self->processEmbeddedTags(1, $apiOwner); # , $sotemplate, $eotemplate, $operator, $parseTokens{soc}, $parseTokens{eoc}, $parseTokens{ilc}, $parseTokens{ilc_b}, $sofunction, # $parseTokens{soprocedure}, $parseTokens{sopreproc}, $parseTokens{lbrace}, $parseTokens{rbrace}, $unionname, $structname, # $enumname, # $typedefname, $varname, $constname, $structisbrace, $parseTokens{macronames}); my $lastnode = undef; my $lastDisplayNode = undef; my $parserState = $self->parserState(); if ($parserState) { $lastnode = $parserState->{lastTreeNode}; $lastDisplayNode = $parserState->{lastDisplayNode}; } if ((!$lastDisplayNode) && $self->{lastDisplayNode}) { $lastDisplayNode = $self->{lastDisplayNode}; } # my ($sotemplate, $eotemplate, $operator, $parseTokens{soc}, $parseTokens{eoc}, $parseTokens{ilc}, $parseTokens{ilc_b}, $sofunction, # $parseTokens{soprocedure}, $parseTokens{sopreproc}, $parseTokens{lbrace}, $parseTokens{rbrace}, $unionname, $structname, # $enumname, # $typedefname, $varname, $constname, $structisbrace, $parseTokens{macronames}, # $classregexp, $classbraceregexp, $classclosebraceregexp, $accessregexp, # $requiredregexp, $propname, $objcdynamicname, $objcsynthesizename, $moduleregexp, $definename, # $functionisbrace, $classisbrace, $parseTokens{lbraceconditionalre}, $parseTokens{lbraceunconditionalre}, $assignmentwithcolon, # $labelregexp, $parmswithcurlybraces, $superclasseswithcurlybraces, $parseTokens{soc}) = parseTokens($lang, $sublang); my %parseTokens = %{parseTokens($lang, $sublang)}; # my %pt = (); # $pt{sotemplate} = $sotemplate; # $pt{soconstructor} = $sotemplate; my $ctstate = newCTState( undef, keep_whitespace => $keep_whitespace, apio => $apiOwner, type => "", depth => 0, inComment => 0, inQuote => 0, inRubyQuote => 0, inObjCMethod => $occmethod, lastBrace => "", parseTokensRef => \%parseTokens, prespace => "", lang => $lang, sublang => $sublang, xmlmode => 1, newlen => 0, breakable => 0, inMacro => 0, inEnum => 0, seenEquals => 0, lastKeyword => "", lastnstoken => "", lastTreeNode => $lastnode, lastTokenType => "", spaceSinceLastToken => 0, inAttribute => 0, inRaises => 0, inTypeOf => 0, drop_pdefine_contents => $drop_pdefine_contents, ASinName => 0, afteradvisoryspace => 0, lastDisplayNode => $lastDisplayNode); my ($retvalref, $junka, $junkb, $junkc, $junkd, $junke, $lastTokenType, $spaceSinceLastToken, $junk_afteradvisoryspace) = $self->colorTreeSub($ctstate); my $retval = ${$retvalref}; # my $retval = ""; # $retval = $self->textTree(); # $self->printTree(); $self->{XMLTREE} = $retval; return $retval; } # /*! # @abstract # Returns an HTML representation of a parse tree/subtree. # @param self # The top of the tree/subtree to print. # @param keep_whitespace # Set to 1 if whitespace should be preserved in the # output, else 0. # @param drop_pdefine_contents # Set to 1 if the contents of #define macros should be # dropped from the output, else 0. # */ sub htmlTree { my $self = shift; my $keep_whitespace = shift; my $drop_pdefine_contents = shift; # my $apiOwner = shift; # print STDERR "TREE\n"; # $self->printTree(); # $self->dbprint(); # print STDERR "ENDTREE\n"; my $apiOwner = undef; my $lang = undef; my $sublang = undef; my $occmethod = 0; my $localDebug = 0; my $debugName = ""; # "TypedefdStructWithCallbacksAndStructs"; # $self->dbprint(); if ($self->{HTMLTREE}) { print STDERR "SHORTCUT\n" if ($localDebug); return $self->{HTMLTREE}; } if ($self->apiOwner()) { $apiOwner = $self->apiOwner(); bless($apiOwner, "HeaderDoc::HeaderElement"); bless($apiOwner, $apiOwner->class()); $lang = $apiOwner->lang(); $sublang = $apiOwner->sublang(); # print STDERR "LANG: $lang APIO: $apiOwner\n"; if (($debugName ne "") && ($apiOwner->name() eq $debugName)) { $colorDebug = 1; } else { $colorDebug = 0; print STDERR $apiOwner->name()."\n" if ($localDebug); } if ($apiOwner->class() eq "HeaderDoc::Method") { $occmethod = 1; } else { $occmethod = 0; } print STDERR "APIOWNERS was type $apiOwner\n" if ($localDebug); } else { warn("WARNING: Could not find API Owner for parse tree $self.\nFaking data structures. Please file a bug.\n"); $lang = "C"; # $HeaderDoc::lang; $sublang = "C"; # $HeaderDoc::sublang; $apiOwner = HeaderDoc::HeaderElement->new("LANG" => $lang, "SUBLANG" => $sublang); cluck("BT\n"); $apiOwner->apiOwner($HeaderDoc::headerObject); $apiOwner->lang($lang); $apiOwner->sublang($sublang); $occmethod = 0; # guess } # colorizer goes here my $lastnode = undef; my $lastDisplayNode = undef; my $parserState = $self->parserState(); if ($parserState) { $lastnode = $parserState->{lastTreeNode}; $lastDisplayNode = $parserState->{lastDisplayNode}; } if ((!$lastDisplayNode) && $self->{lastDisplayNode}) { $lastDisplayNode = $self->{lastDisplayNode}; } # print STDERR "TN: $self LTN: $lastnode LDN: $lastDisplayNode\n"; # if ($lang eq "shell") { # $keep_whitespace = 1; # } # my ($sotemplate, $eotemplate, $operator, $parseTokens{soc}, $parseTokens{eoc}, $parseTokens{ilc}, $parseTokens{ilc_b}, $sofunction, # $parseTokens{soprocedure}, $parseTokens{sopreproc}, $parseTokens{lbrace}, $parseTokens{rbrace}, $unionname, $structname, # $enumname, # $typedefname, $varname, $constname, $structisbrace, $parseTokens{macronames}, # $classregexp, $classbraceregexp, $classclosebraceregexp, $accessregexp, # $requiredregexp, $propname, $objcdynamicname, $objcsynthesizename, $moduleregexp, $definename, # $functionisbrace, $classisbrace, $parseTokens{lbraceconditionalre}, $parseTokens{lbraceunconditionalre}, $assignmentwithcolon, # $labelregexp, $parmswithcurlybraces, $superclasseswithcurlybraces, $soconstructor) = parseTokens($lang, $sublang); my %parseTokens = %{parseTokens($lang, $sublang)}; $self->processEmbeddedTags(0, $apiOwner); # , $sotemplate, $eotemplate, $operator, $parseTokens{soc}, $parseTokens{eoc}, $parseTokens{ilc}, $parseTokens{ilc_b}, $sofunction, # $parseTokens{soprocedure}, $parseTokens{sopreproc}, $parseTokens{lbrace}, $parseTokens{rbrace}, $unionname, $structname, # $enumname, # $typedefname, $varname, $constname, $structisbrace, $parseTokens{macronames}); # my %pt = (); # $pt{sotemplate} = $sotemplate; # $pt{soconstructor} = $sotemplate; my $ctstate = newCTState( undef, keep_whitespace => $keep_whitespace, apio => $apiOwner, type => "", depth => 0, inComment => 0, inQuote => 0, inRubyQuote => 0, inObjCMethod => $occmethod, lastBrace => "", parseTokensRef => \%parseTokens, prespace => "", lang => $lang, sublang => $sublang, xmlmode => 0, newlen => 0, breakable => 0, inMacro => 0, inEnum => 0, seenEquals => 0, lastKeyword => "", lastnstoken => "", lastTreeNode => $lastnode, lastTokenType => "", spaceSinceLastToken => 0, inAttribute => 0, inRaises => 0, inTypeOf => 0, drop_pdefine_contents => $drop_pdefine_contents, ASinName => 0, afteradvisoryspace => 0, lastDisplayNode => $lastDisplayNode); my ($retvalref, $junka, $junkb, $junkc, $junkd, $junke, $lastTokenType, $spaceSinceLastToken, $junk_afteradvisoryspace) = $self->colorTreeSub($ctstate); my $retval = ${$retvalref}; # my $retval = ""; # $retval = $self->textTree(); # $self->printTree(); if ($HeaderDoc::align_columns) { my @retarr = split(/(\n)/s, $retval); my $newret = ""; foreach my $line (@retarr) { my $first = ""; # print STDERR "LINE: $line\n"; if ($line =~ s/^//s) { $first = ""; # print STDERR "FIRST (line = \"$line\")\n"; } if ($line =~ s/^( +)//) { my $spaces = $1; my $count = ($spaces =~ tr/^ //); while ($count--) { $line = " $line"; } $newret .= "$first$line"; } else { $newret .= "$first$line"; } } $retval = $newret; $retval = "
$retval
"; } $self->{HTMLTREE} = $retval; return $retval; } # /*! # @abstract # Recursively walks the descendants of this node (and their # siblngs/descendants) and returns the first non-space node # that is not in a comment. # @param self # The node whose children you want to search. # @param soc # The start-of-comment token for the current programming language. # Obtained by a call to # {@link //apple_ref/perl/instm/HeaderDoc::Utilities/parseTokens//() parseTokens}. # @param ilc # The start-of-comment token for the current programming language. # Obtained by a call to # {@link //apple_ref/perl/instm/HeaderDoc::Utilities/parseTokens//() parseTokens}. # @param ilc_b # The start-of-comment token for the current programming language. # Obtained by a call to # {@link //apple_ref/perl/instm/HeaderDoc::Utilities/parseTokens//() parseTokens}. # */ sub childpeeknc { my $self = shift; my $soc = shift; my $ilc = shift; my $ilc_b = shift; my $cache = $self->{CPNC}; if ($cache) { return $cache; } my $node = $self->{FIRSTCHILD}; if (!$node) { return ""; } bless($node, "HeaderDoc::ParseTree"); if (!$node->token()) { return $node->childpeeknc($soc, $ilc, $ilc_b) || return $node->nextpeeknc($soc, $ilc, $ilc_b); } if ($node->token() =~ /\s/o) { return $node->childpeeknc($soc, $ilc, $ilc_b) || return $node->nextpeeknc($soc, $ilc, $ilc_b); } if ($node->token() eq $soc) { return $node->childpeeknc($soc, $ilc, $ilc_b) || return $node->nextpeeknc($soc, $ilc, $ilc_b); } if ($node->token() eq $ilc || $node->token() eq $ilc_b) { return $node->childpeeknc($soc, $ilc, $ilc_b) || return $node->nextpeeknc($soc, $ilc, $ilc_b); } $cache = $node->token(); $self->{CPNC} = $cache; return $cache; } # /*! # @abstract # Recursively walks the siblings of this node (and their # descendants) and returns the first non-space node. # @param self # The node whose siblings you want to search. # */ sub nextpeek { my $self = shift; # This cache appears to be slowing things down. # if ($self->{NPCACHE}) { return $self->{NPCACHE}; } my $node = undef; if ($self->firstchild()) { $node = $self->firstchild(); $node = $node->next; } else { $node = $self->next(); } if (!$node) { # $self->{NPCACHE} = ""; return ""; } my $token = $node->token(); if ($token =~ /\s/o && $token !~ /[\r\n]/o) { my $ret = $node->nextpeek(); # $self->{NPCACHE} = $ret; return $ret; } if ($node->hidden()) { my $ret = $node->nextpeek(); # $self->{NPCACHE} = $ret; return $ret; } # $self->{NPCACHE} = $node->token(); return $node->token(); } # /*! # @abstract # Recursively walks the siblings of this node (and their # descendants) and returns the first non-space node that # is not in a comment. # @param self # The node whose siblings you want to search. # @param soc # The start-of-comment token for the current programming language. # Obtained by a call to # {@link //apple_ref/perl/instm/HeaderDoc::Utilities/parseTokens//() parseTokens}. # @param ilc # The start-of-comment token for the current programming language. # Obtained by a call to # {@link //apple_ref/perl/instm/HeaderDoc::Utilities/parseTokens//() parseTokens}. # @param ilc_b # The start-of-comment token for the current programming language. # Obtained by a call to # {@link //apple_ref/perl/instm/HeaderDoc::Utilities/parseTokens//() parseTokens}. # */ sub nextpeeknc { my $self = shift; my $soc = shift; my $ilc = shift; my $ilc_b = shift; my $node = $self->nextTokenNoComments($soc, $ilc, $ilc_b, 0, 0); if (!$node) { return ""; } return $node->token(); } # /*! # @abstract # Recursively walks the siblings of this node (and their # descendants) and returns the second non-space node that # is not in a comment. # @param self # The node whose siblings you want to search. # @param soc # The start-of-comment token for the current programming language. # Obtained by a call to # {@link //apple_ref/perl/instm/HeaderDoc::Utilities/parseTokens//() parseTokens}. # @param ilc # The start-of-comment token for the current programming language. # Obtained by a call to # {@link //apple_ref/perl/instm/HeaderDoc::Utilities/parseTokens//() parseTokens}. # @param ilc_b # The start-of-comment token for the current programming language. # Obtained by a call to # {@link //apple_ref/perl/instm/HeaderDoc::Utilities/parseTokens//() parseTokens}. # */ sub nextnextpeeknc { my $self = shift; my $soc = shift; my $ilc = shift; my $ilc_b = shift; my $node = $self->nextTokenNoComments($soc, $ilc, $ilc_b, 0, 0); if (!$node) { return ""; } my $nodeafter = $node->nextTokenNoComments($soc, $ilc, $ilc_b, 0, 0); if (!$nodeafter) { return ""; } return $nodeafter->token(); } # /*! # @abstract # Recursively walks the siblings of this node (and their # descendants) and returns the first node (including spaces) that # is not in a comment. # @param self # The node whose siblings you want to search. # @param soc # The start-of-comment token for the current programming language. # Obtained by a call to # {@link //apple_ref/perl/instm/HeaderDoc::Utilities/parseTokens//() parseTokens}. # @param ilc # The start-of-comment token for the current programming language. # Obtained by a call to # {@link //apple_ref/perl/instm/HeaderDoc::Utilities/parseTokens//() parseTokens}. # @param ilc_b # The start-of-comment token for the current programming language. # Obtained by a call to # {@link //apple_ref/perl/instm/HeaderDoc::Utilities/parseTokens//() parseTokens}. # */ sub nextTokenNoComments($$$$$) { my $self = shift; my $soc = shift; my $ilc = shift; my $ilc_b = shift; my $failOnHDComments = shift; my $enable_javadoc_comments = shift; my $localDebug = 0; my $cache = $self->{NTNC}; if ($cache) { return $cache; } my $node = $self->{NEXT}; if (!$node) { return undef } bless($node, "HeaderDoc::ParseTree"); # print STDERR "SOC: $soc ILC: $ilc ILC_B: $ilc_b\n" if ($colorDebug); # print STDERR "MAYBE ".$node->token()."\n"; if ($failOnHDComments) { # print STDERR "FOHDC\n"; # print STDERR "FC: ".$node->firstchild()."\n"; if ($node->firstchild() && $node->firstchild()->next()) { # print STDERR "POINT 1\n"; # first child always empty. my $testnode = $node->firstchild()->next(); if (($node->token() eq $ilc) || ($node->token() eq $ilc_b)) { # print STDERR "ILC\n"; if ($node->token() eq $ilc) { my $ntoken = ""; if ($testnode->next()) { $ntoken = $testnode->next()->token(); } if ($testnode->token() eq "!" || ($enable_javadoc_comments && $testnode->token eq "*" && $ntoken !~ /^\*/)) { print STDERR "Unexpected HD Comment\n" if ($localDebug); return undef; } } else { if ($testnode->token() eq $ilc) { my $nntoken = ""; if ($testnode->next() && $testnode->next()->next()) { $nntoken = $testnode->next()->next()->token(); } if ($testnode->next() && (($testnode->next()->token() eq "!") || ($enable_javadoc_comments && $testnode->next()->token eq "*" && $nntoken !~ /^\*/))) { print STDERR "Unexpected HD Comment\n" if ($localDebug); return undef; } } } } elsif ($node->token() eq $soc) { # print STDERR "SOC\n"; my $ntoken = ""; if ($testnode->next()) { $ntoken = $testnode->next()->token(); } if (($testnode->token() eq "!") || ($enable_javadoc_comments && $testnode->token eq "*" && $ntoken !~ /^\*/)) { print STDERR "Unexpected HD Comment\n" if ($localDebug); return undef; } # } else { # print STDERR "TOKEN: ".$node->token()."\n"; } } } if (!$node->token()) { return $self->{NTNC} = $node->nextTokenNoComments($soc, $ilc, $ilc_b, $failOnHDComments, $enable_javadoc_comments); } if ($node->token() =~ /\s/o) { return $self->{NTNC} = $node->nextTokenNoComments($soc, $ilc, $ilc_b, $failOnHDComments, $enable_javadoc_comments); } if ($node->token() eq $soc) { return $self->{NTNC} = $node->nextTokenNoComments($soc, $ilc, $ilc_b, $failOnHDComments, $enable_javadoc_comments); } if ($node->token() eq $ilc || $node->token() eq $ilc_b) { return $self->{NTNC} = $node->nextTokenNoComments($soc, $ilc, $ilc_b, $failOnHDComments, $enable_javadoc_comments); } $self->{NTNC} = $node; # weaken($self->{NTNC}); return $node; } # /*! # @abstract # Walks up the parent tree until it reaches a node # that is a sibling of matchingnode, then returns # the node after that one. # @param self # The starting node. # @param matchingnode # The node whose level you want ot match. # @param fullpath # The path of the file containing this parse # tree (used for debugging messages). # */ sub nextAtLevelOf { my $self = shift; my $matchingnode = shift; my $fullpath = shift; my $nextnode = $self; while ($nextnode && !$nextnode->isAfter($matchingnode)) { $nextnode = $nextnode->parent(); } if ($nextnode) { $nextnode = $nextnode->next(); } else { my $tt = $self; while ($tt->parent()) { $tt = $tt->parent(); } my $apio = $tt->apiOwner(); cluck("TT: $tt\n"); # my $fullpath = $apio->fullpath(); warn("$fullpath:0:Ran off top of stack looking for next node.\n"); # $nextnode = $matchingnode->next(); $nextnode = undef; } return $nextnode; } # /*! # @abstract # Trivially returns whether this token should be # treated a the start of a macro. # @param self # This parse tree instance (unused). # @param token # The token to check. # @param lang # The current language. # @param sublang # The current language dialect (e.g. cpp for C++). # */ sub isMacro { my $self = shift; my $token = shift; my $lang = shift; my $sublang = shift; if ($lang ne "C" && $lang ne "Csource") { return 0; } if ($token =~ /^\#\w+/o) { return 1; } return 0; } # /*! # @abstract Prints the contents of a colorTreeSub state object. # */ sub dumpCTState { my $ctstateref = shift; my %ctstate = %{$ctstateref}; print STDERR "DUMPING CTSTATE:\n\n"; foreach my $key (keys %ctstate) { print " $key => ".$ctstate{$key}."\n"; } print "\n\n"; } # /*! # @abstract # Returns an HTML or XML representation of a parse tree/subtree. # @param self # The top of the tree/subtree to print. # @param ctstate # The colortree state object. It should contain the parameters # listed below. # @param keep_whitespace # Set to 1 if whitespace should be preserved in the # output, else 0. # @param apio # The owner of the parse tree. This may be an API owner or # may jut be an ordinary type class instance (e.g. Struct). # # This is mostly used for using methods in the HeaderElement # class and obtaining filenames/line numbers for debugging. # @param type # Indicates information about what we're currently parsing. # Possible values are: # # # @param depth # The nesting depth within the parse tree. Used for leading # space calculations. # @param inComment # On initial call, 0. On recursion, possible values are: # # # @param inQuote # On initial call, 0. On recursion, possible values are: # # @param inRubyQuote # On initial call, 0. On recursion, possible values are: # # @param inObjCMethod # Pass in 1 if this is a parse tree for an Objective-C method, # else pass in 0. # @param lastBrace # The last brace or parentheses character seen, # or +/- in Objective-C methods), or special tokens # that are treated like braces. # @param parseTokensRef # A reference to the parse tokens hash returned by # a call to # {@link //apple_ref/perl/instm/HeaderDoc::Utilities/parseTokens//() parseTokens}. # @param prespace # The leading spaces that should be inserted # to the left of this token if it wraps to the start of # a line. # @param lang # The current language. # @param sublang # The current language dialect (e.g. cpp for C++). # @param xmlmode # Set to 1 if you want XML output, 0 for HTML. # @param newlen # The length of the current line. Set to 0 on first call. # @param breakable # Indicates whether the formatter can break before this token. # # @param inMacro # Set to 0 on first call. During tree walk, this goes high when # the tree walk code enters a C preprocessor macro and remains high # until the end of the macro. # @param inEnum # Set to 0 on first call. Goes high when inside an enumeration. # @param seenEquals # Set to 0 on first call. Goes high after the equals sign in a # variable declaration. # @param lastKeyword # Contains the last keyword encountered. Set to an empty string # on first call. Used for guessing types in generating link # requests. # @param lastnstoken # The last non-space token encountered. Set to an empty string # on first call. # @param lastTreeNode # The tree node at which output stops. This node and its # descendants are included in output, but not this node's siblings. # @param lastTokenType # Contains the type of the last token emitted. One of: # star, string, char, # comment, preprocessor, # number, keyword, # function, var, # template, type, # param, or ignore. # # On initial call, an empty string. # @param spaceSinceLastToken # Indicates that whitespace has been seen since the last non-space # token. This ensures that required space does not get deleted. # # On initial call, 0. # @param inAttribute # On initial call, 0. On recursion, set to 1 after # the attribute keyword (or equivalent) is encountered. # # Causes the next word token to be interpreted as ordinary text. # # Also prevents the normal line break after the open parenthesis. # @param inRaises # On initial call, 0. On recursion, set to 1 after # the raises keyword (or equivalent) is encountered. # # Causes the next word token to be interpreted as a variable (an # exception) instead of a type. # # Also prevents the normal line break after the open parenthesis. # @param inTypeOf # On initial call, 0. On recursion, set to 1 after # the typeof keyword (or equivalent) is encountered. # # Causes the next word token to be interpreted as a variable instead # of a type. # # Also prevents the normal line break after the open parenthesis. # @param drop_pdefine_contents # Set to 1 if the contents of #define macros should be # dropped from the output, else 0. # @param ASinName # On initial call, set to 0. During the recursive walk of the # parse tree, this gets set to 1 when the current tree node in # an AppleScript name. # # @var tokenType # Contains the type of the last token emitted. One of: # star, string, char, # comment, preprocessor, # number, keyword, # function, var, # template, type, # param, or ignore. # # @discussion # This is a helper function shared by {@link htmlTree} and # {@link xmlTree}. You should not call this directly. # */ sub colorTreeSub { my $self = shift; my $ctstateref = shift; my $localDebug = 0; my $psDebug = 0; my $treeDebug = 0; my $dropDebug = 0; my $tokenDebug = 0; my $codePathDebug = 0; my $rubyDebug = 0; my $tokenPrintDebug = 0; my $advisorySpaceDebug = 0; my $continue = 1; my %ctstate = %{$ctstateref}; my $token = $self->{TOKEN}; my $ntoken = $self->nextpeek(); my $tokenname = $token; if ($token eq "\n") { $tokenname = "[NEWLINE]"; } elsif ($token eq "\r") { $tokenname = "[CARRIAGE RETURN]"; } elsif (!length($token)) { $tokenname = "[EMPTY STRING]"; } else { $tokenname = "\"".$tokenname."\""; } my $ntokenname = $ntoken; if ($ntoken eq "\n") { $ntokenname = "[NEWLINE]"; } elsif ($ntoken eq "\r") { $ntokenname = "[CARRIAGE RETURN]"; } elsif (!length($ntoken)) { $ntokenname = "[EMPTY STRING]"; } else { $ntokenname = "\"".$ntokenname."\""; } my $lastnstokenname = $ctstate{lastnstoken}; if ($ctstate{lastnstoken} eq "\n") { $lastnstokenname = "[NEWLINE]"; } elsif ($ctstate{lastnstoken} eq "\r") { $lastnstokenname = "[CARRIAGE RETURN]"; } elsif (!length($ctstate{lastnstoken})) { $lastnstokenname = "[EMPTY STRING]"; } else { $lastnstokenname = "\"".$lastnstokenname."\""; } print STDERR "***** TOKEN: ".$tokenname." *****\n" if ($codePathDebug || $localDebug || $tokenDebug || $tokenPrintDebug); # print STDERR "IN COLORTREESUB\n"; if ($self == $ctstate{lastTreeNode}) { print STDERR "Node is last node in tree. Ending after this node.\n" if ($localDebug || $codePathDebug); $continue = 0; } dumpCTState(\%ctstate) if ($localDebug); print STDERR "NEWLEN: ".$ctstate{newlen}."\n" if ($localDebug); my %parseTokens = %{$ctstate{parseTokensRef}}; my %macroList = %{$parseTokens{macronames}}; my $oldLastBrace = $ctstate{lastBrace}; my $oldDepth = $ctstate{depth}; my $oldInMacro = $ctstate{inMacro}; my $oldInQuote = $ctstate{inQuote}; my $oldLastKeyword = $ctstate{lastKeyword}; my $oldInComment = $ctstate{inComment} ; my $oldInAttribute = $ctstate{inAttribute}; my $oldInRaises = $ctstate{inRaises}; my $oldInTypeOf = $ctstate{inTypeOf}; my $dropFP = 0; # This cache slows things down now that it works.... # if ($self->{CTSUB}) { return (\$self->{CTSTRING}, $self->{CTSUB}); } my $keep_all_newlines = 0; print STDERR "DPC: ".$ctstate{drop_pdefine_contents}."\n" if ($localDebug); if ($ctstate{lang} eq "shell" || $ctstate{sublang} eq "javascript") { $keep_all_newlines = 1; } elsif ($ctstate{lang} eq "python") { $keep_all_newlines = 1; $ctstate{keep_whitespace} = 1; } if ($ctstate{xmlmode} && $localDebug) { print STDERR "XMLMODE.\n"; } # foreach my $listitem (keys %macroList) { print STDERR "ML: $listitem\n"; } print STDERR "IM: ".$ctstate{inMacro}."\n" if ($localDebug); print STDERR "IAtt: ".$ctstate{inAttribute}."\n" if ($localDebug); print STDERR "IRai: ".$ctstate{inRaises}."\n" if ($localDebug); my $mustbreak = 0; my $nextprespace = ""; my $string = ""; my $tailstring = ""; my $escapetoken = ""; my ($case_sensitive, $keywordhashref) = $ctstate{apio}->keywords(); my $tokennl = 0; if ($token =~ /^[\r\n]/o) { $tokennl = 1; } if (($token eq "\t" || $token =~ /^ +$/) && (!$ctstate{keep_whitespace})) { $token = " "; } if ($ctstate{inQuote} == 3) { $keep_all_newlines = 1; $ctstate{keep_whitespace} = 1; } if ($ctstate{keep_whitespace}) { $ctstate{prespace} = ""; $nextprespace = ""; } if (length($token) && (!$tokennl)) { if ($ctstate{afteradvisoryspace}) { # Remove a single space if we're after a newline that # got converted into a space print STDERR "OLD AASTOKEN: \"$token\"\n" if ($localDebug || $advisorySpaceDebug || $codePathDebug); $token =~ s/^ //; $ctstate{afteradvisoryspace} = 0; print STDERR "AASTOKEN NOW: \"$token\"\n" if ($localDebug || $advisorySpaceDebug || $codePathDebug); print STDERR "AAS -> 0\n" if ($localDebug || $advisorySpaceDebug || $codePathDebug); } } print "keep_all_newlines: $keep_all_newlines\n" if ($localDebug); my $tokenIsKeyword = isKeyword($token, $keywordhashref, $case_sensitive); print STDERR "TIK: $tokenIsKeyword\n" if ($localDebug); # my $ctoken = $self->childpeek($soc, $parseTokens{ilc}, $parseTokens{ilc_b}); print STDERR "TK $token\n" if ($colorDebug || $rubyDebug || $colorDebug); print STDERR "IRQ is ".$ctstate{inRubyQuote}."\n" if ($rubyDebug || $colorDebug); print STDERR "IQ is ".$ctstate{inQuote}."\n" if ($rubyDebug || $colorDebug); my $ctoken = $self->childpeeknc($parseTokens{soc}, $parseTokens{ilc}, $parseTokens{ilc_b}); my $ntokennc = $self->nextpeeknc($parseTokens{soc}, $parseTokens{ilc}, $parseTokens{ilc_b}); my $nntokennc = $self->nextnextpeeknc($parseTokens{soc}, $parseTokens{ilc}, $parseTokens{ilc_b}); my $tokenType = undef; my $drop = 0; my $firstCommentToken = 0; my $leavingComment = 0; my $hidden = ($self->hidden() && !$ctstate{xmlmode}); my $isTypeStar = 0; my $begintr = ""; my $endtr = ""; my $newtd = ""; if (!$ctstate{xmlmode} && $HeaderDoc::align_columns) { $begintr = ""; $endtr = ""; $newtd = "
"; } if ($ntoken eq "::" && $ctstate{lang} eq "perl") { $ctstate{tokenAccum} .= $token.$ntoken; $ctstate{skipToken} = 2; # print STDERR "SKIPTOKEN -> 2 ($token $ntoken)\n"; } elsif ($ctstate{tokenAccum} && (!$ctstate{skipToken})) { # print STDERR "ACCUM ($token prepended with ".$ctstate{tokenAccum}.")\n"; $token = $ctstate{tokenAccum}.$token; $ctstate{tokenAccum} = ""; } print STDERR "TOKEN: $tokenname NTOKEN: $ntokenname LASTNSTOKEN: $lastnstokenname IC: ".$ctstate{inComment}."\n" if ($treeDebug || $localDebug || $codePathDebug); print STDERR "OCC: ".$ctstate{inObjCMethod}."\n" if ($colorDebug || $localDebug); print STDERR "HIDDEN: $hidden\n" if ($localDebug); # last one in each chain prior to a "," or at end of chain is "var" # or "parm" (functions) print STDERR "TK $token NT $ntoken NTNC $ntokennc NNTNC $nntokennc LB: ".$ctstate{lastBrace}." PS: ".length($ctstate{prespace})."\n" if ($colorDebug); my $nospaceafter = 0; my $nextbreakable = 0; if ($ctstate{breakable} == 2) { $ctstate{breakable} = 0; $nextbreakable = 1; } elsif ($ctstate{breakable} == 3) { $mustbreak = 1; $ctstate{breakable} = 1; $nextbreakable = 0; } print STDERR "POST_CHECK MUSTBREAK: $mustbreak BREAKABLE: ".$ctstate{breakable}." NEXTBREAKABLE: $nextbreakable\n" if ($localDebug); if (($ctstate{lang} eq "C" || $ctstate{lang} eq "Csource") && $token eq $parseTokens{enumname}) { my $curname = $ctstate{apio}->name(); print STDERR "NAME: $curname\n" if ($localDebug); print STDERR "NOW ENUM\n" if ($localDebug); $ctstate{inEnum} = 1; } if ($parseTokens{propname} && $token eq $parseTokens{propname}) { $ctstate{inObjCProperty} = 1; } ## Enable for certain testing. ## if ($self->parserState()) { ## # reset for new declaration. ## $ctstate{lastBrace} = ""; ## } if ($ctstate{inObjCMethod} && $token =~ /^[+-]/o && ($ctstate{lastBrace} eq "")) { $ctstate{lastBrace} = $token; } my $MIG = 0; if ($ctstate{lang} eq "C" && $ctstate{sublang} eq "MIG") { $MIG = 1; } my $splitre = ""; if ($ctstate{type} =~ /^(typedef|struct|record|union)/o) { $splitre = ";"; } elsif ($ctstate{type} =~ /^(enum|funcptr)/o) { $splitre = ","; } elsif ($ctstate{lastBrace} eq "(") { $splitre = ","; if ($MIG) { $splitre = ";"; } } elsif ($ctstate{lastBrace} eq $parseTokens{lbrace}) { if ($ctstate{lang} eq "tcl") { $splitre = '\s'; } elsif ($ctstate{inEnum}) { $splitre = ","; } else { $splitre = ";"; } } elsif (($ctstate{lastBrace} eq $parseTokens{structname}) && $parseTokens{structisbrace}) { $splitre = ";"; } print STDERR "SPLITRE IS $splitre\n" if ($localDebug); if ($splitre && ($token =~ $splitre)) { # && ($ntoken !~ /^[\r\n]/o)) { print STDERR "WILL SPLIT AFTER \"$token\" AND BEFORE \"$ntoken\".\n" if ($localDebug); $nextbreakable = 3; } print STDERR "SOC: \"$parseTokens{soc}\"\nEOC: \"$parseTokens{eoc}\"\nILC: \"$parseTokens{ilc}\"\nILC_B: \"$parseTokens{ilc_b}\"\nLBRACE: \"$parseTokens{lbrace}\"\nRBRACE: \"$parseTokens{rbrace}\"\nSOPROC: \"$parseTokens{soprocedure}\"\nSOFUNC: \"$parseTokens{sofunction}\"\nVAR: \"$parseTokens{varname}\"\nSTRUCTNAME: \"$parseTokens{structname}\"\nTYPEDEFNAME: \"$parseTokens{typedefname}\"\n" if ($tokenDebug); print STDERR "inQuote: ".$ctstate{inQuote}."\noldInQuote: $oldInQuote\ninComment: ".$ctstate{inComment}."\ninMacro: ".$ctstate{inMacro}."\ninEnum: ".$ctstate{inEnum}."\n" if ($localDebug); print STDERR "oldInMacro: $oldInMacro\noldInComment: $oldInComment\n" if ($localDebug); # print STDERR "TOKEN: $token\n" if ($localDebug); print STDERR "TOKEN: $tokenname ASINNAME: ".$ctstate{ASinName}."\n" if ($localDebug); if ($ctstate{inEnum}) { # If we see this, anything nested below here is clearly not a union. if (casecmp($token, $parseTokens{unionname}, $case_sensitive)) { $ctstate{inEnum} = 0; }; if (casecmp($token, $parseTokens{structname}, $case_sensitive)) { $ctstate{inEnum} = 0; }; if (casecmp($token, $parseTokens{typedefname}, $case_sensitive)) { $ctstate{inEnum} = 0; }; } my $nonword = 0; my $pascal = 0; my $ruby = 0; if ($token =~ /\W/) { $nonword = 1; if ($ctstate{ASinName} && (!$ctstate{inQuote}) && (!$ctstate{inComment}) && ($token !~ /\s/)) { $ctstate{ASinName} = -1; print STDERR "ASinName -> -1 [1]\n" if ($localDebug); } } if ($ctstate{lang} eq "pascal") { $pascal = 1; } if ($ctstate{lang} eq "ruby") { $ruby = 1; } if ($ctstate{lang} eq "applescript" && $token =~ /(given|of|in)/) { print STDERR "ASinName -> 3 [0a]\n" if ($localDebug); $ctstate{ASinName} = 3; print STDERR "AS lastBrace -> \"(\"\n" if ($localDebug); $ctstate{lastBrace} = "("; } elsif ($parseTokens{labelregexp} && $token =~ $parseTokens{labelregexp}) { print STDERR "ASinName -> 3 [0b]\n" if ($localDebug); $ctstate{ASinName} = 3; print STDERR "AS lastBrace -> \"(\"\n" if ($localDebug); $ctstate{lastBrace} = "("; } my $untypedLanguage = 0; if ($ctstate{sublang} eq "javascript" || $ctstate{lang} eq "php" || $ctstate{lang} eq "perl" || $ctstate{lang} eq "python" || $ctstate{lang} eq "shell" || $ctstate{lang} eq "applescript") { $untypedLanguage = 1; } elsif ($ruby || $ctstate{lang} eq "tcl") { $untypedLanguage = 2; } if ($ctstate{skipToken}) { $ctstate{skipToken}--; $drop = 1; $token = ""; } if ($ctstate{lang} eq "C" || $ctstate{lang} eq "java" || $pascal || $ctstate{sublang} eq "javascript" || $ctstate{lang} eq "php" || $ctstate{lang} eq "perl" || $ruby || $ctstate{lang} eq "python" || $ctstate{lang} eq "Csource" || $ctstate{lang} eq "shell" || $ctstate{lang} eq "applescript" || $ctstate{lang} eq "tcl") { if ($ctstate{inQuote} == 1) { print STDERR " STRING\n" if ($localDebug || $codePathDebug); $tokenType = "string"; } elsif ($ctstate{inQuote} == 2) { print STDERR " CHAR\n" if ($localDebug || $codePathDebug); $tokenType = "char"; } elsif ($ctstate{inQuote} == 3 && $ctstate{inRubyQuote} != 2 && !$tokennl) { # print STDERR "preserve newlines. TOKEN: \"$token\"\n" if ($rubyDebug || $colorDebug); $tokenType = "string"; } elsif ($nonword && $token eq $parseTokens{soc} && $parseTokens{soc} ne "") { if (!$hidden) { $tokenType = "comment"; if ($ctstate{ASinName} == -1) { $ctstate{ASinName} = 1; print STDERR "ASinName -> 1 [2]\n" if ($localDebug); } print STDERR " COMMENT [1]\n" if ($localDebug || $codePathDebug); if (!$ctstate{inComment}) { $ctstate{inComment} = 1; $firstCommentToken = 1; if ($ctstate{xmlmode}) { $string .= ""; } else { $string .= ""; } } else { print STDERR " nested comment\n" if ($localDebug || $codePathDebug); } } else { print STDERR " COMMENT [1a]: HIDDEN\n" if ($localDebug || $codePathDebug); } } elsif ($nonword && ((($token eq $parseTokens{ilc}) && ($parseTokens{ilc} ne "")) || (($token eq $parseTokens{ilc_b}) && ($parseTokens{ilc_b} ne "")))) { if (!$hidden) { print STDERR " ILCOMMENT [1]\n" if ($localDebug || $codePathDebug); $tokenType = "comment"; if (!$ctstate{inComment}) { if ($ctstate{ASinName} == -1) { $ctstate{ASinName} = 1; print STDERR "ASinName -> 1 [3]\n" if ($localDebug); } print STDERR " REALILCOMMENT\n" if ($localDebug || $codePathDebug); $ctstate{inComment} = 2; $firstCommentToken = 1; if ($ctstate{xmlmode}) { $string .= ""; } else { $string .= ""; } } else { print STDERR " nested comment\n" if ($localDebug || $codePathDebug); } } else { print STDERR " ILCOMMENT [1a]: HIDDEN\n" if ($localDebug || $codePathDebug); } } elsif ($nonword && $token eq $parseTokens{eoc} && $parseTokens{eoc} ne "") { print STDERR " EOCOMMENT [1]\n" if ($localDebug || $codePathDebug); $tokenType = "comment"; if ($ctstate{xmlmode}) { $tailstring .= ""; } else { $tailstring = ""; } $leavingComment = 1; $ctstate{inComment} = 0; } elsif ($tokennl && ($ntoken !~ /^[\r\n]/o || $ctstate{keep_whitespace} || $keep_all_newlines)) { my $ignored_newline = 1; print STDERR " TOKENNL (KW: ".$ctstate{keep_whitespace}.", KAN: $keep_all_newlines)\n" if ($localDebug || $codePathDebug); if ($ctstate{ASinName}) { $ctstate{ASinName} = 0; print STDERR "ASinName -> 0 [4]\n" if ($localDebug); $ignored_newline = 0; } if ($ctstate{inComment} == 2) { print STDERR " EOL INCOMMENT: END ILCOMMENT [1]\n" if ($localDebug || $codePathDebug); $tokenType = "comment"; if ($ctstate{xmlmode}) { $string .= ""; } else { $string .= ""; } $ctstate{inComment} = 0; $ctstate{newlen} = 0; $mustbreak = 1; # $token = ""; $drop = 1; $ignored_newline = 0; } elsif ($ctstate{inMacro} || $keep_all_newlines) { print STDERR " EOL INMACRO\n" if ($localDebug || $codePathDebug); $mustbreak = 1; $ctstate{newlen} = 0; $ignored_newline = 0; } elsif ($ctstate{inComment}) { print STDERR " EOL INCOMMENT\n" if ($localDebug || $codePathDebug); $mustbreak = 1; $ctstate{newlen} = 0; # $token = ""; $drop = 1; $ignored_newline = 0; } if (!$ignored_newline) { $ctstate{breakable} = 0; $nextbreakable = 0; # $nextprespace = nspaces(4 * $ctstate{depth}); $ctstate{newlen} = 0; } # } elsif ($ntoken =~ /^[\r\n]/o) { # print STDERR " NEXT TOKEN IS NLCR\n" if ($localDebug || $codePathDebug); # $ctstate{breakable} = 0; # $nextbreakable = 0; } elsif ($ctstate{inComment}) { print STDERR " COMMENT [2:".$ctstate{inComment}."]\n" if ($localDebug || $codePathDebug); $tokenType = "comment"; if ($ctstate{inComment} == 1) { if (($token =~ /^\s/o && !$tokennl && $ntoken !~ /^\s/o) && (!$ctstate{keep_whitespace})) { # Only allow wrapping of multi-line comments. # Don't blow in extra newlines at existing ones. $ctstate{breakable} = 1; } } } elsif ($ctstate{inMacro}) { print STDERR " MACRO [IN]\n" if ($localDebug || $codePathDebug); $tokenType = "preprocessor"; } elsif ($token eq "=") { print STDERR " EQUALS\n" if ($localDebug || $codePathDebug); $nextbreakable = 1; if ($ctstate{type} eq "pastd") { $ctstate{type} = ""; print STDERR " END OF VAR\n" if ($localDebug || $codePathDebug); } if ($pascal) { $ctstate{seenEquals} = 1; } } elsif ($token eq "-") { print STDERR " MINUS\n" if ($localDebug || $codePathDebug); if ($ntoken =~ /^\d/o) { $tokenType = "number"; print STDERR " NUMBER [1]\n" if ($localDebug || $codePathDebug); } else { print STDERR " TEXT [1]\n" if ($localDebug || $codePathDebug); $tokenType = ""; } } elsif ($token =~ /^\d+$/o || $token =~ /^0x[\dabcdef]+$/io) { $tokenType = "number"; $ctstate{type} = "hexnumber"; print STDERR " \nNUMBER [2]: $token\n" if ($localDebug || $codePathDebug); } elsif (!$nonword && (casecmp($token, $parseTokens{sofunction}, $case_sensitive) || casecmp($token, $parseTokens{soprocedure}, $case_sensitive) || casecmp($token, $parseTokens{soconstructor}, $case_sensitive))) { $tokenType = "keyword"; $ctstate{lastKeyword} = $token; print STDERR " SOFUNC/SOPROC\n" if ($localDebug || $codePathDebug); $ctstate{type} = "funcproc"; $ctstate{lastBrace} = "("; $oldLastBrace = "("; if ($ctstate{lang} eq "applescript") { $ctstate{ASinName} = 1; print STDERR "ASinName -> 1 [5]\n" if ($localDebug); } } elsif (!$nonword && $ctstate{type} eq "funcproc") { if ($token =~ /^\;/o) { $ctstate{type} = ""; $nextbreakable = 3; } print STDERR " FUNC/PROC NAME\n" if ($localDebug || $codePathDebug); $tokenType = "function"; } elsif (!$nonword && casecmp($token, $parseTokens{constname}, $case_sensitive) && $ctstate{lang} eq "pascal") { $tokenType = "keyword"; print STDERR " VAR\n" if ($localDebug || $codePathDebug); $ctstate{type} = "pasvar"; } elsif (!$nonword && casecmp($token, $parseTokens{varname}, $case_sensitive)) { $tokenType = "keyword"; print STDERR " VAR\n" if ($localDebug || $codePathDebug); $ctstate{type} = "pasvar"; } elsif ($nonword && ($ctstate{type} eq "pasvar" || $ctstate{type} eq "pastd") && ($token =~ /^[\;\:\=]/o)) { # NOTE: '=' is handled elsewhere, # but it is included above for clarity. $ctstate{type} = ""; print STDERR " END OF VAR\n" if ($localDebug || $codePathDebug); } elsif ($ctstate{type} eq "pasvar" || $ctstate{type} eq "pastd") { print STDERR " VAR NAME\n" if ($localDebug || $codePathDebug); $tokenType = "var"; } elsif (!$nonword && ($pascal) && casecmp($token, $parseTokens{typedefname}, $case_sensitive)) { # TYPE: This is the start of a pascal type print STDERR " TYPE\n" if ($localDebug || $codePathDebug); $tokenType = "keyword"; $ctstate{type} = "pastd"; } elsif (!$nonword && ($pascal) && casecmp($token, $parseTokens{structname}, $case_sensitive)) { # RECORD: This is the start of a pascal record print STDERR " RECORD/STRUCT\n" if ($localDebug || $codePathDebug); $ctstate{lastBrace} = $token; $tokenType = "keyword"; $ctstate{type} = "pasrec"; } elsif (!$nonword && $tokenIsKeyword) { # This is a keyword in any language. $tokenType = "keyword"; if ($ctstate{ASinName} != 1 && $ctstate{ASinName} != 3) { $ctstate{ASinName} = 0; print STDERR "ASinName -> 0 [6]\n" if ($localDebug); } if ($tokenIsKeyword == 2) { # This formatting change applies only to children of this node. $ctstate{inAttribute} = 1; } elsif ($tokenIsKeyword == 5) { # This formatting change applies only to children of this node. $ctstate{inRaises} = 1; } elsif ($tokenIsKeyword == 6) { $ctstate{inTypeOf} = 1; } # NOTE: If anybody ever wants "class" to show up colored # as a keyword within a template, the next block should be # made conditional on a command-line option. Personally, # I find it distracting, hence the addition of these lines. if ($ctstate{lastBrace} eq $parseTokens{sotemplate} && $parseTokens{sotemplate} ne "") { $tokenType = "template"; } print STDERR " KEYWORD\n" if ($localDebug || $codePathDebug); # $ctstate{inMacro} = $self->isMacro($token, $ctstate{lang}, $ctstate{sublang}); # We could have keywords in a macro, so don't set this # to zero. It will get zeroed when we pop a level # anyway. Just set it to 1 if needed. if ($case_sensitive) { if ($macroList{$token}) { print STDERR " IN MACRO\n" if ($localDebug || $codePathDebug); $ctstate{inMacro} = 1; } } else { foreach my $cmpToken (keys %macroList) { if (casecmp($token, $cmpToken, $case_sensitive)) { $ctstate{inMacro} = 1; } } } print STDERR " TOKEN IS $token, IM is now ".$ctstate{inMacro}."\n" if ($localDebug || $codePathDebug); if (casecmp($token, $parseTokens{rbrace}, $case_sensitive)) { print STDERR "PS: ".length($ctstate{prespace})." -> " if ($psDebug); # $ctstate{prespace} = nspaces(4 * ($ctstate{depth}-1)); $mustbreak = 2; print STDERR length($ctstate{prespace})."\n" if ($psDebug); } } elsif (!$ctstate{inQuote} && !$ctstate{inComment} && isKnownMacroToken($token, \%macroList, $case_sensitive)) { # This is a preprocessor directive print STDERR " IN MACRO\n" if ($localDebug || $codePathDebug); $ctstate{inMacro} = 1; } elsif (($token eq "*") && ($ctstate{depth} == 1) && ($ctstate{lastTokenType} eq "type" || $ctstate{lastTokenType} eq "star")) { print STDERR " ASTERISK\n" if ($localDebug || $codePathDebug); # spacing fix for '*' characters if (!$ctstate{spaceSinceLastToken} && (!$ctstate{keep_whitespace}) && $ctstate{lastTokenType} ne "star") { if ($ctstate{prespace} == "") { $ctstate{prespace} = " "; } } $tokenType = "type"; $isTypeStar = 1; $nospaceafter = 1; } elsif ($ntokennc eq ":" && $ctstate{inObjCMethod}) { # Detecting of objective-C method separators print STDERR " COLON (FUNCTION [1])\n" if ($localDebug || $codePathDebug); $tokenType = "function"; } elsif ($token eq ":" && $ctstate{inObjCMethod}) { # Detecting of objective-C method separators print STDERR " COLON (FUNCTION [2])\n" if ($localDebug || $codePathDebug); $tokenType = "function"; } elsif ($token eq ":" && $ctoken) { # Don't indent Objective-C method parts so far. print STDERR " COLON AND CTOKEN\n" if ($localDebug || $codePathDebug); $ctstate{depth} = $ctstate{depth} - 1; # We'll change it back before the next token. } elsif ($ntokennc eq "(" && !$ctstate{seenEquals} && !$ctstate{inAttribute} && !$ctstate{inRaises} && !$ctstate{inTypeOf}) { # Upcoming parenthesis handling $tokenType = "function"; print STDERR " LPAREN (FUNCTION [3])\n" if ($localDebug || $codePathDebug); if ($nntokennc eq "(" && !$untypedLanguage) { $tokenType = "type"; $ctstate{type} = "funcptr"; } if ($ctstate{inObjCMethod}) { $tokenType = ""; # shouldn't happen } if ($token eq "(") { $dropFP = 1; } } elsif ((!$untypedLanguage) && $ntokennc eq $parseTokens{lbrace} && $parseTokens{lbrace} ne "") { # Upcoming brace handling $tokenType = "type"; print STDERR " LBRACE (TYPE [1])\n" if ($localDebug || $codePathDebug); } elsif (($ctstate{inAttribute} || $ctstate{inRaises} || $ctstate{inTypeOf}) && $token eq "(") { print STDERR " LPAREN (ATTRIBUTE)\n" if ($localDebug || $codePathDebug); # Parenthesis handling for attributes $nextbreakable = 0; } elsif ($token eq "(") { print STDERR " LPAREN (GENERAL)\n" if ($localDebug || $codePathDebug); # Parenthesis handling $ctstate{type} = ""; if ($ctstate{inObjCProperty}) { $nextbreakable = 0; $oldLastBrace = ""; } elsif ($ctstate{inObjCMethod} && $ctstate{lastBrace} =~ /^[+-]/o) { $nextbreakable = 0; $oldLastBrace = ""; } elsif ($ctoken ne ")") { $nextbreakable = 3; } $ctstate{lastBrace} = $token; #if (!$ctstate{depth}) { #$nospaceafter = 2; #} } elsif ($token eq $parseTokens{sotemplate} && $parseTokens{sotemplate} ne "") { print STDERR " TEMPLATE\n" if ($localDebug || $codePathDebug); # This is the word "template" or similar. $ctstate{lastBrace} = $token; $nextbreakable = 0; $ctstate{breakable} = 0; } elsif (casecmp($token, $parseTokens{lbrace}, $case_sensitive)) { print STDERR " LBRACE (GENERAL)\n" if ($localDebug || $codePathDebug); # Brace handling. $ctstate{type} = ""; $ctstate{lastBrace} = $token; $nextbreakable = 3; if (!casecmp($ctoken, $parseTokens{rbrace}, $case_sensitive)) { $nextbreakable = 3; } } elsif (($ctstate{lang} eq "python") && ($token eq "\"\"\"")) { print STDERR " IRQ STARTEND\n" if ($localDebug || $codePathDebug); if ($ctstate{inQuote}) { $ctstate{inQuote} = 0; } else { $ctstate{inQuote} = 3; } } elsif ($ruby && ($token eq "%{" || $token eq "%Q{")) { print STDERR " IRQ START[1]\n" if ($localDebug || $codePathDebug); $ctstate{inQuote} = 3; $ctstate{inRubyQuote} = 1; } elsif ($ruby && ($token eq "%/")) { print STDERR " IRQ PERCENTSLASH\n" if ($localDebug || $codePathDebug); $ctstate{inQuote} = 3; $ctstate{inRubyQuote} = 1; } elsif ($ruby && ($token eq "<<")) { print STDERR " IRQ START[2]\n" if ($localDebug || $codePathDebug); $ctstate{inQuote} = 3; $ctstate{inRubyQuote} = 2; # quote coloring starts after next token. print STDERR "IRQ -> 2\n" if ($rubyDebug || $colorDebug); } elsif ($ruby && ($ctstate{inRubyQuote} == 2)) { print STDERR " IRQ\n" if ($localDebug || $codePathDebug); $ctstate{inRubyQuote} = 1; print STDERR "IRQ 2 -> 1\n" if ($rubyDebug || $colorDebug); } elsif ($token =~ /^\"/o && !$ctstate{inRubyQuote}) { print STDERR " DQUOTE\n" if ($localDebug || $codePathDebug); # Double quote handling $ctstate{inQuote} = 1; } elsif ($token =~ /^\'/o && !$ctstate{inRubyQuote}) { print STDERR " SQUOTE\n" if ($localDebug || $codePathDebug); # Single quote handling $ctstate{inQuote} = 2; } elsif ($ntokennc =~ /^(\)|\,|\;)/o || casecmp($ntokennc, $parseTokens{rbrace}, $case_sensitive)) { # Detection of the last token before the end of a part of a declaration. # last token print STDERR " LASTTOKEN\n" if ($localDebug || $codePathDebug); if ($nextbreakable != 3) { $nextbreakable = 2; } if ($ctstate{lastBrace} eq $parseTokens{sotemplate} && $parseTokens{sotemplate} ne "") { $nextbreakable = 0; } if ($ctstate{lastBrace} eq "(" || ($ctstate{lastBrace} eq "{" && $parseTokens{parmswithcurlybraces})) { if ($MIG || $pascal) { $tokenType = "type"; print STDERR " TYPE [2]\n" if ($localDebug || $codePathDebug); } else { $tokenType = "param"; print STDERR " PARAM [1]\n" if ($localDebug || $codePathDebug); } } elsif ($ctstate{lastBrace} eq $parseTokens{sotemplate} && $parseTokens{sotemplate} ne "") { print STDERR " TEMPLATE[1]\n" if ($localDebug || $codePathDebug); $tokenType = "template"; } elsif ($ctstate{type} eq "funcptr") { $tokenType = "function"; print STDERR " FUNCTION [1]\n" if ($localDebug || $codePathDebug); $ctstate{breakable} = 0; $nextbreakable = 0; } else { if ($MIG || $pascal) { $tokenType = "type"; print STDERR " TYPE [2a]\n" if ($localDebug || $codePathDebug); } else { $tokenType = "var"; print STDERR " VAR [1] (LB: ".$ctstate{lastBrace}.")\n" if ($localDebug || $codePathDebug); } } if (casecmp($ntokennc, $parseTokens{rbrace}, $case_sensitive) && $ctstate{type} eq "pasrec") { $ctstate{type} = ""; } if ($ntokennc eq ")") { $nextbreakable = 0; if ((!$untypedLanguage) && ($ctstate{inObjCMethod} || ($token eq "*"))) { print STDERR " TYPE [3]\n" if ($localDebug || $codePathDebug); $tokenType = "type"; } } } elsif ($ctstate{prespace} ne "" && ($token =~ /^\)/o || casecmp($token, $parseTokens{rbrace}, $case_sensitive))) { print " CPAREN OR RBRACE\n" if ($codePathDebug); print STDERR "PS: ".length($ctstate{prespace})." -> " if ($psDebug); if (!$ctstate{keep_whitespace}) { $ctstate{prespace} = nspaces(4 * ($ctstate{depth}-1)); } print STDERR length($ctstate{prespace})."\n" if ($psDebug); $mustbreak = 2; } elsif (casecmp($token, $parseTokens{rbrace}, $case_sensitive)) { print " RBRACE\n" if ($codePathDebug); if (!$ctstate{keep_whitespace}) { $ctstate{prespace} = nspaces(4 * ($ctstate{depth}-1)); } print STDERR length($ctstate{prespace})."\n" if ($psDebug); $mustbreak = 2; } else { if ($ctstate{inObjCMethod}) { if ($ctstate{lastBrace} eq "(") { print STDERR " TYPE [4]\n" if ($localDebug || $codePathDebug); $tokenType = "type"; } else { print STDERR " PARAM [2]\n" if ($localDebug || $codePathDebug); $tokenType = "param"; } } elsif ($MIG || $pascal) { if ($ctstate{lastBrace} eq "(") { print STDERR " PARAM [3]\n" if ($localDebug || $codePathDebug); $tokenType = "param"; } } else { if ($ctstate{lastBrace} eq $parseTokens{sotemplate} && ($parseTokens{sotemplate} ne "")) { print STDERR " TEMPLATE [5]\n" if ($localDebug || $codePathDebug); $tokenType = "template"; } elsif ($ctstate{inEnum}) { # Constants are a special case of variable print STDERR " TYPE [5]\n" if ($localDebug || $codePathDebug); $tokenType = "var"; } elsif ($untypedLanguage && $ctstate{lastBrace}) { print STDERR " UNTYPED: PARAM [5]\n" if ($localDebug || $codePathDebug); $tokenType = "param"; } elsif (($untypedLanguage == 1) || ($untypedLanguage == 2 && $ctstate{lastnstoken} !~ $parseTokens{classregexp})) { print STDERR " UNTYPED: VAR [5]\n" if ($localDebug || $codePathDebug); $tokenType = "var"; } else { print STDERR " TYPE [5]\n" if ($localDebug || $codePathDebug); $tokenType = "type"; } } } } else { my $fullpath = $ctstate{apio}->fullpath; my $linenum = $ctstate{apio}->linenum; warn "$fullpath:$linenum: warning: Unknown language ".$ctstate{lang}.". Not coloring. Please file a bug.\n"; } if ($ctstate{inRaises} && $tokenType && ($tokenType ne "keyword")) { $tokenType = "type"; } if ($ctstate{inTypeOf} && $tokenType && ($tokenType ne "keyword")) { $tokenType = "param"; } if ((!$hidden) && $self->isAvailabilityMacro()) { if ($ctstate{xmlmode}) { $string .= ""; $tailstring = "".$tailstring; } else { $string .= ""; $tailstring = "".$tailstring; } } # print STDERR "TOKEN: $token TYPE: $tokenType\n"; if ($ctstate{ASinName} == 1) { $ctstate{ASinName} = 2; print STDERR "ASinName -> 2 [end]\n" if ($localDebug); } elsif ($ctstate{ASinName} == 2) { $tokenType = "function"; } elsif ($ctstate{ASinName} == 3) { $ctstate{ASinName} = 4; print STDERR "ASinName -> 4 [end]\n" if ($localDebug); } elsif ($ctstate{ASinName} == 4) { $tokenType = "param"; } elsif ($ctstate{ASinName} == -1) { $ctstate{ASinName} = 0; print STDERR "ASinName -> 0 [end]\n" if ($localDebug); } if ($hidden) { $tokenType = "ignore"; if ($mustbreak) { $nextbreakable = 3; } else { $nextbreakable = 0; } $mustbreak = 0; $ctstate{breakable} = 0; } if (($ntoken =~ /[,;]/) && ($token =~ /[ \t]/) && !$ctstate{inComment} && !$ctstate{inMacro} && !$ctstate{inQuote}) { # print STDERR "DROP\n"; $hidden = 1; $tokenType = "ignore"; $nextbreakable = 0; $mustbreak = 0; $ctstate{breakable} = 0; } if ($MIG || $pascal) { if ($ctstate{lastnstoken} =~ /:/ && $ctstate{lastTokenType} eq "var") { $string .= $newtd; } } else { if (($ctstate{lastTokenType} eq "type") && !$hidden && ($token =~ /[\w\*]/) && ($tokenType eq "var" || $tokenType eq "param" || $tokenType eq "function" || $token eq "*") && ($ctstate{lastnstoken} =~ /\w/)) { $string .= $newtd; } } if (((($parseTokens{ilc} ne "") && ($ntoken eq $parseTokens{ilc})) || (($parseTokens{ilc_b} ne "") && ($ntoken eq $parseTokens{ilc_b}))) && !$ctstate{inComment}) { $ctstate{breakable} = 0; $nextbreakable = 0; } elsif (($parseTokens{soc} ne "") && $ntoken eq $parseTokens{soc} && !$ctstate{inComment}) { $ctstate{breakable} = 0; $nextbreakable = 0; } print STDERR "NB: $nextbreakable\n" if ($localDebug); print STDERR "TESTTYPE: $tokenType\n" if ($localDebug); if ($ctstate{inObjCMethod}) { print STDERR "OCC METHOD OVERRIDE: " if ($localDebug); $nextbreakable = 0; $ctstate{breakable} = 0; $mustbreak = 0; if ($ntoken eq ":" && $tokenType eq "function") { $ctstate{breakable} = 1; print STDERR "BREAKABLE\n" if ($localDebug); } else { print STDERR "NOT BREAKABLE\n" if ($localDebug); } } if ($ctstate{type} eq "pasrec" && $tokenType eq "") { print STDERR "PASREC VAR\n" if ($localDebug); $tokenType = "var"; } else { print STDERR "TYPE: ".$ctstate{type}." TT: ".$tokenType."\n" if ($localDebug); } print STDERR "IM: ".$ctstate{inMacro}."\n" if ($localDebug); if (!$ctstate{inComment} && $token =~ /^\s/o && !$tokennl && ($mustbreak || !$ctstate{newlen}) && (!$ctstate{keep_whitespace})) { print STDERR "CASEA\n" if ($localDebug); print STDERR "NL: ".$ctstate{newlen}." TOK: \"$token\" PS: \"".$ctstate{prespace}."\" NPS: \"$nextprespace\"\n" if ($localDebug); print STDERR "dropping leading white space\n" if ($localDebug); $drop = 1; } elsif (!$ctstate{inComment} && $tokennl && (!$ctstate{keep_whitespace})) { print STDERR "CASEB\n" if ($localDebug); if ($ctstate{lastnstoken} ne $parseTokens{eoc}) { # Insert a space instead. print STDERR "dropping newline\n" if ($localDebug); $drop = 1; $string .= " "; $ctstate{afteradvisoryspace} = 1; print STDERR "AAS -> 1 [drop]\n" if ($localDebug || $advisorySpaceDebug || $codePathDebug); } else { $mustbreak = 1; } } elsif ($ctstate{inComment} || $token =~ /^\s/o || ($token =~ /^\W/o && $token ne "*") || !$tokenType) { print STDERR "CASEC\n" if ($localDebug); my $macroTail = ""; $escapetoken = $ctstate{apio}->textToXML($token); print STDERR "OLDPS: \"".$ctstate{prespace}."\" ET=\"$escapetoken\" DROP=$drop\n" if ($localDebug); if ($ctstate{inComment} && $ctstate{prespace} ne "" && !$hidden) { if ($ctstate{xmlmode}) { $string .= "\n".$ctstate{prespace}.""; } else { $string .= "\n$endtr".$ctstate{prespace}."$begintr"; } } elsif ($ctstate{inMacro} && $token =~ /^\S/) { # Could be the initial keyword, which contains a '#' if (!$tokenType) { $tokenType = "preprocessor"; } if ($ctstate{xmlmode}) { $string .= $ctstate{prespace}.""; $macroTail = ""; } else { $string .= $ctstate{prespace}.""; $macroTail = ""; } } elsif (!$hidden) { $string .= $ctstate{prespace}; } if ($drop) { $escapetoken = ""; } if ($tokenType eq "ignore") { if (!$HeaderDoc::dumb_as_dirt) { # Drop token. print STDERR "HD: DROPPING IGNORED TOKEN $escapetoken\n" if ($dropDebug); $escapetoken = ""; } else { print STDERR "HD BASIC: KEEPING IGNORED TOKEN $escapetoken\n" if ($dropDebug); } } $string .= "$escapetoken$macroTail"; print STDERR "comment: $token\n" if ($localDebug); } else { print STDERR "CASED\n" if ($localDebug); # my $add_link_requests = $HeaderDoc::add_link_requests; $escapetoken = $ctstate{apio}->textToXML($token); if (($tokenType ne "") && ($token ne "") && token !~ /^\s/o) { my $fontToken = ""; if ($ctstate{xmlmode}) { $fontToken = "$escapetoken"; } else { if ($tokenType ne "ignore") { $fontToken = "$escapetoken"; } elsif (!$HeaderDoc::dumb_as_dirt) { # Drop token. print STDERR "HD: DROPPING IGNORED TOKEN $escapetoken\n" if ($dropDebug); $fontToken = ""; } else { print STDERR "HD BASIC: KEEPING IGNORED TOKEN $escapetoken\n" if ($dropDebug); $fontToken = $escapetoken; } } my $linkTokenType = $tokenType; if ($ctstate{inTypeOf} && ($tokenType ne "keyword")) { $linkTokenType = ""; } # print STDERR "HERE: \"$token\" TYPE: $tokenType\n"; # Don't add noisy link requests in XML. if ((!$drop) && ($HeaderDoc::add_link_requests && ($tokenType =~ /^(function|var|type|preprocessor)/o || $ctstate{inTypeOf}) && !$ctstate{xmlmode})) { my $refToken = $ctstate{apio}->genRefFromDeclaration($ctstate{lastKeyword}, $escapetoken, $fontToken, $linkTokenType); # print "TOKEN: $token RT: $refToken\n"; $string .= $ctstate{prespace}."$refToken"; } else { $string .= $ctstate{prespace}."$fontToken"; } } else { $escapetoken = $ctstate{apio}->textToXML($token); if ($tokenType eq "ignore") { if (!$HeaderDoc::dumb_as_dirt) { # Drop token. print STDERR "HD: DROPPING IGNORED TOKEN $escapetoken\n" if ($dropDebug); $escapetoken = ""; } else { print STDERR "HD BASIC: KEEPING IGNORED TOKEN $escapetoken\n" if ($dropDebug); } } $string .= $ctstate{prespace}."$escapetoken"; } print STDERR "$tokenType: $token\n" if ($localDebug); } $ctstate{prespace} = $nextprespace; if (!$drop) { $ctstate{newlen} += length($token); } print STDERR "NL ".$ctstate{newlen}." MDL $HeaderDoc::maxDecLen BK ".$ctstate{breakable}." IM ".$ctstate{inMacro}."\n" if ($localDebug); if ((!$ctstate{keep_whitespace}) && ($mustbreak || (($ctstate{newlen} > $HeaderDoc::maxDecLen) && $ctstate{breakable} && !$ctstate{inMacro} && !$hidden))) { print STDERR "MUSTBREAK CASE\n" if ($localDebug); if (($token =~ /^\s/o || $token eq "") && (!$ctstate{keep_whitespace})) { $nextprespace = nspaces(4 * ($ctstate{depth}+(1-$mustbreak))); print STDERR "PS WILL BE \"$nextprespace\"\n" if ($localDebug); $nextbreakable = 3; } else { print STDERR "NEWLEN: ".$ctstate{newlen}."\n" if ($localDebug); $ctstate{newlen} = length($token); print STDERR "NEWLEN [2]: ".$ctstate{newlen}."\n" if ($localDebug); print STDERR "MB: $mustbreak, DP: ".$ctstate{depth}."\n" if ($localDebug); my $ps = nspaces(4 * ($ctstate{depth}+(1-$mustbreak))); if (($ctstate{inComment} == 1 && !$firstCommentToken) || $leavingComment) { if ($ctstate{xmlmode}) { $string = "\n$ps$string"; } else { $string = "$endtr\n$begintr$ps$string"; } } else { $string = "$endtr\n$begintr$ps$string"; } print STDERR "PS WAS \"$ps\"\n" if ($localDebug); } } if ($token !~ /^\s/o) { $ctstate{lastnstoken} = $token; } if ($token !~ /\s/) { if ($isTypeStar) { $ctstate{lastTokenType} = "star"; } else { $ctstate{lastTokenType} = $tokenType; } $ctstate{spaceSinceLastToken} = 0; } else { $ctstate{spaceSinceLastToken} = 1; } my $newstring = ""; my $node = $self->{FIRSTCHILD}; my $newstringref = undef; if ($node && $continue) { if ($nospaceafter == 1) { $nospaceafter = 0; } print STDERR "BEGIN CHILDREN\n" if ($localDebug || $colorDebug || $treeDebug); bless($node, "HeaderDoc::ParseTree"); my $childctstate = newCTState(\%ctstate, depth => $ctstate{depth} + 1, breakable => $nextbreakable); ($newstringref, $ctstate{newlen}, $nextbreakable, $ctstate{prespace}, $ctstate{lastnstoken}, $continue, $ctstate{lastTokenType}, $ctstate{spaceSinceLastToken}, $ctstate{afteradvisoryspace}) = $node->colorTreeSub($childctstate); $newstring = ${$newstringref}; print STDERR "END CHILDREN\n" if ($localDebug || $colorDebug || $treeDebug); } if ($self == $ctstate{lastDisplayNode} ) { print STDERR "Node is last node in tree (LDN/AS). Ending after this node.\n" if ($localDebug || $codePathDebug); $continue = 0; } $string .= $newstring; $newstring = ""; print STDERR "SET STRING TO $string\n" if ($localDebug); if (($ctstate{prespace} ne "")) { # if we inherit a need for prespace from a descendant, it means # that the descendant ended with a newline. We don't want to # propagate the extra indentation to the next node, though, so # we'll regenerate the value of prespace here. $ctstate{prespace} = nspaces(4 * $ctstate{depth}); } print STDERR "HMLT: ".$self->{HIDEMACROLASTTOKEN}."\n" if ($localDebug); if ($self->{HIDEMACROLASTTOKEN} && $ctstate{drop_pdefine_contents}) { $continue = 0; } $string .= $tailstring; $tailstring = ""; print STDERR "LB ".$ctstate{lastBrace}." -> $oldLastBrace\n" if ($colorDebug || $localDebug); $ctstate{lastBrace} = $oldLastBrace; $ctstate{depth} = $oldDepth; print STDERR "Resetting inMacro (".$ctstate{inMacro}.") to previous value ($oldInMacro).\n" if ($localDebug); $ctstate{inMacro} = $oldInMacro; $ctstate{lastKeyword} = $oldLastKeyword; $ctstate{inComment} = $oldInComment; $ctstate{inQuote} = $oldInQuote; $ctstate{inAttribute} = $oldInAttribute; $ctstate{inRaises} = $oldInRaises; $ctstate{inTypeOf} = $oldInTypeOf; # if ($ctstate{inComment} && !$oldInComment) { # $ctstate{inComment} = $oldInComment; # if ($ctstate{xmlmode}) { # $string .= ""; # } else { # $string .= ""; # } # } if ($dropFP) { $ctstate{type} = $ctstate{apio}->class(); } $node = $self->{NEXT}; if ($node && $continue) { bless($node, "HeaderDoc::ParseTree"); if ($nospaceafter) { while ($node && ($node->token =~ /[ \t]/)) { print STDERR "SKIPPED NODE (\"".$node->token."\").\n" if ($localDebug); $node = $node->next; bless($node, "HeaderDoc::ParseTree"); } print STDERR "STOPPED SKIPPING AT NODE \"".$node->token."\".\n" if ($localDebug); } print STDERR "CONTINUING TO NODE \"".$node->token."\".\n" if ($localDebug); if ($node) { my $nextctstate = newCTState(\%ctstate, breakable => $nextbreakable, afteradvisoryspace => $ctstate{afteradvisoryspace}); ($newstringref, $ctstate{newlen}, $nextbreakable, $ctstate{prespace}, $ctstate{lastnstoken}, $continue, $ctstate{lastTokenType}, $ctstate{spaceSinceLastToken}, $ctstate{afteradvisoryspace}) = $node->colorTreeSub($nextctstate); $newstring = ${$newstringref}; } } $string .= $newstring; print STDERR "SET STRING TO $string\n" if ($localDebug); # $self->{CTSTRING} = $string; # $self->{CTSUB} = ($ctstate{newlen}, $nextbreakable, $ctstate{prespace}, $ctstate{lastnstoken}); return (\$string, $ctstate{newlen}, $nextbreakable, $ctstate{prespace}, $ctstate{lastnstoken}, $continue, $ctstate{lastTokenType}, $ctstate{spaceSinceLastToken}, $ctstate{afteradvisoryspace}); } # /*! # @abstract # The recursive part of {@link test_output_dump}. # @param self # The parse tree/subtree to dump. # @param depth # The number of nesting levels above this token. # @param lastnode # The last node to emit. This node and its children # appear in the output, but not its siblings. # */ sub test_output_dump_rec { my $self = shift; my $depth = shift; my $lastnode = shift; my $ret = ""; my $parserState = $self->parserState(); if ($parserState && !$lastnode) { $lastnode = $parserState->{lastTreeNode}; } if ($self->token ne "") { my $i = $depth-1; while ($i > 0) { $ret .= "| "; $i--; } my $HYPHEN = "-"; my $psString = ""; if ($self->parserState()) { $HYPHEN = "*"; $psString = " (HAS STATE)"; } if ($depth) { $ret .= "+-$HYPHEN-"; } if ($self->token =~ /\n$/) { $ret .= "[ NEWLINE ]$psString\n"; } else { $ret .= $self->token()."$psString\n"; # if ($self->token !~ /\n$/) { $ret .= "\n"; } } } if ($self == $lastnode) { $ret .= "-=-=-=-=-=-=- EODEC -=-=-=-=-=-=-\n"; } if ($self->firstchild()) { $ret .= $self->firstchild()->test_output_dump_rec($depth+1, $lastnode); } if ($self->next()) { $ret .= $self->next()->test_output_dump_rec($depth, $lastnode); } return $ret; } # /*! # @abstract # The recursive part of # {@link //apple_ref/perl/instm/HeaderDoc::ParseTree/dbprint//() dbprint}. # @param self # The parse tree/subtree to dump. # @param depth # The number of nesting levels above this token. # @param lastnode # The last node to emit. This node and its children # appear in the output, but not its siblings. # */ sub dbprintrec { my $self = shift; my $depth = shift; my $lastnode = shift; my $include_noise = shift; # print STDERR "IN: $include_noise\n"; my $localDebug = 0; # cluck("here\n"); my $parserState = $self->parserState(); if ($parserState && !$lastnode) { $lastnode = $parserState->{lastTreeNode}; } if ($self->token ne "") { my $i = $depth-1; while ($i > 0) { print STDERR "| "; $i--; } my $HYPHEN = "-"; my $psString = ""; my $reStateString = ""; if ($self->{RE_STATE}) { $reStateString = " ".$self->{RE_STATE}; } if ($self->parserState()) { $HYPHEN = "*"; $psString = " (TOKENID: ".$self.", PSID: ".$self->parserState().$reStateString.")"; } else { $psString = " (TOKENID: ".$self.$reStateString.")"; } if ($depth) { print STDERR "+-$HYPHEN-"; } if ($self->token =~ /\n$/) { print STDERR "[ NEWLINE ]$psString\n"; } else { print STDERR $self->token()."$psString\n"; # if ($self->token !~ /\n$/) { print STDERR "\n"; } } } if ($include_noise) { printHash(%{$self}); print STDERR "API OWNERS:\n"; foreach my $apio (@{$self->{APIOWNERS}} ) { print " ".$apio."\n"; } print STDERR "END OF API OWNERS\n"; } if ($self == $lastnode) { my $deb = ""; if ($localDebug || 1) { $deb = $lastnode; } print STDERR "-=-=-=-=-=-=- EODEC $deb-=-=-=-=-=-=-\n"; } if ($self->firstchild()) { $self->firstchild()->dbprintrec($depth+1, $lastnode, $include_noise); } if ($self->next()) { $self->next()->dbprintrec($depth, $lastnode, $include_noise); } } # /*! # @abstract # Dumps a parse tree in a manner formatted for use by the # {@link //apple_ref/perl/cl/HeaderDoc::Test Test} class # (used for running HeaderDoc self tests). # @param self # The parse tree/subtree to dump. # @param depth # The number of nesting levels above this token. # @param lastnode # The last node to emit. This node and its children # appear in the output, but not its siblings. # @discussion # This is similar to dbprint in many ways, but it # omits object IDs that change from run to run. # */ sub test_output_dump { my $self = shift; return $self->test_output_dump_rec(1); } # /*! # @abstract # Prints a parse tree in a format designed for debugging. # @param self # The parse tree object to print. # */ sub dbprint { my $self = shift; my $enable_noise = shift; $self->dbprintrec(1, undef, $enable_noise); } # /*! # @abstract # Gets/sets the filename associated with this tree node. # @param self # This tree node. # @param FILENAME # The new filename value to set. (Optional.) # */ sub filename { my $self = shift; if (@_) { $self->{FILENAME} = shift; } return $self->{FILENAME}; } # /*! # @abstract # Gets/sets the path associated with this tree node. # @param self # This tree node. # @param FILENAME # The new filename value to set. (Optional.) # @discussion # This contains the (relative or absolute) path # for the owning header, as passed in on the # command line. # */ sub fullpath { my $self = shift; if (@_) { $self->{FULLPATH} = shift; } return $self->{FULLPATH}; } # /*! # @abstract # Gets/sets the line number associated with this tree node. # @param self # This tree node. # @param LINENUM # The new line number value to set. (Optional.) # @discussion # This value indicates where this tree node came fron # within the enclosing header. # */ sub linenum { my $self = shift; if (@_) { $self->{LINENUM} = shift; } return $self->{LINENUM}; } # /*! # @abstract # Prints this object for debugging purposes. # @param self # This object. # */ sub printObject { my $self = shift; print STDERR "----- ParseTree Object ------\n"; print STDERR "token: $self->{TOKEN}\n"; print STDERR "next: $self->{NEXT}\n"; print STDERR "firstchild: $self->{FIRSTCHILD}\n"; print STDERR "\n"; } # /*! # @abstract # Adds the parsed parameter strings from an # array (reference) to this node's parsed # parameters list. # @param self # This parse tree node. # @param pplref # A reference to a parsed parameter list. # @seealso rawParsedParams rawParsedParams # */ sub addRawParsedParams { my $self = shift; my $pplref = shift; my @array = @{$pplref}; foreach my $param (@array) { push(@{$self->{RAWPARSEDPARAMETERS}}, $pplref); } return $self->{RAWPARSEDPARAMETERS}; } # /*! # @abstract # Returns a reference to an array of parsed parameter # strings associated with this node. # @param self # This parse tree node. # @seealso addRawParsedParams # */ sub rawParsedParams { my $self = shift; return $self->{RAWPARSEDPARAMETERS}; } # /*! # @abstract # Gets/sets the parser state object associated with # this tree node. # @param self # This tree node. # @param PARSERSTATE # The new parser state value to set. (Optional.) # @discussion # Parser state objects are inserted into the parse # tree to make it easier to interpret declarations # within classes. # */ sub parserState { my $self = shift; my $localDebug = 0; if (@_) { my $state = shift; print STDERR "Setting parser state for $self\n" if ($localDebug); print STDERR "Last token (raw) is $state->{lastTreeNode}\n" if ($localDebug); print STDERR "Last token (text) is ".$state->{lastTreeNode}->token()."\n" if ($localDebug); $self->{PARSERSTATE} = $state; } return $self->{PARSERSTATE}; } # /*! # @abstract # Uses gcc to calculate the value for a # string containing a complex #define # macro expression. # @param self # The parse tree object. (Unused.) # @param rawvalue # The expression to interpret. # @discussion # Used by {@link getPTvalue}. # # Although it should be possible for the # new code in MacroFilter.pm to be easily # adapted to do this job, at the time, that # code did not exist. # */ sub trygcc { my $self = shift; my $rawvalue = shift; my $success = 0; my $value = 0; my $timestamp = time(); my $localDebug = 0; # print STDERR "RV: $rawvalue\n"; if (open(GCCFILE, ">/tmp/headerdoc-gcctemp-$timestamp.c")) { print GCCFILE "#include \nmain(){printf(\"%d\\n\", $rawvalue);}\n"; close(GCCFILE); if (open(GCCPIPE, $HeaderDoc::c_compiler." /tmp/headerdoc-gcctemp-$timestamp.c -o /tmp/headerdoc-gcctemp-$timestamp > /dev/null 2> /dev/null |")) { my $junkstring = ; close(GCCPIPE); if ($?) { $success = 0; } else { $success = 1; } if ($success) { if (open(EXECPIPE, "/tmp/headerdoc-gcctemp-$timestamp |")) { my $retstring = ; $value = $retstring; $value =~ s/\n//sg; print STDERR "VALUE: $value\nSUCCESS: $success\n" if ($localDebug); } else { $success = 0; } } } unlink("/tmp/headerdoc-gcctemp-$timestamp.c"); unlink("/tmp/headerdoc-gcctemp-$timestamp"); } print STDERR "RET $success, $value\n" if ($localDebug); return ($success, $value); } # /*! # @abstract # Uses gcc to calculate the value for a # parse tree containing a complex #define # macro expression # @param self # The parse tree object. # @discussion # Although it should be possible for the # new code in MacroFilter.pm to be easily # adapted to do this job, at the time, that # code did not exist. # */ sub getPTvalue { my $self = shift; my $success = 0; my $value = 0; my $localDebug = 0; my $pos = $self; while ($pos && ($pos->token() ne "#define")) { $pos = $pos->next(); } if (!$pos) { return($success, $value); } $pos = $pos->firstchild(); while ($pos && ($pos->hidden != 3) && ($pos->{HIDEMACROLASTTOKEN} != 2)) { $pos = $pos->next(); } if ($pos) { my $rawvalue = $pos->textTree(); print STDERR "getPTvalue: WE HAVE A WINNER.\n" if ($localDebug); print STDERR "RAWVALUE IS: $rawvalue\n" if ($localDebug); ($success, $value) = $self->trygcc($rawvalue); } return($success, $value); } # /*! # @abstract # Releases references in an attempt to allow Perl to # garbage collect this parse tree. # @param self # The root of the parse tree to release. # */ sub dispose { my $self = shift; # Decrement the reference count. # cluck("Dispose called on parse tree\n"); if ($self->{REFCOUNT} > 0) { if ($HeaderDoc::debugAllocations) { print STDERR "Parse tree reference count is ".$self->{REFCOUNT}.". Not disposing.\n"; print STDERR "SELF: ".$self."\n"; print STDERR "FC: ".$self->{FIRSTCHILD}."\n"; print STDERR "NEXT: ".$self->{NEXT}."\n"; print STDERR "PARENT: ".$self->{PARENT}."\n"; print STDERR "APIOLIST: ".$self->{APIOWNERS}."\n"; foreach my $obj (@{$self->{APIOWNERS}}) { print STDERR " OWNER: $obj\n"; } print STDERR "END OF LIST\n"; } return; } print STDERR "Disposing of tree\n" if ($HeaderDoc::debugAllocations); $self->{PARENT} = undef; if ($self->{FIRSTCHILD}) { $self->{FIRSTCHILD}->dispose(); $self->{FIRSTCHILD} = undef; } if ($self->{NEXT}) { $self->{NEXT}->dispose(); $self->{NEXT} = undef; } $self->{NTNC} = undef; if ($self->{APIOWNERS}) { $self->{APIOWNERS} = (); } if ($self->{PARSERSTATE}) { $self->{PARSERSTATE} = undef; } $self->{PARSEDPARAMS} = (); $self->{RAWPARSEDPARAMETERS} = (); $self->{TOKEN} = undef; $self->dbprint() if ($HeaderDoc::debugAllocations); } # /*! # @abstract # Called by Perl when a node is garbage collected. # @param self # The root of the parse tree to release. # @discussion # Used for debugging memory behavior. # */ sub DESTROY { my $self = shift; print STDERR "Destroying $self\n" if ($HeaderDoc::debugAllocations); } # /*! # @abstract # Counts the number of curly braces at this level in the parse tree. # @param self # This parse tree node. # @param lbrace # The left brace token to look for. # @param last # The last node to include. # @discussion # Because this function checks at a given level, # it updates the last node to be the ancestor # of the last node at the same level as the # starting node. (Why doesn't this use # {@link nextAtLevelOf}?) # */ sub curlycount { my $self = shift; my $lbrace = shift; my $last = shift; my $pos = $self; my $count = 0; while ($last && !$last->isAfter($self)) { $last = $last->parent(); } if ($last) { $last = $last->next(); } # first node after this declaration while ($pos && $pos != $last) { if ($pos->token eq "$lbrace") { $count++; } $pos = $pos->next(); } return $count; } # /*! # @abstract # Walks this parse tree node's sibling chain and # returns the node after the nth left brace. # @param self # The starting parse tree node. # @param lbrace # The left brace token to look for. # @param count # The number of braces to skip past. # */ sub skipcurly { my $self = shift; my $lbrace = shift; my $count = shift; my $localDebug = 0; my $pos = $self; print STDERR "SKIPPING $count curly braces (lbrace = '$lbrace') at POS=$pos\n" if ($localDebug); if (!$count) { return $self; } while ($pos) { my $tok = $pos->token(); print STDERR "TOKEN: '$tok'\n" if ($localDebug); if ($tok eq "$lbrace") { print STDERR "MATCH\n" if ($localDebug); if (!--$count) { my $next = $pos->next; if ($localDebug) { print STDERR "FOUND ONE. Next tree is:\n"; if ($next) { $next->dbprint(); } else { print STDERR "UNDEFINED!\n"; } } return $next; } } $pos = $pos->next(); } warn "Yikes! Ran out of open braces!\n"; return $pos; } # /*! # @abstract # Returns whether this token is a start token # for a C preprocessor macro. # @param token # The token to check. # @param macroListRef # A reference to the macro hash returned by a # call to # {@link //apple_ref/perl/instm/HeaderDoc::Utilities/parseTokens//() parseTokens}. # @param case_sensitive # Set to 0/1 depending on whether the language uses # case-sensitive token matching. This value is # generally obtained with a call to # {@link //apple_ref/perl/instm/HeaderDoc::HeaderElement/keywords//() keywords}. # */ sub isKnownMacroToken { my $token = shift; my $macroListRef = shift; my $case_sensitive = shift; my %macroList = %{$macroListRef}; if ($case_sensitive) { if ($macroList{$token}) { return 1; } return 0; } foreach my $cmpToken (keys %macroList) { if (casecmp($token, $cmpToken, $case_sensitive)) { return 1; } } return 0; } # /*! # @abstract # Scrapes out the children of a new-style availability # macro and returns a sanitized array. # @param self # The parse tree node, taken from # {@link //apple_ref/perl/data/HeaderDoc::ParserState/availabilityNodesArray availabilityNodesArray} # in the # {@link //apple_ref/perl/cl/HeaderDoc::ParserState ParserState} object. # @discussion # Most of the hard work is done by # {@link //apple_ref/perl/instm/HeaderDoc::Utilities/complexAvailabilityToArray//() complexAvailabilityToArray}. # # This function is exclusively used by # {@link //apple_ref/perl/instm/HeaderDoc::BlockParse/mergeComplexAvailability//() mergeComplexAvailability}. # */ sub parseComplexAvailability { my $self = shift; my $localDebug = 0; print STDERR "parseComplexAvailability: dumping tree for $self.\n" if ($localDebug); $self->dbprint() if ($localDebug); my $token = $self->token(); my $availstring = ""; my $pos = $self->firstchild(); while ($pos && ($pos->token() ne "(")) { $pos = $pos->next(); } if (!$pos) { my @arr = (); return \@arr; } $pos = $pos->next(); while ($pos && ($pos->token() ne ")")) { $availstring .= $pos->token(); $pos = $pos->next(); } print STDERR "TOKEN: $token\nSTRING: $availstring\n" if ($localDebug); return complexAvailabilityToArray($token, $availstring); } # /*! # @abstract # Gets/sets whether this token is an availability macro. # @param self # The parse tree node. # @param newvalue # The new value to set. (Optional.) # */ sub isAvailabilityMacro { my $self = shift; if (@_) { $self->{ISAVAILABILITYMACRO} = shift; } if ($self->{ISAVAILABILITYMACRO}) { return $self->{ISAVAILABILITYMACRO}; } # my $parent = $self->parent(); # if ($parent) { return $parent->isAvailabilityMacro(); } return 0; } # /*! # @abstract # Partially translates Perl code into C # */ sub translateTree { my $self = shift; my %state = (); $self->translateTreeRec(\%state); } # /*! # @abstract # Returns the parse tree node containing the # next non-space token at the current level. # @param self # The initial parse tree node. # */ sub nextNSToken { my $self = shift; my $next = $self->{NEXT}; while ($next && $next->{TOKEN} !~ /\S/s) { $next = $next->{NEXT}; } return $next; } # /*! @abstract # Does the real work of translating a Perl script (partially) # to C. # @param self # The tree to translate. # @param self # A translation state object (initially empty). # */ sub translateTreeRec { my $self = shift; my $stateref = shift; my %state = %{$stateref}; my $localDebug = 0; my $enteringComment = 0; my $enteringString = 0; my $enteringSingle = 0; my $leavingHDComment = 0; print STDERR "TOK: ".$self->{TOKEN}."\n" if ($localDebug); if ((!$state{seenCode}) && $self->{TOKEN} && $self->{TOKEN} =~ /\S/s && $self->{TOKEN} ne "if" && $self->{TOKEN} ne "#" && $self->{TOKEN} ne "{" && $self->{TOKEN} ne "}") { print STDERR "SEENCODE -> 1\n" if ($localDebug); $state{seenCode} = 1; $state{insertIfBeforeNode} = $self; } print STDERR "CHECK: INSTRING: ".$state{inString}." INCOMMENT: ".$state{inComment}." INREGEXP: ".$state{inRegExp}." INSINGLE: ".$state{inSingle}."\n" if ($localDebug); if (!($state{inString} || $state{inComment} || $state{inRegExp} || $state{inSingle})) { # Don't mess with the guts of these things, for the most part. print STDERR "FIXUP OKAY\n" if ($localDebug); if ($self->{TOKEN} eq "\"") { $state{inString} = 1; $enteringString = 1; } elsif ($self->{TOKEN} eq "'") { $state{inSingle} = 1; $enteringSingle = 1; } elsif ($self->{TOKEN} eq "#") { print STDERR "ILC\n" if ($localDebug); $state{inComment} = 1; $enteringComment = 1; $state{socNode} = $self; if ($state{inHDComment}) { $self->{TOKEN} = " "; } else { $self->{TOKEN} = "//"; } if ($self->{FIRSTCHILD}->textTree() =~ /\*\//) { $leavingHDComment = 1; } } elsif ($self->{TOKEN} eq "\$") { # Merge with next token. my $next = $self->{NEXT}; if ($next) { $self->{TOKEN} = ""; $self->{NEXT}->{TOKEN} = "\$".$self->{NEXT}->{TOKEN}; } } elsif ($self->{TOKEN} eq ":") { if ($self->{NEXT} && $self->{NEXT}->{TOKEN} eq ":") { $self->{TOKEN} = "_"; $self->{NEXT}->{TOKEN} = "_"; } } elsif ($self->{TOKEN} eq "elsif") { $self->{TOKEN} = "else if"; } elsif ($self->{TOKEN} eq "STDERR") { $self->{TOKEN} = "stderr"; } elsif ($self->{TOKEN} eq "STDOUT") { $self->{TOKEN} = "stdout"; } elsif ($self->{TOKEN} eq "STDIN") { $self->{TOKEN} = "stdin"; } elsif ($self->{TOKEN} eq "<" && $self->{FIRSTCHILD} && $self->{FIRSTCHILD}->{NEXT} && $self->{FIRSTCHILD}->{NEXT}->{TOKEN} =~ /^[a-zA-Z]+$/ && $self->{FIRSTCHILD}->{NEXT}->{NEXT} && $self->{FIRSTCHILD}->{NEXT}->{NEXT}->{TOKEN} eq ">") { # print STDERR "FC: ".$self->{FIRSTCHILD}->{TOKEN}." NC ".$self->{FIRSTCHILD}->{NEXT}->{TOKEN}."\n"; print STDERR "Read from file.\n" if ($localDebug); $self->{TOKEN} = "stringFromFP("; $self->{FIRSTCHILD}->{NEXT}->{NEXT}->{TOKEN} = ")"; } elsif ($self->{TOKEN} eq "print" || $self->{TOKEN} eq "warn") { my $nextnstoken = $self->nextNSToken(); if ($self->{TOKEN} eq "print") { my $ns = $self->nextNSToken(); if ($ns && ($ns->{TOKEN} eq "\"" || $ns->{TOKEN} eq "\$")) { $self->{TOKEN} = "printf"; } else { $self->{TOKEN} = "fprintf"; if ($ns->{TOKEN} eq "STDERR") { $ns->{TOKEN} = "stderr"; } elsif ($ns->{TOKEN} eq "STDOUT") { $ns->{TOKEN} = "stdout"; } $ns->{TOKEN} .= ","; } } if ($nextnstoken->{TOKEN} ne "(") { $self->{TOKEN} .= "("; $state{addParenAtSemi} = 1; $self->{NEXT} = $nextnstoken; } } elsif ($self->{TOKEN} eq "split") { $self->{TOKEN} = "regexpSplit"; $state{inSplit} = 1; print STDERR "inSplit -> 1\n" if ($localDebug); } elsif ($self->{TOKEN} eq "(" && $state{inSplit}) { $state{inSplit} = 2; print STDERR "inSplit -> 1\n" if ($localDebug); } elsif ($self->{TOKEN} eq "->") { print STDERR "->" if ($localDebug); my $pos = $self->{NEXT}; while ($pos && $pos->{TOKEN} !~ /\S/s) { $pos = $pos->{NEXT}; } if ($pos && $pos->{TOKEN} eq "{") { my $posb = $pos->{FIRSTCHILD}; while ($posb && $posb->{TOKEN} ne "}") { $posb = $posb->{NEXT}; } if ($pos && $posb) { $pos->{TOKEN} = ""; $posb->{TOKEN} = ""; } } } elsif ($self->{TOKEN} eq "eq" || $self->{TOKEN} eq "ne") { my $prev = $self->slowprev(); while ($prev && ($prev->token !~ /\S/s)) { $prev = $prev->slowprev(); } while ($prev && $prev->{TOKEN} !~ /\&/ && $prev->{TOKEN} !~/\|/) { print STDERR "PREV: $prev\n" if ($localDebug); print STDERR "TOK: ".$prev->{TOKEN}."\n" if ($localDebug); $prev = $prev->slowprev(); } # }) # If we run off the beginning of the chain, we hit an open parenthesis, # so we know we can always insert after the first (always-empty) # child. my $addspace = ""; if (!$prev) { $prev = $self->parent()->firstchild(); } else { # Add a space after || or && $addspace = " "; } my $next = $self->{NEXT}; while ($next && ($next->token !~ /\S/s)) { $next = $next->{NEXT}; } print STDERR "NEXT IS \"".$next->{TOKEN}."\"\n" if ($localDebug); while ($next && $next->{NEXT} && $next->{NEXT}->{TOKEN} !~ /\&/ && $next->{NEXT}->{TOKEN} !~ /\|/ && $next->{NEXT}->{TOKEN} !~ /\)/) { $next = $next->{NEXT}; print STDERR "INLOOP NEXT IS ".$next->{TOKEN}."\n" if ($localDebug); } if ($prev && $next) { my $newnode = HeaderDoc::ParseTree->new(); if ($self->{TOKEN} eq "eq") { $newnode->{TOKEN} = $addspace."!strcmp("; } else { $newnode->{TOKEN} = $addspace."strcmp("; } $newnode->{NEXT} = $prev->{NEXT}; $prev->{NEXT} = $newnode; if ($next->{TOKEN} eq "\$") { $next->{TOKEN} = ""; $next->{NEXT}->{TOKEN} = "\$".$next->{NEXT}->{TOKEN}; $next = $next->{NEXT}; } $newnode = HeaderDoc::ParseTree->new(); $newnode->{TOKEN} = ")"; $newnode->{NEXT} = $next->{NEXT}; $next->{NEXT} = $newnode; $self->{TOKEN} = ","; $prev = $self->slowprev(); while ($prev && ($prev->{TOKEN} =~ /\s/s)) { $prev->{TOKEN} = ""; $prev = $prev->slowprev(); } } else { warn("Could not fix \"eq\" because previous/next token not found (PREV: $prev NEXT: $next).\n"); } } elsif ($state{seenCode} && ($self->{TOKEN} eq ";" || $self->{TOKEN} eq "{")) { $state{seenCode} = 0; if ($state{addParenAtSemi}) { $state{addParenAtSemi} = 0; $self->{TOKEN} = ")".$self->{TOKEN}; } print STDERR "SEENCODE -> 0\n" if ($localDebug); if ($state{captureIf}) { my $lastcapture = $self->slowprev(); my $beforenode = $state{insertIfBeforeNode}; my $prev = $beforenode->slowprev(); if (!$prev) { my $newnode = HeaderDoc::ParseTree->new(); $newnode->{TOKEN} = $beforenode->{TOKEN}; $newnode->{NEXT} = $beforenode->{NEXT}; $beforenode->{NEXT} = $newnode; $beforenode->{TOKEN} = ""; $prev = $beforenode; $beforenode = $newnode; } my $trailingspace = $self->slowprev(); if (!$trailingspace) { $trailingspace = $self->parent(); } while ($trailingspace && ((!$trailingspace->{TOKEN}) || ($trailingspace->{TOKEN} =~ /\s/s))) { # print STDERR "TS: $trailingspace\n" if ($localDebug); # print STDERR "PARENT: ".$trailingspace->parent()."\n" if ($localDebug); $trailingspace->{TOKEN} = ""; my $temp = $trailingspace->slowprev(); if ($temp) { $trailingspace = $temp; } else { $trailingspace = $trailingspace->parent(); } } my $copiedDataNode = HeaderDoc::ParseTree->new(); $copiedDataNode->{NEXT} = $prev->{NEXT}; $prev->{NEXT} = $copiedDataNode; $copiedDataNode->{TOKEN} = $state{capturedText}." "; print STDERR "ACCUM: ".$state{capturedText}."\n" if ($localDebug); $state{captureIf} = 0; print STDERR "captureIf -> 0\n" if ($localDebug); } } elsif ($self->{TOKEN} eq "if" && $state{seenCode}) { $state{capturedText} = ""; $state{captureIf} = 1; print STDERR "captureIf -> 1\n" if ($localDebug); } } elsif ($state{inComment} == 1 && ($self->{TOKEN} eq "/*!" || $self->{TOKEN} eq "/**")) { $state{socNode}->{TOKEN} = " "; $state{inHDComment} = 1; } elsif ($state{inComment} && $self->{TOKEN} =~ /\S/) { $state{inComment} = 2; } print STDERR "RE_STATE: ".$self->{RE_STATE}."\n" if ($localDebug); if ($self->{RE_STATE} eq "RE_PREFIX") { print STDERR "Regexp prefix found.\n" if ($localDebug); if ($self->{TOKEN} eq "s") { $state{inRegExp} = 3; $state{rePrefix} = $self->{TOKEN}; $self->{TOKEN} = ""; } elsif ($self->{TOKEN} eq "tr") { $state{inRegExp} = 4; $state{rePrefix} = $self->{TOKEN}; $self->{TOKEN} = ""; } elsif ($self->{TOKEN} eq "m") { $self->{TOKEN} = ""; print STDERR "Dropping prefix because 'm' is equivalent to no prefix.\n" if ($localDebug); } else { print STDERR "Unsupported prefix. Ignoring this expression.\n" if ($localDebug); $state{inRegExp} = -1; # No 'tr' support yet. } } elsif ($self->{RE_STATE} eq "RE_PARTSEP") { if ($self->{NEXT}->{RE_STATE} eq "RE_PARTSEP") { $self->{NEXT}->{TOKEN} = ""; $self->{NEXT}->{RE_STATE} = ""; } $self->{TOKEN} = "\", \""; } elsif ($self->{RE_STATE} eq "RE_START" && ((!$state{inRegExp}) || $state{inRegExp} == 3 || $state{inRegExp} == 4)) { print STDERR "Regexp start found. INSPLIT: ".$state{inSplit}."\n" if ($localDebug); $state{regexpName} = $state{prevTokenNode}->{TOKEN}; print STDERR "PTN: ".$state{prevTokenNode}." (".$state{prevTokenNode}->{TOKEN}.") REGEXPNAME: ".$state{regexpName}."\n" if ($localDebug); if ($state{inSplit}) { $self->{TOKEN} = "\""; $state{inRegExp} = 2; } elsif ($state{regexpName} =~ /^\$[0-9A-Za-z_]+$/) { if (!$state{inRegExp}) { $state{inRegExp} = 1; $state{prevTokenNode}->{TOKEN} = "regexpMatch(".$state{regexpName}.", "; } elsif ($state{inRegExp} == 3) { $state{prevTokenNode}->{TOKEN} = "regexpReplace(".$state{regexpName}.", \@\@\@DEST\@\@\@, "; } elsif ($state{inRegExp} == 4) { $state{prevTokenNode}->{TOKEN} = "regexptr(".$state{regexpName}.", \@\@\@DEST\@\@\@, "; } my $pos = $state{prevTokenNode}->{NEXT}; print STDERR "Deleting the =~ .\n" if ($localDebug); while ($pos && !$pos->{RE_STATE}) { print STDERR "Deleting \"".$pos->{TOKEN}."\" ($pos)\n" if ($localDebug); # Nuke the "=~" if ($pos->{TOKEN} eq "!") { $state{prevTokenNode}->{TOKEN} = "!".$state{prevTokenNode}->{TOKEN}; } $pos->{TOKEN} = ""; $pos = $pos->{NEXT} } if ($state{rePrefix} eq "s") { $self->{TOKEN} = "\""; # } elsif ($state{rePrefix} eq "tr") { # $self->{TOKEN} = "\"["; } else { $self->{TOKEN} = "\""; } } else { $state{inRegExp} = -1; } } elsif ($self->{RE_STATE} eq "RE_END") { print STDERR "End of regexp found.\n" if ($localDebug); # Defaults. my $multiline = 0; my $insensitive = 0; my $global = 0; my $complement = 0; my $delete = 0; my $nondestructive = 0; my $squash = 0; my $next = $self->{NEXT}; if ($next && $next->{TOKEN} =~ /^[siogmcdr]*$/) { my $re_trailer = $next->{TOKEN}; if ($re_trailer =~ /s/) { $multiline = 0; # for 's' $squash = 1; # for 'tr' } elsif ($re_trailer =~ /m/) { $multiline = 1; } if ($re_trailer =~ /g/) { $global = 1; } if ($re_trailer =~ /i/) { $insensitive = 1; } if ($re_trailer =~ /c/) { $complement = 1; } if ($re_trailer =~ /d/) { $delete = 1; } if ($re_trailer =~ /r/) { $nondestructive = 1; } $next->{TOKEN} = ""; } if ($state{inRegExp} == 1) { $self->{TOKEN} = "\", $multiline, $insensitive, NULL)"; } elsif ($state{inRegExp} == 2) { # Leave parentheses open. $self->{TOKEN} = "\""; my $next = $self->nextNSToken(); print STDERR "THISNEXT: ".$next->{TOKEN}."\n" if ($localDebug); if (!$next) { die("Malformed split\n"); } # $next should be the comma after the regular expression. $next = $next->nextNSToken(); if ($next->{TOKEN} eq "\$") { $next->{TOKEN} = ""; $next->{NEXT}->{TOKEN} = "\$".$next->{NEXT}->{TOKEN}; $next = $next->{NEXT}; } # $next should be the data to split. my $argNode = HeaderDoc::ParseTree->new(); $argNode->{NEXT} = $next->{NEXT}; $next->{NEXT} = $argNode; $argNode->{TOKEN} = ", $multiline, $insensitive"; my $afternext = $argNode->nextNSToken(); print STDERR "AN: ".$afternext->{TOKEN}."\n" if ($localDebug); if (!$afternext || ($afternext->{TOKEN} ne ",")) { $argNode->{TOKEN} .= ", 0"; } } elsif ($state{inRegExp} == 3) { if ($state{rePrefix} eq "s") { $self->{TOKEN} = "\", $multiline, $insensitive, $global)"; # } elsif ($state{rePrefix} eq "tr") { # $self->{TOKEN} = "]\""; } else { die("Unknown RE prefix \"".$state{rePrefix}."\" [1]\n"); } } elsif ($state{inRegExp} == 4) { if ($state{rePrefix} eq "tr") { # Allowed: "s", but not "m", not "i", not "g" $self->{TOKEN} = "\", $squash, $complement, $delete)"; if ($nondestructive) { $state{prevTokenNode}->{TOKEN} = "regexptr(".$state{regexpName}.", NULL, "; } } else { die("Unknown RE prefix \"".$state{rePrefix}."\" [2]\n"); } } $state{inRegExp} = 0; $state{rePrefix} = ""; } elsif ($state{inRegExp}) { $self->{TOKEN} =~ s/\\/\\\\/g; } if ($state{inComment} && $self->{TOKEN} =~ /[\n\r]/) { print STDERR "Leaving comment.\n" if ($localDebug); $state{inComment} = 0; } print STDERR "Checking token.\n" if ($localDebug); if ($self->{TOKEN} =~ /^\$[a-zA-Z0-9_]+$/) { print STDERR "setting PTN\n" if ($localDebug); $state{prevTokenNode} = $self; } if ($state{captureIf}) { print STDERR "APPEND ".$self->{TOKEN}."\n" if ($localDebug); $state{capturedText} .= $self->{TOKEN}; $self->{TOKEN} = ""; } my $resetSplit = 0; if ($state{inSplit} == 2) { $resetSplit = 1; $state{inSplit} = 3; } if ($self->{FIRSTCHILD}) { print STDERR "Processing children.\n" if ($localDebug); my $tempstateref = $self->{FIRSTCHILD}->translateTreeRec(\%state); my %tempstate = %{$tempstateref}; if ($state{inComment} && $tempstate{inHDComment}) { $state{inHDComment} = 1; } if ($state{captureIf}) { $state{capturedText} = $tempstate{capturedText}; } $state{seenCode} = $tempstate{seenCode}; $state{prevTokenNode} = $tempstate{prevtoken}; } if ($resetSplit) { $state{inSplit} = 0; print STDERR "inSplit -> 0\n" if ($localDebug) }; if ($enteringComment) { $state{inComment} = 0; $state{seenCode} = 0; } if ($enteringString) { $state{inString} = 0; } if ($enteringSingle) { $state{inSingle} = 0; } if ($leavingHDComment) { $state{inHDComment} = 0; $state{seenCode} = 0; } if ($self->{NEXT}) { print STDERR "Processing next.\n" if ($localDebug); $stateref = $self->{NEXT}->translateTreeRec(\%state); %state = %{$stateref}; } print STDERR "Returning.\n" if ($localDebug); return \%state; } # /*! # @abstract Returns the first token inside the body of an AppleScript # function. # */ sub ASFunctionBodyStart { my $self = shift; my $localDebug = 0; while ($self && ($self->token() ne "\n") && ($self->token() ne "\r") && ($self->token() ne "(")) { print STDERR "SKIPPING ".$self->token()." (LOOP 1)\n" if ($localDebug); $self = $self->next(); } while ($self && ($self->token() eq "\n") || ($self->token() eq "\r") || ($self->token() eq "(")) { print STDERR "SKIPPING ".$self->token()." (LOOP 2)\n" if ($localDebug); $self = $self->next(); } print STDERR "RETURNING TREE.\n" if ($localDebug); $self->dbprint() if ($localDebug); return $self; } 1;