CMake/Source/cmCableClassSet.cxx

753 lines
20 KiB
C++

/*=========================================================================
Program: Insight Segmentation & Registration Toolkit
Module: $RCSfile$
Language: C++
Date: $Date$
Version: $Revision$
Copyright (c) 2002 Insight Consortium. All rights reserved.
See ITKCopyright.txt or http://www.itk.org/HTML/Copyright.htm for details.
This software is distributed WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the above copyright notices for more information.
=========================================================================*/
#include "cmCableClassSet.h"
/**
* Add to the set of required sources to define the class.
*/
void cmCableClass::AddSources(const Sources& sources)
{
for(Sources::const_iterator s = sources.begin(); s != sources.end(); ++s)
{
m_Sources.insert(*s);
}
}
/**
* Add to the set of required sources to define the class.
*/
void cmCableClass::AddSource(const char* source)
{
m_Sources.insert(source);
}
/**
* The destructor frees all the cmCableClass instances in the set.
*/
cmCableClassSet::~cmCableClassSet()
{
for(CableClassMap::const_iterator i = m_CableClassMap.begin();
i != m_CableClassMap.end(); ++i)
{
delete i->second;
}
}
/**
* Add a class to the set.
* Automatically replace ">>" with "> >" to prevent template class name
* problems after replacements.
*/
void cmCableClassSet::AddClass(const char* in_name,
cmCableClass* cableClass)
{
cmStdString name = in_name;
for(cmStdString::size_type pos = name.find(">>");
pos != cmStdString::npos; pos = name.find(">>", pos+2))
{
name.replace(pos, 2, "> >");
}
m_CableClassMap.insert(CableClassMap::value_type(name, cableClass));
}
/**
* Add a source to every class in the set. This should only be done after
* all classes have been inserted.
*/
void cmCableClassSet::AddSource(const char* name)
{
for(CableClassMap::iterator c = m_CableClassMap.begin();
c != m_CableClassMap.end(); ++c)
{
c->second->AddSource(name);
}
}
/**
* Get the size of the internal CableClassMap used to store the set.
*/
unsigned int cmCableClassSet::Size() const
{
return m_CableClassMap.size();
}
/**
* Get a begin iterator to the internal CableClassMap used to store the
* set.
*/
cmCableClassSet::CableClassMap::const_iterator cmCableClassSet::Begin() const
{
return m_CableClassMap.begin();
}
/**
* Get an end iterator to the internal CableClassMap used to store the
* set.
*/
cmCableClassSet::CableClassMap::const_iterator cmCableClassSet::End() const
{
return m_CableClassMap.end();
}
/**
* A utility class to generate element combinations from all possible
* substitutions of set members into a $ token.
*/
class ElementCombinationGenerator
{
public:
ElementCombinationGenerator(const char* in_element, cmMakefile* in_makefile,
cmCableClassSet* out_set):
m_Makefile(in_makefile), m_OutputSet(out_set)
{
this->ParseInputElement(in_element);
}
~ElementCombinationGenerator();
void Generate();
public:
/**
* Represent a substitution.
*/
class Substitution
{
public:
Substitution() {}
void Bind(const cmStdString& in_code, const cmCableClass* in_class)
{
m_Code = in_code;
m_Class = in_class;
}
const cmCableClass* GetClass() const
{ return m_Class; }
const cmStdString& GetCode() const
{ return m_Code; }
private:
/**
* The cmCableClass associated with this substitution.
*/
const cmCableClass* m_Class;
/**
* The code to be used for the substitution.
*/
cmStdString m_Code;
};
/**
* Interface to the parts of an input string of code, possibly with
* $SomeSetName tokens in it. An indivitual Portion will be either
* a StringPortion, which has no substitutions, or a ReplacePortion,
* which has only a substitution, and no hard-coded text.
*
* This is used by cmCableClassSet::GenerateElementCombinations() to
* hold the pieces of a string after the set substitution tokens
* have been extracted.
*/
class Portion
{
public:
/**
* Get the C++ code corresponding to this Portion of a string.
*/
virtual cmStdString GetCode() const =0;
/**
* Get the class corresponding to this Portion of a string. This is NULL
* for StringPortion, and points to a cmCableClass for ReplacePortion.
*/
virtual const cmCableClass* GetClass() const
{ return NULL; }
virtual ~Portion() {}
};
/**
* Represent a hard-coded part of an input string, that has no substitutions
* in it. The tag for this part of a string is always empty.
*/
class StringPortion: public Portion
{
public:
StringPortion(const cmStdString& in_code): m_Code(in_code) {}
virtual cmStdString GetCode() const
{ return m_Code; }
virtual const cmCableClass* GetClass() const
{ return NULL; }
virtual ~StringPortion() {}
private:
/**
* Hold this Portion's contribution to the output string.
*/
cmStdString m_Code;
};
/**
* Represent the "$SomeSetName" portion of an input string. This has a
* reference to the Substitution holding the real output to generate.
*/
class ReplacePortion;
friend class ReplacePortion;
class ReplacePortion: public Portion
{
public:
ReplacePortion(const Substitution& in_substitution):
m_Substitution(in_substitution) {}
virtual cmStdString GetCode() const
{ return m_Substitution.GetCode(); }
virtual const cmCableClass* GetClass() const
{ return m_Substitution.GetClass(); }
virtual ~ReplacePortion() {}
private:
/**
* Refer to the real Substitution for this Portion's contribution.
*/
const Substitution& m_Substitution;
};
typedef std::list<Portion*> Portions;
typedef std::map<const cmCableClassSet*, Substitution*> Substitutions;
/**
* The makefile in which to lookup set names.
*/
cmMakefile* m_Makefile;
/**
* The cmCableClassSet instance to be filled with combinations.
*/
cmCableClassSet* m_OutputSet;
/**
* The class name parsed out for this element, before set expansion.
*/
cmStdString m_ClassName;
/**
* The tag name parsed out or generated for this element.
*/
cmStdString m_Tag;
/**
* The set of sources parsed out for this element.
*/
cmCableClass::Sources m_Sources;
/**
* The parts of the input string after parsing of the tokens.
*/
Portions m_Portions;
/**
* Map from substitution's Set to actual Substitution.
*/
Substitutions m_Substitutions;
private:
void Generate(Substitutions::const_iterator);
void ParseInputElement(const char*);
void SplitClassName();
cmStdString ParseSetName(cmStdString::const_iterator&,
cmStdString::const_iterator) const;
void FindTagSource();
bool GenerateTag(const cmStdString&);
};
/**
* Destructor frees portions and substitutions that were allocated by
* constructor.
*/
ElementCombinationGenerator
::~ElementCombinationGenerator()
{
// Free the string portions that were allocated.
for(Portions::iterator portion = m_Portions.begin();
portion != m_Portions.end(); ++portion)
{
delete *portion;
}
// Free the substitutions that were allocated.
for(Substitutions::iterator sub = m_Substitutions.begin();
sub != m_Substitutions.end(); ++sub)
{
delete sub->second;
}
}
/**
* Generate all element combinations possible with the set of
* substitutions available. The given output set is filled with
* all the combinations.
*/
void
ElementCombinationGenerator
::Generate()
{
// If there are no substitutions to be made, just generate this
// single combination.
if(m_Substitutions.empty())
{
cmCableClass* cableClass = new cmCableClass(m_Tag);
cableClass->AddSources(m_Sources);
m_OutputSet->AddClass(m_ClassName.c_str(), cableClass);
return;
}
// We must generate all combinations of substitutions.
// Begin the recursion with the first substitution.
this->Generate(m_Substitutions.begin());
}
/**
* Internal helper to Generate() which generates all
* combinations in a recursive, depth-first order.
*/
void
ElementCombinationGenerator
::Generate(Substitutions::const_iterator substitution)
{
// Test our position in the list of substitutions to be bound.
if(substitution == m_Substitutions.end())
{
// All substitutions have been prepared. Generate this combination.
cmStdString tag = m_Tag;
cmStdString code = "";
// The set of sources for the generated combination. It will
// always include the sources parsed from the original element
// string.
cmCableClass::Sources sources = m_Sources;
// Put together all the pieces, with substitutions.
for(Portions::const_iterator i = m_Portions.begin();
i != Portions::const_iterator(m_Portions.end()); ++i)
{
// See if there is a class associated with this portion.
const cmCableClass* curClassPortion = (*i)->GetClass();
if(curClassPortion)
{
// Append the tag from the class portion.
tag.append(curClassPortion->GetTag());
// Include any sources needed by the class in this combination's set.
for(cmCableClass::Sources::const_iterator
s = curClassPortion->SourcesBegin();
s != curClassPortion->SourcesEnd(); ++s)
{
sources.insert(*s);
}
}
// Append the portion's code to this combination's code.
code.append((*i)->GetCode());
}
// Add this combination to the output set.
cmCableClass* cableClass = new cmCableClass(tag);
cableClass->AddSources(sources);
m_OutputSet->AddClass(code.c_str(), cableClass);
}
else
{
// Get the set for this substitution.
const cmCableClassSet* set = substitution->first;
if(set == m_OutputSet)
{
// We cannot iterate over the set currently being defined.
cmSystemTools::Error("CABLE class set self-reference!");
return;
}
// Prepare an iterator to the next substitution.
Substitutions::const_iterator nextSubstitution = substitution;
++nextSubstitution;
// We must iterate over all possible values for this substitution.
for(cmCableClassSet::CableClassMap::const_iterator element = set->Begin();
element != set->End(); ++element)
{
// Bind the substitution to this element.
substitution->second->Bind(element->first, element->second);
// Move on to the next substitution.
this->Generate(nextSubstitution);
}
}
}
/**
* Called from constructor. Parses the given string to extract the
* class information specified.
*
* The format of the string is
* [tag:]class_name[;source1;source2;...]
*/
void
ElementCombinationGenerator
::ParseInputElement(const char* in_element)
{
// A regular expression to match the tagged element specification.
cmRegularExpression taggedElement =
"^([A-Za-z_0-9]*)[ \t]*:[ \t]*([^:].*|::.*)$";
// A regular expression to match the element when more source files are given.
cmRegularExpression sourcesRemain("^([^;]*);(.*)$");
cmStdString elementWithoutTag;
cmStdString sourceString;
bool tagGiven = false;
// See if the element was tagged, and if so, pull off the tag.
if(taggedElement.find(in_element))
{
// A tag was given. Use it.
tagGiven = true;
m_Tag = taggedElement.match(1);
elementWithoutTag = taggedElement.match(2);
}
else
{
// No tag was given. We will try to generate it later.
elementWithoutTag = in_element;
}
// Separate the class name.
if(sourcesRemain.find(elementWithoutTag.c_str()))
{
m_ClassName = sourcesRemain.match(1);
sourceString = sourcesRemain.match(2);
}
else
{
m_ClassName = elementWithoutTag;
}
// Find any source files specified with the ";source" syntax.
while(sourcesRemain.find(sourceString.c_str()))
{
m_Sources.insert(sourcesRemain.match(1));
sourceString = sourcesRemain.match(2);
}
if(sourceString != "")
{
m_Sources.insert(sourceString);
}
// If no tag was given, try to generate one.
if(!tagGiven)
{
if(!this->GenerateTag(m_ClassName))
{
cmSystemTools::Error("Cannot generate tag for class name: ",
m_ClassName.c_str(),
"\nPlease supply one with the \"tag:..\" syntax.");
}
}
// If there is a .h with the name of the tag, add it as a source.
this->FindTagSource();
// Split the class name up into portions for the combination
// generation method.
this->SplitClassName();
}
/**
* Parses the class name into portions. Plain text in the string is
* held by a StringPortion, and a $ token for replacement is
* represented by a ReplacePortion.
*/
void
ElementCombinationGenerator
::SplitClassName()
{
// Break the input code into blocks alternating between literal code and
// set-substitution tokens (like $SomeSetName).
cmStdString currentPortion = "";
for(cmStdString::const_iterator c=m_ClassName.begin();
c != m_ClassName.end(); ++c)
{
// Look for the '$' to mark the beginning of a token.
if(*c != '$')
{
currentPortion.insert(currentPortion.end(), *c);
}
else
{
// If there is a portion of the string, record it.
if(currentPortion.length() > 0)
{
m_Portions.push_back(new StringPortion(currentPortion));
currentPortion = "";
}
// Skip over the '$' character.
++c;
// Get element set name token.
cmStdString setName = this->ParseSetName(c, m_ClassName.end());
// We have a complete set name. Look it up in makefile's data
// collection.
cmData* d = m_Makefile->LookupData(setName.c_str());
// This should be a dynamic_cast, but we don't want to require RTTI.
cmCableClassSet* set = static_cast<cmCableClassSet*>(d);
if(set)
{
// We have a valid set name. Prepare the substitution entry
// for it.
Substitution* sub;
if(m_Substitutions.count(set) == 0)
{
sub = new Substitution();
m_Substitutions[set] = sub;
}
else
{
sub = m_Substitutions[set];
}
m_Portions.push_back(new ReplacePortion(*sub));
setName = "";
}
else
{
// Invalid set name. Complain.
cmSystemTools::Error("Unknown name of CABLE class set: ",
setName.c_str());
}
// Let the loop look at this character again.
--c;
}
}
// If there is a final portion of the string, record it.
if(currentPortion.length() > 0)
{
m_Portions.push_back(new StringPortion(currentPortion));
}
}
/**
* Parse out the name of a Set specified after a $ in the element's string.
* This is called with "c" pointing to the first character after the $,
* and "end" equal to the string's end iterator.
*
* Returns the set name after parsing. "c" will point to the first
* character after the end of the set name.
*/
cmStdString
ElementCombinationGenerator
::ParseSetName(cmStdString::const_iterator& c, cmStdString::const_iterator end) const
{
cmStdString setName = "";
// Check for the $(setName) syntax.
// If the first character after the '$' is a left paren, we scan for the
// matching paren, and take everything in-between as the set name.
if((c != end) && (*c == '('))
{
unsigned int depth = 1;
++c;
while(c != end)
{
char ch = *c++;
if(ch == '(') { ++depth; }
else if(ch == ')') { --depth; }
if(depth == 0) { break; }
setName.insert(setName.end(), ch);
}
return setName;
}
// The $(setName) syntax was not used.
// Look for all characters that can be part of a qualified C++
// identifier.
while(c != end)
{
char ch = *c;
if(((ch >= 'a') && (ch <= 'z'))
|| ((ch >= 'A') && (ch <= 'Z'))
|| ((ch >= '0') && (ch <= '9'))
|| (ch == '_') || (ch == ':'))
{
setName.insert(setName.end(), ch);
++c;
}
else
{
break;
}
}
return setName;
}
/**
* After the tag for an element has been determined, but before
* combination expansion is done, this is called to search for a
* header file in the makefile's include path with the name of the
* tag. This makes specifying lists of classes that are declared in
* header files with their own name very convenient.
*/
void ElementCombinationGenerator::FindTagSource()
{
// If there is no tag, don't bother with this step.
if(m_Tag == "")
{
return;
}
// Get the makefile's include path.
const std::vector<std::string>& includePath =
m_Makefile->GetIncludeDirectories();
// Search the path for a file called "(m_Tag).h".
for(std::vector<std::string>::const_iterator dir = includePath.begin();
dir != includePath.end(); ++dir)
{
cmStdString filePath = *dir;
m_Makefile->ExpandVariablesInString(filePath);
filePath += "/"+m_Tag+".h";
if(cmSystemTools::FileExists(filePath.c_str()))
{
m_Sources.insert(m_Tag+".h");
return;
}
}
}
/**
* Given the string representing a set element, automatically generate
* the element tag for it. This function determines how the output
* language of all CABLE-generated wrappers will look.
*/
bool ElementCombinationGenerator::GenerateTag(const cmStdString& element)
{
// Hold the regular expressions for matching against the element.
cmRegularExpression regex;
// If the element's code begins in a $, it is referring to a set name.
// The set's elements have their own tags, so we don't need one.
regex.compile("^[ \t]*\\$");
if(regex.find(element))
{ m_Tag = ""; return true; }
// Test for simple integer
regex.compile("^[ \t]*([0-9]*)[ \t]*$");
if(regex.find(element))
{
m_Tag = "_";
m_Tag.append(regex.match(1));
return true;
}
// Test for basic integer type
regex.compile("^[ \t]*(unsigned[ ]|signed[ ])?[ \t]*(char|short|int|long|long[ ]long)[ \t]*$");
if(regex.find(element))
{
m_Tag = "_";
if(regex.match(1) == "unsigned ")
{ m_Tag.append("u"); }
if(regex.match(2) == "long long")
{ m_Tag.append("llong"); }
else
{ m_Tag.append(regex.match(2)); }
return true;
}
// Test for basic floating-point type
regex.compile("^[ \t]*(long[ ])?[ \t]*(float|double)[ \t]*$");
if(regex.find(element))
{
m_Tag = "_";
if(static_cast<int>(regex.start(1)) > 0 && regex.match(1) == "long ")
{
m_Tag.append("l");
}
m_Tag.append(regex.match(2));
return true;
}
// Test for basic wide-character type
regex.compile("^[ \t]*(wchar_t)[ \t]*$");
if(regex.find(element))
{
m_Tag = "_wchar";
return true;
}
// Test for type name (possibly with template arguments).
regex.compile("^[ \t]*([A-Za-z_][A-Za-z0-9_]*)(<.*)?[ \t]*$");
if(regex.find(element))
{
// The tag is the same as the type. If there were template arguments,
// they are ignored since they may have their own tags.
m_Tag = regex.match(1);
return true;
}
// Test for a name with a single namespace qualifier.
regex.compile("^[ \t]*([A-Za-z_][A-Za-z0-9_]*)::([A-Za-z_][A-Za-z0-9_]*)(<.*)?[ \t]*$");
if(regex.find(element))
{
// The tag is the same as the namespace and type concatenated together.
m_Tag = regex.match(1);
m_Tag.append(regex.match(2));
return true;
}
// We can't generate a tag.
m_Tag = "";
return false;
}
/**
* Given an element in string form, parse out the information from it,
* generate the combinations of set substitutions, and add all the
* elements that result.
*/
void cmCableClassSet::ParseAndAddElement(const char* in_element,
cmMakefile* makefile)
{
// Create an object to handle the generation.
ElementCombinationGenerator combinationGenerator(in_element, makefile, this);
// Generate the combinations.
combinationGenerator.Generate();
}