----------------------------------------------------------------------------- -- Name: ParseTheTags.lua, based on ParseTags.lua -- Purpose: Lua routine to process the output from 'ctags' and the -- original header files to extract classes, definitions, etc. -- Author: J Winwood, John Labenski -- Modified by: -- Created: 17/11/2001 -- Copyright: (c) 2001 J Winwood, John Labenski -- RCS-ID: -- Licence: wxWindows licence ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- -- Use the program "Exuberant CTags" >= v5.5 to generate the input file for this program -- for example to generate the all the the wxWindows header files use -- -- ctags -u --excmd=number --fields=afikms --c-types=cdefgmnpstuvx --c++-types=cdefgmnpstuvx -R *.h ctagsFilename = "tags" -- print the private and protected members? printPrivate = false printProtected = false -- This is the base path of the files in the "tags" file it will be removed -- so that the generated %include statements will have the proper path. -- see the function GetInclude() basePath = "../" -- These are files that you want to ignore from the ctags "tags" file excludeList = {} excludeList["plotto_wdr.h"] = true ----------------------------------------------------------------------------- fileCache = {} -- cache of recently loaded files fileList = {} --includeList = {} --classList = {} --definitionList = {} --typesList = {} --enumList = {} resultTable = {} letSpace = string.byte(" ") letTab = string.byte("\t") -- letTab = 9 ------------------------------------------------------------------------------ -- Basic string/data processing functions ------------------------------------------------------------------------------ -- simple table dump function for an array type table function DumpTable(atable) if #atable > 0 then local entry = nil for idx = 1, #atable do if entry == nil then entry = idx..": "..atable[idx] else entry = entry..", "..idx..": "..atable[idx] end end print(entry) end end -- read a file into a table in the file cache function ReadFile(fileName) if fileCache[fileName] == nil then local lineTable = {} for line in io.lines(fileName) do table.insert(lineTable, line) end fileCache[fileName] = lineTable end return fileCache[fileName] end -- trim the leading blanks function trim(str) local breakIdx = 1 for idx = 1, string.len(str) do if string.byte(str, idx) ~= letSpace and string.byte(str, idx) ~= letTab then breakIdx = idx break end end return string.sub(str, breakIdx) end -- trim trailing blanks function rtrim(str) for idx = string.len(str), 1, -1 do if string.byte(str, idx) ~= letSpace and string.byte(str, idx) ~= letTab then breakIdx = idx break end end return string.sub(str, 1, breakIdx) end -- trim out c style comments /* ... */ function trimCcomments(str) local f0 = string.find(str, "/*", 1, 1) if (f0) then local f1 = string.find(str, "*/", 1, 1) if (f1) then str = string.sub(str, 1, f0-1)..string.sub(str, f1+2) else str = rtrim(string.sub(str, 1, f0-1)) end end return str end -- trim off c and c++ style comments // function trimComments(str) local last_str = "" repeat last_str = str str = trimCcomments(str) until (last_str == str) local f0 = string.find(str, "//", 1, 1) if (f0) then return rtrim(string.sub(str, 1, f0-1)) end return str end -- does the definition, or whatever, end in \ function multiline(str) local a = rtrim(str) if (string.sub(a, -1, -1) == "\\") then return true end return false end ------------------------------------------------------------------------------ -- Tag specific processing functions ------------------------------------------------------------------------------ -- remove the "basePath" from the includename string returning right hand side function GetInclude(includename) local f0, f1 = string.find(includename, basePath, 1, 1) if (f1) then return string.sub(includename, f1+1) end return includename end -- parse a line number, 90;" -> 90 function GetLineNumber(lineString) local lineNumber = {} string.gsub(lineString, "([%d]+)", function (lineNo) table.insert(lineNumber, lineNo) end ) return tonumber(lineNumber[1]) end -- parse a classname, class:wxSomeClass -> wxSomeClass function GetClassName(classString) local classTable = {} string.gsub(classString, "([%w_]+)", function (matched) table.insert(classTable, matched) end ) return classTable[2] end -- parse a enumname, enum:wxSomeClass::::sometype -> wxSomeClass::sometype function GetEnumName(enumString) local enumName = string.gsub(enumString, "enum:", "") enumName = string.gsub(enumName, "::", "") --enumName = string.gsub(enumName, "::::", "::") return enumName end -- parse a structname function GetStructName(structString) local structName = string.gsub(structString, "struct:", "") structName = string.gsub(structName, "::", "") return structName end -- parse a unionname function GetUnionName(unionString) local unionName = string.gsub(unionString, "union:", "") unionName = string.gsub(unionName, "::", "") return unionName end -- extract a line from a file, read from the .h file to read the whole method function GetName(methodName, fileData, lineNumber) local nextLine = fileData[lineNumber] nextLine = trim(nextLine) if methodName == nil then methodName = rtrim(nextLine) else methodName = methodName..rtrim(nextLine) end -- this is a bit of a hack - I should really do bracket matching. -- and scan forwards and backward to ensure an even number of parentheses -- and other brackets, still this seems to work for wxWindows. methodName = trimComments(methodName) -- if the line ends in a comma append the next line if string.sub(methodName, -1, -1) == "," then methodName = GetName(methodName, fileData, lineNumber + 1) end methodName = string.gsub(methodName, "inline ", "") methodName = string.gsub(methodName, "extern ", "") -- This deletes { ... } stuff -- It's useful to know if a function is only {} though local f0 = string.find(methodName, "{", 1, 1) if (f0) then methodName = string.sub(methodName, 1, f0-1) end methodName = trim(rtrim(string.gsub(methodName, ";", ""))) return methodName end -- build a list of included files **** NOT USED **** function ProcessInclude(item, fileTable) local includeFile = item.name if not fileTable.includes[includeFile] then fileTable.includes[includeFile] = 1 end end -- processing of definitions function ProcessDefinition(item, fileTable) local fileData = ReadFile(item.filename) local lineNumber = item.linenumber local definitionName = GetName(nil, fileData, lineNumber) -- don't care about #undef if (string.find(definitionName, "undef") or multiline(definitionName)) then return nil end -- don't bother with #ifdef _WX_INCLUDE_FILE_ #defines if (string.byte(definitionName, 1) == string.byte("_") or string.byte(definitionName, string.len(definitionName)) == string.byte("_")) then return nil end -- only want first part of this local f0 = string.find(definitionName, "DECLARE_EVENT_TABLE_ENTRY", 1, 1) if (f0) then definitionName = string.sub(definitionName, 1, f0-1) end -- for some reason string.gsub(str, "#define", "%%define") doesn't work f0 = string.find(definitionName, "#define", 1, 1) if (f0) then definitionName = "%"..string.sub(definitionName, f0+1) end table.insert(fileTable.defs, definitionName) return definitionName end -- processing of typedefs function ProcessTypedef(item, fileTable) local fileData = ReadFile(item.filename) local lineNumber = item.linenumber local typeName = GetName(nil, fileData, lineNumber) table.insert(fileTable.typedefs, typeName) return typeName end -- processing of classes function ProcessClass(item, fileTable) local className = item.name if fileTable.classes[className] == nil then fileTable.classes[className] = {} end table.insert(fileTable.classes[className], item) return className end -- extract build a function prototype and all parameters function ProcessPrototype(item, fileTable) local fileData = ReadFile(item.filename) local lineNumber = item.linenumber local methodName = GetName(nil, fileData, lineNumber) local className = nil if item.base then className = GetClassName(item.base) end if (not className) then className = "function" end if fileTable.classes[className] == nil then fileTable.classes[className] = {} end methodName = string.gsub(methodName, "WXDLLEXPORT ", "") methodName = string.gsub(methodName, "WXCRITICAL_INLINE ", "") table.insert(fileTable.classes[className], item) if (string.find(item.name, "DECLARE_CLASS", 1, 1) or string.find(item.name, "DECLARE_ABSTRACT_CLASS", 1, 1) or string.find(item.name, "DECLARE_DYNAMIC_CLASS", 1, 1)) then fileTable.classes[className][1].classinfo = true end return methodName end -- processing of members function ProcessMember(item, fileTable) local fileData = ReadFile(item.filename) local lineNumber = item.linenumber local memberName = GetName(nil, fileData, lineNumber) -- this could be more clever -- if you want member "a", but it's declared as int a, b, c you get them all local className = "" if item.base then className = GetClassName(item.base) end if (not className) then className = "function" end if fileTable.classes[className] == nil then fileTable.classes[className] = {} end table.insert(fileTable.classes[className], item) return memberName end -- processing of enums function ProcessEnum(item, fileTable, type) local enumName = GetEnumName(item.base) if fileTable.enums[enumName] == nil then fileTable.enums[enumName] = {} end table.insert(fileTable.enums[enumName], item) return item.name end -- processing of structs function ProcessStruct(item, fileTable, type) local structName = GetStructName(item.base) if (item.base == "") then structName = item.name end if fileTable.structs[structName] == nil then fileTable.structs[structName] = {} end table.insert(fileTable.structs[structName], item) return item.name end -- processing of unions function ProcessUnion(item, fileTable, type) local unionName = GetUnionName(item.base) if (item.base == "") then unionName = item.name end if fileTable.unions[unionName] == nil then fileTable.unions[unionName] = {} end table.insert(fileTable.unions[unionName], item) return item.name end function TabsToTable(line) -- break up line by tabs local item = {} local last_tab = 1 for i = 1, string.len(line) do if (string.byte(line, i) == letTab) then table.insert(item, string.sub(line, last_tab, i-1)) --print(string.sub(line, last_tab, i-1)) last_tab = i+1 end end if (last_tab ~= 1) then table.insert(item, string.sub(line, last_tab)) end return item; end -- if includename is part of "ExcludeInclude" then return true, else false function ExcludeInclude(includename) if (excludeList[includename]) then return true end return false; end function ProcessTagLine(item, fileTable) -- included header files (this tag field doesn't exist?) if item.kind == "z" then item.method = ProcessInclude(item, fileTable) if (item.method) then item.wrapper = "%include \""..item.method.."\"" end -- function prototypes elseif item.kind == "p" or item.kind == "f" then item.method = ProcessPrototype(item, fileTable) item.wrapper = item.method -- definitions elseif item.kind == "d" then item.method = ProcessDefinition(item, fileTable) item.wrapper = item.method -- typedefs elseif item.kind == "t" then item.method = ProcessTypedef(item, fileTable) item.wrapper = item.method -- classes elseif item.kind == "c" then item.method = ProcessClass(item, fileTable, "class") item.wrapper = item.method -- enum names --elseif item.kind == "g" then -- item.method = ProcessClass(item, fileTable, "enum") -- item.wrapper = item.method -- if (a) then a = "%enum "..a end -- enums elseif item.kind == "e" then item.method = ProcessEnum(item, fileTable) item.wrapper = item.method -- structs elseif item.kind == "s" then item.method = ProcessStruct(item, fileTable, "struct") if (item.method) then item.wrapper = "%struct "..item.method end -- unions elseif item.kind == "u" then item.method = ProcessUnion(item, fileTable, "union") if (item.method) then item.wrapper = "%union "..item.method end -- members elseif item.kind == "m" then if ((item.access == nil) or string.find(item.access, "access:public")) then item.method = ProcessMember(item, fileTable) if (item.method) then item.wrapper = "%member "..item.method end end else --print("Unexpected data") -- DumpTable(item) end return item end function PrintFileTable(fileTable) print() print("// *****************************************************************") print("// "..fileTable.file) print("// *****************************************************************") if (#fileTable.defs > 0) then print() end for defIndex, defValue in pairs(fileTable.defs) do print(defValue) end if (#fileTable.typedefs > 0) then print() end for typedefIndex, typedefValue in pairs(fileTable.typedefs) do print(typedefValue) end for enumIndex, enumValue in pairs(fileTable.enums) do print() print("%enum ".. enumIndex) for itemIndex, itemValue in pairs(enumValue) do print(" "..itemValue.wrapper) end print("%end") end for structIndex, structValue in pairs(fileTable.structs) do print() print("%struct ".. structIndex) for itemIndex, itemValue in pairs(structValue) do print(" "..itemValue.wrapper) end print("%end") end for unionIndex, unionValue in pairs(fileTable.unions) do print() print("%union ".. unionIndex) for itemIndex, itemValue in pairs(unionValue) do print(" "..itemValue.wrapper) end print("%end") end for classIndex, classValue in pairs(fileTable.classes) do print() if (classIndex ~= "function") then print("// ---------------------------------------------------------------------------") print("// "..classIndex) print("// ---------------------------------------------------------------------------\n") --print("wxLUA_USE_"..classIndex) print("%include \""..fileTable.file.."\"") end for itemIndex, itemValue in pairs(classValue) do if (classIndex == "function") then print("%function "..itemValue.wrapper) elseif (itemValue.kind == "c") then local s = "%class " if (not classValue[1].classinfo) then s = s.."%noclassinfo " end s = s..itemValue.wrapper if (itemValue.base and (string.find(itemValue.base, "inherits"))) then s = s..", "..string.gsub(itemValue.base, "inherits:", "") end print(s) elseif (not itemValue.access or string.find(itemValue.access, "access:public")) then print(" "..itemValue.wrapper) elseif (string.find(itemValue.access, "access:protected")) then if (printProtected) then print(" %protected "..itemValue.wrapper) end elseif (string.find(itemValue.access, "access:private")) then if (printPrivate) then print(" %private "..itemValue.wrapper) end end end if (classIndex ~= "function") then print("%endclass") --print("%endif wxLUA_USE_"..classIndex) end end end function main() local lastItem = {} lastItem.base = " this is not a valid base-" lastItem.filename = "this is not a filename*" local currentName = "" -- read the ctags generated file for line in io.lines(ctagsFilename) do while true do -- call break to continue if (string.len(line) < 2) or (string.sub(line, 1, 3) == "!_") then break end -- convert tab delimited ctag line to a table local tagLineTable = TabsToTable(line) if (not tagLineTable[4]) then break end local item = {} item.name = tagLineTable[1] item.filename = tagLineTable[2] item.file = GetInclude(tagLineTable[2]) item.linenumber = GetLineNumber(tagLineTable[3]) item.kind = tagLineTable[4] item.base = tagLineTable[5] or "" item.access = tagLineTable[6] -- skip excluded includes if ExcludeInclude(item.file) then break end -- if we're on a new file dump out the last one if (item.filename ~= lastItem.filename) then -- create a new table for the file to use if (not fileList[item.file]) then local fileTable = {} fileTable.file = item.file fileTable.includes = {} fileTable.defs = {} fileTable.classes = {} fileTable.enums = {} fileTable.structs = {} fileTable.unions = {} fileTable.typedefs = {} fileList[item.file] = fileTable end -- print out the last one, if not the first time through if fileList[lastItem.file] then PrintFileTable(fileList[lastItem.file]) end end item = ProcessTagLine(item, fileList[item.file]) lastItem = item break end end if fileList[lastItem.file] then PrintFileTable(fileList[lastItem.file]) end end main()