diff options
Diffstat (limited to 'tools/mhmake/src/util.cpp')
-rw-r--r-- | tools/mhmake/src/util.cpp | 708 |
1 files changed, 708 insertions, 0 deletions
diff --git a/tools/mhmake/src/util.cpp b/tools/mhmake/src/util.cpp new file mode 100644 index 000000000..2e0b7dea6 --- /dev/null +++ b/tools/mhmake/src/util.cpp @@ -0,0 +1,708 @@ +/* This file is part of mhmake.
+ *
+ * Copyright (C) 2001-2009 Marc Haesen
+ *
+ * Mhmake is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Mhmake is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Mhmake. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* $Rev$ */
+
+#include "stdafx.h"
+
+#include "rule.h"
+#include "util.h"
+#include "mhmakeparser.h"
+
+static char s_UsageString[]=
+"\
+Usage: mhmake [-f <Makefile>] [-[c|C] <RunDir>] [<Var>=<Value>]\n\
+ [-a] [-q] [-s] [-v] [targets]+\n"
+#ifdef _DEBUG
+"\
+ [-p] [-n] [-e] [-l] [-w] [-d] [-CD] [-m] [-b]\n"
+#endif
+"\n\
+ <Makefile> : Makefile to load (if not specified 'makefile' is used\n\
+ <RunDir> : Make is setting the current directory to this directory at the\n\
+ start.\n\
+ <Var> : Defines a variable\n\
+ <Value> : Value of the variable\n\
+ -a : Rebuild all targets\n\
+ -s : Rescan automatic dependencies\n\
+ -v : Print version information\n\
+ -q : Quiet. Disable all output \n"
+#ifdef _DEBUG
+"\n\
+ The following options are additional options in mhmake_dbg which are not\n\
+ available in mhmake. These are mainly options for debugging purposes.\n\
+ -e : Dump Vars and Rules on error\n\
+ -w : Print additional information\n\
+ -p : Prints the variables and the rules before building\n\
+ -n : Only prints the commands, but does not execute them\n\
+ -l : Print parser debug information\n\
+ -d : Print the dependency checking\n\
+ -CD : Do circular dependency checking (targets depending on itself)\n\
+ -m : Create md5 database in md5.database in start directory. \n\
+ -b : Print build tree. \n\
+ -D : Print all double defined rules (even if commands are the same) \n\
+"
+#else
+"\
+\n\
+It is adviced during creation of makefiles to use mhmake_dbg. It has additional\n\
+debugging options and does some extra error checking.\n\
+For a description of the additional options: run mhmake_dbg -h\n\
+"
+#endif
+;
+
+#ifdef _DEBUG
+bool g_PrintVarsAndRules=false;
+bool g_DoNotExecute=false;
+bool g_BuildMd5Db=false;
+bool g_GenProjectTree=false;
+bool g_DumpOnError=false;
+bool g_PrintAdditionalInfo=false;
+bool g_pPrintDependencyCheck=false;
+bool g_CheckCircularDeps=false;
+bool g_PrintLexYacc=false;
+bool g_PrintMultipleDefinedRules=false;
+#endif
+
+bool g_Quiet=false;
+bool g_RebuildAll=false;
+bool g_ForceAutoDepRescan=false;
+
+const string g_EmptyString;
+const string g_SpaceString(" ");
+
+///////////////////////////////////////////////////////////////////////////////
+void PrintVersionInfo(void)
+{
+ static const char VersionStr[]="\
+mhmake : GNU compatible make tool with extensions\n\
+version: "MHMAKEVER"\n\
+Remarks and bug reports -> Marc Haesen\n\
+";
+ cerr << VersionStr;
+ exit(1);
+}
+///////////////////////////////////////////////////////////////////////////////
+makecommand::makecommand()
+{
+ char ExeName[MAX_PATH];
+#ifdef WIN32
+ GetModuleFileName(NULL,ExeName,sizeof(ExeName));
+ m_BuildCommand=ExeName;
+ transform(m_BuildCommand.begin(),m_BuildCommand.end(),m_BuildCommand.begin(),(int(__CDECL *)(int))tolower);
+#else
+ int NrChars=readlink ("/proc/self/exe", ExeName, sizeof(ExeName));
+ ExeName[NrChars]=0;
+ m_BuildCommand=ExeName;
+#endif
+}
+
+///////////////////////////////////////////////////////////////////////////////
+string Substitute(const string &ToSubst,const string &iSrcStr,const string &iToStr)
+{
+ string Ret=g_EmptyString;
+ matchres Res;
+ string SrcStr=iSrcStr;
+ string ToStr=iToStr;
+
+ if (string::npos==SrcStr.find('%'))
+ {
+ string PerStr("%");
+ SrcStr=PerStr+SrcStr;
+ ToStr=PerStr+ToStr;
+ }
+ const char *pTmp=ToSubst.c_str();
+ bool first=true;
+ while (*pTmp)
+ {
+ if (!first)
+ {
+ Ret+=g_SpaceString;
+ }
+ else
+ {
+ first=false;
+ }
+ string Item;
+ pTmp=NextItem(pTmp,Item);
+
+ if (PercentMatch(Item,SrcStr,&Res))
+ {
+ Ret+=ReplaceWithStem(ToStr,Res.m_Stem);
+ }
+ else
+ {
+ Ret+=Item;
+ }
+ }
+
+ return Ret;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+bool PercentMatch(const string &String,const string &Expr,matchres *pRes,const char Char)
+{
+ const char *pFirst=String.c_str();
+ const char *pFirstExpr=Expr.c_str();
+ while (*pFirstExpr && *pFirstExpr!=Char)
+ {
+ if (*pFirst!=*pFirstExpr)
+ return false;
+ pFirst++;
+ pFirstExpr++;
+ }
+
+ if (!*pFirstExpr)
+ {
+ if (!*pFirst)
+ {
+ if (pRes)
+ {
+ pRes->m_First=String;
+ pRes->m_Stem=pRes->m_Last=g_EmptyString;
+ }
+ return true;
+ } else
+ return false;
+ }
+ else if (!*pFirst)
+ return false;
+
+ const char *pEnd=pFirst+strlen(pFirst);
+
+ const char *pLast=pEnd;
+ const char *pLastExpr=pFirstExpr+strlen(pFirstExpr)-1;
+ if (pLastExpr!=pFirstExpr)
+ {
+ pLast--;
+
+ while (pLastExpr>pFirstExpr)
+ {
+ if (*pLastExpr!=*pLast)
+ return false;
+ pLastExpr--;
+ pLast--;
+ }
+ pLast++;
+ }
+ string Stem=string(pFirst,pLast-pFirst);
+
+ if (pRes)
+ {
+ pRes->m_First=string(String.c_str(),pFirst-String.c_str());
+
+ pRes->m_Stem=Stem;
+
+ pRes->m_Last=string(pLast,pEnd-pLast);
+ }
+ return true;
+}
+///////////////////////////////////////////////////////////////////////////////
+bool PercentMatchList(const string &String,const string &ExprList,matchres *pRes)
+{
+ const char *pTmp=ExprList.c_str();
+ while (*pTmp)
+ {
+ string Expr;
+ pTmp=NextItem(pTmp,Expr);
+ if (PercentMatch(String,Expr,pRes))
+ return true;
+ }
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+string ReplaceWithStem(const string &String,const string &Stem)
+{
+ string Ret=String;
+ int Pos=Ret.find('%');
+ while (Pos!=string::npos)
+ {
+ Ret=Ret.substr(0,Pos)+Stem+Ret.substr(Pos+1);
+ Pos=Ret.find('%');
+ }
+ return Ret;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+refptr<loadedmakefile> LOADEDMAKEFILES::find(const loadedmakefile &ToSearch)
+{
+ vector<refptr<loadedmakefile> >::const_iterator It=begin();
+ while (It!=end())
+ {
+ if (*(*It)==ToSearch)
+ return *It;
+ It++;
+ }
+ return refptr<loadedmakefile>();
+}
+
+LOADEDMAKEFILES g_LoadedMakefiles;
+
+///////////////////////////////////////////////////////////////////////////////
+loadedmakefile::loadedmakefile_statics::loadedmakefile_statics()
+{
+ m_GlobalCommandLineVars[MAKE]=g_MakeCommand;
+ const char *pEnv=getenv(MHMAKECONF);
+ if (pEnv)
+ {
+ m_GlobalCommandLineVars[MHMAKECONF]=pEnv;
+ m_MhMakeConf=GetFileInfo(pEnv);
+
+ // Get the revision of the working copy
+ // We do it the fastest way: this means just parsing the entries file in the .svn directory of the mhmakeconf directory
+ string EntriesFile=m_MhMakeConf->GetFullFileName()+OSPATHSEPSTR".svn"OSPATHSEPSTR"entries";
+ FILE *pFile=fopen(EntriesFile.c_str(),"r");
+ if (!pFile)
+ {
+ /* Entries file cannot be opened. Maybe it is not an svn working copy, so ignore it */
+ #ifdef _DEBUG
+ if (g_PrintAdditionalInfo) cout<<"Warning: Assuming no subversion working copy: Error opening "<<EntriesFile<<endl;
+ #endif
+ return;
+ }
+
+ /* Check the format of this file, if it is 8 it is the new format else it is the old format. We do
+ * this by inspecting the first line of the file
+ */
+ char Line[255];
+ if (fgets(Line,sizeof(Line),pFile))
+ {
+ int iVersion=atoi(Line);
+ if (iVersion<8)
+ {
+ // This is the old format
+ while (fgets(Line,sizeof(Line),pFile))
+ {
+ if (!strncmp(Line," url=\"",8))
+ {
+ char *pUrl=Line+8+7;
+ char *pEnd=strchr(pUrl,'"');
+ #ifdef _DEBUG
+ if (!pEnd)
+ {
+ cerr<<"Format of entries file in .svn directory must have been changed. Update mhmake!\n";
+ exit(1);
+ }
+ #endif
+ *pEnd='\0';
+ m_GlobalCommandLineVars[WC_URL]=pUrl;
+ }
+ if (!strncmp(Line," revision=\"",13))
+ {
+ char *pRevision=Line+13;
+ char *pEnd=strchr(pRevision,'"');
+ #ifdef _DEBUG
+ if (!pEnd)
+ {
+ cerr<<"Format of entries file in .svn directory must have been changed. Update mhmake!\n";
+ exit(1);
+ }
+ #endif
+ *pEnd='\0';
+ m_GlobalCommandLineVars[WC_REVISION]=pRevision;
+ break;
+ }
+ }
+ }
+ else
+ {
+ /* This is the new format */
+ fgets(Line,sizeof(Line),pFile);
+ fgets(Line,sizeof(Line),pFile);
+ if (fgets(Line,sizeof(Line),pFile))
+ {
+ Line[strlen(Line)-1]=0;
+ m_GlobalCommandLineVars[WC_REVISION]=Line;
+ }
+ if (fgets(Line,sizeof(Line),pFile))
+ {
+ Line[strlen(Line)-1]=0;
+ m_GlobalCommandLineVars[WC_URL]=Line+7;
+ }
+ }
+ }
+ fclose(pFile);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+loadedmakefile::loadedmakefile(vector<string> &Args,const string&Makefile)
+{
+ m_CommandLineVars=sm_Statics.m_GlobalCommandLineVars;
+
+ m_MakeDir=NullFileInfo;
+ vector<string>::iterator ArgIt=Args.begin();
+ while (ArgIt!=Args.end())
+ {
+ int EqPos=ArgIt->find('=');
+ if (EqPos!=string::npos)
+ {
+ string Var=ArgIt->substr(0,EqPos);
+ string Val=ArgIt->substr(EqPos+1);
+ m_CommandLineVars[Var]=Val;
+ }
+ else if ((*ArgIt)[0]=='-')
+ {
+ switch ((*ArgIt)[1])
+ {
+ case 'f':
+ if (ArgIt->size()>2)
+ {
+ if (!m_MakeDir)
+ {
+ m_Makefile=GetFileInfo(ArgIt->substr(2));
+ }
+ else
+ {
+ m_Makefile=GetFileInfo(ArgIt->substr(2),m_MakeDir);
+ }
+ }
+ else
+ {
+ ArgIt++;
+ if (!m_MakeDir)
+ {
+ m_Makefile=GetFileInfo(*ArgIt);
+ }
+ else
+ {
+ m_Makefile=GetFileInfo(*ArgIt,m_MakeDir);
+ }
+ }
+ break;
+ case 'C':
+#ifdef _DEBUG
+ if ((*ArgIt)[2]=='D')
+ {
+ g_CheckCircularDeps=true;
+ break;
+ }
+#endif
+ /* Fall through */
+ case 'c':
+ if (ArgIt->size()>2)
+ m_MakeDir=GetFileInfo(ArgIt->substr(2));
+ else
+ {
+ ArgIt++;
+ m_MakeDir=GetFileInfo(*ArgIt);
+ }
+ break;
+ case 'a':
+ g_RebuildAll=true;
+ break;
+ case 'q':
+ g_Quiet=true;
+ break;
+ case 's':
+ g_ForceAutoDepRescan=true;
+ break;
+ case 'v':
+ PrintVersionInfo();
+ break;
+#ifdef _DEBUG
+ case 'p':
+ g_PrintVarsAndRules=true;
+ break;
+ case 'n':
+ g_DoNotExecute=true;
+ break;
+ case 'w':
+ g_PrintAdditionalInfo=true;
+ break;
+ case 'd':
+ g_pPrintDependencyCheck=true;
+ break;
+ case 'D':
+ g_PrintMultipleDefinedRules=true;
+ break;
+ case 'l':
+ g_PrintLexYacc=true;
+ break;
+ case 'e':
+ g_DumpOnError=true;
+ break;
+ case 'm':
+ g_BuildMd5Db=true;
+ break;
+ case 'b':
+ g_GenProjectTree=true;
+ break;
+#endif
+ default:
+ cerr << "\nUnknown option: "<<*ArgIt<<endl<<endl;
+ cerr << s_UsageString;
+ throw(1);
+ }
+ }
+ else
+ {
+ m_CommandLineTargets.push_back(*ArgIt);
+ }
+ ArgIt++;
+ }
+ if (!m_Makefile)
+ {
+ if (!Makefile.empty())
+ {
+ if (!m_MakeDir)
+ m_Makefile=GetFileInfo(Makefile);
+ else
+ m_Makefile=GetFileInfo(Makefile,m_MakeDir);
+ }
+ }
+ if (!m_Makefile)
+ {
+ if (!m_MakeDir)
+ m_Makefile=GetFileInfo(m_CommandLineTargets[0]);
+ else
+ m_Makefile=GetFileInfo(m_CommandLineTargets[0],m_MakeDir);
+
+ m_CommandLineTargets.erase(m_CommandLineTargets.begin());
+ }
+ if (!m_MakeDir)
+ {
+ if (Makefile==g_EmptyString)
+ m_MakeDir=m_Makefile->GetDir(); /* This is one from load_makefile, so we take the directory of the makefile itself. */
+ else
+ m_MakeDir=curdir::GetCurDir(); /* This means that this is the main makefile given on the command line, so we take the current directory */
+ }
+
+ if (loadedmakefile::sm_Statics.m_MhMakeConf)
+ {
+ const string &RootDir=loadedmakefile::sm_Statics.m_MhMakeConf->GetFullFileName();
+ string MakeDir=m_MakeDir->GetFullFileName();
+ if (RootDir.length()>MakeDir.length() || _strnicmp(RootDir.c_str(),MakeDir.c_str(),RootDir.length()))
+ {
+ cerr<<"mhmake needs to run in a directory that is a subdirectory of the directory specified with %MHMAKECONF : "<<RootDir<<", make dir : "<<m_MakeDir->GetFullFileName()<<endl;
+ exit(1);
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+void loadedmakefile::LoadMakefile()
+{
+ refptr<fileinfo> CurDir=curdir::GetCurDir();
+
+ curdir::ChangeCurDir(m_MakeDir);
+
+ #ifdef _DEBUG
+ if (g_PrintAdditionalInfo)
+ cout << "Loading makefile "<<m_Makefile->GetFullFileName()<<endl;
+ #endif
+
+ m_pParser=refptr<mhmakeparser>(new mhmakeparser(m_CommandLineVars));
+
+ // Add the MAKECMDGOALS environment variable
+ string MakeCmdGoals;
+ bool First=true;
+ vector<string>::iterator TarIt=m_CommandLineTargets.begin();
+ while (TarIt!=m_CommandLineTargets.end())
+ {
+ if (First)
+ First=false;
+ else
+ MakeCmdGoals+=g_SpaceString;
+ MakeCmdGoals+=*TarIt;
+ TarIt++;
+ }
+ m_pParser->SetVariable("MAKECMDGOALS",MakeCmdGoals);
+
+ string BaseAutoMak;
+
+ // First parse the makefile.before makefile which is in the directory $(MHMAKECONF) environment variable
+ refptr<fileinfo> DepFile;
+ if (sm_Statics.m_MhMakeConf)
+ {
+ BaseAutoMak=m_pParser->ExpandVar(BASEAUTOMAK);
+ if (BaseAutoMak.empty())
+ {
+ BaseAutoMak="makefile";
+ m_pParser->SetVariable(BASEAUTOMAK,BaseAutoMak);
+ }
+ refptr<fileinfo> BeforeMakefile=GetFileInfo(BaseAutoMak+".before",sm_Statics.m_MhMakeConf);
+
+ int result=m_pParser->ParseFile(BeforeMakefile,true);
+ if (result)
+ {
+ printf("Error parsing %s\n",BeforeMakefile->GetFullFileName().c_str());
+ throw(1);
+ }
+ m_pParser->UpdateDate(BeforeMakefile->GetDate());
+
+ // Now parse the automaticly generated dependency file, which needs to be in the objdir directory
+ string ObjDirName=m_pParser->ExpandExpression("$(OBJDIR)");
+ if (!ObjDirName.size())
+ {
+ printf("When making use of MHMAKECONF, you have to define OBJDIR in makefile.before");
+ throw(1);
+ }
+ DepFile=GetFileInfo(ObjDirName+OSPATHSEPSTR MAKEDEPFILE);
+ m_pParser->SetVariable(AUTODEPFILE,DepFile->GetFullFileName().c_str());
+ }
+ else
+ {
+ /* Create a file that is depending on the makefile name and the arguments */
+ md5_context ctx;
+
+ md5_starts( &ctx );
+
+ map<string,string>::const_iterator pIt=m_CommandLineVars.begin();
+ while (pIt!=m_CommandLineVars.end())
+ {
+ md5_update(&ctx, (uint8*)pIt->first.c_str(), pIt->first.size());
+ md5_update(&ctx, (uint8*)pIt->second.c_str(), pIt->second.size());
+ pIt++;
+ }
+ md5_update(&ctx, (uint8*)m_Makefile->GetFullFileName().c_str(), m_Makefile->GetFullFileName().size());
+
+ char ID[10];
+ sprintf(ID,"_%x",md5_finish32( &ctx));
+
+ DepFile=GetFileInfo(string(MAKEDEPFILE)+ID);
+ m_pParser->SetVariable(AUTODEPFILE,DepFile->GetFullFileName().c_str());
+ }
+
+ if (DepFile->Exists())
+ m_pParser->LoadAutoDepsFile(DepFile); /* Already load this autodep file before parsing of the makefile to avoid needless rebuilds. */
+
+ //m_pParser->yydebug=1;
+ int result=m_pParser->ParseFile(m_Makefile,true);
+ if (result)
+ {
+ printf("Error parsing %s\n",m_Makefile->GetFullFileName().c_str());
+ throw(1);
+ }
+ #ifdef _DEBUG
+ /* Check if the makefile has changed the AUTODEPFILE variable, if so generate a warning that a
+ * rebuild could happen for the rules defined for making included makefiles */
+ if (m_pParser->ExpandVar(AUTODEPFILE)!=DepFile->GetFullFileName())
+ {
+ cout << "\n\nWARNING:\n makefile '"<< m_Makefile->GetFullFileName() <<"' re-defines AUTODEPFILE\n from '"<< DepFile->GetFullFileName() <<"'\n to '"<<
+ m_pParser->ExpandVar(AUTODEPFILE) << "'\n (may cause needless rebuilds when having rules for included makefiles!!!!!)\n\n\n";
+ }
+
+ if (g_PrintAdditionalInfo)
+ {
+ if (m_pParser->GetFirstTarget())
+ cout<<"First target of "<<m_Makefile->GetFullFileName()<<" is "<<m_pParser->GetFirstTarget()->GetFullFileName()<<endl;
+ else
+ cout<<"No First target for "<<m_Makefile->GetFullFileName()<<endl;
+ }
+ #endif
+ m_pParser->UpdateDate(m_Makefile->GetDate());
+
+ if (sm_Statics.m_MhMakeConf)
+ {
+ refptr<fileinfo> AfterMakefile=GetFileInfo(BaseAutoMak+".after",sm_Statics.m_MhMakeConf);
+ int result=m_pParser->ParseFile(AfterMakefile);
+ if (result) {
+ printf("Error parsing %s\n",AfterMakefile->GetFullFileName().c_str());
+ throw(1);
+ }
+ m_pParser->UpdateDate(AfterMakefile->GetDate());
+ }
+ bool ForceAutoDepRescan=g_ForceAutoDepRescan;
+ if (DepFile->Exists())
+ m_pParser->LoadAutoDepsFile(DepFile);
+ else
+ ForceAutoDepRescan=true;
+ if (ForceAutoDepRescan)
+ m_pParser->EnableAutoDepRescan();
+
+ vector<string> &MakefilesToLoad=m_pParser->GetMakefilesToLoad();
+ vector<string>::iterator It=MakefilesToLoad.begin();
+ while (It!=MakefilesToLoad.end())
+ {
+ // First split the command into arguments
+ const char *pTmp=It->c_str();
+ vector<string> Args;
+ while (*pTmp)
+ {
+ string Item;
+ pTmp=NextItem(pTmp,Item);
+ Args.push_back(Item);
+ }
+
+ refptr<loadedmakefile> pLoadedMakefile(new loadedmakefile(Args));
+ refptr<loadedmakefile> Found=g_LoadedMakefiles.find(*pLoadedMakefile);
+ if (Found)
+ {
+ #ifdef _DEBUG
+ if (g_PrintAdditionalInfo)
+ cout << "Makefile already loaded: "<<Found->m_Makefile->GetFullFileName()<<endl;
+ #endif
+ }
+ else
+ {
+ g_LoadedMakefiles.push_back(pLoadedMakefile);
+
+ /* If there is a rule to build the makefile, first check if it needs to be rebuild */
+ m_pParser->BuildTarget(pLoadedMakefile->m_Makefile);
+ pLoadedMakefile->LoadMakefile();
+ }
+ It++;
+ }
+ curdir::ChangeCurDir(CurDir);
+
+ if (m_pParser->CompareEnv())
+ {
+ #ifdef _DEBUG
+ if (!g_GenProjectTree)
+ cout << "Rebuilding everything of "<< m_Makefile->GetFullFileName() <<" because environment and/or command-line variables have been changed.\n";
+ #endif
+ m_pParser->SetRebuildAll();
+ }
+
+}
+
+#ifdef _DEBUG
+///////////////////////////////////////////////////////////////////////////////
+void DumpVarsAndRules()
+{
+ int i;
+ LOADEDMAKEFILES::iterator LoadMakIt=g_LoadedMakefiles.begin();
+ while (LoadMakIt!=g_LoadedMakefiles.end())
+ {
+ for (i=0; i<80; i++) cout << "_";
+ cout << endl;
+ cout << "Variables of makefile " << (*LoadMakIt)->m_Makefile->GetFullFileName() << endl;
+ for (i=0; i<80; i++) cout << "_";
+ cout << endl;
+ (*LoadMakIt)->m_pParser->PrintVariables(true);
+ cout << endl;
+ LoadMakIt++;
+ }
+ for (i=0; i<80; i++) cout << "_";
+ cout << endl;
+ cout << "All Rules\n";
+ for (i=0; i<80; i++) cout << "_";
+ cout << endl;
+ PrintFileInfos();
+ for (i=0; i<80; i++) cout << "_";
+ cout << endl;
+ cout << "All Implicit Rules\n";
+ for (i=0; i<80; i++) cout << "_";
+ cout << endl;
+ IMPLICITRULE::PrintImplicitRules();
+}
+#endif
+
|