skia2/tools/bookmaker/includeParser.cpp
Cary Clark 154beea859 Add docs for SkMatrix, SkRect, SkIRect, SkBitmap
Also minor changes to earlier docs.

Many small changes to improve indentation in generated includes.
Added support for matrix math illustrations.

Docs-Preview: https://skia.org/?cl=58500
Bug: skia:6898
Change-Id: I7da58ad55f82d7fd41d19288beb2cd71730fb01f
Reviewed-on: https://skia-review.googlesource.com/58500
Commit-Queue: Cary Clark <caryclark@skia.org>
Reviewed-by: Cary Clark <caryclark@google.com>
Reviewed-by: Cary Clark <caryclark@skia.org>
2017-10-26 12:17:36 +00:00

2078 lines
82 KiB
C++

/*
* Copyright 2017 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "bookmaker.h"
const IncludeKey kKeyWords[] = {
{ "", KeyWord::kNone, KeyProperty::kNone },
{ "SK_API", KeyWord::kSK_API, KeyProperty::kModifier },
{ "SK_BEGIN_REQUIRE_DENSE", KeyWord::kSK_BEGIN_REQUIRE_DENSE, KeyProperty::kModifier },
{ "bool", KeyWord::kBool, KeyProperty::kNumber },
{ "char", KeyWord::kChar, KeyProperty::kNumber },
{ "class", KeyWord::kClass, KeyProperty::kObject },
{ "const", KeyWord::kConst, KeyProperty::kModifier },
{ "constexpr", KeyWord::kConstExpr, KeyProperty::kModifier },
{ "define", KeyWord::kDefine, KeyProperty::kPreprocessor },
{ "double", KeyWord::kDouble, KeyProperty::kNumber },
{ "elif", KeyWord::kElif, KeyProperty::kPreprocessor },
{ "else", KeyWord::kElse, KeyProperty::kPreprocessor },
{ "endif", KeyWord::kEndif, KeyProperty::kPreprocessor },
{ "enum", KeyWord::kEnum, KeyProperty::kObject },
{ "float", KeyWord::kFloat, KeyProperty::kNumber },
{ "friend", KeyWord::kFriend, KeyProperty::kModifier },
{ "if", KeyWord::kIf, KeyProperty::kPreprocessor },
{ "ifdef", KeyWord::kIfdef, KeyProperty::kPreprocessor },
{ "ifndef", KeyWord::kIfndef, KeyProperty::kPreprocessor },
{ "include", KeyWord::kInclude, KeyProperty::kPreprocessor },
{ "inline", KeyWord::kInline, KeyProperty::kModifier },
{ "int", KeyWord::kInt, KeyProperty::kNumber },
{ "operator", KeyWord::kOperator, KeyProperty::kFunction },
{ "private", KeyWord::kPrivate, KeyProperty::kClassSection },
{ "protected", KeyWord::kProtected, KeyProperty::kClassSection },
{ "public", KeyWord::kPublic, KeyProperty::kClassSection },
{ "signed", KeyWord::kSigned, KeyProperty::kNumber },
{ "size_t", KeyWord::kSize_t, KeyProperty::kNumber },
{ "static", KeyWord::kStatic, KeyProperty::kModifier },
{ "struct", KeyWord::kStruct, KeyProperty::kObject },
{ "template", KeyWord::kTemplate, KeyProperty::kObject },
{ "typedef", KeyWord::kTypedef, KeyProperty::kObject },
{ "uint16_t", KeyWord::kUint16_t, KeyProperty::kNumber },
{ "uint32_t", KeyWord::kUint32_t, KeyProperty::kNumber },
{ "uint64_t", KeyWord::kUint64_t, KeyProperty::kNumber },
{ "uint8_t", KeyWord::kUint8_t, KeyProperty::kNumber },
{ "union", KeyWord::kUnion, KeyProperty::kObject },
{ "unsigned", KeyWord::kUnsigned, KeyProperty::kNumber },
{ "void", KeyWord::kVoid, KeyProperty::kNumber },
};
const size_t kKeyWordCount = SK_ARRAY_COUNT(kKeyWords);
KeyWord IncludeParser::FindKey(const char* start, const char* end) {
int ch = 0;
for (size_t index = 0; index < kKeyWordCount; ) {
if (start[ch] > kKeyWords[index].fName[ch]) {
++index;
if (ch > 0 && kKeyWords[index - 1].fName[ch - 1] < kKeyWords[index].fName[ch - 1]) {
return KeyWord::kNone;
}
continue;
}
if (start[ch] < kKeyWords[index].fName[ch]) {
return KeyWord::kNone;
}
++ch;
if (start + ch >= end) {
if (end - start < (int) strlen(kKeyWords[index].fName)) {
return KeyWord::kNone;
}
return kKeyWords[index].fKeyWord;
}
}
return KeyWord::kNone;
}
void IncludeParser::ValidateKeyWords() {
for (size_t index = 1; index < kKeyWordCount; ++index) {
SkASSERT((int) kKeyWords[index - 1].fKeyWord + 1
== (int) kKeyWords[index].fKeyWord);
SkASSERT(0 > strcmp(kKeyWords[index - 1].fName, kKeyWords[index].fName));
}
}
void IncludeParser::addKeyword(KeyWord keyWord) {
fParent->fTokens.emplace_back(keyWord, fIncludeWord, fChar, fLineCount, fParent);
fIncludeWord = nullptr;
if (KeyProperty::kObject == kKeyWords[(int) keyWord].fProperty) {
Definition* def = &fParent->fTokens.back();
this->addDefinition(def);
if (KeyWord::kEnum == fParent->fKeyWord) {
fInEnum = true;
}
}
}
void IncludeParser::checkForMissingParams(const vector<string>& methodParams,
const vector<string>& foundParams) {
for (auto& methodParam : methodParams) {
bool found = false;
for (auto& foundParam : foundParams) {
if (methodParam == foundParam) {
found = true;
break;
}
}
if (!found) {
this->writeIncompleteTag("Param", methodParam, 2);
}
}
for (auto& foundParam : foundParams) {
bool found = false;
for (auto& methodParam : methodParams) {
if (methodParam == foundParam) {
found = true;
break;
}
}
if (!found) {
this->reportError("doxygen param does not match method declaration");
}
}
}
bool IncludeParser::checkForWord() {
if (!fIncludeWord) {
return true;
}
KeyWord keyWord = FindKey(fIncludeWord, fChar);
if (KeyWord::kNone != keyWord) {
if (KeyProperty::kPreprocessor != kKeyWords[(int) keyWord].fProperty) {
this->addKeyword(keyWord);
return true;
}
} else {
this->addWord();
return true;
}
Definition* poundDef = fParent;
if (!fParent) {
return reportError<bool>("expected parent");
}
if (Definition::Type::kBracket != poundDef->fType) {
return reportError<bool>("expected bracket");
}
if (Bracket::kPound != poundDef->fBracket) {
return reportError<bool>("expected preprocessor");
}
if (KeyWord::kNone != poundDef->fKeyWord) {
return reportError<bool>("already found keyword");
}
poundDef->fKeyWord = keyWord;
fIncludeWord = nullptr;
switch (keyWord) {
// these do not link to other # directives
case KeyWord::kDefine:
case KeyWord::kInclude:
break;
// these start a # directive link
case KeyWord::kIf:
case KeyWord::kIfdef:
case KeyWord::kIfndef:
break;
// these continue a # directive link
case KeyWord::kElif:
case KeyWord::kElse: {
this->popObject(); // pop elif
if (Bracket::kPound != fParent->fBracket) {
return this->reportError<bool>("expected preprocessor directive");
}
this->popBracket(); // pop if
poundDef->fParent = fParent;
this->addDefinition(poundDef); // push elif back
} break;
// this ends a # directive link
case KeyWord::kEndif:
// FIXME : should this be calling popBracket() instead?
this->popObject(); // pop endif
if (Bracket::kPound != fParent->fBracket) {
return this->reportError<bool>("expected preprocessor directive");
}
this->popBracket(); // pop if/else
break;
default:
SkASSERT(0);
}
return true;
}
string IncludeParser::className() const {
string name(fParent->fName);
size_t slash = name.find_last_of("/");
if (string::npos == slash) {
slash = name.find_last_of("\\");
}
SkASSERT(string::npos != slash);
string result = name.substr(slash);
result = result.substr(1, result.size() - 3);
return result;
}
#include <sstream>
#include <iostream>
bool IncludeParser::crossCheck(BmhParser& bmhParser) {
for (auto& classMapper : fIClassMap) {
string className = classMapper.first;
auto finder = bmhParser.fClassMap.find(className);
if (bmhParser.fClassMap.end() == finder) {
SkASSERT(string::npos != className.find("::"));
continue;
}
RootDefinition* root = &finder->second;
root->clearVisited();
}
for (auto& classMapper : fIClassMap) {
string className = classMapper.first;
std::istringstream iss(className);
string classStr;
string classBase;
RootDefinition* root = nullptr;
while (std::getline(iss, classStr, ':')) {
if (root) {
if (!classStr.length()) {
continue;
}
classBase += "::" + classStr;
auto finder = root->fBranches.find(classBase);
if (root->fBranches.end() != finder) {
root = finder->second;
} else {
SkASSERT(0);
}
} else {
classBase = classStr;
auto finder = bmhParser.fClassMap.find(classBase);
if (bmhParser.fClassMap.end() != finder) {
root = &finder->second;
} else {
SkASSERT(0);
}
}
}
auto& classMap = classMapper.second;
auto& tokens = classMap.fTokens;
for (const auto& token : tokens) {
if (token.fPrivate) {
continue;
}
string fullName = classMapper.first + "::" + token.fName;
const Definition* def = root->find(fullName, RootDefinition::AllowParens::kYes);
switch (token.fMarkType) {
case MarkType::kMethod: {
if (this->internalName(token)) {
continue;
}
if (!def) {
string paramName = className + "::";
paramName += string(token.fContentStart,
token.fContentEnd - token.fContentStart);
def = root->find(paramName, RootDefinition::AllowParens::kYes);
if (!def && 0 == token.fName.find("operator")) {
string operatorName = className + "::";
TextParser oper("", token.fStart, token.fContentEnd, 0);
const char* start = oper.strnstr("operator", token.fContentEnd);
SkASSERT(start);
oper.skipTo(start);
oper.skipToEndBracket('(');
int parens = 0;
do {
if ('(' == oper.peek()) {
++parens;
} else if (')' == oper.peek()) {
--parens;
}
} while (!oper.eof() && oper.next() && parens > 0);
operatorName += string(start, oper.fChar - start);
def = root->find(operatorName, RootDefinition::AllowParens::kYes);
}
}
if (!def) {
int skip = !strncmp(token.fContentStart, "explicit ", 9) ? 9 : 0;
skip = !strncmp(token.fContentStart, "virtual ", 8) ? 8 : skip;
string constructorName = className + "::";
constructorName += string(token.fContentStart + skip,
token.fContentEnd - token.fContentStart - skip);
def = root->find(constructorName, RootDefinition::AllowParens::kYes);
}
if (!def && 0 == token.fName.find("SK_")) {
string incName = token.fName + "()";
string macroName = className + "::" + incName;
def = root->find(macroName, RootDefinition::AllowParens::kYes);
if (def) {
if (def->fName == incName) {
def->fVisited = true;
if ("SK_TO_STRING_NONVIRT" == token.fName) {
def = root->find(className + "::toString",
RootDefinition::AllowParens::kYes);
if (def) {
def->fVisited = true;
} else {
SkDebugf("missing toString bmh: %s\n", fullName.c_str());
}
}
break;
} else {
SkDebugf("method macro differs from bmh: %s\n", fullName.c_str());
}
}
}
if (!def) {
bool allLower = true;
for (size_t index = 0; index < token.fName.length(); ++index) {
if (!islower(token.fName[index])) {
allLower = false;
break;
}
}
if (allLower) {
string lowerName = className + "::" + token.fName + "()";
def = root->find(lowerName, RootDefinition::AllowParens::kYes);
}
}
if (!def) {
if ("SK_ATTR_DEPRECATED" == token.fName) {
break;
}
if (0 == token.fName.find("SkDEBUGCODE")) {
break;
}
}
if (!def) {
// simple method names inside nested classes have a bug and are missing trailing parens
string withParens = fullName + "()"; // FIXME: this shouldn't be necessary
def = root->find(withParens, RootDefinition::AllowParens::kNo);
}
if (!def) {
SkDebugf("method missing from bmh: %s\n", fullName.c_str());
break;
}
if (def->crossCheck2(token)) {
def->fVisited = true;
if (MarkType::kDefinedBy == def->fMarkType) {
def->fParent->fVisited = true;
}
} else {
SkDebugf("method differs from bmh: %s\n", fullName.c_str());
}
} break;
case MarkType::kComment:
break;
case MarkType::kEnumClass:
case MarkType::kEnum: {
if (!def) {
// work backwards from first word to deduce #Enum name
TextParser firstMember("", token.fStart, token.fContentEnd, 0);
SkAssertResult(firstMember.skipName("enum"));
SkAssertResult(firstMember.skipToEndBracket('{'));
firstMember.next();
firstMember.skipWhiteSpace();
SkASSERT('k' == firstMember.peek());
const char* savePos = firstMember.fChar;
firstMember.skipToNonAlphaNum();
const char* wordEnd = firstMember.fChar;
firstMember.fChar = savePos;
const char* lastUnderscore = nullptr;
do {
if (!firstMember.skipToEndBracket('_')) {
break;
}
if (firstMember.fChar > wordEnd) {
break;
}
lastUnderscore = firstMember.fChar;
} while (firstMember.next());
if (lastUnderscore) {
++lastUnderscore;
string anonName = className + "::" + string(lastUnderscore,
wordEnd - lastUnderscore) + 's';
def = root->find(anonName, RootDefinition::AllowParens::kYes);
}
if (!def) {
SkDebugf("enum missing from bmh: %s\n", fullName.c_str());
break;
}
}
def->fVisited = true;
for (auto& child : def->fChildren) {
if (MarkType::kCode == child->fMarkType) {
def = child;
break;
}
}
if (MarkType::kCode != def->fMarkType) {
SkDebugf("enum code missing from bmh: %s\n", fullName.c_str());
break;
}
if (def->crossCheck(token)) {
def->fVisited = true;
} else {
SkDebugf("enum differs from bmh: %s\n", def->fName.c_str());
}
for (auto& child : token.fChildren) {
string constName = MarkType::kEnumClass == token.fMarkType ?
fullName : className;
constName += "::" + child->fName;
def = root->find(constName, RootDefinition::AllowParens::kYes);
if (!def) {
string innerName = classMapper.first + "::" + child->fName;
def = root->find(innerName, RootDefinition::AllowParens::kYes);
}
if (!def) {
if (string::npos == child->fName.find("Legacy_")) {
SkDebugf("const missing from bmh: %s\n", constName.c_str());
}
} else {
def->fVisited = true;
}
}
} break;
case MarkType::kMember:
if (def) {
def->fVisited = true;
} else {
SkDebugf("member missing from bmh: %s\n", fullName.c_str());
}
break;
default:
SkASSERT(0); // unhandled
break;
}
}
}
for (auto& classMapper : fIClassMap) {
string className = classMapper.first;
auto finder = bmhParser.fClassMap.find(className);
if (bmhParser.fClassMap.end() == finder) {
continue;
}
RootDefinition* root = &finder->second;
if (!root->dumpUnVisited()) {
SkDebugf("some struct elements not found; struct finding in includeParser is missing\n");
}
SkDebugf("cross-checked %s\n", className.c_str());
}
bmhParser.fWroteOut = true;
return true;
}
IClassDefinition* IncludeParser::defineClass(const Definition& includeDef,
const string& name) {
string className;
const Definition* test = fParent;
while (Definition::Type::kFileType != test->fType) {
if (Definition::Type::kMark == test->fType && KeyWord::kClass == test->fKeyWord) {
className = test->fName + "::";
break;
}
test = test->fParent;
}
className += name;
unordered_map<string, IClassDefinition>& map = fIClassMap;
IClassDefinition& markupDef = map[className];
if (markupDef.fStart) {
typedef IClassDefinition* IClassDefPtr;
return INHERITED::reportError<IClassDefPtr>("class already defined");
}
markupDef.fFileName = fFileName;
markupDef.fStart = includeDef.fStart;
markupDef.fContentStart = includeDef.fStart;
markupDef.fName = className;
markupDef.fContentEnd = includeDef.fContentEnd;
markupDef.fTerminator = includeDef.fTerminator;
markupDef.fParent = fParent;
markupDef.fLineCount = fLineCount;
markupDef.fMarkType = KeyWord::kStruct == includeDef.fKeyWord ?
MarkType::kStruct : MarkType::kClass;
markupDef.fKeyWord = includeDef.fKeyWord;
markupDef.fType = Definition::Type::kMark;
fParent = &markupDef;
return &markupDef;
}
void IncludeParser::dumpClassTokens(IClassDefinition& classDef) {
auto& tokens = classDef.fTokens;
for (auto& token : tokens) {
if (Definition::Type::kMark == token.fType && MarkType::kComment == token.fMarkType) {
continue;
}
if (MarkType::kMember != token.fMarkType) {
this->writeString(
"# ------------------------------------------------------------------------------");
this->lf(2);
}
switch (token.fMarkType) {
case MarkType::kEnum:
this->dumpEnum(token);
break;
case MarkType::kMethod:
this->dumpMethod(token);
break;
case MarkType::kMember:
this->dumpMember(token);
continue;
break;
default:
SkASSERT(0);
}
this->lf(2);
this->writeTag("Example");
this->lf(1);
this->writeString("// incomplete");
this->lf(1);
this->writeEndTag();
this->lf(2);
this->writeTag("SeeAlso");
this->writeSpace();
this->writeString("incomplete");
this->lf(2);
this->writeEndTag("Method");
this->lf(2);
}
}
void IncludeParser::dumpComment(const Definition& token) {
fLineCount = token.fLineCount;
fChar = fLine = token.fContentStart;
fEnd = token.fContentEnd;
bool sawParam = false;
bool multiline = false;
bool sawReturn = false;
bool sawComment = false;
bool methodHasReturn = false;
vector<string> methodParams;
vector<string> foundParams;
Definition methodName;
TextParser methodParser(token.fFileName, token.fContentStart, token.fContentEnd,
token.fLineCount);
if (MarkType::kMethod == token.fMarkType) {
methodName.fName = string(token.fContentStart,
(int) (token.fContentEnd - token.fContentStart));
methodHasReturn = !methodParser.startsWith("void ")
&& !methodParser.strnchr('~', methodParser.fEnd);
const char* paren = methodParser.strnchr('(', methodParser.fEnd);
const char* nextEnd = paren;
do {
string paramName;
methodParser.fChar = nextEnd + 1;
methodParser.skipSpace();
if (!methodName.nextMethodParam(&methodParser, &nextEnd, &paramName)) {
continue;
}
methodParams.push_back(paramName);
} while (')' != nextEnd[0]);
}
for (const auto& child : token.fTokens) {
if (Definition::Type::kMark == child.fType && MarkType::kMember == child.fMarkType) {
break;
}
if (Definition::Type::kMark == child.fType && MarkType::kComment == child.fMarkType) {
if (child.fPrivate) {
break;
}
if ('@' == child.fContentStart[0]) {
TextParser parser(&child);
do {
parser.next();
if (parser.startsWith("param ")) {
parser.skipWord("param");
const char* parmStart = parser.fChar;
parser.skipToSpace();
string parmName = string(parmStart, (int) (parser.fChar - parmStart));
parser.skipWhiteSpace();
do {
size_t nextComma = parmName.find(',');
string piece;
if (string::npos == nextComma) {
piece = parmName;
parmName = "";
} else {
piece = parmName.substr(0, nextComma);
parmName = parmName.substr(nextComma + 1);
}
if (sawParam) {
if (multiline) {
this->lf(1);
}
this->writeEndTag();
} else {
if (sawComment) {
this->nl();
}
this->lf(2);
}
foundParams.emplace_back(piece);
this->writeTag("Param", piece);
this->writeSpace(2);
this->writeBlock(parser.fEnd - parser.fChar, parser.fChar);
this->lf(1);
sawParam = true;
sawComment = false;
} while (parmName.length());
parser.skipTo(parser.fEnd);
} else if (parser.startsWith("return ") || parser.startsWith("returns ")) {
parser.skipWord("return");
if ('s' == parser.peek()) {
parser.next();
}
if (sawParam) {
if (multiline) {
this->lf(1);
}
this->writeEndTag();
}
this->checkForMissingParams(methodParams, foundParams);
sawParam = false;
sawComment = false;
multiline = false;
this->lf(2);
this->writeTag("Return");
this->writeSpace(2);
this->writeBlock(parser.fEnd - parser.fChar, parser.fChar);
this->lf(1);
sawReturn = true;
parser.skipTo(parser.fEnd);
} else {
this->reportError("unexpected doxygen directive");
}
} while (!parser.eof());
} else if (child.length() > 1) {
const char* start = child.fContentStart;
ptrdiff_t length = child.fContentEnd - start;
SkASSERT(length >= 0);
while (length && '/' == start[0]) {
start += 1;
--length;
}
while (length && '/' == start[length - 1]) {
length -= 1;
if (length && '*' == start[length - 1]) {
length -= 1;
}
}
if (length) {
this->lfAlways(sawComment || sawParam || sawReturn ? 1 : 2);
if (sawParam || sawReturn) {
this->indentToColumn(8);
}
this->writeBlock(length, start);
this->writeSpace();
sawComment = true;
if (sawParam || sawReturn) {
multiline = true;
}
}
}
}
}
if (sawParam || sawReturn) {
if (multiline) {
this->lf(1);
}
this->writeEndTag();
}
if (!sawReturn) {
if (!sawParam) {
if (sawComment) {
this->nl();
}
this->lf(2);
}
this->checkForMissingParams(methodParams, foundParams);
}
if (methodHasReturn != sawReturn) {
if (!methodHasReturn) {
this->reportError("unexpected doxygen return");
} else {
if (sawComment) {
this->nl();
}
this->lf(2);
this->writeIncompleteTag("Return");
}
}
}
void IncludeParser::dumpEnum(const Definition& token) {
this->writeTag("Enum", token.fName);
this->lf(2);
this->writeString("#Code");
this->lfAlways(1);
this->indentToColumn(4);
this->writeString("enum");
this->writeSpace();
if ("_anonymous" != token.fName.substr(0, 10)) {
this->writeString(token.fName);
this->writeSpace();
}
this->writeString("{");
this->lfAlways(1);
for (auto& child : token.fChildren) {
this->indentToColumn(8);
this->writeString(child->fName);
if (child->length()) {
this->writeSpace();
this->writeBlock(child->length(), child->fContentStart);
}
if (',' != fLastChar) {
this->writeString(",");
}
this->lfAlways(1);
}
this->indentToColumn(4);
this->writeString("};");
this->lf(1);
this->writeString("##");
this->lf(2);
this->dumpComment(token);
for (auto& child : token.fChildren) {
// start here;
// get comments before
// or after const values
this->writeString("#Const");
this->writeSpace();
this->writeString(child->fName);
TextParser val(child);
if (!val.eof()) {
if ('=' == val.fStart[0] || ',' == val.fStart[0]) {
val.next();
val.skipSpace();
const char* valEnd = val.anyOf(",\n");
if (!valEnd) {
valEnd = val.fEnd;
}
this->writeSpace();
this->writeBlock(valEnd - val.fStart, val.fStart);
} else {
this->writeSpace();
this->writeDefinition(*child);
}
}
this->lf(1);
for (auto comment : child->fChildren) {
if (MarkType::kComment == comment->fMarkType) {
TextParser parser(comment);
parser.skipExact("*");
parser.skipExact("*");
while (!parser.eof() && parser.skipWhiteSpace()) {
parser.skipExact("*");
parser.skipWhiteSpace();
const char* start = parser.fChar;
parser.skipToEndBracket('\n');
this->lf(1);
this->writeBlock(parser.fChar - start, start);
}
}
}
this->writeEndTag();
}
this->lf(2);
}
void IncludeParser::dumpMethod(const Definition& token) {
this->writeString("#Method");
this->writeSpace();
if ("SK_TO_STRING_NONVIRT" == token.fName) {
this->writeString("void toString(SkString* str) const;");
this->lf(2);
this->writeEndTag("DefinedBy", "SK_TO_STRING_NONVIRT()");
this->lf(2);
this->writeTag("Private");
this->lf(1);
this->writeString("macro expands to: void toString(SkString* str) const;");
this->writeEndTag();
this->lf(2);
const char desc[] =
"Creates string representation. The representation is read by\n"
"internal debugging tools. The interface and implementation may be\n"
"suppressed by defining SK_IGNORE_TO_STRING.";
this->writeBlock(sizeof(desc) - 1, desc);
this->lf(2);
this->writeTag("Param", "str");
this->writeSpace(2);
this->writeString("storage for string representation");
this->writeSpace();
this->writeString("##");
this->lf(2);
return;
}
this->writeBlock(token.length(), token.fStart);
this->lf(1);
this->dumpComment(token);
}
void IncludeParser::dumpMember(const Definition& token) {
this->writeTag("Member");
this->writeSpace();
this->writeDefinition(token, token.fName, 2);
lf(1);
for (auto child : token.fChildren) {
this->writeDefinition(*child);
}
this->writeEndTag();
lf(2);
}
bool IncludeParser::dumpTokens(const string& dir) {
for (const auto& member : fIClassMap) {
if (string::npos != member.first.find("::")) {
continue;
}
if (!this->dumpTokens(dir, member.first)) {
return false;
}
}
return true;
}
// dump equivalent markup
bool IncludeParser::dumpTokens(const string& dir, const string& skClassName) {
string fileName = dir;
if (dir.length() && '/' != dir[dir.length() - 1]) {
fileName += '/';
}
fileName += skClassName + "_Reference.bmh";
fOut = fopen(fileName.c_str(), "wb");
if (!fOut) {
SkDebugf("could not open output file %s\n", fileName.c_str());
return false;
}
string prefixName = skClassName.substr(0, 2);
string topicName = skClassName.length() > 2 && isupper(skClassName[2]) &&
("Sk" == prefixName || "Gr" == prefixName) ? skClassName.substr(2) : skClassName;
this->writeTagNoLF("Topic", topicName);
this->writeTag("Alias", topicName + "_Reference");
this->lf(2);
auto& classMap = fIClassMap[skClassName];
const char* containerType = kKeyWords[(int) classMap.fKeyWord].fName;
this->writeTag(containerType, skClassName);
this->lf(2);
auto& tokens = classMap.fTokens;
for (auto& token : tokens) {
if (Definition::Type::kMark != token.fType || MarkType::kComment != token.fMarkType) {
continue;
}
this->writeDefinition(token);
this->lf(1);
}
this->lf(2);
string className(skClassName.substr(2));
vector<string> sortedClasses;
size_t maxLen = 0;
for (const auto& oneClass : fIClassMap) {
if (skClassName + "::" != oneClass.first.substr(0, skClassName.length() + 2)) {
continue;
}
string structName = oneClass.first.substr(skClassName.length() + 2);
maxLen = SkTMax(maxLen, structName.length());
sortedClasses.emplace_back(structName);
}
this->writeTag("Topic", "Overview");
this->lf(2);
this->writeTag("Subtopic", "Subtopics");
this->writeEndTag("ToDo", "manually add subtopics");
this->writeTableHeader("topics", 0, "description");
this->writeTableTrailer();
this->writeEndTag();
this->lf(2);
if (maxLen) {
this->writeTag("Subtopic", "Structs");
this->writeTableHeader("description", maxLen, "struct");
for (auto& name : sortedClasses) {
this->writeTableRow(maxLen, name);
}
this->writeTableTrailer();
this->writeEndTag("Subtopic");
this->lf(2);
}
maxLen = 0;
size_t constructorMax = 0;
size_t operatorMax = 0;
vector<string> sortedNames;
vector<string> constructorNames;
vector<string> operatorNames;
for (const auto& token : classMap.fTokens) {
if (Definition::Type::kMark != token.fType || MarkType::kMethod != token.fMarkType) {
continue;
}
string name = token.fName;
if (name.substr(0, 7) == "android" || string::npos != name.find("nternal_")) {
continue;
}
if ((name.substr(0, 2) == "Sk" && 2 == name.find(className)) || '~' == name[0]) {
name = string(token.fContentStart, (int) (token.fContentEnd - token.fContentStart));
constructorMax = SkTMax(constructorMax, name.length());
constructorNames.emplace_back(name);
continue;
}
if (name.substr(0, 8) == "operator") {
name = string(token.fContentStart, (int) (token.fContentEnd - token.fContentStart));
operatorMax = SkTMax(operatorMax, name.length());
operatorNames.emplace_back(name);
continue;
}
if (name[name.length() - 2] == '_' && isdigit(name[name.length() - 1])) {
continue;
}
if ("SK_TO_STRING_NONVIRT" == name) {
name = "toString";
}
size_t paren = name.find('(');
size_t funcLen = string::npos == paren ? name.length() : paren;
maxLen = SkTMax(maxLen, funcLen);
sortedNames.emplace_back(name);
}
if (constructorMax) {
std::sort(constructorNames.begin(), constructorNames.end());
this->writeTag("Subtopic", "Constructors");
this->writeTableHeader("description", constructorMax, "function");
for (auto& name : constructorNames) {
this->writeTableRow(constructorMax, name);
}
this->writeTableTrailer();
this->writeEndTag("Subtopic");
this->lf(2);
}
if (operatorMax) {
std::sort(operatorNames.begin(), operatorNames.end());
this->writeTag("Subtopic", "Operators");
this->writeTableHeader("description", operatorMax, "function");
for (auto& name : operatorNames) {
this->writeTableRow(operatorMax, name);
}
this->writeTableTrailer();
this->writeEndTag("Subtopic");
this->lf(2);
}
std::sort(sortedNames.begin(), sortedNames.end());
this->writeTag("Subtopic", "Member_Functions");
this->writeTableHeader("description", maxLen, "function");
for (auto& name : sortedNames) {
size_t paren = name.find('(');
size_t funcLen = string::npos == paren ? name.length() : paren;
this->writeTableRow(maxLen, name.substr(0, funcLen));
}
this->writeTableTrailer();
this->writeEndTag("Subtopic");
this->lf(2);
this->writeEndTag("Topic");
this->lf(2);
for (auto& oneClass : fIClassMap) {
if (skClassName + "::" != oneClass.first.substr(0, skClassName.length() + 2)) {
continue;
}
string innerName = oneClass.first.substr(skClassName.length() + 2);
this->writeString(
"# ------------------------------------------------------------------------------");
this->lf(2);
const char* containerType = kKeyWords[(int) oneClass.second.fKeyWord].fName;
this->writeTag(containerType, innerName);
this->lf(2);
this->writeTag("Code");
this->writeEndTag("ToDo", "fill this in manually");
this->writeEndTag();
this->lf(2);
for (auto& token : oneClass.second.fTokens) {
if (Definition::Type::kMark != token.fType || MarkType::kComment != token.fMarkType) {
continue;
}
this->writeDefinition(token);
}
this->lf(2);
this->dumpClassTokens(oneClass.second);
this->lf(2);
this->writeEndTag(containerType, innerName);
this->lf(2);
}
this->dumpClassTokens(classMap);
this->writeEndTag(containerType, skClassName);
this->lf(2);
this->writeEndTag("Topic", topicName);
this->lfAlways(1);
fclose(fOut);
SkDebugf("wrote %s\n", fileName.c_str());
return true;
}
bool IncludeParser::findComments(const Definition& includeDef, Definition* markupDef) {
// add comment preceding class, if any
const Definition* parent = includeDef.fParent;
int index = includeDef.fParentIndex;
auto wordIter = parent->fTokens.begin();
std::advance(wordIter, index);
SkASSERT(&*wordIter == &includeDef);
while (parent->fTokens.begin() != wordIter) {
auto testIter = std::prev(wordIter);
if (Definition::Type::kWord != testIter->fType
&& Definition::Type::kKeyWord != testIter->fType
&& (Definition::Type::kBracket != testIter->fType
|| Bracket::kAngle != testIter->fBracket)
&& (Definition::Type::kPunctuation != testIter->fType
|| Punctuation::kAsterisk != testIter->fPunctuation)) {
break;
}
wordIter = testIter;
}
auto commentIter = wordIter;
while (parent->fTokens.begin() != commentIter) {
auto testIter = std::prev(commentIter);
bool isComment = Definition::Type::kBracket == testIter->fType
&& (Bracket::kSlashSlash == testIter->fBracket
|| Bracket::kSlashStar == testIter->fBracket);
if (!isComment) {
break;
}
commentIter = testIter;
}
while (commentIter != wordIter) {
if (!this->parseComment(commentIter->fFileName, commentIter->fContentStart,
commentIter->fContentEnd, commentIter->fLineCount, markupDef)) {
return false;
}
commentIter = std::next(commentIter);
}
return true;
}
bool IncludeParser::internalName(const Definition& token) const {
return 0 == token.fName.find("internal_")
|| 0 == token.fName.find("Internal_")
|| 0 == token.fName.find("legacy_")
|| 0 == token.fName.find("temporary_")
|| 0 == token.fName.find("private_");
}
// caller calls reportError, so just return false here
bool IncludeParser::parseClass(Definition* includeDef, IsStruct isStruct) {
SkASSERT(includeDef->fTokens.size() > 0);
// parse class header
auto iter = includeDef->fTokens.begin();
if (!strncmp(iter->fStart, "SK_API", iter->fContentEnd - iter->fStart)) {
// todo : documentation is ignoring this for now
iter = std::next(iter);
}
string nameStr(iter->fStart, iter->fContentEnd - iter->fStart);
includeDef->fName = nameStr;
iter = std::next(iter);
if (iter == includeDef->fTokens.end()) {
return true; // forward declaration only
}
do {
if (iter == includeDef->fTokens.end()) {
return includeDef->reportError<bool>("unexpected end");
}
if ('{' == iter->fStart[0] && Definition::Type::kPunctuation == iter->fType) {
break;
}
} while (static_cast<void>(iter = std::next(iter)), true);
if (Punctuation::kLeftBrace != iter->fPunctuation) {
return iter->reportError<bool>("expected left brace");
}
IClassDefinition* markupDef = this->defineClass(*includeDef, nameStr);
if (!markupDef) {
return iter->reportError<bool>("expected markup definition");
}
markupDef->fStart = iter->fStart;
if (!this->findComments(*includeDef, markupDef)) {
return iter->reportError<bool>("find comments failed");
}
// if (1 != includeDef->fChildren.size()) {
// return false; // fix me: SkCanvasClipVisitor isn't correctly parsed
// }
includeDef = includeDef->fChildren.front();
iter = includeDef->fTokens.begin();
// skip until public
int publicIndex = 0;
if (IsStruct::kNo == isStruct) {
const char* publicName = kKeyWords[(int) KeyWord::kPublic].fName;
size_t publicLen = strlen(publicName);
while (iter != includeDef->fTokens.end()
&& (publicLen != (size_t) (iter->fContentEnd - iter->fStart)
|| strncmp(iter->fStart, publicName, publicLen))) {
iter = std::next(iter);
++publicIndex;
}
}
auto childIter = includeDef->fChildren.begin();
while (childIter != includeDef->fChildren.end() && (*childIter)->fParentIndex < publicIndex) {
(*childIter)->fPrivate = true;
childIter = std::next(childIter);
}
int keyIndex = publicIndex;
KeyWord currentKey = KeyWord::kPublic;
const char* publicName = kKeyWords[(int) KeyWord::kPublic].fName;
size_t publicLen = strlen(publicName);
const char* protectedName = kKeyWords[(int) KeyWord::kProtected].fName;
size_t protectedLen = strlen(protectedName);
const char* privateName = kKeyWords[(int) KeyWord::kPrivate].fName;
size_t privateLen = strlen(privateName);
while (childIter != includeDef->fChildren.end()) {
Definition* child = *childIter;
while (child->fParentIndex > keyIndex && iter != includeDef->fTokens.end()) {
const char* testStart = iter->fStart;
size_t testLen = (size_t) (iter->fContentEnd - testStart);
iter = std::next(iter);
++keyIndex;
if (publicLen == testLen && !strncmp(testStart, publicName, testLen)) {
currentKey = KeyWord::kPublic;
break;
}
if (protectedLen == testLen && !strncmp(testStart, protectedName, testLen)) {
currentKey = KeyWord::kProtected;
break;
}
if (privateLen == testLen && !strncmp(testStart, privateName, testLen)) {
currentKey = KeyWord::kPrivate;
break;
}
}
fLastObject = nullptr;
if (KeyWord::kPublic == currentKey) {
if (!this->parseObject(child, markupDef)) {
return false;
}
} else {
child->fPrivate = true;
}
fLastObject = child;
childIter = std::next(childIter);
}
SkASSERT(fParent->fParent);
fParent = fParent->fParent;
return true;
}
bool IncludeParser::parseComment(const string& filename, const char* start, const char* end,
int lineCount, Definition* markupDef) {
TextParser parser(filename, start, end, lineCount);
// parse doxygen if present
if (parser.startsWith("**")) {
parser.next();
parser.next();
parser.skipWhiteSpace();
if ('\\' == parser.peek()) {
parser.next();
if (!parser.skipWord(kKeyWords[(int) markupDef->fKeyWord].fName)) {
return reportError<bool>("missing object type");
}
if (!parser.skipWord(markupDef->fName.c_str()) &&
KeyWord::kEnum != markupDef->fKeyWord) {
return reportError<bool>("missing object name");
}
}
}
// remove leading '*' if present
Definition* parent = markupDef->fTokens.size() ? &markupDef->fTokens.back() : markupDef;
while (!parser.eof() && parser.skipWhiteSpace()) {
while ('*' == parser.peek()) {
parser.next();
if (parser.eof()) {
break;
}
parser.skipWhiteSpace();
}
if (parser.eof()) {
break;
}
const char* lineEnd = parser.trimmedLineEnd();
markupDef->fTokens.emplace_back(MarkType::kComment, parser.fChar, lineEnd,
parser.fLineCount, parent);
parser.skipToEndBracket('\n');
}
return true;
}
bool IncludeParser::parseDefine() {
return true;
}
bool IncludeParser::parseEnum(Definition* child, Definition* markupDef) {
string nameStr;
if (child->fTokens.size() > 0) {
auto token = child->fTokens.begin();
if (Definition::Type::kKeyWord == token->fType && KeyWord::kClass == token->fKeyWord) {
token = token->fTokens.begin();
}
if (Definition::Type::kWord == token->fType) {
nameStr += string(token->fStart, token->fContentEnd - token->fStart);
}
}
markupDef->fTokens.emplace_back(MarkType::kEnum, child->fContentStart, child->fContentEnd,
child->fLineCount, markupDef);
Definition* markupChild = &markupDef->fTokens.back();
SkASSERT(KeyWord::kNone == markupChild->fKeyWord);
markupChild->fKeyWord = KeyWord::kEnum;
TextParser enumName(child);
enumName.skipExact("enum ");
if (enumName.skipExact("class ")) {
markupChild->fMarkType = MarkType::kEnumClass;
}
const char* nameStart = enumName.fChar;
enumName.skipToSpace();
markupChild->fName = markupDef->fName + "::" +
string(nameStart, (size_t) (enumName.fChar - nameStart));
if (!this->findComments(*child, markupChild)) {
return false;
}
TextParser parser(child);
parser.skipToEndBracket('{');
parser.next();
const char* dataEnd;
do {
parser.skipWhiteSpace();
if ('}' == parser.peek()) {
break;
}
Definition* comment = nullptr;
// note that comment, if any, can be before or after (on the same line, though) as member
if ('#' == parser.peek()) {
// fixme: handle preprecessor, but just skip it for now
parser.skipToLineStart();
}
while (parser.startsWith("/*") || parser.startsWith("//")) {
parser.next();
const char* start = parser.fChar;
const char* end;
if ('*' == parser.peek()) {
end = parser.strnstr("*/", parser.fEnd);
parser.fChar = end;
parser.next();
parser.next();
} else {
end = parser.trimmedLineEnd();
parser.skipToLineStart();
}
markupChild->fTokens.emplace_back(MarkType::kComment, start, end, parser.fLineCount,
markupChild);
comment = &markupChild->fTokens.back();
comment->fTerminator = end;
if (!this->parseComment(parser.fFileName, start, end, parser.fLineCount, comment)) {
return false;
}
parser.skipWhiteSpace();
}
parser.skipWhiteSpace();
const char* memberStart = parser.fChar;
if ('}' == memberStart[0]) {
break;
}
// if there's comment on same the line as member def, output first as if it was before
parser.skipToNonAlphaNum();
string memberName(memberStart, parser.fChar);
if (parser.eof() || !parser.skipWhiteSpace()) {
return this->reportError<bool>("enum member must end with comma 1");
}
const char* dataStart = parser.fChar;
if ('=' == parser.peek()) {
parser.skipToEndBracket(',');
}
if (parser.eof() || ',' != parser.peek()) {
return this->reportError<bool>("enum member must end with comma 2");
}
dataEnd = parser.fChar;
const char* start = parser.anyOf("/\n");
SkASSERT(start);
parser.skipTo(start);
if ('/' == parser.next()) {
char slashStar = parser.next();
if ('/' == slashStar || '*' == slashStar) {
TextParser::Save save(&parser);
char doxCheck = parser.next();
if ((slashStar != doxCheck && '!' != doxCheck) || '<' != parser.next()) {
save.restore();
}
}
parser.skipWhiteSpace();
const char* commentStart = parser.fChar;
if ('/' == slashStar) {
parser.skipToEndBracket('\n');
} else {
parser.skipToEndBracket("*/");
}
SkASSERT(!parser.eof());
const char* commentEnd = parser.fChar;
markupChild->fTokens.emplace_back(MarkType::kComment, commentStart, commentEnd,
parser.fLineCount, markupChild);
comment = &markupChild->fTokens.back();
comment->fTerminator = commentEnd;
}
markupChild->fTokens.emplace_back(MarkType::kMember, dataStart, dataEnd, parser.fLineCount,
markupChild);
Definition* member = &markupChild->fTokens.back();
member->fName = memberName;
if (comment) {
member->fChildren.push_back(comment);
comment->fPrivate = true;
}
markupChild->fChildren.push_back(member);
} while (true);
for (auto count : child->fChildren) {
if (Definition::Type::kBracket == count->fType) {
continue;
}
SkASSERT(Definition::Type::kKeyWord == count->fType);
if (KeyWord::kClass == count->fKeyWord) {
continue;
}
SkASSERT(KeyWord::kStatic == count->fKeyWord);
markupChild->fTokens.emplace_back(MarkType::kMember, count->fContentStart,
count->fContentEnd, count->fLineCount, markupChild);
Definition* member = &markupChild->fTokens.back();
member->fName = count->fName;
// FIXME: ? add comment as well ?
markupChild->fChildren.push_back(member);
break;
}
IClassDefinition& classDef = fIClassMap[markupDef->fName];
SkASSERT(classDef.fStart);
string uniqueName = this->uniqueName(classDef.fEnums, nameStr);
markupChild->fName = uniqueName;
classDef.fEnums[uniqueName] = markupChild;
return true;
}
bool IncludeParser::parseInclude(const string& name) {
fParent = &fIncludeMap[name];
fParent->fName = name;
fParent->fFileName = fFileName;
fParent->fType = Definition::Type::kFileType;
fParent->fContentStart = fChar;
fParent->fContentEnd = fEnd;
// parse include file into tree
while (fChar < fEnd) {
if (!this->parseChar()) {
return false;
}
}
// parse tree and add named objects to maps
fParent = &fIncludeMap[name];
if (!this->parseObjects(fParent, nullptr)) {
return false;
}
return true;
}
bool IncludeParser::parseMember(Definition* child, Definition* markupDef) {
const char* typeStart = child->fChildren[0]->fContentStart;
markupDef->fTokens.emplace_back(MarkType::kMember, typeStart, child->fContentStart,
child->fLineCount, markupDef);
Definition* markupChild = &markupDef->fTokens.back();
TextParser nameParser(child);
nameParser.skipToNonAlphaNum();
string nameStr = string(child->fContentStart, nameParser.fChar - child->fContentStart);
IClassDefinition& classDef = fIClassMap[markupDef->fName];
string uniqueName = this->uniqueName(classDef.fMethods, nameStr);
markupChild->fName = uniqueName;
markupChild->fTerminator = markupChild->fContentEnd;
classDef.fMembers[uniqueName] = markupChild;
if (child->fParentIndex >= 2) {
auto comment = child->fParent->fTokens.begin();
std::advance(comment, child->fParentIndex - 2);
if (Definition::Type::kBracket == comment->fType
&& (Bracket::kSlashStar == comment->fBracket
|| Bracket::kSlashSlash == comment->fBracket)) {
TextParser parser(&*comment);
do {
parser.skipToAlpha();
if (parser.eof()) {
break;
}
const char* start = parser.fChar;
const char* end = parser.trimmedBracketEnd('\n');
if (Bracket::kSlashStar == comment->fBracket) {
const char* commentEnd = parser.strnstr("*/", end);
if (commentEnd) {
end = commentEnd;
}
}
markupDef->fTokens.emplace_back(MarkType::kComment, start, end, child->fLineCount,
markupDef);
Definition* commentChild = &markupDef->fTokens.back();
markupChild->fChildren.emplace_back(commentChild);
parser.skipTo(end);
} while (!parser.eof());
}
}
return true;
}
bool IncludeParser::parseMethod(Definition* child, Definition* markupDef) {
auto tokenIter = child->fParent->fTokens.begin();
std::advance(tokenIter, child->fParentIndex);
tokenIter = std::prev(tokenIter);
const char* nameEnd = tokenIter->fContentEnd;
bool add2 = false;
if ('[' == tokenIter->fStart[0]) {
auto closeParen = std::next(tokenIter);
SkASSERT(Definition::Type::kBracket == closeParen->fType &&
'(' == closeParen->fContentStart[0]);
nameEnd = closeParen->fContentEnd + 1;
closeParen = std::next(closeParen);
add2 = true;
if (Definition::Type::kKeyWord == closeParen->fType &&
KeyWord::kConst == closeParen->fKeyWord) {
add2 = false;
}
tokenIter = std::prev(tokenIter);
}
string nameStr(tokenIter->fStart, nameEnd - tokenIter->fStart);
if (add2) {
nameStr += "_2";
}
while (tokenIter != child->fParent->fTokens.begin()) {
auto testIter = std::prev(tokenIter);
switch (testIter->fType) {
case Definition::Type::kWord:
if (testIter == child->fParent->fTokens.begin() &&
(KeyWord::kIfdef == child->fParent->fKeyWord ||
KeyWord::kIfndef == child->fParent->fKeyWord ||
KeyWord::kIf == child->fParent->fKeyWord)) {
std::next(tokenIter);
break;
}
goto keepGoing;
case Definition::Type::kKeyWord: {
KeyProperty keyProperty = kKeyWords[(int) testIter->fKeyWord].fProperty;
if (KeyProperty::kNumber == keyProperty || KeyProperty::kModifier == keyProperty) {
goto keepGoing;
}
} break;
case Definition::Type::kBracket:
if (Bracket::kAngle == testIter->fBracket) {
goto keepGoing;
}
break;
case Definition::Type::kPunctuation:
if (Punctuation::kSemicolon == testIter->fPunctuation
|| Punctuation::kLeftBrace == testIter->fPunctuation
|| Punctuation::kColon == testIter->fPunctuation) {
break;
}
keepGoing:
tokenIter = testIter;
continue;
default:
break;
}
break;
}
tokenIter->fName = nameStr;
tokenIter->fMarkType = MarkType::kMethod;
tokenIter->fPrivate = string::npos != nameStr.find("::");
auto testIter = child->fParent->fTokens.begin();
SkASSERT(child->fParentIndex > 0);
std::advance(testIter, child->fParentIndex - 1);
if (tokenIter->fParent && KeyWord::kIfdef == tokenIter->fParent->fKeyWord &&
0 == tokenIter->fParentIndex) {
tokenIter = std::next(tokenIter);
}
const char* start = tokenIter->fContentStart;
const char* end = tokenIter->fContentEnd;
const char kDebugCodeStr[] = "SkDEBUGCODE";
const size_t kDebugCodeLen = sizeof(kDebugCodeStr) - 1;
if (end - start == kDebugCodeLen && !strncmp(start, kDebugCodeStr, kDebugCodeLen)) {
std::advance(testIter, 1);
start = testIter->fContentStart + 1;
end = testIter->fContentEnd - 1;
} else {
end = testIter->fContentEnd;
while (testIter != child->fParent->fTokens.end()) {
testIter = std::next(testIter);
switch (testIter->fType) {
case Definition::Type::kPunctuation:
SkASSERT(Punctuation::kSemicolon == testIter->fPunctuation
|| Punctuation::kLeftBrace == testIter->fPunctuation
|| Punctuation::kColon == testIter->fPunctuation);
end = testIter->fStart;
break;
case Definition::Type::kKeyWord: {
KeyProperty keyProperty = kKeyWords[(int) testIter->fKeyWord].fProperty;
if (KeyProperty::kNumber == keyProperty || KeyProperty::kModifier == keyProperty) {
continue;
}
} break;
default:
continue;
}
break;
}
}
while (end > start && ' ' >= end[-1]) {
--end;
}
if (!markupDef) {
auto parentIter = child->fParent->fTokens.begin();
SkASSERT(child->fParentIndex > 0);
std::advance(parentIter, child->fParentIndex - 1);
Definition* methodName = &*parentIter;
TextParser name(methodName);
if (name.skipToEndBracket(':') && name.startsWith("::")) {
return true; // expect this is inline class definition outside of class
}
SkASSERT(0); // code incomplete
}
markupDef->fTokens.emplace_back(MarkType::kMethod, start, end, tokenIter->fLineCount,
markupDef);
Definition* markupChild = &markupDef->fTokens.back();
// do find instead -- I wonder if there is a way to prevent this in c++
IClassDefinition& classDef = fIClassMap[markupDef->fName];
SkASSERT(classDef.fStart);
string uniqueName = this->uniqueName(classDef.fMethods, nameStr);
markupChild->fName = uniqueName;
if (!this->findComments(*child, markupChild)) {
return false;
}
classDef.fMethods[uniqueName] = markupChild;
return true;
}
bool IncludeParser::parseObjects(Definition* parent, Definition* markupDef) {
for (auto& child : parent->fChildren) {
if (!this->parseObject(child, markupDef)) {
return false;
}
}
return true;
}
bool IncludeParser::parseObject(Definition* child, Definition* markupDef) {
// set up for error reporting
fLine = fChar = child->fStart;
fEnd = child->fContentEnd;
// todo: put original line number in child as well
switch (child->fType) {
case Definition::Type::kKeyWord:
switch (child->fKeyWord) {
case KeyWord::kClass:
if (!this->parseClass(child, IsStruct::kNo)) {
return false;
}
break;
case KeyWord::kEnum:
if (!this->parseEnum(child, markupDef)) {
return child->reportError<bool>("failed to parse enum");
}
break;
case KeyWord::kStruct:
if (!this->parseClass(child, IsStruct::kYes)) {
return child->reportError<bool>("failed to parse struct");
}
break;
case KeyWord::kTemplate:
if (!this->parseTemplate()) {
return child->reportError<bool>("failed to parse template");
}
break;
case KeyWord::kTypedef:
if (!this->parseTypedef()) {
return child->reportError<bool>("failed to parse typedef");
}
break;
case KeyWord::kUnion:
if (!this->parseUnion()) {
return child->reportError<bool>("failed to parse union");
}
break;
default:
return child->reportError<bool>("unhandled keyword");
}
break;
case Definition::Type::kBracket:
switch (child->fBracket) {
case Bracket::kParen:
if (fLastObject) {
TextParser checkDeprecated(child->fFileName, fLastObject->fTerminator + 1,
child->fStart, fLastObject->fLineCount);
if (!checkDeprecated.eof()) {
checkDeprecated.skipWhiteSpace();
if (checkDeprecated.startsWith("SK_ATTR_DEPRECATED")) {
break;
}
}
}
{
auto tokenIter = child->fParent->fTokens.begin();
std::advance(tokenIter, child->fParentIndex);
tokenIter = std::prev(tokenIter);
TextParser checkDeprecated(&*tokenIter);
if (checkDeprecated.startsWith("SK_ATTR_DEPRECATED")) {
break;
}
}
if (!this->parseMethod(child, markupDef)) {
return child->reportError<bool>("failed to parse method");
}
break;
case Bracket::kSlashSlash:
case Bracket::kSlashStar:
// comments are picked up by parsing objects first
break;
case Bracket::kPound:
// special-case the #xxx xxx_DEFINED entries
switch (child->fKeyWord) {
case KeyWord::kIfndef:
case KeyWord::kIfdef:
if (child->boilerplateIfDef(fParent)) {
if (!this->parseObjects(child, markupDef)) {
return false;
}
break;
}
goto preproError;
case KeyWord::kDefine:
if (child->boilerplateDef(fParent)) {
break;
}
goto preproError;
case KeyWord::kEndif:
if (child->boilerplateEndIf()) {
break;
}
case KeyWord::kInclude:
// ignored for now
break;
case KeyWord::kElse:
case KeyWord::kElif:
// todo: handle these
break;
default:
preproError:
return child->reportError<bool>("unhandled preprocessor");
}
break;
case Bracket::kAngle:
// pick up templated function pieces when method is found
break;
case Bracket::kDebugCode:
if (!this->parseObjects(child, markupDef)) {
return false;
}
break;
case Bracket::kSquare: {
// check to see if parent is operator, the only case we handle so far
auto prev = child->fParent->fTokens.begin();
std::advance(prev, child->fParentIndex - 1);
if (KeyWord::kOperator != prev->fKeyWord) {
return child->reportError<bool>("expected operator overload");
}
} break;
default:
return child->reportError<bool>("unhandled bracket");
}
break;
case Definition::Type::kWord:
if (MarkType::kMember != child->fMarkType) {
return child->reportError<bool>("unhandled word type");
}
if (!this->parseMember(child, markupDef)) {
return child->reportError<bool>("unparsable member");
}
break;
default:
return child->reportError<bool>("unhandled type");
break;
}
return true;
}
bool IncludeParser::parseTemplate() {
return true;
}
bool IncludeParser::parseTypedef() {
return true;
}
bool IncludeParser::parseUnion() {
return true;
}
bool IncludeParser::parseChar() {
char test = *fChar;
if ('\\' == fPrev) {
if ('\n' == test) {
// ++fLineCount;
fLine = fChar + 1;
}
goto done;
}
switch (test) {
case '\n':
// ++fLineCount;
fLine = fChar + 1;
if (fInChar) {
return reportError<bool>("malformed char");
}
if (fInString) {
return reportError<bool>("malformed string");
}
if (!this->checkForWord()) {
return false;
}
if (Bracket::kPound == this->topBracket()) {
KeyWord keyWord = fParent->fKeyWord;
if (KeyWord::kNone == keyWord) {
return this->reportError<bool>("unhandled preprocessor directive");
}
if (KeyWord::kInclude == keyWord || KeyWord::kDefine == keyWord) {
this->popBracket();
}
} else if (Bracket::kSlashSlash == this->topBracket()) {
this->popBracket();
}
break;
case '*':
if (!fInCharCommentString && '/' == fPrev) {
this->pushBracket(Bracket::kSlashStar);
}
if (!this->checkForWord()) {
return false;
}
if (!fInCharCommentString) {
this->addPunctuation(Punctuation::kAsterisk);
}
break;
case '/':
if ('*' == fPrev) {
if (!fInCharCommentString) {
return reportError<bool>("malformed closing comment");
}
if (Bracket::kSlashStar == this->topBracket()) {
this->next(); // include close in bracket -- FIXME? will this skip stuff?
this->popBracket();
}
break;
}
if (!fInCharCommentString && '/' == fPrev) {
this->pushBracket(Bracket::kSlashSlash);
break;
}
if (!this->checkForWord()) {
return false;
}
break;
case '\'':
if (Bracket::kChar == this->topBracket()) {
this->popBracket();
} else if (!fInComment && !fInString) {
if (fIncludeWord) {
return this->reportError<bool>("word then single-quote");
}
this->pushBracket(Bracket::kChar);
}
break;
case '\"':
if (Bracket::kString == this->topBracket()) {
this->popBracket();
} else if (!fInComment && !fInChar) {
if (fIncludeWord) {
return this->reportError<bool>("word then double-quote");
}
this->pushBracket(Bracket::kString);
}
break;
case ':':
case '(':
case '[':
case '{': {
if (fIncludeWord && '(' == test && fChar - fIncludeWord >= 10 &&
!strncmp("SkDEBUGCODE", fIncludeWord, 10)) {
this->pushBracket(Bracket::kDebugCode);
break;
}
if (fInCharCommentString) {
break;
}
if (':' == test && (fInBrace || ':' == fChar[-1] || ':' == fChar[1])) {
break;
}
if (!fInBrace) {
if (!this->checkForWord()) {
return false;
}
if (':' == test && !fInFunction) {
break;
}
if ('{' == test) {
this->addPunctuation(Punctuation::kLeftBrace);
} else if (':' == test) {
this->addPunctuation(Punctuation::kColon);
}
}
if (fInBrace && '{' == test && Definition::Type::kBracket == fInBrace->fType
&& Bracket::kColon == fInBrace->fBracket) {
Definition* braceParent = fParent->fParent;
braceParent->fChildren.pop_back();
braceParent->fTokens.pop_back();
fParent = braceParent;
fInBrace = nullptr;
}
this->pushBracket(
'(' == test ? Bracket::kParen :
'[' == test ? Bracket::kSquare :
'{' == test ? Bracket::kBrace :
Bracket::kColon);
if (!fInBrace
&& ('{' == test || (':' == test && ' ' >= fChar[1]))
&& fInFunction) {
fInBrace = fParent;
}
} break;
case '<':
if (fInCharCommentString || fInBrace) {
break;
}
if (!this->checkForWord()) {
return false;
}
if (fInEnum) {
break;
}
this->pushBracket(Bracket::kAngle);
break;
case ')':
case ']':
case '}': {
if (fInCharCommentString) {
break;
}
if (!fInBrace) {
if (!this->checkForWord()) {
return false;
}
}
bool popBraceParent = fInBrace == fParent;
if ((')' == test ? Bracket::kParen :
']' == test ? Bracket::kSquare : Bracket::kBrace) == this->topBracket()) {
this->popBracket();
if (!fInFunction) {
bool deprecatedMacro = false;
if (')' == test) {
auto iter = fParent->fTokens.end();
bool lookForWord = false;
while (fParent->fTokens.begin() != iter) {
--iter;
if (lookForWord) {
if (Definition::Type::kWord != iter->fType) {
break;
}
string word(iter->fContentStart, iter->length());
if ("SK_ATTR_EXTERNALLY_DEPRECATED" == word) {
deprecatedMacro = true;
// remove macro paren (would confuse method parsing later)
fParent->fTokens.pop_back();
fParent->fChildren.pop_back();
}
break;
}
if (Definition::Type::kBracket != iter->fType) {
break;
}
if (Bracket::kParen != iter->fBracket) {
break;
}
lookForWord = true;
}
}
fInFunction = ')' == test && !deprecatedMacro;
} else {
fInFunction = '}' != test;
}
} else if (')' == test && Bracket::kDebugCode == this->topBracket()) {
this->popBracket();
} else {
return reportError<bool>("malformed close bracket");
}
if (popBraceParent) {
Definition* braceParent = fInBrace->fParent;
braceParent->fChildren.pop_back();
braceParent->fTokens.pop_back();
fInBrace = nullptr;
}
} break;
case '>':
if (fInCharCommentString || fInBrace) {
break;
}
if (!this->checkForWord()) {
return false;
}
if (fInEnum) {
break;
}
if (Bracket::kAngle == this->topBracket()) {
this->popBracket();
} else {
return reportError<bool>("malformed close angle bracket");
}
break;
case '#': {
if (fInCharCommentString || fInBrace) {
break;
}
SkASSERT(!fIncludeWord); // don't expect this, curious if it is triggered
this->pushBracket(Bracket::kPound);
break;
}
case '&':
case ',':
case ' ':
if (fInCharCommentString || fInBrace) {
break;
}
if (!this->checkForWord()) {
return false;
}
break;
case ';':
if (fInCharCommentString || fInBrace) {
break;
}
if (!this->checkForWord()) {
return false;
}
if (Definition::Type::kKeyWord == fParent->fType
&& KeyProperty::kObject == (kKeyWords[(int) fParent->fKeyWord].fProperty)) {
if (KeyWord::kClass == fParent->fKeyWord && fParent->fParent &&
KeyWord::kEnum == fParent->fParent->fKeyWord) {
this->popObject();
}
if (KeyWord::kEnum == fParent->fKeyWord) {
fInEnum = false;
}
this->popObject();
} else if (Definition::Type::kBracket == fParent->fType
&& fParent->fParent && Definition::Type::kKeyWord == fParent->fParent->fType
&& KeyWord::kStruct == fParent->fParent->fKeyWord) {
list<Definition>::iterator baseIter = fParent->fTokens.end();
list<Definition>::iterator namedIter = fParent->fTokens.end();
for (auto tokenIter = fParent->fTokens.end();
fParent->fTokens.begin() != tokenIter--; ) {
if (tokenIter->fLineCount == fLineCount) {
if ('f' == tokenIter->fStart[0] && isupper(tokenIter->fStart[1])) {
if (namedIter != fParent->fTokens.end()) {
return reportError<bool>("found two named member tokens");
}
namedIter = tokenIter;
}
baseIter = tokenIter;
} else {
break;
}
}
// FIXME: if a member definition spans multiple lines, this won't work
if (namedIter != fParent->fTokens.end()) {
if (baseIter == namedIter) {
return this->reportError<bool>("expected type before named token");
}
Definition* member = &*namedIter;
member->fMarkType = MarkType::kMember;
if (!member->fTerminator) {
member->fTerminator = member->fContentEnd;
}
fParent->fChildren.push_back(member);
for (auto nameType = baseIter; nameType != namedIter; ++nameType) {
member->fChildren.push_back(&*nameType);
}
}
} else if (fParent->fChildren.size() > 0) {
auto lastIter = fParent->fChildren.end();
Definition* priorEnum;
while (fParent->fChildren.begin() != lastIter) {
std::advance(lastIter, -1);
priorEnum = *lastIter;
if (Definition::Type::kBracket != priorEnum->fType ||
(Bracket::kSlashSlash != priorEnum->fBracket
&& Bracket::kSlashStar != priorEnum->fBracket)) {
break;
}
}
if (Definition::Type::kKeyWord == priorEnum->fType
&& KeyWord::kEnum == priorEnum->fKeyWord) {
auto tokenWalker = fParent->fTokens.begin();
std::advance(tokenWalker, priorEnum->fParentIndex);
SkASSERT(KeyWord::kEnum == tokenWalker->fKeyWord);
while (tokenWalker != fParent->fTokens.end()) {
std::advance(tokenWalker, 1);
if (Punctuation::kSemicolon == tokenWalker->fPunctuation) {
break;
}
}
while (tokenWalker != fParent->fTokens.end()) {
std::advance(tokenWalker, 1);
const Definition* test = &*tokenWalker;
if (Definition::Type::kBracket != test->fType ||
(Bracket::kSlashSlash != test->fBracket
&& Bracket::kSlashStar != test->fBracket)) {
break;
}
}
Definition* start = &*tokenWalker;
bool foundExpected = true;
for (KeyWord expected : {KeyWord::kStatic, KeyWord::kConstExpr, KeyWord::kInt}){
const Definition* test = &*tokenWalker;
if (expected != test->fKeyWord) {
foundExpected = false;
break;
}
if (tokenWalker == fParent->fTokens.end()) {
break;
}
std::advance(tokenWalker, 1);
}
if (foundExpected && tokenWalker != fParent->fTokens.end()) {
const char* nameStart = tokenWalker->fStart;
std::advance(tokenWalker, 1);
if (tokenWalker != fParent->fTokens.end()) {
TextParser tp(fFileName, nameStart, tokenWalker->fStart, fLineCount);
tp.skipToNonAlphaNum();
start->fName = string(nameStart, tp.fChar - nameStart);
start->fContentEnd = fChar;
priorEnum->fChildren.emplace_back(start);
}
}
}
}
this->addPunctuation(Punctuation::kSemicolon);
fInFunction = false;
break;
case '~':
if (fInEnum) {
break;
}
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
// TODO: don't want to parse numbers, but do need to track for enum defs
// break;
case 'A': case 'B': case 'C': case 'D': case 'E':
case 'F': case 'G': case 'H': case 'I': case 'J':
case 'K': case 'L': case 'M': case 'N': case 'O':
case 'P': case 'Q': case 'R': case 'S': case 'T':
case 'U': case 'V': case 'W': case 'X': case 'Y':
case 'Z': case '_':
case 'a': case 'b': case 'c': case 'd': case 'e':
case 'f': case 'g': case 'h': case 'i': case 'j':
case 'k': case 'l': case 'm': case 'n': case 'o':
case 'p': case 'q': case 'r': case 's': case 't':
case 'u': case 'v': case 'w': case 'x': case 'y':
case 'z':
if (fInCharCommentString || fInBrace) {
break;
}
if (!fIncludeWord) {
fIncludeWord = fChar;
}
break;
}
done:
fPrev = test;
this->next();
return true;
}
void IncludeParser::validate() const {
for (int index = 0; index <= (int) Last_MarkType; ++index) {
SkASSERT(fMaps[index].fMarkType == (MarkType) index);
}
IncludeParser::ValidateKeyWords();
}