Traverse Clang AST: visitor

我們在前一篇 Clang AST dump 中交代了指令可以利用 clang 完整 dump AST。其實已經可以自己寫程式 traverse AST 了。但又何必再 parse 一次呢? 可以直接利用 libclang 使用建立好的 AST。

 

走訪 AST 主要有兩種方式,可以利用 AST Visitor 或 AST Matcher,兩種是完全不同的概念,使用上也有互補的特性。本篇主要介紹 visitor ,借用這邊的 Visitor作法。

#include <clang-c/Index.h>

#include <iostream>
#include <string>

std::string getCursorKindName( CXCursorKind cursorKind )
{
  CXString kindName  = clang_getCursorKindSpelling( cursorKind );
  std::string result = clang_getCString( kindName );

  clang_disposeString( kindName );
  return result;
}

std::string getCursorSpelling( CXCursor cursor )
{
  CXString cursorSpelling = clang_getCursorSpelling( cursor );
  std::string result      = clang_getCString( cursorSpelling );

  clang_disposeString( cursorSpelling );
  return result;
}

CXChildVisitResult visitor( CXCursor cursor, CXCursor /* parent */, CXClientData clientData )
{
  CXSourceLocation location = clang_getCursorLocation( cursor );
  if( clang_Location_isFromMainFile( location ) == 0 )
    return CXChildVisit_Continue;

  CXCursorKind cursorKind = clang_getCursorKind( cursor );

  unsigned int curLevel  = *( reinterpret_cast<unsigned int*>( clientData ) );
  unsigned int nextLevel = curLevel + 1;

  std::cout << std::string( curLevel, '-' ) << " " << getCursorKindName(
  cursorKind ) << " (" << getCursorSpelling( cursor ) << ")\n";

  clang_visitChildren( cursor,
                       visitor,
                       &nextLevel ); 

  return CXChildVisit_Continue;
}

int main( int argc, char** argv )
{
  if( argc < 2 )
    return -1;

  CXIndex index        = clang_createIndex( 0, 1 );
  CXTranslationUnit tu = clang_createTranslationUnit( index, argv[1] );

  if( !tu )
    return -1;

  CXCursor rootCursor  = clang_getTranslationUnitCursor( tu );

  unsigned int treeLevel = 0;

  clang_visitChildren( rootCursor, visitor, &treeLevel );

  clang_disposeTranslationUnit( tu );
  clang_disposeIndex( index );

  return 0;
}

 

稍微改寫成一個自動產生 header 的版本

#include <clang-c/Index.h>

#include <iostream>
#include <string>
#include <vector>
using namespace std;

vector<int>    arrSizes;
vector<string> varNames;
vector<string> varTypes;

string getCursorKindName(CXCursorKind cursorKind) {
	CXString kindName = clang_getCursorKindSpelling(cursorKind);
	string result = clang_getCString(kindName);
	clang_disposeString(kindName);
	return result;
}

string getCursorSpelling(CXCursor cursor) {
	CXString cursorSpelling = clang_getCursorSpelling(cursor);
	string result = clang_getCString(cursorSpelling);
	clang_disposeString(cursorSpelling);
	return result;
}

string getCursorType(CXCursor cursor) {
	CXType t = clang_getCursorType(cursor);
	CXString s = clang_getTypeSpelling(t);
	std::string result = clang_getCString(s);
	clang_disposeString(s);
	return result;
}

CXChildVisitResult visitor(CXCursor cursor, CXCursor parent,
CXClientData clientData) {
	// check end of file
	CXSourceLocation location = clang_getCursorLocation(cursor);
	if (clang_Location_isFromMainFile(location) == 0) return CXChildVisit_Continue;

	CXCursorKind cursorKind = clang_getCursorKind(cursor);
	CXType       cursorType = clang_getCursorType( cursor );
	CXTypeKind   typeKind   = cursorType.kind;

	// skip while not variable declaration
	if (!clang_isDeclaration(cursorKind)) return CXChildVisit_Continue;
	if (cursorKind != CXCursor_VarDecl)   return CXChildVisit_Continue;

	// variable name
	string varName = getCursorSpelling(cursor);
	varNames.push_back( varName );

	if (typeKind == CXType_ConstantArray) { // is array type
		// get array size
		int    arrSize = clang_getArraySize( cursorType );

		// get type name
		CXType arrType   = clang_getArrayElementType( cursorType );
		CXString s       = clang_getTypeSpelling( arrType );
		string   varType = clang_getCString(s);
		clang_disposeString(s);

		arrSizes.push_back(arrSize);
		varTypes.push_back(varType);
	} else {
		string varType = getCursorType(cursor);
		arrSizes.push_back(0);
		varTypes.push_back(varType);
	}

	return CXChildVisit_Continue;
}

void printHeader(FILE *file) {
	fprintf(file, "// Automatic generated.\n");
	int num = varNames.size();
	for(int i = 0; i < num; ++i) {
		const int     arrSize = arrSizes[i];
		const string &varType = varTypes[i];
		const string &varName = varNames[i];
		if ( arrSize != 0 ) {
			fprintf(file, "extern %-40s %s[%d];\n", varType.c_str(), varName.c_str(), arrSize);
		} else {
			fprintf(file, "extern %-40s %s;\n", varType.c_str(), varName.c_str());
		}

	}
}

void printMacroLine(FILE *file, const char *funName) {
	int num = varNames.size();
	for(int i = 0; i < num; ++i) {
		const string &varName = varNames[i];
		if ( i == num - 1) {
			fprintf(file, "%s( %s );\n", funName, varName.c_str());
		} else {
			fprintf(file, "%s( %s ); \\\n", funName, varName.c_str());
		}
	}
}

void printMacro(FILE *file) {
	fprintf(file, "// Automatic generated.\n");
	fprintf(file, "#define CFG_READ_MACRO \\\n");
	printMacroLine(file, "CFG_READ");
	fprintf(file, "#define CFG_WRITE_MACRO \\\n");
	printMacroLine(file, "CFG_WRITE");
	fprintf(file, "#define CFG_PRINT_MACRO \\\n");
	printMacroLine(file, "CFG_PRINT");
}

int main(int argc, char** argv) {
	if (argc < 2) return -1;

	CXIndex index        = clang_createIndex(0, 1);
	CXTranslationUnit tu = clang_createTranslationUnit(index, argv[1]);

	if (!tu) return -1;

	CXCursor rootCursor  = clang_getTranslationUnitCursor(tu);

	clang_visitChildren(rootCursor, visitor, 0);

	clang_disposeTranslationUnit(tu);
	clang_disposeIndex(index);

	// print result
	FILE *fileHeader = fopen("header.inc", "w");
	FILE *fileMacro  = fopen("macro.inc", "w");
	printHeader(fileHeader);
	printMacro(fileMacro);

	fclose(fileHeader);
	fclose(fileMacro);
	return 0;
}
:: input file
set astInput=test.cpp

:: generate temp ast file for traversing 
clang -emit-ast %astInput% -o tmp.ast

:: save header.inc & macro.inc
..\Release\clangparser tmp.ast

:: print result
type header.inc
type macro.inc

pause
D:\workspace\clangparser\src>set astInput=test.cpp

D:\workspace\clangparser\src>clang -emit-ast test.cpp -o tmp.ast

D:\workspace\clangparser\src>..\Release\clangparser tmp.ast

D:\workspace\clangparser\src>type header.inc
// Automatic generated.
extern int                                      REG_A;
extern float                                    REG_B;
extern int                                      REG_C[10];
extern bool                                     REG_D;
extern clock_t                                  t0;
extern string                                   str;

D:\workspace\clangparser\src>type macro.inc
// Automatic generated.
#define CFG_READ_MACRO \
CFG_READ( REG_A ) \
CFG_READ( REG_B ) \
CFG_READ( REG_C ) \
CFG_READ( REG_D ) \
CFG_READ( t0 ) \
CFG_READ( str )
#define CFG_WRITE_MACRO \
CFG_WRITE( REG_A ) \
CFG_WRITE( REG_B ) \
CFG_WRITE( REG_C ) \
CFG_WRITE( REG_D ) \
CFG_WRITE( t0 ) \
CFG_WRITE( str )
#define CFG_PRINT_MACRO \
CFG_PRINT( REG_A ) \
CFG_PRINT( REG_B ) \
CFG_PRINT( REG_C ) \
CFG_PRINT( REG_D ) \
CFG_PRINT( t0 ) \
CFG_PRINT( str )

 

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *