HashObject.pm   [plain text]


#! /usr/bin/perl -w
#
# Class name: HashObject
# Synopsis: Class for a tree of CPP hashes
#
# Last Updated: $Date: 2011/02/18 19:02:58 $
#
# Copyright (c) 1999-2008 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
#         <code>HashObject</code> class package file.
#     @discussion
#         This file contains the <code>HashObject</code> class, a class
#         for nodes in a tree of CPP hashes.
#
#         For details, see the class documentation below.
#     @indexgroup HeaderDoc Miscellaneous Helpers
#  */

# /*!
#     @abstract
#         Stores C preprocessor hashes.
#     @discussion
#         Each instance of the <code>HashObject</code> class stores a
#         CPP hash pair for use in handling trees of
#         <code>#if/#else/#elif/#endif</code> statements.
#
#         The purpose of this module is not entirely obvious until
#         you see an example.  Consider the following code:
#
#         <pre>
#         #if BLAH
#             #define function_1 function_2
#         #else
#             void function_1(int arg);
#         #endif
#         </pre>
#
#         Normally, with a C preprocessor, either the <code>#if</code>
#         or <code>#else</code> side of this CPP directive is parsed,
#         but not both.
#
#         With HeaderDoc's C preprocessor, most of the time, HeaderDoc
#         cannot know which version to include.  Thus, it errs on the
#         side of completeness and includes both.  (HeaderDoc does,
#         however, include only one side if you pass a <code>-D</code>
#         or <code>-U</code> flag on the command line or if you provide a
#         HeaderDoc comment for a definition of <code>BLAH</code>
#         earlier in the header or in any header that it includes.)
#
#         Thus, without this module, the macro definition inside the
#         <code>#if</code> side would rewrite the code inside the
#         <code>#else</code> side, causing it to be parsed as:
#
#         <pre>
#             void function_2(int arg);
#         </pre>
#
#         This is clearly not correct.  This module fixes that problem
#         by allowing the entire set of currently known C preprocessor macros
#         to be stored in a tree structure and later restored while parsing
#         these <code>#if/#else/#elif/#endif</code> directives.
#
#         Whenever the parser encounters any C preprocessor directive that this
#         code cares about, the parser calls its helper function
#         {@link //apple_ref/perl/instm/HeaderDoc::BlockParse/cppHashMerge//() cppHashMerge},
#         where the control logic for this module actually appears.
#
#         If the parser encounters a <code>#if</code> directive, that function
#         stores the current C preprocessor macro set in the current tree node,
#         then calls {@link cppHashNodeNewChild}.  This function creates
#         a new, nested chain containing a single entry (the <code>#if</code>
#         node).
#
#         If the parser encounters a <code>#else</code>, <code>#elif</code>, or
#         <code>#endif</code> directive, that function similarly stores the
#         C preprocessor macro set in the current node (in this case, the
#         <code>#if</code> node).
#
#         Next, if the directive was a <code>#else</code> or <code>#elif</code>
#         directive, it also calls {@link cppHashNodeNewSibling} to create a new sibling
#         and {@link cppHashNodeResetToParent} to obtain the parent's C preprocessor
#         macro set for restoration by the parser (effectively undoing the result
#         of the <code>#if</code> clause).
#
#         If the directive was the closing <code>#endif</code> directive, after
#         storing the macro set, the parser calls {@link cppHashNodePop}.  This
#         function pops the entire <code>#if</code> chain off the tree, then merges
#         the macro sets from all of the chains together.  The result is that the
#         contents of the <code>#if</code> clause do not alter the contents of
#         the <code>#else</code> clause, but become active upon reaching the
#         terminating <code>#endif</code> clause.  In cases of conflicting
#         definitions of symbols, the first definition wins.
#
#     @var DEBUGNAME
#         A name for the hash object used for debugging purposes.
#         This is set to the contents of the second parameter to
#         {@link cppHashNodeNewChild}.
#
#     @var CPPHASH
#         The CPP name hash associated with this hash object.
#
#     @var CPPARGHASH
#         The CPP argument hash associated with this hash object.
#
#  */

package HeaderDoc::HashObject;

# use HeaderDoc::Utilities qw();
# use HeaderDoc::HeaderElement;

use Carp qw(cluck);
# @ISA = qw( HeaderDoc::HeaderElement );

use strict;
use vars qw($VERSION @ISA);

# /*!
#     @abstract
#         The revision control revision number for this module.
#     @discussion
#         In the git repository, contains the number of seconds since
#         January 1, 1970.
#  */
$HeaderDoc::HashObject::VERSION = '$Revision: 1298084578 $';

my $hashNodeDebug = 0;

# /*!
#     @abstract
#         Creates a new <code>HashObject</code> object.
#     @param param
#         A reference to the relevant package object (e.g.
#         <code>HeaderDoc::MinorAPIElement->new()</code> to allocate
#         a new instance of this class).
#  */
sub new {
    my($param) = shift;
    my($class) = ref($param) || $param;
    my $self = {};
    
    print STDERR "new CPP Hash node($param)\n" if ($hashNodeDebug);
    # cluck("Created $self\n");

    bless($self, $class);
    $self->_initialize();
    return ($self);
}

# /*!
#     @abstract
#         Initializes an instance of a MinorAPIElement object.
#     @param self
#         The object to initialize.
#  */
sub _initialize {
    my($self) = shift;
    $self->{DEBUGNAME} = "";
    $self->{PARENT} = undef;
    $self->{FIRSTCHILD} = undef;
    $self->{NEXT} = undef;
    $self->{CPPHASH} = undef;
    $self->{CPPARGHASH} = undef;
}

# /*!
#     @abstract
#         Pops a CPP hash node chain off the tree.
#     @param self
#         Any node in the topmost chain on the tree.
#     @discussion
#         This function is called at the end of a
#         <code>#if ... #else ... #elif ... #endif</code>
#         grouping.  It pops the top chain off the tree,
#         then returns the union of the <code>#define</code>
#         declarations from each part of the group.
#
#         For example, if you have a <code>#if</code>, a
#         <code>#define</code>, a <code>#else</code>, a
#         <code>#define</code>, and a <code>#endif</code>,
#         this pops the <code>#if</code> chain off of the
#         tree and returns both of the two <code>#define</code>
#         declarations if they do not conflict.  If they
#         conflict, it returns the first declaration
#         encountered.
#  */
sub cppHashNodePop
{
    my $self = shift;
    my $parent = $self->{PARENT};

    print STDERR "cppHashNodePop($self)\n" if ($hashNodeDebug);

    if (!$parent) {
	cluck("backtrace:\n");
	die("cppHashNodePop called on top of tree!\n");
    }

    my $childnode = $parent->{FIRSTCHILD};
    $parent->{FIRSTCHILD} = undef;

    my %newhash = ();
    my %newarghash = ();

    while ($childnode) {
	# print STDERR "MERGING IN $childnode (".$childnode->{DEBUGNAME}.")\n";

	my $childcpphashref = $childnode->{CPPHASH};
	my $childcpparghashref = $childnode->{CPPARGHASH};

	my ($mergedhashref, $mergedarghashref) = cppHashNodeMergeHashes(
		\%newhash,\%newarghash,
		$childcpphashref, $childcpparghashref);

	%newhash = %{$mergedhashref};
	%newarghash = %{$mergedarghashref};

	$childnode = $childnode->{NEXT};
    }
    $parent->{CPPHASH} = \%newhash;
    $parent->{CPPARGHASH} = \%newarghash;

    # print "CPPHASHREF: $cpphashref\n";

    return ($parent, $parent->{CPPHASH}, $parent->{CPPARGHASH});
}

# /*!
#     @abstract
#         Creates a CPP hash tree node as a sibling of another node
#     @param self
#         The last node in the topmost chain.
#     @param debugname
#         A name used when printing the object for debugging.
#  */
sub cppHashNodeNewSibling
{
    my $self = shift;
    my $debugname = shift;

    my $parent = $self->{PARENT};

    print STDERR "cppHashNodeNewSibling($self, $debugname)\n" if ($hashNodeDebug);

	# cluck("SELF: $self PARENT: $parent\n");

    return $parent->cppHashNodeNewChild($debugname)
}

# /*!
#     @abstract
#         Creates a CPP hash tree node as a child of another node
#     @param self
#         The last node in the topmost chain.
#     @param debugname
#         A name used when printing the object for debugging.
#  */
sub cppHashNodeNewChild
{
    my $self = shift;
    my $debugname = shift;

    print STDERR "cppHashNodeNewChild($self, $debugname)\n" if ($hashNodeDebug);

    my $newchild = HeaderDoc::HashObject->new();

    $newchild->{PARENT} = $self;
    $newchild->{DEBUGNAME} = $debugname;

    my $lastchild = $self->cppHashNodeLastChild();
    if ($lastchild) {
	$lastchild->{NEXT} = $newchild;
    } else {
	$self->{FIRSTCHILD} = $newchild;
    }

    return $newchild;
}

# /*!
#     @abstract
#         Returns the last child of a CPP hash tree node.
#     @param self
#         The node whose child you are requesting.
#  */
sub cppHashNodeLastChild
{
    my $self = shift;

    print STDERR "cppHashNodeLastChild($self)\n" if ($hashNodeDebug);

    my $child = $self->{FIRSTCHILD};
    my $lastchild = $child;

    while ($child) {
	$lastchild = $child;
	$child = $child->{NEXT};
    }

    return $lastchild;
}

# /*!
#     @abstract
#         Stores a copy of C preprocessor macro sets
#         (hash and argument hash) into a <code>HashObject</code>
#         node.
#     @param self
#         The node to modify.
#     @param cpphashref
#         The C preprocessor name hash to store.
#     @param cpparghashref
#         The C preprocessor argument hash to store.
#  */
sub cppHashNodeSetHashes
{
    my $self = shift;

    my $cpphashref = shift;
    my $cpparghashref = shift;

    print STDERR "cppHashNodeSetHashes($self, $cpphashref, $cpparghashref)\n" if ($hashNodeDebug);

    # print STDERR "Setting hashes on node $self to $cpphashref, $cpparghashref\n";

    my %hash = %{$cpphashref};
    my %copyhash = %hash;

    my %arghash = %{$cpparghashref};
    my %copyarghash = %arghash;

    $self->{CPPHASH} = \%copyhash;
    $self->{CPPARGHASH} = \%copyarghash;

    return ($self->{CPPHASH}, $self->{CPPARGHASH});
}

# /*!
#     @abstract
#         Returns the C preprocessor macro set
#         (hash and argument hash) from the parent of
#         the specified node.
#     @param self
#         The child of the node whose macro set
#         you wish to obtain.  This is usually the
#         current node being manipulated.
#  */
sub cppHashNodeResetToParent
{
    my $self = shift;

    print STDERR "cppHashNodeResetToParent($self)\n" if ($hashNodeDebug);

    my $parent = $self->{PARENT};

    # print STDERR "cppHashNodeResetToParent: NODE $self PARENT $parent\n";

    my %hash = %{$parent->{CPPHASH}};
    my %arghash = %{$parent->{CPPARGHASH}};

    return (\%hash, \%arghash);
}


# /*!
#     @abstract
#         Merges two C preprocessor macro sets, with
#         precedence given to the first.
#     @param hashref_1
#         A reference to the first CPP name hash.
#     @param arghashref_1
#         A reference to the first CPP argument hash.
#     @param hashref_2
#         A reference to the second CPP name hash.
#     @param arghashref_2
#         A reference to the second CPP argument hash.
#     @result
#         Returns an array containing a reference to the
#         combined name hash and a reference to the
#         combined argument hash.
#  */
sub cppHashNodeMergeHashes
{
    my $hashref_1 = shift;
    my $arghashref_1 = shift;
    my $hashref_2 = shift;
    my $arghashref_2 = shift;

    print STDERR "cppHashNodeMergeHashes($hashref_1, $arghashref_1, $hashref_2, $arghashref_2)\n" if ($hashNodeDebug);

    my %hash_1 = %{$hashref_1};
    my %arghash_1 = %{$arghashref_1};
    my %hash_2 = %{$hashref_2};
    my %arghash_2 = %{$arghashref_2};

    foreach my $val ( keys %hash_2 ) {
        if (!exists $hash_1{$val}) {
                $hash_1{$val} = $hash_2{$val};
        }
    }
    foreach my $val ( keys %arghash_2 ) {
        if (!exists $arghash_1{$val}) {
                $arghash_1{$val} = $arghash_2{$val};
        }
    }

    return (\%hash_1, \%arghash_1);
}

# /*!
#       @abstract
#           Prints a CPP <code>HashObject</code> tree for debugging.
#       @param self
#           The tree to print.
#  */
sub dbprint
{
    my $self = shift;
    my $indent = "";

    if (@_) {
	$indent = shift;
    } else {
	# cluck("Dumping hash tree.\n");
	print STDERR "Dumping hash tree.\n";
    }

    print $indent."\n";
    print $indent."-- NODE $self\n";
    print $indent."   |\n";
    print $indent."   |   DEBUGNAME:  ".$self->{DEBUGNAME}."\n";
    print $indent."   |   PARENT:     ".$self->{PARENT}."\n";
    print $indent."   |   FIRSTCHILD: ".$self->{FIRSTCHILD}."\n";
    print $indent."   |   NEXT:       ".$self->{NEXT}."\n";
    print $indent."   |   CPPHASH:    ".$self->{CPPHASH}."\n";
    print $indent."   |   CPPARGHASH: ".$self->{CPPARGHASH}."\n";
    print $indent."   |\n";

    my $fc = $self->{FIRSTCHILD};
    if ($fc) {
        my $newindent = $indent."   |";

	$fc->dbprint($newindent);
    }

    my $next = $self->{NEXT};
    if ($next) {
	$next->dbprint($indent);
    }
}


1;