summaryrefslogtreecommitdiffstats
path: root/Source/cmCableClassSet.cxx
diff options
context:
space:
mode:
authorBrad King <brad.king@kitware.com>2001-07-26 15:07:18 (GMT)
committerBrad King <brad.king@kitware.com>2001-07-26 15:07:18 (GMT)
commit6d54c3d6f8b03b137368c5868454f727ef707ceb (patch)
tree44496e16a8d409de5b9bc7999e92a7a53f67ee31 /Source/cmCableClassSet.cxx
parentcb858f511ed262977eac99b5a3800f549702ec62 (diff)
downloadCMake-6d54c3d6f8b03b137368c5868454f727ef707ceb.zip
CMake-6d54c3d6f8b03b137368c5868454f727ef707ceb.tar.gz
CMake-6d54c3d6f8b03b137368c5868454f727ef707ceb.tar.bz2
ENH: Added cable class-set expansion and tagging for alternate name generation. This should make the generated wrappers much easier to setup and use.
Diffstat (limited to 'Source/cmCableClassSet.cxx')
-rw-r--r--Source/cmCableClassSet.cxx646
1 files changed, 599 insertions, 47 deletions
diff --git a/Source/cmCableClassSet.cxx b/Source/cmCableClassSet.cxx
index 85dc4b4..ed56ad6 100644
--- a/Source/cmCableClassSet.cxx
+++ b/Source/cmCableClassSet.cxx
@@ -63,10 +63,23 @@ void cmCableClass::AddSource(const char* 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.
*/
void cmCableClassSet::AddClass(const char* name,
- const cmCableClass& cableClass)
+ cmCableClass* cableClass)
{
m_CableClassMap.insert(CableClassMap::value_type(name, cableClass));
}
@@ -81,10 +94,11 @@ void cmCableClassSet::AddSource(const char* name)
for(CableClassMap::iterator c = m_CableClassMap.begin();
c != m_CableClassMap.end(); ++c)
{
- c->second.AddSource(name);
+ c->second->AddSource(name);
}
}
+
/**
* Get the size of the internal CableClassMap used to store the set.
*/
@@ -113,100 +127,638 @@ 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();
+
+private:
+ /**
+ * Represent a substitution.
+ */
+ class Substitution
+ {
+ public:
+ Substitution() {}
+ void Bind(const std::string& in_code, const cmCableClass* in_class)
+ {
+ m_Code = in_code;
+ m_Class = in_class;
+ }
+ const cmCableClass* GetClass() const
+ { return m_Class; }
+ const std::string& GetCode() const
+ { return m_Code; }
+
+ private:
+ /**
+ * The cmCableClass associated with this substitution.
+ */
+ const cmCableClass* m_Class;
+
+ /**
+ * The code to be used for the substitution.
+ */
+ std::string 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 std::string 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 std::string& in_code): m_Code(in_code) {}
+ virtual std::string GetCode() const
+ { return m_Code; }
+ virtual const cmCableClass* GetClass() const
+ { return NULL; }
+ virtual ~StringPortion() {}
+ private:
+ /**
+ * Hold this Portion's contribution to the output string.
+ */
+ std::string 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: public Portion
+ {
+ public:
+ ReplacePortion(const Substitution& in_substitution):
+ m_Substitution(in_substitution) {}
+ virtual std::string 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.
+ */
+ std::string m_ClassName;
+
+ /**
+ * The tag name parsed out or generated for this element.
+ */
+ std::string 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();
+ std::string ParseSetName(std::string::const_iterator&,
+ std::string::const_iterator) const;
+ void FindTagSource();
+ bool GenerateTag(const std::string&);
+};
+
+
+/**
+ * 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.
+ std::string tag = m_Tag;
+ std::string 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 != 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);
+ }
+ }
+}
+
+
/**
- * Parse the given string to extract the class information specified.
+ * 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 cmCableClassSet::ParseAndAddElement(const char* element,
- cmMakefile* makefile)
+void
+ElementCombinationGenerator
+::ParseInputElement(const char* in_element)
{
// A regular expression to match the tagged element specification.
- cmRegularExpression tagGiven("^([A-Za-z_0-9]*)[ \t]*:[ \t]*([^:].*|::.*)$");
+ cmRegularExpression taggedElement =
+ "^([A-Za-z_0-9]*)[ \t]*:[ \t]*([^:].*|::.*)$";
// A regular expression to match the element when more source files are given.
cmRegularExpression sourcesRemain("^([^;]*);(.*)$");
-
- std::string tag;
+
std::string elementWithoutTag;
- std::string className;
std::string sourceString;
-
- if(tagGiven.find(element))
+ 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.
- tag = tagGiven.match(1);
- elementWithoutTag = tagGiven.match(2);
+ tagGiven = true;
+ m_Tag = taggedElement.match(1);
+ elementWithoutTag = taggedElement.match(2);
}
else
{
- // No tag was given. Try to generate one.
- //if(!this->GenerateTag(element, tag))
- // { return false; }
- elementWithoutTag = element;
+ // No tag was given. We will try to generate it later.
+ elementWithoutTag = in_element;
}
-
+
+ // Separate the class name.
if(sourcesRemain.find(elementWithoutTag.c_str()))
{
- className = sourcesRemain.match(1);
+ m_ClassName = sourcesRemain.match(1);
sourceString = sourcesRemain.match(2);
}
else
{
- className = elementWithoutTag;
+ m_ClassName = elementWithoutTag;
}
- cmCableClass::Sources sources;
-
+ // Find any source files specified with the ";source" syntax.
while(sourcesRemain.find(sourceString.c_str()))
{
- sources.insert(sourcesRemain.match(1));
+ m_Sources.insert(sourcesRemain.match(1));
sourceString = sourcesRemain.match(2);
}
if(sourceString != "")
{
- sources.insert(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.");
+ }
}
- // A regular expression to match a class name that is just a set.
- cmRegularExpression setDereference("^\\$(.*)$");
- if(setDereference.find(className))
+ // 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).
+ std::string currentPortion = "";
+ for(std::string::const_iterator c=m_ClassName.begin();
+ c != m_ClassName.end(); ++c)
{
- std::string setName = setDereference.match(1);
- cmData* d = makefile->LookupData(setName.c_str());
- // This should be a dynamic_cast, but we don't want to require RTTI.
- cmCableClassSet* classSet = static_cast<cmCableClassSet*>(d);
- if(classSet)
+ // Look for the '$' to mark the beginning of a token.
+ if(*c != '$')
{
- this->AddCableClassSet(*classSet, sources);
+ currentPortion.insert(currentPortion.end(), *c);
}
else
{
- cmSystemTools::Error("Unknown CABLE class set ", setName.c_str());
+ // 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.
+ std::string 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;
}
}
- else
+
+ // If there is a final portion of the string, record it.
+ if(currentPortion.length() > 0)
{
- cmCableClass cableClass;
- cableClass.AddSources(sources);
- this->AddClass(className.c_str(), cableClass);
+ m_Portions.push_back(new StringPortion(currentPortion));
}
}
/**
- * Add all elements from the given cmCableClassSet to this set, with the given
- * sources added to each element.
+ * 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.
*/
-void cmCableClassSet::AddCableClassSet(const cmCableClassSet& set,
- const cmCableClass::Sources& sources)
+std::string
+ElementCombinationGenerator
+::ParseSetName(std::string::const_iterator& c, std::string::const_iterator end) const
{
- for(CableClassMap::const_iterator c = set.Begin(); c != set.End(); ++c)
+ std::string 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 == '('))
{
- cmCableClass cableClass = c->second;
- cableClass.AddSources(sources);
- this->AddClass(c->first.c_str(), cableClass);
+ 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)
+ {
+ std::string 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 std::string& 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(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();
+}
+