#! /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
# HashObject class package file.
# @discussion
# This file contains the HashObject 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 HashObject class stores a
# CPP hash pair for use in handling trees of
# #if/#else/#elif/#endif statements.
#
# The purpose of this module is not entirely obvious until
# you see an example. Consider the following code:
#
#
# #if BLAH # #define function_1 function_2 # #else # void function_1(int arg); # #endif ## # Normally, with a C preprocessor, either the
#if
# or #else 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 -D
# or -U flag on the command line or if you provide a
# HeaderDoc comment for a definition of BLAH
# earlier in the header or in any header that it includes.)
#
# Thus, without this module, the macro definition inside the
# #if side would rewrite the code inside the
# #else side, causing it to be parsed as:
#
# # void function_2(int arg); ## # 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
#if/#else/#elif/#endif 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 #if 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 #if
# node).
#
# If the parser encounters a #else, #elif, or
# #endif directive, that function similarly stores the
# C preprocessor macro set in the current node (in this case, the
# #if node).
#
# Next, if the directive was a #else or #elif
# 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 #if clause).
#
# If the directive was the closing #endif directive, after
# storing the macro set, the parser calls {@link cppHashNodePop}. This
# function pops the entire #if chain off the tree, then merges
# the macro sets from all of the chains together. The result is that the
# contents of the #if clause do not alter the contents of
# the #else clause, but become active upon reaching the
# terminating #endif 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 HashObject object.
# @param param
# A reference to the relevant package object (e.g.
# HeaderDoc::MinorAPIElement->new() 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
# #if ... #else ... #elif ... #endif
# grouping. It pops the top chain off the tree,
# then returns the union of the #define
# declarations from each part of the group.
#
# For example, if you have a #if, a
# #define, a #else, a
# #define, and a #endif,
# this pops the #if chain off of the
# tree and returns both of the two #define
# 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 HashObject
# 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 HashObject 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;