/* * Copyright 2017 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #ifndef bookmaker_DEFINED #define bookmaker_DEFINED #define STDOUT_TO_IDE_OUT 0 #include "SkData.h" #include #include #include #include #include #include #include #include // std::to_string isn't implemented on android #include template std::string to_string(T value) { std::ostringstream os ; os << value ; return os.str() ; } using std::forward_list; using std::list; using std::unordered_map; using std::string; using std::vector; enum class KeyWord { kNone, kBool, kChar, kClass, kConst, kConstExpr, kDefine, kDouble, kElif, kElse, kEndif, kEnum, kFloat, kFriend, kIf, kIfdef, kIfndef, kInclude, kInline, kInt, kOperator, kPrivate, kProtected, kPublic, kSigned, kSize_t, kStatic, kStruct, kTemplate, kTypedef, kUint32_t, kUnion, kUnsigned, kVoid, }; enum class MarkType { kNone, kAnchor, kAlias, kBug, kClass, kCode, kColumn, kComment, kConst, kDefine, kDefinedBy, kDeprecated, kDescription, kDoxygen, kEnum, kEnumClass, kError, kExample, kExperimental, kExternal, kFile, kFormula, kFunction, kHeight, kImage, kLegend, kLink, kList, kMarkChar, kMember, kMethod, kNoExample, kParam, kPlatform, kPrivate, kReturn, kRoot, kRow, kSeeAlso, kStdOut, kStruct, kSubstitute, kSubtopic, kTable, kTemplate, kText, kTime, kToDo, kTopic, kTrack, kTypedef, kUnion, kVolatile, kWidth, }; enum { Last_MarkType = (int) MarkType::kWidth, }; enum class Bracket { kNone, kParen, kSquare, kBrace, kAngle, kString, kChar, kSlashStar, kSlashSlash, kPound, kColon, }; enum class Punctuation { // catch-all for misc symbols tracked in C kNone, kAsterisk, // for pointer-to kSemicolon, // e.g., to delinate xxx() const ; const int* yyy() kLeftBrace, kColon, // for foo() : bar(1), baz(2) {} }; static inline bool has_nonwhitespace(const string& s) { bool nonwhite = false; for (const char& c : s) { if (' ' < c) { nonwhite = true; break; } } return nonwhite; } static inline void trim_end(string &s) { s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun(std::isspace))).base(), s.end()); } static inline void trim_end_spaces(string &s) { while (s.length() > 0 && ' ' == s.back()) { s.pop_back(); } } static inline void trim_start(string &s) { s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(std::isspace)))); } static inline void trim_start_end(string& s) { trim_start(s); trim_end(s); } class NonAssignable { public: NonAssignable(NonAssignable const&) = delete; NonAssignable& operator=(NonAssignable const&) = delete; NonAssignable() {} }; class Definition; class TextParser : public NonAssignable { TextParser() {} // only for ParserCommon to call friend class ParserCommon; public: enum OneLine { kNo, kYes }; class Save { public: Save(TextParser* parser) { fParser = parser; fLine = parser->fLine; fChar = parser->fChar; fLineCount = parser->fLineCount; } void restore() const { fParser->fLine = fLine; fParser->fChar = fChar; fParser->fLineCount = fLineCount; } private: TextParser* fParser; const char* fLine; const char* fChar; int fLineCount; }; TextParser(const string& fileName, const char* start, const char* end, int lineCount) : fFileName(fileName) , fStart(start) , fLine(start) , fChar(start) , fEnd(end) , fLineCount(lineCount) { } TextParser(const Definition* ); const char* anyOf(const char* str) const { const char* ptr = fChar; while (ptr < fEnd) { if (strchr(str, ptr[0])) { return ptr; } ++ptr; } return nullptr; } const char* anyOf(const char* wordStart, const char* wordList[], size_t wordListCount) const { const char** wordPtr = wordList; const char** wordEnd = wordPtr + wordListCount; const size_t matchLen = fChar - wordStart; while (wordPtr < wordEnd) { const char* word = *wordPtr++; if (strlen(word) == matchLen && !strncmp(wordStart, word, matchLen)) { return word; } } return nullptr; } char backup(const char* pattern) const { size_t len = strlen(pattern); const char* start = fChar - len; if (start <= fStart) { return '\0'; } if (strncmp(start, pattern, len)) { return '\0'; } return start[-1]; } bool contains(const char* match, const char* lineEnd, const char** loc) const { *loc = this->strnstr(match, lineEnd); return *loc; } bool eof() const { return fChar >= fEnd; } const char* lineEnd() const { const char* ptr = fChar; do { if (ptr >= fEnd) { return ptr; } char test = *ptr++; if (test == '\n' || test == '\0') { break; } } while (true); return ptr; } ptrdiff_t lineLength() const { return this->lineEnd() - fLine; } bool match(TextParser* ); char next() { SkASSERT(fChar < fEnd); char result = *fChar++; if ('\n' == result) { ++fLineCount; fLine = fChar; } return result; } char peek() const { SkASSERT(fChar < fEnd); return *fChar; } void restorePlace(const TextParser& save) { fChar = save.fChar; fLine = save.fLine; fLineCount = save.fLineCount; } void savePlace(TextParser* save) { save->fChar = fChar; save->fLine = fLine; save->fLineCount = fLineCount; } void reportError(const char* errorStr) const; void reportWarning(const char* errorStr) const; template T reportError(const char* errorStr) const { this->reportError(errorStr); return T(); } bool sentenceEnd(const char* check) const { while (check > fStart) { --check; if (' ' < check[0] && '.' != check[0]) { return false; } if ('.' == check[0]) { return ' ' >= check[1]; } if ('\n' == check[0] && '\n' == check[1]) { return true; } } return true; } bool skipToEndBracket(char endBracket, const char* end = nullptr) { if (nullptr == end) { end = fEnd; } while (fChar[0] != endBracket) { if (fChar >= end) { return false; } (void) this->next(); } return true; } bool skipToEndBracket(const char* endBracket) { size_t len = strlen(endBracket); while (strncmp(fChar, endBracket, len)) { if (fChar >= fEnd) { return false; } (void) this->next(); } return true; } bool skipLine() { return skipToEndBracket('\n'); } void skipTo(const char* skip) { while (fChar < skip) { this->next(); } } void skipToAlpha() { while (fChar < fEnd && !isalpha(fChar[0])) { fChar++; } } void skipToAlphaNum() { while (fChar < fEnd && !isalnum(fChar[0])) { fChar++; } } bool skipExact(const char* pattern) { if (!this->startsWith(pattern)) { return false; } this->skipName(pattern); return true; } // differs from skipToNonAlphaNum in that a.b isn't considered a full name, // since a.b can't be found as a named definition void skipFullName() { while (fChar < fEnd && (isalnum(fChar[0]) || '_' == fChar[0] || '-' == fChar[0] || (':' == fChar[0] && fChar +1 < fEnd && ':' == fChar[1]))) { if (':' == fChar[0] && fChar +1 < fEnd && ':' == fChar[1]) { fChar++; } fChar++; } } bool skipToLineStart() { if (!this->skipLine()) { return false; } if (!this->eof()) { return this->skipWhiteSpace(); } return true; } void skipToNonAlphaNum() { while (fChar < fEnd && (isalnum(fChar[0]) || '_' == fChar[0] || '-' == fChar[0] || (':' == fChar[0] && fChar +1 < fEnd && ':' == fChar[1]) || ('.' == fChar[0] && fChar + 1 < fEnd && isalpha(fChar[1])))) { if (':' == fChar[0] && fChar +1 < fEnd && ':' == fChar[1]) { fChar++; } fChar++; } } void skipToSpace() { while (fChar < fEnd && ' ' != fChar[0]) { fChar++; } } bool skipName(const char* word) { size_t len = strlen(word); if (len < (size_t) (fEnd - fChar) && !strncmp(word, fChar, len)) { fChar += len; } return this->eof() || ' ' >= fChar[0]; } bool skipSpace() { while (' ' == this->peek()) { (void) this->next(); if (fChar >= fEnd) { return false; } } return true; } bool skipWord(const char* word) { if (!this->skipWhiteSpace()) { return false; } const char* save = fChar; if (!this->skipName(word)) { fChar = save; return false; } if (!this->skipWhiteSpace()) { return false; } return true; } bool skipWhiteSpace() { while (' ' >= this->peek()) { (void) this->next(); if (fChar >= fEnd) { return false; } } return true; } bool startsWith(const char* str) const { size_t len = strlen(str); ptrdiff_t lineLen = this->lineLength(); return len <= (size_t) lineLen && 0 == strncmp(str, fChar, len); } // ignores minor white space differences bool startsWith(const char* str, size_t oLen) const { size_t tIndex = 0; size_t tLen = fEnd - fChar; size_t oIndex = 0; while (oIndex < oLen && tIndex < tLen) { bool tSpace = ' ' >= fChar[tIndex]; bool oSpace = ' ' >= str[oIndex]; if (tSpace != oSpace) { break; } if (tSpace) { do { ++tIndex; } while (tIndex < tLen && ' ' >= fChar[tIndex]); do { ++oIndex; } while (oIndex < oLen && ' ' >= str[oIndex]); continue; } if (fChar[tIndex] != str[oIndex]) { break; } ++tIndex; ++oIndex; } return oIndex >= oLen; } const char* strnchr(char ch, const char* end) const { const char* ptr = fChar; while (ptr < end) { if (ptr[0] == ch) { return ptr; } ++ptr; } return nullptr; } const char* strnstr(const char *match, const char* end) const { size_t matchLen = strlen(match); SkASSERT(matchLen > 0); ptrdiff_t len = end - fChar; SkASSERT(len >= 0); if ((size_t) len < matchLen ) { return nullptr; } size_t count = len - matchLen; for (size_t index = 0; index <= count; index++) { if (0 == strncmp(&fChar[index], match, matchLen)) { return &fChar[index]; } } return nullptr; } const char* trimmedBracketEnd(const char bracket, OneLine oneLine) const { int max = (int) (OneLine::kYes == oneLine ? this->lineLength() : fEnd - fChar); int index = 0; while (index < max && bracket != fChar[index]) { ++index; } SkASSERT(index < max); while (index > 0 && ' ' >= fChar[index - 1]) { --index; } return fChar + index; } const char* trimmedLineEnd() const { const char* result = this->lineEnd(); while (result > fChar && ' ' >= result[-1]) { --result; } return result; } void trimEnd() { while (fEnd > fStart && ' ' >= fEnd[-1]) { --fEnd; } } const char* wordEnd() const { const char* end = fChar; while (isalnum(end[0]) || '_' == end[0] || '-' == end[0]) { ++end; } return end; } string fFileName; const char* fStart; const char* fLine; const char* fChar; const char* fEnd; size_t fLineCount; }; class EscapeParser : public TextParser { public: EscapeParser(const char* start, const char* end) : TextParser("", start, end, 0) { const char* reader = fStart; fStorage = new char[end - start]; char* writer = fStorage; while (reader < fEnd) { char ch = *reader++; if (ch != '\\') { *writer++ = ch; } else { char ctrl = *reader++; if (ctrl == 'u') { unsigned unicode = 0; for (int i = 0; i < 4; ++i) { unicode <<= 4; SkASSERT((reader[0] >= '0' && reader[0] <= '9') || (reader[0] >= 'A' && reader[0] <= 'F')); int nibble = *reader++ - '0'; if (nibble > 9) { nibble = 'A'- '9' + 1; } unicode |= nibble; } SkASSERT(unicode < 256); *writer++ = (unsigned char) unicode; } else { SkASSERT(ctrl == 'n'); *writer++ = '\n'; } } } fStart = fLine = fChar = fStorage; fEnd = writer; } virtual ~EscapeParser() { delete fStorage; } private: char* fStorage; }; class RootDefinition; class Definition : public NonAssignable { public: enum Type { kNone, kWord, kMark, kKeyWord, kBracket, kPunctuation, kFileType, }; enum class TrimExtract { kNo, kYes }; enum class MethodType { kNone, kConstructor, kDestructor, kOperator, }; Definition() {} Definition(const char* start, const char* end, int line, Definition* parent) : fStart(start) , fContentStart(start) , fContentEnd(end) , fParent(parent) , fLineCount(line) , fType(Type::kWord) { if (parent) { SkASSERT(parent->fFileName.length() > 0); fFileName = parent->fFileName; } this->setParentIndex(); } Definition(MarkType markType, const char* start, int line, Definition* parent) : Definition(markType, start, nullptr, line, parent) { } Definition(MarkType markType, const char* start, const char* end, int line, Definition* parent) : Definition(start, end, line, parent) { fMarkType = markType; fType = Type::kMark; } Definition(Bracket bracket, const char* start, int lineCount, Definition* parent) : Definition(start, nullptr, lineCount, parent) { fBracket = bracket; fType = Type::kBracket; } Definition(KeyWord keyWord, const char* start, const char* end, int lineCount, Definition* parent) : Definition(start, end, lineCount, parent) { fKeyWord = keyWord; fType = Type::kKeyWord; } Definition(Punctuation punctuation, const char* start, int lineCount, Definition* parent) : Definition(start, nullptr, lineCount, parent) { fPunctuation = punctuation; fType = Type::kPunctuation; } virtual ~Definition() {} virtual RootDefinition* asRoot() { SkASSERT(0); return nullptr; } virtual const RootDefinition* asRoot() const { SkASSERT(0); return nullptr; } bool boilerplateIfDef(Definition* parent) { const Definition& label = fTokens.front(); if (Type::kWord != label.fType) { return false; } fName = string(label.fContentStart, label.fContentEnd - label.fContentStart); return true; } // todo: this is matching #ifndef SkXXX_DEFINED for no particular reason // it doesn't do anything useful with arbitrary input, e.g. #ifdef SK_SUPPORT_LEGACY_CANVAS_HELPERS // also doesn't know what to do with SK_REQUIRE_LOCAL_VAR() bool boilerplateDef(Definition* parent) { if (!this->boilerplateIfDef(parent)) { return false; } const char* s = fName.c_str(); const char* e = strchr(s, '_'); return true; // fixme: if this is trying to do something useful with define, do it here if (!e) { return false; } string prefix(s, e - s); const char* inName = strstr(parent->fName.c_str(), prefix.c_str()); if (!inName) { return false; } if ('/' != inName[-1] && '\\' != inName[-1]) { return false; } if (strcmp(inName + prefix.size(), ".h")) { return false; } return true; } bool boilerplateEndIf() { return true; } bool checkMethod() const; void setCanonicalFiddle(); bool crossCheck(const char* tokenName, const Definition& includeToken) const; bool crossCheck(const Definition& includeToken) const; bool crossCheckInside(const char* start, const char* end, const Definition& includeToken) const; bool exampleToScript(string* result) const; string extractText(TrimExtract trimExtract) const { string result; TextParser parser(fFileName, fContentStart, fContentEnd, fLineCount); int childIndex = 0; char mc = '#'; while (parser.fChar < parser.fEnd) { if (TrimExtract::kYes == trimExtract && !parser.skipWhiteSpace()) { break; } if (parser.next() == mc) { if (parser.next() == mc) { if (parser.next() == mc) { mc = parser.next(); } } else { // fixme : more work to do if # style comment is in text // if in method definition, could be alternate method name --parser.fChar; if (' ' < parser.fChar[0]) { if (islower(parser.fChar[0])) { result += '\n'; parser.skipLine(); } else { SkASSERT(isupper(parser.fChar[0])); parser.skipTo(fChildren[childIndex]->fTerminator); if (mc == parser.fChar[0] && mc == parser.fChar[1]) { parser.next(); parser.next(); } childIndex++; } } else { parser.skipLine(); } continue; } } else { --parser.fChar; } const char* end = parser.fEnd; const char* mark = parser.strnchr(mc, end); if (mark) { end = mark; } string fragment(parser.fChar, end - parser.fChar); trim_end(fragment); if (TrimExtract::kYes == trimExtract) { trim_start(fragment); if (result.length()) { result += '\n'; result += '\n'; } } if (TrimExtract::kYes == trimExtract || has_nonwhitespace(fragment)) { result += fragment; } parser.skipTo(end); } return result; } string fiddleName() const; string formatFunction() const; const Definition* hasChild(MarkType markType) const; const Definition* hasParam(const string& ref) const; bool isClone() const { return fClone; } Definition* iRootParent() { Definition* test = fParent; while (test) { if (Type::kKeyWord == test->fType && KeyWord::kClass == test->fKeyWord) { return test; } test = test->fParent; } return nullptr; } virtual bool isRoot() const { return false; } int length() const { return (int) (fContentEnd - fContentStart); } bool methodHasReturn(const string& name, TextParser* methodParser) const; string methodName() const; bool nextMethodParam(TextParser* methodParser, const char** nextEndPtr, string* paramName) const; bool paramsMatch(const string& fullRef, const string& name) const; string printableName() const { string result(fName); std::replace(result.begin(), result.end(), '_', ' '); return result; } virtual RootDefinition* rootParent() { SkASSERT(0); return nullptr; } void setParentIndex() { fParentIndex = fParent ? (int) fParent->fTokens.size() : -1; } string fText; // if text is constructed instead of in a file, it's put here const char* fStart = nullptr; // .. in original text file, or the start of fText const char* fContentStart; // start past optional markup name string fName; string fFiddle; // if its a constructor or operator, fiddle name goes here const char* fContentEnd = nullptr; // the end of the contained text const char* fTerminator = nullptr; // the end of the markup, normally ##\n or \n Definition* fParent = nullptr; list fTokens; vector fChildren; string fHash; // generated by fiddle string fFileName; size_t fLineCount = 0; int fParentIndex = 0; MarkType fMarkType = MarkType::kNone; KeyWord fKeyWord = KeyWord::kNone; Bracket fBracket = Bracket::kNone; Punctuation fPunctuation = Punctuation::kNone; MethodType fMethodType = MethodType::kNone; Type fType = Type::kNone; bool fClone = false; bool fCloned = false; bool fPrivate = false; bool fShort = false; bool fMemberStart = false; mutable bool fVisited = false; }; class RootDefinition : public Definition { public: RootDefinition() { } RootDefinition(MarkType markType, const char* start, int line, Definition* parent) : Definition(markType, start, line, parent) { } RootDefinition(MarkType markType, const char* start, const char* end, int line, Definition* parent) : Definition(markType, start, end, line, parent) { } ~RootDefinition() override { for (auto& iter : fBranches) { delete iter.second; } } RootDefinition* asRoot() override { return this; } const RootDefinition* asRoot() const override { return this; } void clearVisited(); bool dumpUnVisited(); const Definition* find(const string& ref) const; bool isRoot() const override { return true; } RootDefinition* rootParent() override { return fRootParent; } void setRootParent(RootDefinition* rootParent) { fRootParent = rootParent; } unordered_map fBranches; unordered_map fLeaves; private: RootDefinition* fRootParent = nullptr; }; struct IClassDefinition : public Definition { unordered_map fEnums; unordered_map fMembers; unordered_map fMethods; unordered_map fStructs; }; struct Reference { Reference() : fLocation(nullptr) , fDefinition(nullptr) { } const char* fLocation; // .. in original text file const Definition* fDefinition; }; struct TypeNames { const char* fName; MarkType fMarkType; }; class ParserCommon : public TextParser { public: ParserCommon() : TextParser() , fParent(nullptr) { } virtual ~ParserCommon() { } void addDefinition(Definition* def) { fParent->fChildren.push_back(def); fParent = def; } void indentToColumn(int column) { SkASSERT(column >= fColumn); #if STDOUT_TO_IDE_OUT SkDebugf("%*s", column - fColumn, ""); #endif fprintf(fOut, "%*s", column - fColumn, ""); fColumn = column; fSpaces += column - fColumn; } bool leadingPunctuation(const char* str, size_t len) const { if (!fPendingSpace) { return false; } if (len < 2) { return false; } if ('.' != str[0] && ',' != str[0] && ';' != str[0] && ':' != str[0]) { return false; } return ' ' >= str[1]; } void lf(int count) { fPendingLF = SkTMax(fPendingLF, count); this->nl(); } void lfAlways(int count) { this->lf(count); this->writePending(); } void lfcr() { this->lf(1); } void nl() { fLinefeeds = 0; fSpaces = 0; fColumn = 0; fPendingSpace = false; } bool parseFile(const char* file, const char* suffix); virtual bool parseFromFile(const char* path) = 0; bool parseSetup(const char* path); void popObject() { fParent->fContentEnd = fParent->fTerminator = fChar; fParent = fParent->fParent; } virtual void reset() = 0; void resetCommon() { fLine = fChar = fStart; fLineCount = 0; fParent = nullptr; fIndent = 0; fOut = nullptr; fMaxLF = 2; fPendingLF = 0; fPendingSpace = false; nl(); } void setAsParent(Definition* definition) { if (fParent) { fParent->fChildren.push_back(definition); definition->fParent = fParent; } fParent = definition; } void singleLF() { fMaxLF = 1; } bool writeBlockTrim(int size, const char* data) { while (size && ' ' >= data[0]) { ++data; --size; } while (size && ' ' >= data[size - 1]) { --size; } if (size <= 0) { return false; } SkASSERT(size < 16000); if (size > 3 && !strncmp("#end", data, 4)) { fMaxLF = 1; } if (this->leadingPunctuation(data, (size_t) size)) { fPendingSpace = false; } writePending(); #if STDOUT_TO_IDE_OUT string check(data, size); SkDebugf("%s", check.c_str()); #endif fprintf(fOut, "%.*s", size, data); int added = 0; while (size > 0 && '\n' != data[--size]) { ++added; } fColumn = size ? added : fColumn + added; fSpaces = 0; fLinefeeds = 0; fMaxLF = added > 2 && !strncmp("#if", &data[size + (size > 0)], 3) ? 1 : 2; return true; } void writeBlock(int size, const char* data) { SkAssertResult(writeBlockTrim(size, data)); } void writeCommentHeader() { this->lf(2); this->writeString("/**"); this->writeSpace(); } void writeCommentTrailer() { this->writeString("*/"); this->lfcr(); } // write a pending space, so that two consecutive calls // don't double write, and trailing spaces on lines aren't written void writeSpace() { SkASSERT(!fPendingLF); SkASSERT(!fLinefeeds); SkASSERT(fColumn > 0); SkASSERT(!fSpaces); fPendingSpace = true; } void writeString(const char* str) { SkASSERT(strlen(str) > 0); SkASSERT(' ' < str[0]); SkASSERT(' ' < str[strlen(str) - 1]); if (this->leadingPunctuation(str, strlen(str))) { fPendingSpace = false; } writePending(); #if STDOUT_TO_IDE_OUT SkDebugf("%s", str); #endif SkASSERT(!strchr(str, '\n')); fprintf(fOut, "%s", str); fColumn += strlen(str); fSpaces = 0; fLinefeeds = 0; fMaxLF = 2; } void writePending() { fPendingLF = SkTMin(fPendingLF, fMaxLF); bool wroteLF = false; while (fLinefeeds < fPendingLF) { #if STDOUT_TO_IDE_OUT SkDebugf("\n"); #endif fprintf(fOut, "\n"); ++fLinefeeds; wroteLF = true; } fPendingLF = 0; if (wroteLF) { SkASSERT(0 == fColumn); SkASSERT(fIndent >= fSpaces); #if STDOUT_TO_IDE_OUT SkDebugf("%*s", fIndent - fSpaces, ""); #endif fprintf(fOut, "%*s", fIndent - fSpaces, ""); fColumn = fIndent; fSpaces = fIndent; } if (fPendingSpace) { #if STDOUT_TO_IDE_OUT SkDebugf(" "); #endif fprintf(fOut, " "); ++fColumn; fPendingSpace = false; } } unordered_map> fRawData; unordered_map> fLFOnly; Definition* fParent; FILE* fOut; int fLinefeeds; // number of linefeeds last written, zeroed on non-space int fMaxLF; // number of linefeeds allowed int fPendingLF; // number of linefeeds to write (can be suppressed) int fSpaces; // number of spaces (indent) last written, zeroed on non-space int fColumn; // current column; number of chars past last linefeed int fIndent; // desired indention bool fPendingSpace; // a space should preceed the next string or block private: typedef TextParser INHERITED; }; class BmhParser : public ParserCommon { public: enum class MarkLookup { kRequire, kAllowUnknown, }; enum class Resolvable { kNo, // neither resolved nor output kYes, // resolved, output kOut, // not resolved, but output }; enum class Exemplary { kNo, kYes, kOptional, }; #define M(mt) (1LL << (int) MarkType::k##mt) #define M_D M(Description) #define M_CS M(Class) | M(Struct) #define M_ST M(Subtopic) | M(Topic) #define M_CSST M_CS | M_ST #ifdef M_E #undef M_E #endif #define M_E M(Enum) | M(EnumClass) #define R_Y Resolvable::kYes #define R_N Resolvable::kNo #define R_O Resolvable::kOut #define E_Y Exemplary::kYes #define E_N Exemplary::kNo #define E_O Exemplary::kOptional BmhParser() : ParserCommon() , fMaps { // names without formal definitions (e.g. Column) aren't included // fill in other names once they're actually used { "", nullptr, MarkType::kNone, R_Y, E_N, 0 } , { "A", nullptr, MarkType::kAnchor, R_Y, E_N, 0 } , { "Alias", nullptr, MarkType::kAlias, R_N, E_N, 0 } , { "Bug", nullptr, MarkType::kBug, R_N, E_N, 0 } , { "Class", &fClassMap, MarkType::kClass, R_Y, E_O, M_CSST | M(Root) } , { "Code", nullptr, MarkType::kCode, R_Y, E_N, M_CSST | M_E } , { "", nullptr, MarkType::kColumn, R_Y, E_N, M(Row) } , { "", nullptr, MarkType::kComment, R_N, E_N, 0 } , { "Const", &fConstMap, MarkType::kConst, R_Y, E_N, M_E | M_ST } , { "Define", nullptr, MarkType::kDefine, R_O, E_N, M_ST } , { "DefinedBy", nullptr, MarkType::kDefinedBy, R_N, E_N, M(Method) } , { "Deprecated", nullptr, MarkType::kDeprecated, R_Y, E_N, 0 } , { "Description", nullptr, MarkType::kDescription, R_Y, E_N, M(Example) } , { "Doxygen", nullptr, MarkType::kDoxygen, R_Y, E_N, 0 } , { "Enum", &fEnumMap, MarkType::kEnum, R_Y, E_O, M_CSST | M(Root) } , { "EnumClass", &fClassMap, MarkType::kEnumClass, R_Y, E_O, M_CSST | M(Root) } , { "Error", nullptr, MarkType::kError, R_N, E_N, M(Example) } , { "Example", nullptr, MarkType::kExample, R_O, E_N, M_CSST | M_E | M(Method) } , { "Experimental", nullptr, MarkType::kExperimental, R_Y, E_N, 0 } , { "External", nullptr, MarkType::kExternal, R_Y, E_N, M(Root) } , { "File", nullptr, MarkType::kFile, R_N, E_N, M(Track) } , { "Formula", nullptr, MarkType::kFormula, R_O, E_N, M_ST | M(Member) | M(Method) | M_D } , { "Function", nullptr, MarkType::kFunction, R_O, E_N, M(Example) } , { "Height", nullptr, MarkType::kHeight, R_N, E_N, M(Example) } , { "Image", nullptr, MarkType::kImage, R_N, E_N, M(Example) } , { "Legend", nullptr, MarkType::kLegend, R_Y, E_N, M(Table) } , { "", nullptr, MarkType::kLink, R_Y, E_N, M(Anchor) } , { "List", nullptr, MarkType::kList, R_Y, E_N, M(Method) | M_CSST | M_E | M_D } , { "", nullptr, MarkType::kMarkChar, R_N, E_N, 0 } , { "Member", nullptr, MarkType::kMember, R_Y, E_N, M(Class) | M(Struct) } , { "Method", &fMethodMap, MarkType::kMethod, R_Y, E_Y, M_CSST } , { "NoExample", nullptr, MarkType::kNoExample, R_Y, E_N, 0 } , { "Param", nullptr, MarkType::kParam, R_Y, E_N, M(Method) } , { "Platform", nullptr, MarkType::kPlatform, R_Y, E_N, M(Example) } , { "Private", nullptr, MarkType::kPrivate, R_N, E_N, 0 } , { "Return", nullptr, MarkType::kReturn, R_Y, E_N, M(Method) } , { "", nullptr, MarkType::kRoot, R_Y, E_N, 0 } , { "", nullptr, MarkType::kRow, R_Y, E_N, M(Table) | M(List) } , { "SeeAlso", nullptr, MarkType::kSeeAlso, R_Y, E_N, M_CSST | M_E | M(Method) } , { "StdOut", nullptr, MarkType::kStdOut, R_N, E_N, M(Example) } , { "Struct", &fClassMap, MarkType::kStruct, R_Y, E_O, M(Class) | M(Root) | M_ST } , { "Substitute", nullptr, MarkType::kSubstitute, R_N, E_N, M_ST } , { "Subtopic", nullptr, MarkType::kSubtopic, R_Y, E_Y, M_CSST } , { "Table", nullptr, MarkType::kTable, R_Y, E_N, M(Method) | M_CSST | M_E } , { "Template", nullptr, MarkType::kTemplate, R_Y, E_N, 0 } , { "", nullptr, MarkType::kText, R_Y, E_N, 0 } , { "Time", nullptr, MarkType::kTime, R_Y, E_N, M(Track) } , { "ToDo", nullptr, MarkType::kToDo, R_N, E_N, 0 } , { "Topic", nullptr, MarkType::kTopic, R_Y, E_Y, M_CS | M(Root) | M(Topic) } , { "Track", nullptr, MarkType::kTrack, R_Y, E_N, M_E | M_ST } , { "Typedef", &fTypedefMap, MarkType::kTypedef, R_Y, E_N, M(Subtopic) | M(Topic) } , { "", nullptr, MarkType::kUnion, R_Y, E_N, 0 } , { "Volatile", nullptr, MarkType::kVolatile, R_N, E_N, M(StdOut) } , { "Width", nullptr, MarkType::kWidth, R_N, E_N, M(Example) } } { this->reset(); } #undef R_O #undef R_N #undef R_Y #undef M_E #undef M_CSST #undef M_ST #undef M_CS #undef M_D #undef M ~BmhParser() override {} bool addDefinition(const char* defStart, bool hasEnd, MarkType markType, const vector& typeNameBuilder); bool childOf(MarkType markType) const; string className(MarkType markType); bool collectExternals(); int endHashCount() const; RootDefinition* findBmhObject(MarkType markType, const string& typeName) { auto map = fMaps[(int) markType].fBmh; if (!map) { return nullptr; } return &(*map)[typeName]; } bool findDefinitions(); MarkType getMarkType(MarkLookup lookup) const; bool hasEndToken() const; string memberName(); string methodName(); const Definition* parentSpace() const; bool parseFromFile(const char* path) override { if (!INHERITED::parseSetup(path)) { return false; } fCheckMethods = !strstr(path, "undocumented.bmh"); return findDefinitions(); } bool popParentStack(Definition* definition); void reset() override { INHERITED::resetCommon(); fRoot = nullptr; fMC = '#'; fInChar = false; fInCharCommentString = false; fInComment = false; fInEnum = false; fInString = false; fCheckMethods = false; } bool skipNoName(); bool skipToDefinitionEnd(MarkType markType); void spellCheck(const char* match) const; vector topicName(); vector typeName(MarkType markType, bool* expectEnd); string uniqueName(const string& base, MarkType markType); string uniqueRootName(const string& base, MarkType markType); void validate() const; string word(const string& prefix, const string& delimiter); public: struct DefinitionMap { const char* fName; unordered_map* fBmh; MarkType fMarkType; Resolvable fResolve; Exemplary fExemplary; // worthy of an example uint64_t fParentMask; }; DefinitionMap fMaps[Last_MarkType + 1]; forward_list fTopics; forward_list fMarkup; forward_list fExternals; vector fInputFiles; unordered_map fClassMap; unordered_map fConstMap; unordered_map fEnumMap; unordered_map fMethodMap; unordered_map fTypedefMap; unordered_map fTopicMap; unordered_map fAliasMap; RootDefinition* fRoot; mutable char fMC; // markup character bool fAnonymous; bool fCloned; bool fInChar; bool fInCharCommentString; bool fInEnum; bool fInComment; bool fInString; bool fCheckMethods; private: typedef ParserCommon INHERITED; }; class IncludeParser : public ParserCommon { public: enum class IsStruct { kNo, kYes, }; IncludeParser() : ParserCommon() , fMaps { { nullptr, MarkType::kNone } , { nullptr, MarkType::kAnchor } , { nullptr, MarkType::kAlias } , { nullptr, MarkType::kBug } , { nullptr, MarkType::kClass } , { nullptr, MarkType::kCode } , { nullptr, MarkType::kColumn } , { nullptr, MarkType::kComment } , { nullptr, MarkType::kConst } , { &fIDefineMap, MarkType::kDefine } , { nullptr, MarkType::kDefinedBy } , { nullptr, MarkType::kDeprecated } , { nullptr, MarkType::kDescription } , { nullptr, MarkType::kDoxygen } , { &fIEnumMap, MarkType::kEnum } , { &fIEnumMap, MarkType::kEnumClass } , { nullptr, MarkType::kError } , { nullptr, MarkType::kExample } , { nullptr, MarkType::kExperimental } , { nullptr, MarkType::kExternal } , { nullptr, MarkType::kFile } , { nullptr, MarkType::kFormula } , { nullptr, MarkType::kFunction } , { nullptr, MarkType::kHeight } , { nullptr, MarkType::kImage } , { nullptr, MarkType::kLegend } , { nullptr, MarkType::kLink } , { nullptr, MarkType::kList } , { nullptr, MarkType::kMarkChar } , { nullptr, MarkType::kMember } , { nullptr, MarkType::kMethod } , { nullptr, MarkType::kNoExample } , { nullptr, MarkType::kParam } , { nullptr, MarkType::kPlatform } , { nullptr, MarkType::kPrivate } , { nullptr, MarkType::kReturn } , { nullptr, MarkType::kRoot } , { nullptr, MarkType::kRow } , { nullptr, MarkType::kSeeAlso } , { nullptr, MarkType::kStdOut } , { &fIStructMap, MarkType::kStruct } , { nullptr, MarkType::kSubstitute } , { nullptr, MarkType::kSubtopic } , { nullptr, MarkType::kTable } , { &fITemplateMap, MarkType::kTemplate } , { nullptr, MarkType::kText } , { nullptr, MarkType::kTime } , { nullptr, MarkType::kToDo } , { nullptr, MarkType::kTopic } , { nullptr, MarkType::kTrack } , { &fITypedefMap, MarkType::kTypedef } , { &fIUnionMap, MarkType::kUnion } , { nullptr, MarkType::kVolatile } , { nullptr, MarkType::kWidth } } { this->reset(); } ~IncludeParser() override {} void addKeyword(KeyWord keyWord); void addPunctuation(Punctuation punctuation) { fParent->fTokens.emplace_back(punctuation, fChar, fLineCount, fParent); } void addWord() { fParent->fTokens.emplace_back(fIncludeWord, fChar, fLineCount, fParent); fIncludeWord = nullptr; } void checkForMissingParams(const vector& methodParams, const vector& foundParams); bool checkForWord(); string className() const; bool crossCheck(BmhParser& ); IClassDefinition* defineClass(const Definition& includeDef, const string& className); void dumpClassTokens(IClassDefinition& classDef); void dumpComment(Definition* token); void dumpTokens(); bool findComments(const Definition& includeDef, Definition* markupDef); Definition* findIncludeObject(const Definition& includeDef, MarkType markType, const string& typeName) { typedef Definition* DefinitionPtr; unordered_map* map = fMaps[(int) markType].fInclude; if (!map) { return reportError("invalid mark type"); } string name = this->uniqueName(*map, typeName); Definition& markupDef = (*map)[name]; if (markupDef.fStart) { return reportError("definition already defined"); } markupDef.fFileName = fFileName; markupDef.fStart = includeDef.fStart; markupDef.fContentStart = includeDef.fStart; markupDef.fName = name; markupDef.fContentEnd = includeDef.fContentEnd; markupDef.fTerminator = includeDef.fTerminator; markupDef.fParent = fParent; markupDef.fLineCount = includeDef.fLineCount; markupDef.fMarkType = markType; markupDef.fKeyWord = includeDef.fKeyWord; markupDef.fType = Definition::Type::kMark; return &markupDef; } static KeyWord FindKey(const char* start, const char* end); bool internalName(const Definition& ) const; void keywordEnd(); void keywordStart(const char* keyword); bool parseChar(); bool parseComment(const string& filename, const char* start, const char* end, int lineCount, Definition* markupDef); bool parseClass(Definition* def, IsStruct); bool parseDefine(); bool parseEnum(Definition* child, Definition* markupDef); bool parseFromFile(const char* path) override { if (!INHERITED::parseSetup(path)) { return false; } string name(path); return parseInclude(name); } bool parseInclude(const string& name); bool parseMember(Definition* child, Definition* markupDef); bool parseMethod(Definition* child, Definition* markupDef); bool parseObject(Definition* child, Definition* markupDef); bool parseObjects(Definition* parent, Definition* markupDef); bool parseTemplate(); bool parseTypedef(); bool parseUnion(); void popBracket() { SkASSERT(Definition::Type::kBracket == fParent->fType); this->popObject(); Bracket bracket = this->topBracket(); this->setBracketShortCuts(bracket); } void pushBracket(Bracket bracket) { this->setBracketShortCuts(bracket); fParent->fTokens.emplace_back(bracket, fChar, fLineCount, fParent); Definition* container = &fParent->fTokens.back(); this->addDefinition(container); } void reset() override { INHERITED::resetCommon(); fRootTopic = nullptr; fInBrace = nullptr; fIncludeWord = nullptr; fPrev = '\0'; fInChar = false; fInCharCommentString = false; fInComment = false; fInEnum = false; fInFunction = false; fInString = false; } void setBracketShortCuts(Bracket bracket) { fInComment = Bracket::kSlashSlash == bracket || Bracket::kSlashStar == bracket; fInString = Bracket::kString == bracket; fInChar = Bracket::kChar == bracket; fInCharCommentString = fInChar || fInComment || fInString; } Bracket topBracket() const { Definition* parent = fParent; while (parent && Definition::Type::kBracket != parent->fType) { parent = parent->fParent; } return parent ? parent->fBracket : Bracket::kNone; } template string uniqueName(const unordered_map& m, const string& typeName) { string base(typeName.size() > 0 ? typeName : "_anonymous"); string name(base); int anonCount = 1; do { auto iter = m.find(name); if (iter == m.end()) { return name; } name = base + '_'; name += to_string(++anonCount); } while (true); // should never get here return string(); } void validate() const; protected: static void ValidateKeyWords(); struct DefinitionMap { unordered_map* fInclude; MarkType fMarkType; }; DefinitionMap fMaps[Last_MarkType + 1]; unordered_map fIncludeMap; unordered_map fIClassMap; unordered_map fIDefineMap; unordered_map fIEnumMap; unordered_map fIStructMap; unordered_map fITemplateMap; unordered_map fITypedefMap; unordered_map fIUnionMap; Definition* fRootTopic; Definition* fInBrace; const char* fIncludeWord; char fPrev; bool fInChar; bool fInCharCommentString; bool fInComment; bool fInEnum; bool fInFunction; bool fInString; typedef ParserCommon INHERITED; }; class IncludeWriter : public IncludeParser { public: enum class Word { kStart, kCap, kFirst, kUnderline, kMixed, }; enum class PunctuationState { kStart, kDelimiter, kPeriod, kSpace, }; enum class Wrote { kNone, kLF, kChars, }; struct IterState { IterState (list::iterator tIter, list::iterator tIterEnd) : fDefIter(tIter) , fDefEnd(tIterEnd) { } list::iterator fDefIter; list::iterator fDefEnd; }; IncludeWriter() : IncludeParser() {} ~IncludeWriter() override {} bool contentFree(int size, const char* data) const { while (size > 0 && data[0] <= ' ') { --size; ++data; } while (size > 0 && data[size - 1] <= ' ') { --size; } return 0 == size; } void enumHeaderOut(const RootDefinition* root, const Definition& child); void enumMembersOut(const RootDefinition* root, Definition& child); void enumSizeItems(const Definition& child); int lookupMethod(const PunctuationState punctuation, const Word word, const int start, const int run, int lastWrite, const char* data); int lookupReference(const PunctuationState punctuation, const Word word, const int start, const int run, int lastWrite, const char last, const char* data); void methodOut(const Definition* method, const Definition& child); bool populate(Definition* def, RootDefinition* root); bool populate(BmhParser& bmhParser); void reset() override { INHERITED::resetCommon(); fBmhMethod = nullptr; fBmhParser = nullptr; fEnumDef = nullptr; fMethodDef = nullptr; fStructDef = nullptr; fAnonymousEnumCount = 1; fInStruct = false; } string resolveMethod(const char* start, const char* end, bool first); string resolveRef(const char* start, const char* end, bool first); Wrote rewriteBlock(int size, const char* data); Definition* structMemberOut(const Definition* memberStart, const Definition& child); void structOut(const Definition* root, const Definition& child, const char* commentStart, const char* commentEnd); void structSizeMembers(Definition& child); private: BmhParser* fBmhParser; Definition* fDeferComment; Definition* fLastComment; const Definition* fBmhMethod; const Definition* fEnumDef; const Definition* fMethodDef; const Definition* fStructDef; const char* fContinuation; // used to construct paren-qualified method name int fAnonymousEnumCount; int fEnumItemValueTab; int fEnumItemCommentTab; int fStructMemberTab; int fStructValueTab; int fStructCommentTab; bool fInStruct; typedef IncludeParser INHERITED; }; class FiddleParser : public ParserCommon { public: FiddleParser(BmhParser* bmh) : ParserCommon() , fBmhParser(bmh) { this->reset(); } Definition* findExample(const string& name) const; bool parseFromFile(const char* path) override { if (!INHERITED::parseSetup(path)) { return false; } return parseFiddles(); } void reset() override { INHERITED::resetCommon(); } private: bool parseFiddles(); BmhParser* fBmhParser; // must be writable; writes example hash typedef ParserCommon INHERITED; }; class HackParser : public ParserCommon { public: HackParser() : ParserCommon() { this->reset(); } bool parseFromFile(const char* path) override { if (!INHERITED::parseSetup(path)) { return false; } return hackFiles(); } void reset() override { INHERITED::resetCommon(); } private: bool hackFiles(); typedef ParserCommon INHERITED; }; class MdOut : public ParserCommon { public: MdOut(const BmhParser& bmh) : ParserCommon() , fBmhParser(bmh) { this->reset(); } bool buildReferences(const char* path, const char* outDir); private: enum class TableState { kNone, kRow, kColumn, }; string addReferences(const char* start, const char* end, BmhParser::Resolvable ); bool buildRefFromFile(const char* fileName, const char* outDir); void childrenOut(const Definition* def, const char* contentStart); const Definition* isDefined(const TextParser& parser, const string& ref, bool report) const; string linkName(const Definition* ) const; string linkRef(const string& leadingSpaces, const Definition*, const string& ref) const; void markTypeOut(Definition* ); void mdHeaderOut(int depth) { mdHeaderOutLF(depth, 2); } void mdHeaderOutLF(int depth, int lf); bool parseFromFile(const char* path) override { return true; } void reset() override { INHERITED::resetCommon(); fEnumClass = nullptr; fMethod = nullptr; fRoot = nullptr; fTableState = TableState::kNone; fHasFiddle = false; fInDescription = false; fInList = false; } BmhParser::Resolvable resolvable(MarkType markType) { if ((MarkType::kExample == markType || MarkType::kFunction == markType) && fHasFiddle) { return BmhParser::Resolvable::kNo; } return fBmhParser.fMaps[(int) markType].fResolve; } void resolveOut(const char* start, const char* end, BmhParser::Resolvable ); const BmhParser& fBmhParser; const Definition* fEnumClass; Definition* fMethod; RootDefinition* fRoot; TableState fTableState; bool fHasFiddle; bool fInDescription; // FIXME: for now, ignore unfound camelCase in description since it may // be defined in example which at present cannot be linked to bool fInList; typedef ParserCommon INHERITED; }; // some methods cannot be trivially parsed; look for class-name / ~ / operator class MethodParser : public TextParser { public: MethodParser(const string& className, const string& fileName, const char* start, const char* end, int lineCount) : TextParser(fileName, start, end, lineCount) , fClassName(className) { } void skipToMethodStart() { if (!fClassName.length()) { this->skipToAlphaNum(); return; } while (!this->eof() && !isalnum(this->peek()) && '~' != this->peek()) { this->next(); } } void skipToMethodEnd() { if (this->eof()) { return; } if (fClassName.length()) { if ('~' == this->peek()) { this->next(); if (!this->startsWith(fClassName.c_str())) { --fChar; return; } } if (this->startsWith(fClassName.c_str()) || this->startsWith("operator")) { const char* ptr = this->anyOf(" ("); if (ptr && '(' == *ptr) { this->skipToEndBracket(')'); SkAssertResult(')' == this->next()); return; } } } if (this->startsWith("Sk") && this->wordEndsWith(".h")) { // allow include refs this->skipToNonAlphaNum(); } else { this->skipFullName(); } } bool wordEndsWith(const char* str) const { const char* space = this->strnchr(' ', fEnd); if (!space) { return false; } size_t len = strlen(str); if (space < fChar + len) { return false; } return !strncmp(str, space - len, len); } private: string fClassName; typedef TextParser INHERITED; }; #endif