diff options
author | Cary Clark <caryclark@skia.org> | 2018-11-01 09:29:36 -0400 |
---|---|---|
committer | Skia Commit-Bot <skia-commit-bot@chromium.org> | 2018-11-01 13:58:32 +0000 |
commit | 2da9fb836696a2ffc344688236cbad5b0d0386e4 (patch) | |
tree | 93552b012e77d3e9f9e270d70838e3f79dfd9b62 | |
parent | 3bdaa46bff71b839c806c0955417b1abaae650b0 (diff) | |
download | skqp-2da9fb836696a2ffc344688236cbad5b0d0386e4.tar.gz |
refactor bookmaker
mostly create include files with minimum
content
TBR=caryclark@google.com
Bug: skia:
Change-Id: I3db0f913cc108bd5ef8ec6c3b229eaccc3f25198
Reviewed-on: https://skia-review.googlesource.com/c/167302
Commit-Queue: Cary Clark <caryclark@skia.org>
Auto-Submit: Cary Clark <caryclark@skia.org>
Reviewed-by: Cary Clark <caryclark@skia.org>
24 files changed, 5338 insertions, 5166 deletions
@@ -1649,16 +1649,19 @@ if (skia_enable_tools) { test_app("bookmaker") { sources = [ + "tools/bookmaker/bmhParser.cpp", "tools/bookmaker/bookmaker.cpp", "tools/bookmaker/cataloger.cpp", "tools/bookmaker/definition.cpp", "tools/bookmaker/fiddleParser.cpp", + "tools/bookmaker/hackParser.cpp", "tools/bookmaker/includeParser.cpp", "tools/bookmaker/includeWriter.cpp", "tools/bookmaker/mdOut.cpp", "tools/bookmaker/parserCommon.cpp", "tools/bookmaker/selfCheck.cpp", "tools/bookmaker/spellCheck.cpp", + "tools/bookmaker/textParser.cpp", ] deps = [ ":flags", diff --git a/tools/bookmaker/bmhParser.cpp b/tools/bookmaker/bmhParser.cpp new file mode 100644 index 0000000000..bfdffc704a --- /dev/null +++ b/tools/bookmaker/bmhParser.cpp @@ -0,0 +1,2358 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "bmhParser.h" + +const string kSpellingFileName("spelling.txt"); + +#define M(mt) (1LL << (int) MarkType::k##mt) +#define M_D M(Description) +#define M_CS M(Class) | M(Struct) +#define M_MD M(Method) | M(Define) +#define M_MDCM M_MD | M(Const) | M(Member) +#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 R_K Resolvable::kCode +#define R_F Resolvable::kFormula +#define R_C Resolvable::kClone + +#define E_Y Exemplary::kYes +#define E_N Exemplary::kNo +#define E_O Exemplary::kOptional + +// ToDo: add column to denote which marks are one-liners +BmhParser::MarkProps BmhParser::kMarkProps[] = { +// names without formal definitions (e.g. Column) aren't included + { "", MarkType::kNone, R_Y, E_N, 0 } +, { "A", MarkType::kAnchor, R_N, E_N, 0 } +, { "Alias", MarkType::kAlias, R_N, E_N, M_ST | M(Const) } +, { "Bug", MarkType::kBug, R_N, E_N, M_CSST | M_MDCM | M_E + | M(Example) | M(NoExample) } +, { "Class", MarkType::kClass, R_Y, E_O, M_CSST } +, { "Code", MarkType::kCode, R_K, E_N, M_CSST | M_E | M_MD | M(Typedef) } +, { "", MarkType::kColumn, R_Y, E_N, M(Row) } +, { "", MarkType::kComment, R_N, E_N, 0 } +, { "Const", MarkType::kConst, R_Y, E_O, M_E | M_CSST } +, { "Define", MarkType::kDefine, R_O, E_Y, M_ST } +, { "Deprecated", MarkType::kDeprecated, R_Y, E_N, M_CS | M_MDCM | M_E } +, { "Description", MarkType::kDescription, R_Y, E_N, M(Example) | M(NoExample) } +, { "Details", MarkType::kDetails, R_N, E_N, M(Const) } +, { "Duration", MarkType::kDuration, R_N, E_N, M(Example) | M(NoExample) } +, { "Enum", MarkType::kEnum, R_Y, E_O, M_CSST } +, { "EnumClass", MarkType::kEnumClass, R_Y, E_O, M_CSST } +, { "Example", MarkType::kExample, R_O, E_N, M_CSST | M_E | M_MD | M(Const) } +, { "Experimental", MarkType::kExperimental, R_Y, E_N, M_CS | M_MDCM | M_E } +, { "External", MarkType::kExternal, R_Y, E_N, 0 } +, { "File", MarkType::kFile, R_Y, E_N, M(Topic) } +, { "Filter", MarkType::kFilter, R_N, E_N, M(Subtopic) | M(Code) } +, { "Formula", MarkType::kFormula, R_F, E_N, M(Column) | M(Description) + | M_E | M_ST | M_MDCM } +, { "Function", MarkType::kFunction, R_O, E_N, M(Example) | M(NoExample) } +, { "Height", MarkType::kHeight, R_N, E_N, M(Example) | M(NoExample) } +, { "Illustration", MarkType::kIllustration, R_N, E_N, M_CSST | M_MD } +, { "Image", MarkType::kImage, R_N, E_N, M(Example) | M(NoExample) } +, { "In", MarkType::kIn, R_N, E_N, M_CSST | M_E | M(Method) | M(Typedef) | M(Code) } +, { "Legend", MarkType::kLegend, R_Y, E_N, M(Table) } +, { "Line", MarkType::kLine, R_N, E_N, M_CSST | M_E | M(Method) | M(Typedef) } +, { "", MarkType::kLink, R_N, E_N, M(Anchor) } +, { "List", MarkType::kList, R_Y, E_N, M(Method) | M_CSST | M_E | M_D } +, { "Literal", MarkType::kLiteral, R_N, E_N, M(Code) } +, { "", MarkType::kMarkChar, R_N, E_N, 0 } +, { "Member", MarkType::kMember, R_Y, E_O, M_CSST } +, { "Method", MarkType::kMethod, R_Y, E_Y, M_CSST } +, { "NoExample", MarkType::kNoExample, R_N, E_N, M_CSST | M_E | M_MD } +, { "NoJustify", MarkType::kNoJustify, R_N, E_N, M(Const) | M(Member) } +, { "Outdent", MarkType::kOutdent, R_N, E_N, M(Code) } +, { "Param", MarkType::kParam, R_Y, E_N, M(Method) | M(Define) } +, { "PhraseDef", MarkType::kPhraseDef, R_Y, E_N, M_ST } +, { "", MarkType::kPhraseParam, R_Y, E_N, 0 } +, { "", MarkType::kPhraseRef, R_N, E_N, 0 } +, { "Platform", MarkType::kPlatform, R_N, E_N, M(Example) | M(NoExample) } +, { "Populate", MarkType::kPopulate, R_N, E_N, M(Code) | M(Method) } +, { "Private", MarkType::kPrivate, R_N, E_N, M_CSST | M_MDCM | M_E } +, { "Return", MarkType::kReturn, R_Y, E_N, M(Method) } +, { "", MarkType::kRow, R_Y, E_N, M(Table) | M(List) } +, { "SeeAlso", MarkType::kSeeAlso, R_C, E_N, M_CSST | M_E | M_MD | M(Typedef) } +, { "Set", MarkType::kSet, R_N, E_N, M(Example) | M(NoExample) } +, { "StdOut", MarkType::kStdOut, R_N, E_N, M(Example) | M(NoExample) } +, { "Struct", MarkType::kStruct, R_Y, E_O, M(Class) | M_ST } +, { "Substitute", MarkType::kSubstitute, R_N, E_N, M(Alias) | M_ST } +, { "Subtopic", MarkType::kSubtopic, R_Y, E_Y, M_CSST | M_E } +, { "Table", MarkType::kTable, R_Y, E_N, M(Method) | M_CSST | M_E } +, { "Template", MarkType::kTemplate, R_Y, E_N, M_CSST } +, { "", MarkType::kText, R_N, E_N, 0 } +, { "ToDo", MarkType::kToDo, R_N, E_N, 0 } +, { "Topic", MarkType::kTopic, R_Y, E_Y, 0 } +, { "Typedef", MarkType::kTypedef, R_Y, E_O, M_CSST | M_E } +, { "Union", MarkType::kUnion, R_Y, E_N, M_CSST } +, { "Using", MarkType::kUsing, R_Y, E_O, M_CSST } +, { "Volatile", MarkType::kVolatile, R_N, E_N, M(StdOut) } +, { "Width", MarkType::kWidth, R_N, E_N, M(Example) | M(NoExample) } +}; + +#undef R_O +#undef R_N +#undef R_Y +#undef R_K +#undef R_F +#undef R_C + +#undef M_E +#undef M_CSST +#undef M_ST +#undef M_CS +#undef M_MCD +#undef M_D +#undef M + +#undef E_Y +#undef E_N +#undef E_O + +bool BmhParser::addDefinition(const char* defStart, bool hasEnd, MarkType markType, + const vector<string>& typeNameBuilder, HasTag hasTag) { + Definition* definition = nullptr; + switch (markType) { + case MarkType::kComment: + if (!this->skipToDefinitionEnd(markType)) { + return false; + } + return true; + // these types may be referred to by name + case MarkType::kClass: + case MarkType::kStruct: + case MarkType::kConst: + case MarkType::kDefine: + case MarkType::kEnum: + case MarkType::kEnumClass: + case MarkType::kMember: + case MarkType::kMethod: + case MarkType::kTypedef: { + if (!typeNameBuilder.size()) { + return this->reportError<bool>("unnamed markup"); + } + if (typeNameBuilder.size() > 1) { + return this->reportError<bool>("expected one name only"); + } + string name = typeNameBuilder[0]; + if (nullptr == fRoot) { + fRoot = this->findBmhObject(markType, name); + fRoot->fFileName = fFileName; + fRoot->fName = name; + fRoot->fNames.fName = name; + fRoot->fNames.fParent = &fGlobalNames; + definition = fRoot; + } else { + if (nullptr == fParent) { + return this->reportError<bool>("expected parent"); + } + if (fParent == fRoot && hasEnd) { + RootDefinition* rootParent = fRoot->rootParent(); + if (rootParent) { + fRoot = rootParent; + } + definition = fParent; + } else { + if (!hasEnd && fRoot->find(name, RootDefinition::AllowParens::kNo)) { + return this->reportError<bool>("duplicate symbol"); + } + if (MarkType::kStruct == markType || MarkType::kClass == markType + || MarkType::kEnumClass == markType) { + // if class or struct, build fRoot hierarchy + // and change isDefined to search all parents of fRoot + SkASSERT(!hasEnd); + RootDefinition* childRoot = new RootDefinition; + (fRoot->fBranches)[name] = childRoot; + childRoot->setRootParent(fRoot); + childRoot->fFileName = fFileName; + SkASSERT(MarkType::kSubtopic != fRoot->fMarkType + && MarkType::kTopic != fRoot->fMarkType); + childRoot->fNames.fName = name; + childRoot->fNames.fParent = &fRoot->fNames; + fRoot = childRoot; + definition = fRoot; + } else { + definition = &fRoot->fLeaves[name]; + } + } + } + if (hasEnd) { + Exemplary hasExample = Exemplary::kNo; + bool hasExcluder = false; + for (auto child : definition->fChildren) { + if (MarkType::kExample == child->fMarkType) { + hasExample = Exemplary::kYes; + } + hasExcluder |= MarkType::kPrivate == child->fMarkType + || MarkType::kDeprecated == child->fMarkType + || MarkType::kExperimental == child->fMarkType + || MarkType::kNoExample == child->fMarkType; + } + if (kMarkProps[(int) markType].fExemplary != hasExample + && kMarkProps[(int) markType].fExemplary != Exemplary::kOptional) { + if (string::npos == fFileName.find("undocumented") + && !hasExcluder) { + hasExample == Exemplary::kNo ? + this->reportWarning("missing example") : + this->reportWarning("unexpected example"); + } + + } + if (MarkType::kMethod == markType) { + if (fCheckMethods && !definition->checkMethod()) { + return false; + } + } + if (HasTag::kYes == hasTag) { + if (!this->checkEndMarker(markType, definition->fName)) { + return false; + } + } + if (!this->popParentStack(definition)) { + return false; + } + if (fRoot == definition) { + fRoot = nullptr; + } + } else { + definition->fStart = defStart; + this->skipSpace(); + definition->fFileName = fFileName; + definition->fContentStart = fChar; + definition->fLineCount = fLineCount; + definition->fClone = fCloned; + if (MarkType::kConst == markType) { + // todo: require that fChar points to def on same line as markup + // additionally add definition to class children if it is not already there + if (definition->fParent != fRoot) { +// fRoot->fChildren.push_back(definition); + } + } + SkASSERT(string::npos == name.find('\n')); + definition->fName = name; + if (MarkType::kMethod == markType) { + if (string::npos != name.find(':', 0)) { + definition->setCanonicalFiddle(); + } else { + definition->fFiddle = name; + } + } else { + definition->fFiddle = Definition::NormalizedName(name); + } + definition->fMarkType = markType; + definition->fAnonymous = fAnonymous; + this->setAsParent(definition); + } + } break; + case MarkType::kTopic: + case MarkType::kSubtopic: + SkASSERT(1 == typeNameBuilder.size()); + if (!hasEnd) { + if (!typeNameBuilder.size()) { + return this->reportError<bool>("unnamed topic"); + } + fTopics.emplace_front(markType, defStart, fLineCount, fParent, fMC); + RootDefinition* rootDefinition = &fTopics.front(); + definition = rootDefinition; + definition->fFileName = fFileName; + definition->fContentStart = fChar; + if (MarkType::kTopic == markType) { + if (fParent) { + return this->reportError<bool>("#Topic must be root"); + } + // topic name is unappended + definition->fName = typeNameBuilder[0]; + } else { + if (!fParent) { + return this->reportError<bool>("#Subtopic may not be root"); + } + Definition* parent = fParent; + while (MarkType::kTopic != parent->fMarkType && MarkType::kSubtopic != parent->fMarkType) { + parent = parent->fParent; + if (!parent) { + // subtopic must have subtopic or topic in parent chain + return this->reportError<bool>("#Subtopic missing parent"); + } + } + if (MarkType::kSubtopic == parent->fMarkType) { + // subtopic prepends parent subtopic name, but not parent topic name + definition->fName = parent->fName + '_'; + } + definition->fName += typeNameBuilder[0]; + definition->fFiddle = parent->fFiddle + '_'; + } + rootDefinition->fNames.fName = rootDefinition->fName; + definition->fFiddle += Definition::NormalizedName(typeNameBuilder[0]); + this->setAsParent(definition); + } + { + SkASSERT(hasEnd ? fParent : definition); + string fullTopic = hasEnd ? fParent->fFiddle : definition->fFiddle; + Definition* defPtr = fTopicMap[fullTopic]; + if (hasEnd) { + if (HasTag::kYes == hasTag && !this->checkEndMarker(markType, fullTopic)) { + return false; + } + if (!definition) { + definition = defPtr; + } else if (definition != defPtr) { + return this->reportError<bool>("mismatched topic"); + } + } else { + if (nullptr != defPtr) { + return this->reportError<bool>("already declared topic"); + } + fTopicMap[fullTopic] = definition; + } + } + if (hasEnd) { + if (!this->popParentStack(definition)) { + return false; + } + } + break; + case MarkType::kFormula: + // hasEnd : single line / multiple line + if (!fParent || MarkType::kFormula != fParent->fMarkType) { + SkASSERT(!definition || MarkType::kFormula == definition->fMarkType); + fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC); + definition = &fMarkup.front(); + definition->fContentStart = fChar; + definition->fName = typeNameBuilder[0]; + definition->fFiddle = fParent->fFiddle; + fParent = definition; + } else { + SkASSERT(fParent && MarkType::kFormula == fParent->fMarkType); + SkASSERT(fMC == defStart[0]); + SkASSERT(fMC == defStart[-1]); + definition = fParent; + definition->fTerminator = fChar; + if (!this->popParentStack(definition)) { + return false; + } + this->parseHashFormula(definition); + fParent->fChildren.push_back(definition); + } + break; + // these types are children of parents, but are not in named maps + case MarkType::kDescription: + case MarkType::kStdOut: + // may be one-liner + case MarkType::kAlias: + case MarkType::kNoExample: + case MarkType::kParam: + case MarkType::kPhraseDef: + case MarkType::kReturn: + case MarkType::kToDo: + if (hasEnd) { + if (markType == fParent->fMarkType) { + definition = fParent; + if (MarkType::kBug == markType || MarkType::kReturn == markType + || MarkType::kToDo == markType) { + this->skipNoName(); + } + if (!this->popParentStack(fParent)) { // if not one liner, pop + return false; + } + if (MarkType::kParam == markType || MarkType::kReturn == markType + || MarkType::kPhraseDef == markType) { + if (!this->checkParamReturn(definition)) { + return false; + } + } + if (MarkType::kPhraseDef == markType) { + string key = definition->fName; + if (fPhraseMap.end() != fPhraseMap.find(key)) { + this->reportError<bool>("duplicate phrase key"); + } + fPhraseMap[key] = definition; + } + } else { + fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC); + definition = &fMarkup.front(); + definition->fName = typeNameBuilder[0]; + definition->fFiddle = fParent->fFiddle; + definition->fContentStart = fChar; + string endBracket; + endBracket += fMC; + endBracket += fMC; + definition->fContentEnd = this->trimmedBracketEnd(endBracket); + this->skipToEndBracket(endBracket.c_str()); + SkAssertResult(fMC == this->next()); + SkAssertResult(fMC == this->next()); + definition->fTerminator = fChar; + TextParser checkForChildren(definition); + if (checkForChildren.strnchr(fMC, definition->fContentEnd)) { + this->reportError<bool>("put ## on separate line"); + } + fParent->fChildren.push_back(definition); + } + if (MarkType::kAlias == markType) { + const char* end = definition->fChildren.size() > 0 ? + definition->fChildren[0]->fStart : definition->fContentEnd; + TextParser parser(definition->fFileName, definition->fContentStart, end, + definition->fLineCount); + parser.trimEnd(); + string key = string(parser.fStart, parser.lineLength()); + if (fAliasMap.end() != fAliasMap.find(key)) { + return this->reportError<bool>("duplicate alias"); + } + fAliasMap[key] = definition; + definition->fFiddle = definition->fParent->fFiddle; + } + break; + } else if (MarkType::kPhraseDef == markType) { + bool hasParams = '(' == this->next(); + fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC); + definition = &fMarkup.front(); + definition->fName = typeNameBuilder[0]; + definition->fFiddle = fParent->fFiddle; + definition->fContentStart = fChar; + if (hasParams) { + char lastChar; + do { + const char* subEnd = this->anyOf(",)\n"); + if (!subEnd || '\n' == *subEnd) { + return this->reportError<bool>("unexpected phrase list end"); + } + fMarkup.emplace_front(MarkType::kPhraseParam, fChar, fLineCount, fParent, + fMC); + Definition* phraseParam = &fMarkup.front(); + phraseParam->fContentStart = fChar; + phraseParam->fContentEnd = subEnd; + phraseParam->fName = string(fChar, subEnd - fChar); + definition->fChildren.push_back(phraseParam); + this->skipTo(subEnd); + lastChar = this->next(); + phraseParam->fTerminator = fChar; + } while (')' != lastChar); + this->skipWhiteSpace(); + definition->fContentStart = fChar; + } + this->setAsParent(definition); + break; + } + // not one-liners + case MarkType::kCode: + case MarkType::kExample: + case MarkType::kFile: + case MarkType::kFunction: + case MarkType::kLegend: + case MarkType::kList: + case MarkType::kPrivate: + case MarkType::kTable: + if (hasEnd) { + definition = fParent; + if (markType != fParent->fMarkType) { + return this->reportError<bool>("end element mismatch"); + } else if (!this->popParentStack(fParent)) { + return false; + } + if (MarkType::kExample == markType) { + if (definition->fChildren.size() == 0) { + TextParser emptyCheck(definition); + if (emptyCheck.eof() || !emptyCheck.skipWhiteSpace()) { + return this->reportError<bool>("missing example body"); + } + } +// can't do this here; phrase refs may not have been defined yet +// this->setWrapper(definition); + } + } else { + fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC); + definition = &fMarkup.front(); + definition->fContentStart = fChar; + definition->fName = typeNameBuilder[0]; + definition->fFiddle = fParent->fFiddle; + char suffix = '\0'; + bool tryAgain; + do { + tryAgain = false; + for (const auto& child : fParent->fChildren) { + if (child->fFiddle == definition->fFiddle) { + if (MarkType::kExample != child->fMarkType) { + continue; + } + if ('\0' == suffix) { + suffix = 'a'; + } else if (++suffix > 'z') { + return reportError<bool>("too many examples"); + } + definition->fFiddle = fParent->fFiddle + '_'; + definition->fFiddle += suffix; + tryAgain = true; + break; + } + } + } while (tryAgain); + this->setAsParent(definition); + } + break; + // always treated as one-liners (can't detect misuse easily) + case MarkType::kAnchor: + case MarkType::kBug: + case MarkType::kDeprecated: + case MarkType::kDetails: + case MarkType::kDuration: + case MarkType::kExperimental: + case MarkType::kFilter: + case MarkType::kHeight: + case MarkType::kIllustration: + case MarkType::kImage: + case MarkType::kIn: + case MarkType::kLine: + case MarkType::kLiteral: + case MarkType::kNoJustify: + case MarkType::kOutdent: + case MarkType::kPlatform: + case MarkType::kPopulate: + case MarkType::kSeeAlso: + case MarkType::kSet: + case MarkType::kSubstitute: + case MarkType::kVolatile: + case MarkType::kWidth: + // todo : add check disallowing children? + if (hasEnd && MarkType::kAnchor != markType && MarkType::kLine != markType) { + return this->reportError<bool>("one liners omit end element"); + } else if (!hasEnd && MarkType::kAnchor == markType) { + return this->reportError<bool>("anchor line must have end element last"); + } + fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC); + definition = &fMarkup.front(); + definition->fName = typeNameBuilder[0]; + definition->fFiddle = Definition::NormalizedName(typeNameBuilder[0]); + definition->fContentStart = fChar; + definition->fContentEnd = this->trimmedBracketEnd('\n'); + definition->fTerminator = this->lineEnd() - 1; + fParent->fChildren.push_back(definition); + if (MarkType::kAnchor == markType) { + this->parseHashAnchor(definition); + } else if (MarkType::kLine == markType) { + this->parseHashLine(definition); + } else if (IncompleteAllowed(markType)) { + this->skipSpace(); + fParent->fDeprecated = true; + fParent->fDetails = + this->skipExact("soon") ? Definition::Details::kSoonToBe_Deprecated : + this->skipExact("testing") ? Definition::Details::kTestingOnly_Experiment : + this->skipExact("do not use") ? Definition::Details::kDoNotUse_Experiment : + this->skipExact("not ready") ? Definition::Details::kNotReady_Experiment : + Definition::Details::kNone; + this->skipSpace(); + if ('\n' != this->peek()) { + return this->reportError<bool>("unexpected text after #Deprecated"); + } + } + break; + case MarkType::kExternal: + (void) this->collectExternals(); // FIXME: detect errors in external defs? + break; + default: + SkASSERT(0); // fixme : don't let any types be invisible + return true; + } + if (fParent) { + SkASSERT(definition); + SkASSERT(definition->fName.length() > 0); + } + return true; +} + +void BmhParser::reportDuplicates(const Definition& def, string dup) const { + if (MarkType::kExample == def.fMarkType && dup == def.fFiddle) { + TextParser reporter(&def); + reporter.reportError("duplicate example name"); + } + for (auto& child : def.fChildren ) { + reportDuplicates(*child, dup); + } +} + + +static Definition* find_fiddle(Definition* def, string name) { + if (MarkType::kExample == def->fMarkType && name == def->fFiddle) { + return def; + } + for (auto& child : def->fChildren) { + Definition* result = find_fiddle(child, name); + if (result) { + return result; + } + } + return nullptr; +} + +Definition* BmhParser::findExample(string name) const { + for (const auto& topic : fTopicMap) { + if (topic.second->fParent) { + continue; + } + Definition* def = find_fiddle(topic.second, name); + if (def) { + return def; + } + } + return nullptr; +} + +static bool check_example_hashes(Definition* def) { + if (MarkType::kExample == def->fMarkType) { + if (def->fHash.length()) { + return true; + } + for (auto child : def->fChildren) { + if (MarkType::kPlatform == child->fMarkType) { + if (string::npos != string(child->fContentStart, child->length()).find("!fiddle")) { + return true; + } + } + } + return def->reportError<bool>("missing hash"); + } + for (auto& child : def->fChildren) { + if (!check_example_hashes(child)) { + return false; + } + } + return true; +} + +bool BmhParser::checkExampleHashes() const { + for (const auto& topic : fTopicMap) { + if (!topic.second->fParent && !check_example_hashes(topic.second)) { + return false; + } + } + return true; +} + +static void reset_example_hashes(Definition* def) { + if (MarkType::kExample == def->fMarkType) { + def->fHash.clear(); + return; + } + for (auto& child : def->fChildren) { + reset_example_hashes(child); + } +} + +void BmhParser::resetExampleHashes() { + for (const auto& topic : fTopicMap) { + if (!topic.second->fParent) { + reset_example_hashes(topic.second); + } + } +} + +static void find_examples(const Definition& def, vector<string>* exampleNames) { + if (MarkType::kExample == def.fMarkType) { + exampleNames->push_back(def.fFiddle); + } + for (auto& child : def.fChildren ) { + find_examples(*child, exampleNames); + } +} + +bool BmhParser::checkEndMarker(MarkType markType, string match) const { + TextParser tp(fFileName, fLine, fChar, fLineCount); + tp.skipSpace(); + if (fMC != tp.next()) { + return this->reportError<bool>("mismatched end marker expect #"); + } + const char* nameStart = tp.fChar; + tp.skipToNonName(); + string markName(nameStart, tp.fChar - nameStart); + if (kMarkProps[(int) markType].fName != markName) { + return this->reportError<bool>("expected #XXX ## to match"); + } + tp.skipSpace(); + nameStart = tp.fChar; + tp.skipToNonName(); + markName = string(nameStart, tp.fChar - nameStart); + if ("" == markName) { + if (fMC != tp.next() || fMC != tp.next()) { + return this->reportError<bool>("expected ##"); + } + return true; + } + std::replace(markName.begin(), markName.end(), '-', '_'); + auto defPos = match.rfind(markName); + if (string::npos == defPos) { + return this->reportError<bool>("mismatched end marker v1"); + } + if (markName.size() != match.size() - defPos) { + return this->reportError<bool>("mismatched end marker v2"); + } + return true; +} + +bool BmhParser::checkExamples() const { + vector<string> exampleNames; + for (const auto& topic : fTopicMap) { + if (topic.second->fParent) { + continue; + } + find_examples(*topic.second, &exampleNames); + } + std::sort(exampleNames.begin(), exampleNames.end()); + string* last = nullptr; + string reported; + bool checkOK = true; + for (auto& nameIter : exampleNames) { + if (last && *last == nameIter && reported != *last) { + reported = *last; + SkDebugf("%s\n", reported.c_str()); + for (const auto& topic : fTopicMap) { + if (topic.second->fParent) { + continue; + } + this->reportDuplicates(*topic.second, reported); + } + checkOK = false; + } + last = &nameIter; + } + return checkOK; +} + +bool BmhParser::checkParamReturn(const Definition* definition) const { + const char* parmEndCheck = definition->fContentEnd; + while (parmEndCheck < definition->fTerminator) { + if (fMC == parmEndCheck[0]) { + break; + } + if (' ' < parmEndCheck[0]) { + this->reportError<bool>( + "use full end marker on multiline #Param and #Return"); + } + ++parmEndCheck; + } + return true; +} + +bool BmhParser::childOf(MarkType markType) const { + auto childError = [this](MarkType markType) -> bool { + string errStr = "expected "; + errStr += kMarkProps[(int) markType].fName; + errStr += " parent"; + return this->reportError<bool>(errStr.c_str()); + }; + + if (markType == fParent->fMarkType) { + return true; + } + if (this->hasEndToken()) { + if (!fParent->fParent) { + return this->reportError<bool>("expected grandparent"); + } + if (markType == fParent->fParent->fMarkType) { + return true; + } + } + return childError(markType); +} + +string BmhParser::className(MarkType markType) { + const char* end = this->lineEnd(); + const char* mc = this->strnchr(fMC, end); + string classID; + TextParserSave savePlace(this); + this->skipSpace(); + const char* wordStart = fChar; + this->skipToNonName(); + const char* wordEnd = fChar; + classID = string(wordStart, wordEnd - wordStart); + if (!mc) { + savePlace.restore(); + } + string builder; + const Definition* parent = this->parentSpace(); + if (parent && parent->fName != classID) { + builder += parent->fName; + } + if (mc) { + if (mc + 1 < fEnd && fMC == mc[1]) { // if ## + if (markType != fParent->fMarkType) { + return this->reportError<string>("unbalanced method"); + } + if (builder.length() > 0 && classID.size() > 0) { + if (builder != fParent->fName) { + builder += "::"; + builder += classID; + if (builder != fParent->fName) { + return this->reportError<string>("name mismatch"); + } + } + } + this->skipLine(); + return fParent->fName; + } + fChar = mc; + this->next(); + } + this->skipWhiteSpace(); + if (MarkType::kEnum == markType && fChar >= end) { + fAnonymous = true; + builder += "::_anonymous"; + return uniqueRootName(builder, markType); + } + builder = this->word(builder, "::"); + return builder; +} + +bool BmhParser::collectExternals() { + do { + this->skipWhiteSpace(); + if (this->eof()) { + break; + } + if (fMC == this->peek()) { + this->next(); + if (this->eof()) { + break; + } + if (fMC == this->peek()) { + this->skipLine(); + break; + } + if (' ' >= this->peek()) { + this->skipLine(); + continue; + } + if (this->startsWith(kMarkProps[(int) MarkType::kExternal].fName)) { + this->skipToNonName(); + continue; + } + } + this->skipToAlpha(); + const char* wordStart = fChar; + this->skipToWhiteSpace(); + if (fChar - wordStart > 0) { + fExternals.emplace_front(MarkType::kExternal, wordStart, fChar, fLineCount, fParent, + fMC); + RootDefinition* definition = &fExternals.front(); + definition->fFileName = fFileName; + definition->fName = string(wordStart ,fChar - wordStart); + definition->fFiddle = Definition::NormalizedName(definition->fName); + } + } while (!this->eof()); + return true; +} + +bool BmhParser::dumpExamples(FILE* fiddleOut, Definition& def, bool* continuation) const { + if (MarkType::kExample == def.fMarkType) { + string result; + if (!this->exampleToScript(&def, BmhParser::ExampleOptions::kAll, &result)) { + return false; + } + if (result.length() > 0) { + result += "\n"; + result += "}"; + if (*continuation) { + fprintf(fiddleOut, ",\n"); + } else { + *continuation = true; + } + fprintf(fiddleOut, "%s", result.c_str()); + } + return true; + } + for (auto& child : def.fChildren ) { + if (!this->dumpExamples(fiddleOut, *child, continuation)) { + return false; + } + } + return true; +} + +bool BmhParser::dumpExamples(const char* fiddleJsonFileName) const { + string oldFiddle(fiddleJsonFileName); + string newFiddle(fiddleJsonFileName); + newFiddle += "_new"; + FILE* fiddleOut = fopen(newFiddle.c_str(), "wb"); + if (!fiddleOut) { + SkDebugf("could not open output file %s\n", newFiddle.c_str()); + return false; + } + fprintf(fiddleOut, "{\n"); + bool continuation = false; + for (const auto& topic : fTopicMap) { + if (topic.second->fParent) { + continue; + } + this->dumpExamples(fiddleOut, *topic.second, &continuation); + } + fprintf(fiddleOut, "\n}\n"); + fclose(fiddleOut); + if (ParserCommon::WrittenFileDiffers(oldFiddle, newFiddle)) { + ParserCommon::CopyToFile(oldFiddle, newFiddle); + SkDebugf("wrote %s\n", fiddleJsonFileName); + } else { + remove(newFiddle.c_str()); + } + return true; +} + +int BmhParser::endHashCount() const { + const char* end = fLine + this->lineLength(); + int count = 0; + while (fLine < end && fMC == *--end) { + count++; + } + return count; +} + +bool BmhParser::endTableColumn(const char* end, const char* terminator) { + if (!this->popParentStack(fParent)) { + return false; + } + fWorkingColumn->fContentEnd = end; + fWorkingColumn->fTerminator = terminator; + fColStart = fChar - 1; + this->skipSpace(); + fTableState = TableState::kColumnStart; + return true; +} + +static size_t count_indent(string text, size_t test, size_t end) { + size_t result = test; + while (test < end) { + if (' ' != text[test]) { + break; + } + ++test; + } + return test - result; +} + +static void add_code(string text, int pos, int end, + size_t outIndent, size_t textIndent, string& example) { + do { + // fix this to move whole paragraph in, out, but preserve doc indent + int nextIndent = count_indent(text, pos, end); + size_t len = text.find('\n', pos); + if (string::npos == len) { + len = end; + } + if ((size_t) (pos + nextIndent) < len) { + size_t indent = outIndent + nextIndent; + SkASSERT(indent >= textIndent); + indent -= textIndent; + for (size_t index = 0; index < indent; ++index) { + example += ' '; + } + pos += nextIndent; + while ((size_t) pos < len) { + example += '"' == text[pos] ? "\\\"" : + '\\' == text[pos] ? "\\\\" : + text.substr(pos, 1); + ++pos; + } + example += "\\n"; + } else { + pos += nextIndent; + } + if ('\n' == text[pos]) { + ++pos; + } + } while (pos < end); +} + +bool BmhParser::IsExemplary(const Definition* def) { + return kMarkProps[(int) def->fMarkType].fExemplary != Exemplary::kNo; +} + +bool BmhParser::exampleToScript(Definition* def, ExampleOptions exampleOptions, + string* result) const { + bool hasFiddle = true; + const Definition* platform = def->hasChild(MarkType::kPlatform); + if (platform) { + TextParser platParse(platform); + hasFiddle = !platParse.strnstr("!fiddle", platParse.fEnd); + } + if (!hasFiddle) { + *result = ""; + return true; + } + string text = this->extractText(def, TrimExtract::kNo); + bool textOut = string::npos != text.find("SkDebugf(") + || string::npos != text.find("dump(") + || string::npos != text.find("dumpHex("); + string heightStr = "256"; + string widthStr = "256"; + string normalizedName(def->fFiddle); + string code; + string imageStr = "0"; + string srgbStr = "false"; + string durationStr = "0"; + for (auto iter : def->fChildren) { + switch (iter->fMarkType) { + case MarkType::kDuration: + durationStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart); + break; + case MarkType::kHeight: + heightStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart); + break; + case MarkType::kWidth: + widthStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart); + break; + case MarkType::kDescription: + // ignore for now + break; + case MarkType::kFunction: { + // emit this, but don't wrap this in draw() + string funcText = this->extractText(&*iter, TrimExtract::kNo); + size_t pos = 0; + while (pos < funcText.length() && ' ' > funcText[pos]) { + ++pos; + } + size_t indent = count_indent(funcText, pos, funcText.length()); + add_code(funcText, pos, funcText.length(), 0, indent, code); + code += "\\n"; + } break; + case MarkType::kComment: + break; + case MarkType::kImage: + imageStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart); + break; + case MarkType::kToDo: + break; + case MarkType::kBug: + case MarkType::kMarkChar: + case MarkType::kPlatform: + case MarkType::kPhraseRef: + // ignore for now + break; + case MarkType::kSet: + if ("sRGB" == string(iter->fContentStart, + iter->fContentEnd - iter->fContentStart)) { + srgbStr = "true"; + } else { + SkASSERT(0); // more work to do + return false; + } + break; + case MarkType::kStdOut: + textOut = true; + break; + default: + SkASSERT(0); // more coding to do + } + } + string animatedStr = "0" != durationStr ? "true" : "false"; + string textOutStr = textOut ? "true" : "false"; + size_t pos = 0; + while (pos < text.length() && ' ' > text[pos]) { + ++pos; + } + size_t end = text.length(); + size_t outIndent = 0; + size_t textIndent = count_indent(text, pos, end); + if ("" == def->fWrapper) { + this->setWrapper(def); + } + if (def->fWrapper.length() > 0) { + code += def->fWrapper; + code += "\\n"; + outIndent = 4; + } + add_code(text, pos, end, outIndent, textIndent, code); + if (def->fWrapper.length() > 0) { + code += "}"; + } + string example = "\"" + normalizedName + "\": {\n"; + string filename = def->fileName(); + string baseFile = filename.substr(0, filename.length() - 4); + if (ExampleOptions::kText == exampleOptions) { + example += " \"code\": \"" + code + "\",\n"; + example += " \"hash\": \"" + def->fHash + "\",\n"; + example += " \"file\": \"" + baseFile + "\",\n"; + example += " \"name\": \"" + def->fName + "\","; + } else { + example += " \"code\": \"" + code + "\",\n"; + if (ExampleOptions::kPng == exampleOptions) { + example += " \"width\": " + widthStr + ",\n"; + example += " \"height\": " + heightStr + ",\n"; + example += " \"hash\": \"" + def->fHash + "\",\n"; + example += " \"file\": \"" + baseFile + "\",\n"; + example += " \"name\": \"" + def->fName + "\"\n"; + example += "}"; + } else { + example += " \"options\": {\n"; + example += " \"width\": " + widthStr + ",\n"; + example += " \"height\": " + heightStr + ",\n"; + example += " \"source\": " + imageStr + ",\n"; + example += " \"srgb\": " + srgbStr + ",\n"; + example += " \"f16\": false,\n"; + example += " \"textOnly\": " + textOutStr + ",\n"; + example += " \"animated\": " + animatedStr + ",\n"; + example += " \"duration\": " + durationStr + "\n"; + example += " },\n"; + example += " \"fast\": true"; + } + } + *result = example; + return true; +} + +string BmhParser::extractText(const Definition* def, TrimExtract trimExtract) const { + string result; + TextParser parser(def); + auto childIter = def->fChildren.begin(); + while (!parser.eof()) { + const char* end = def->fChildren.end() == childIter ? parser.fEnd : (*childIter)->fStart; + 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); + if (def->fChildren.end() != childIter) { + Definition* child = *childIter; + if (MarkType::kPhraseRef == child->fMarkType) { + auto phraseIter = fPhraseMap.find(child->fName); + if (fPhraseMap.end() == phraseIter) { + return def->reportError<string>("missing phrase definition"); + } + Definition* phrase = phraseIter->second; + // count indent of last line in result + size_t lastLF = result.rfind('\n'); + size_t startPos = string::npos == lastLF ? 0 : lastLF; + size_t lastLen = result.length() - startPos; + size_t indent = count_indent(result, startPos, result.length()) + 4; + string phraseStr = this->extractText(phrase, TrimExtract::kNo); + startPos = 0; + bool firstTime = true; + size_t endPos; + do { + endPos = phraseStr.find('\n', startPos); + size_t len = (string::npos != endPos ? endPos : phraseStr.length()) - startPos; + if (firstTime && lastLen + len + 1 < 100) { // FIXME: make 100 global const or something + result += ' '; + } else { + result += '\n'; + result += string(indent, ' '); + } + firstTime = false; + string tmp = phraseStr.substr(startPos, len); + result += tmp; + startPos = endPos + 1; + } while (string::npos != endPos); + result += '\n'; + } + parser.skipTo(child->fTerminator); + std::advance(childIter, 1); + } + } + return result; +} + +string BmhParser::loweredTopic(string name, Definition* def) { + string lowered; + SkASSERT('_' != name[0]); + char last = '_'; + for (char c : name) { + SkASSERT(' ' != c); + if (isupper(last)) { + lowered += islower(c) ? tolower(last) : last; + last = '\0'; + } + if ('_' == c) { + last = c; + c = ' '; + } else if ('_' == last && isupper(c)) { + last = c; + continue; + } + lowered += c; + if (' ' == c) { + this->setUpPartialSubstitute(lowered); + } + } + if (isupper(last)) { + lowered += tolower(last); + } + return lowered; +} + +void BmhParser::setUpGlobalSubstitutes() { + for (auto& entry : fExternals) { + string externalName = entry.fName; + SkASSERT(fGlobalNames.fRefMap.end() == fGlobalNames.fRefMap.find(externalName)); + fGlobalNames.fRefMap[externalName] = nullptr; + } + for (auto bMap : { &fClassMap, &fConstMap, &fDefineMap, &fEnumMap, &fMethodMap, + &fTypedefMap } ) { + for (auto& entry : *bMap) { + Definition* parent = (Definition*) &entry.second; + string name = parent->fName; + SkASSERT(fGlobalNames.fLinkMap.end() == fGlobalNames.fLinkMap.find(name)); + string ref = ParserCommon::HtmlFileName(parent->fFileName) + '#' + parent->fFiddle; + fGlobalNames.fLinkMap[name] = ref; + SkASSERT(fGlobalNames.fRefMap.end() == fGlobalNames.fRefMap.find(name)); + fGlobalNames.fRefMap[name] = const_cast<Definition*>(parent); + NameMap* names = MarkType::kClass == parent->fMarkType + || MarkType::kStruct == parent->fMarkType + || MarkType::kEnumClass == parent->fMarkType ? &parent->asRoot()->fNames : + &fGlobalNames; + this->setUpSubstitutes(parent, names); + if (names != &fGlobalNames) { + names->copyToParent(&fGlobalNames); + } + } + } + for (auto& topic : fTopicMap) { + bool hasSubstitute = false; + for (auto& child : topic.second->fChildren) { + bool isAlias = MarkType::kAlias == child->fMarkType; + bool isSubstitute = MarkType::kSubstitute == child->fMarkType; + if (!isAlias && !isSubstitute) { + continue; + } + hasSubstitute |= isSubstitute; + string name(child->fContentStart, child->length()); + if (isAlias) { + name = ParserCommon::ConvertRef(name, false); + for (auto aliasChild : child->fChildren) { + if (MarkType::kSubstitute == aliasChild->fMarkType) { + string sub(aliasChild->fContentStart, aliasChild->length()); + this->setUpSubstitute(sub, topic.second); + } + } + } + this->setUpSubstitute(name, topic.second); + } + if (hasSubstitute) { + continue; + } + string lowered = this->loweredTopic(topic.first, topic.second); + SkDEBUGCODE(auto globalIter = fGlobalNames.fLinkMap.find(lowered)); + SkASSERT(fGlobalNames.fLinkMap.end() == globalIter); + fGlobalNames.fLinkMap[lowered] = + ParserCommon::HtmlFileName(topic.second->fFileName) + '#' + topic.first; + SkASSERT(fGlobalNames.fRefMap.end() == fGlobalNames.fRefMap.find(lowered)); + fGlobalNames.fRefMap[lowered] = topic.second; + } + size_t slash = fRawFilePathDir.rfind('/'); + size_t bslash = fRawFilePathDir.rfind('\\'); + string spellFile; + if (string::npos == slash && string::npos == bslash) { + spellFile = fRawFilePathDir; + } else { + if (string::npos != bslash && bslash > slash) { + slash = bslash; + } + spellFile = fRawFilePathDir.substr(0, slash); + } + spellFile += '/'; + spellFile += kSpellingFileName; + FILE* file = fopen(spellFile.c_str(), "r"); + if (!file) { + SkDebugf("missing %s\n", spellFile.c_str()); + return; + } + fseek(file, 0L, SEEK_END); + int sz = (int) ftell(file); + rewind(file); + char* buffer = new char[sz]; + memset(buffer, ' ', sz); + int read = (int)fread(buffer, 1, sz, file); + SkAssertResult(read > 0); + sz = read; // FIXME: ? why are sz and read different? + fclose(file); + int i = 0; + int start = i; + string word; + do { + if (' ' < buffer[i]) { + ++i; + continue; + } + word = string(&buffer[start], i - start); + if (fGlobalNames.fRefMap.end() == fGlobalNames.fRefMap.find(word)) { + fGlobalNames.fRefMap[word] = nullptr; + } else { + SkDebugf("%s ", word.c_str()); // debugging: word missing from spelling list + } + do { + ++i; + } while (i < sz && ' ' >= buffer[i]); + start = i; + } while (i < sz); + delete[] buffer; +} + +void BmhParser::setUpSubstitutes(const Definition* parent, NameMap* names) { + for (const auto& child : parent->fChildren) { + MarkType markType = child->fMarkType; + if (MarkType::kAlias == markType) { + continue; + } + if (MarkType::kSubstitute == markType) { + continue; + } + string name(child->fName); + if (&fGlobalNames != names) { + size_t lastDC = name.rfind("::"); + if (string::npos != lastDC) { + name = name.substr(lastDC + 2); + } + if ("" == name) { + continue; + } + } + size_t lastUnder = name.rfind('_'); + if (string::npos != lastUnder && ++lastUnder < name.length()) { + bool numbers = true; + for (size_t index = lastUnder; index < name.length(); ++index) { + numbers &= (bool) isdigit(name[index]); + } + if (numbers) { + continue; + } + } + string ref; + if (&fGlobalNames == names) { + ref = ParserCommon::HtmlFileName(child->fFileName); + } + ref += '#' + child->fFiddle; + if (MarkType::kClass == markType || MarkType::kStruct == markType + || MarkType::kMethod == markType || MarkType::kEnum == markType + || MarkType::kEnumClass == markType || MarkType::kConst == markType + || MarkType::kMember == markType || MarkType::kDefine == markType + || MarkType::kTypedef == markType) { + SkASSERT(names->fLinkMap.end() == names->fLinkMap.find(name)); + names->fLinkMap[name] = ref; + SkASSERT(names->fRefMap.end() == names->fRefMap.find(name)); + names->fRefMap[name] = child; + } + if (MarkType::kClass == markType || MarkType::kStruct == markType + || MarkType::kEnumClass == markType) { + RootDefinition* rootDef = child->asRoot(); + NameMap* nameMap = &rootDef->fNames; + this->setUpSubstitutes(child, nameMap); + nameMap->copyToParent(names); + } + if (MarkType::kEnum == markType) { + this->setUpSubstitutes(child, names); + } + if (MarkType::kSubtopic == markType) { + if (&fGlobalNames != names && string::npos != child->fName.find('_')) { + string lowered = this->loweredTopic(child->fName, child); + SkDEBUGCODE(auto refIter = names->fRefMap.find(lowered)); + SkDEBUGCODE(auto iter = names->fLinkMap.find(lowered)); + SkASSERT(names->fLinkMap.end() == iter); + names->fLinkMap[lowered] = '#' + child->fName; + SkASSERT(names->fRefMap.end() == refIter); + names->fRefMap[lowered] = child; + } + this->setUpSubstitutes(child, names); + } + } +} + +void BmhParser::setUpPartialSubstitute(string name) { + auto iter = fGlobalNames.fRefMap.find(name); + if (fGlobalNames.fRefMap.end() != iter) { + SkASSERT(nullptr == iter->second); + return; + } + fGlobalNames.fRefMap[name] = nullptr; +} + +void BmhParser::setUpSubstitute(string name, Definition* def) { + SkASSERT(fGlobalNames.fRefMap.end() == fGlobalNames.fRefMap.find(name)); + fGlobalNames.fRefMap[name] = def; + SkASSERT(fGlobalNames.fLinkMap.end() == fGlobalNames.fLinkMap.find(name)); + string str = ParserCommon::HtmlFileName(def->fFileName) + '#' + def->fName; + fGlobalNames.fLinkMap[name] = str; + size_t stop = name.length(); + do { + size_t space = name.rfind(' ', stop); + if (string::npos == space) { + break; + } + string partial = name.substr(0, space + 1); + stop = space - 1; + this->setUpPartialSubstitute(partial); + } while (true); +} + +void BmhParser::setWrapper(Definition* def) const { + const char drawWrapper[] = "void draw(SkCanvas* canvas) {"; + const char drawNoCanvas[] = "void draw(SkCanvas* ) {"; + string text = this->extractText(def, TrimExtract::kNo); + size_t nonSpace = 0; + while (nonSpace < text.length() && ' ' >= text[nonSpace]) { + ++nonSpace; + } + bool hasFunc = !text.compare(nonSpace, sizeof(drawWrapper) - 1, drawWrapper); + bool noCanvas = !text.compare(nonSpace, sizeof(drawNoCanvas) - 1, drawNoCanvas); + bool hasCanvas = string::npos != text.find("SkCanvas canvas"); + SkASSERT(!hasFunc || !noCanvas); + bool preprocessor = text[0] == '#'; + bool wrapCode = !hasFunc && !noCanvas && !preprocessor; + if (wrapCode) { + def->fWrapper = hasCanvas ? string(drawNoCanvas) : string(drawWrapper); + } +} + +RootDefinition* BmhParser::findBmhObject(MarkType markType, string typeName) { + const auto& mapIter = std::find_if(fMaps.begin(), fMaps.end(), + [markType](DefinitionMap& defMap){ return markType == defMap.fMarkType; } ); + if (mapIter == fMaps.end()) { + return nullptr; + } + return &(*mapIter->fMap)[typeName]; +} + +// FIXME: some examples may produce different output on different platforms +// if the text output can be different, think of how to author that + +bool BmhParser::findDefinitions() { + bool lineStart = true; + const char* lastChar = nullptr; + const char* lastMC = nullptr; + fParent = nullptr; + while (!this->eof()) { + if (this->peek() == fMC) { + lastMC = fChar; + this->next(); + if (this->peek() == fMC) { + this->next(); + if (!lineStart && ' ' < this->peek()) { + if (!fParent || MarkType::kFormula != fParent->fMarkType) { + return this->reportError<bool>("expected definition"); + } + } + if (this->peek() != fMC) { + if (MarkType::kColumn == fParent->fMarkType) { + SkASSERT(TableState::kColumnEnd == fTableState); + if (!this->endTableColumn(lastChar, lastMC)) { + return false; + } + SkASSERT(fRow); + if (!this->popParentStack(fParent)) { + return false; + } + fRow->fContentEnd = fWorkingColumn->fContentEnd; + fWorkingColumn = nullptr; + fRow = nullptr; + fTableState = TableState::kNone; + } else { + vector<string> parentName; + parentName.push_back(fParent->fName); + if (!this->addDefinition(fChar - 1, true, fParent->fMarkType, parentName, + HasTag::kNo)) { + return false; + } + } + } else { + SkAssertResult(this->next() == fMC); + fMC = this->next(); // change markup character + if (' ' >= fMC) { + return this->reportError<bool>("illegal markup character"); + } + fMarkup.emplace_front(MarkType::kMarkChar, fChar - 4, fLineCount, fParent, fMC); + Definition* markChar = &fMarkup.front(); + markChar->fContentStart = fChar - 1; + this->skipToEndBracket('\n'); + markChar->fContentEnd = fChar; + markChar->fTerminator = fChar; + fParent->fChildren.push_back(markChar); + } + } else if (this->peek() >= 'A' && this->peek() <= 'Z') { + const char* defStart = fChar - 1; + MarkType markType = this->getMarkType(MarkLookup::kRequire); + bool hasEnd = this->hasEndToken(); + if (!hasEnd && fParent) { + MarkType parentType = fParent->fMarkType; + uint64_t parentMask = kMarkProps[(int) markType].fParentMask; + if (parentMask && !(parentMask & (1LL << (int) parentType))) { + return this->reportError<bool>("invalid parent"); + } + } + if (!this->skipName(kMarkProps[(int) markType].fName)) { + return this->reportError<bool>("illegal markup character"); + } + if (!this->skipSpace()) { + return this->reportError<bool>("unexpected end"); + } + lineStart = '\n' == this->peek(); + bool expectEnd = true; + vector<string> typeNameBuilder = this->typeName(markType, &expectEnd); + if (fCloned && MarkType::kMethod != markType && MarkType::kExample != markType + && !fAnonymous) { + return this->reportError<bool>("duplicate name"); + } + if (hasEnd && expectEnd) { + if (fMC == this->peek()) { + return this->reportError<bool>("missing body"); + } + } + if (!this->addDefinition(defStart, hasEnd, markType, typeNameBuilder, + HasTag::kYes)) { + return false; + } + continue; + } else if (this->peek() == ' ') { + if (!fParent || (MarkType::kFormula != fParent->fMarkType + && MarkType::kLegend != fParent->fMarkType + && MarkType::kList != fParent->fMarkType + && MarkType::kLine != fParent->fMarkType + && MarkType::kTable != fParent->fMarkType)) { + int endHashes = this->endHashCount(); + if (endHashes <= 1) { + if (fParent) { + if (TableState::kColumnEnd == fTableState) { + if (!this->endTableColumn(lastChar, lastMC)) { + return false; + } + } else { // one line comment + fMarkup.emplace_front(MarkType::kComment, fChar - 1, fLineCount, + fParent, fMC); + Definition* comment = &fMarkup.front(); + comment->fContentStart = fChar - 1; + this->skipToEndBracket('\n'); + comment->fContentEnd = fChar; + comment->fTerminator = fChar; + fParent->fChildren.push_back(comment); + } + } else { + fChar = fLine + this->lineLength() - 1; + } + } else { // table row + if (2 != endHashes) { + string errorStr = "expect "; + errorStr += fMC; + errorStr += fMC; + return this->reportError<bool>(errorStr.c_str()); + } + if (!fParent || MarkType::kTable != fParent->fMarkType) { + return this->reportError<bool>("missing table"); + } + } + } else if (TableState::kNone == fTableState) { + // fixme? no nested tables for now + fColStart = fChar - 1; + fMarkup.emplace_front(MarkType::kRow, fColStart, fLineCount, fParent, fMC); + fRow = &fMarkup.front(); + fRow->fName = fParent->fName; + this->skipWhiteSpace(); + fRow->fContentStart = fChar; + this->setAsParent(fRow); + fTableState = TableState::kColumnStart; + } + if (TableState::kColumnStart == fTableState) { + fMarkup.emplace_front(MarkType::kColumn, fColStart, fLineCount, fParent, fMC); + fWorkingColumn = &fMarkup.front(); + fWorkingColumn->fName = fParent->fName; + fWorkingColumn->fContentStart = fChar; + this->setAsParent(fWorkingColumn); + fTableState = TableState::kColumnEnd; + continue; + } + } else if (this->peek() >= 'a' && this->peek() <= 'z') { + // expect zero or more letters and underscores (no spaces) then hash + const char* phraseNameStart = fChar; + this->skipPhraseName(); + string phraseKey = string(phraseNameStart, fChar - phraseNameStart); + char delimiter = this->next(); + vector<string> params; + vector<const char*> paramsLoc; + if (fMC != delimiter) { + if ('(' != delimiter) { + return this->reportError<bool>("expect # after phrase name"); + } + // phrase may take comma delimited parameter list + do { + const char* subEnd = this->anyOf(",)\n"); + if (!subEnd || '\n' == *subEnd) { + return this->reportError<bool>("unexpected phrase list end"); + } + params.push_back(string(fChar, subEnd - fChar)); + paramsLoc.push_back(fChar); + this->skipTo(subEnd); + + } while (')' != this->next()); + } + const char* start = phraseNameStart; + SkASSERT('#' == start[-1]); + --start; + if (start > fStart && ' ' >= start[-1]) { + --start; // preserve whether to add whitespace before substitution + } + fMarkup.emplace_front(MarkType::kPhraseRef, start, fLineCount, fParent, fMC); + Definition* markChar = &fMarkup.front(); + this->skipExact("#"); + markChar->fContentStart = fChar; + markChar->fContentEnd = fChar; + markChar->fTerminator = fChar; + markChar->fName = phraseKey; + fParent->fChildren.push_back(markChar); + int paramLocIndex = 0; + for (auto param : params) { + const char* paramLoc = paramsLoc[paramLocIndex++]; + fMarkup.emplace_front(MarkType::kPhraseParam, paramLoc, fLineCount, fParent, + fMC); + Definition* phraseParam = &fMarkup.front(); + phraseParam->fContentStart = paramLoc; + phraseParam->fContentEnd = paramLoc + param.length(); + phraseParam->fTerminator = paramLoc + param.length(); + phraseParam->fName = param; + markChar->fChildren.push_back(phraseParam); + } + } + } + char nextChar = this->next(); + if (' ' < nextChar) { + lastChar = fChar; + lineStart = false; + } else if (nextChar == '\n') { + lineStart = true; + } + } + if (fParent) { + return fParent->reportError<bool>("mismatched end"); + } + return true; +} + +MarkType BmhParser::getMarkType(MarkLookup lookup) const { + for (int index = 0; index <= Last_MarkType; ++index) { + int typeLen = strlen(kMarkProps[index].fName); + if (typeLen == 0) { + continue; + } + if (fChar + typeLen >= fEnd || fChar[typeLen] > ' ') { + continue; + } + int chCompare = strncmp(fChar, kMarkProps[index].fName, typeLen); + if (chCompare < 0) { + goto fail; + } + if (chCompare == 0) { + return (MarkType) index; + } + } +fail: + if (MarkLookup::kRequire == lookup) { + return this->reportError<MarkType>("unknown mark type"); + } + return MarkType::kNone; +} + +bool BmhParser::hasEndToken() const { + const char* ptr = fLine; + char test; + do { + if (ptr >= fEnd) { + return false; + } + test = *ptr++; + if ('\n' == test) { + return false; + } + } while (fMC != test || fMC != *ptr); + return true; +} + +string BmhParser::memberName() { + const char* wordStart; + const char* prefixes[] = { "static", "const" }; + do { + this->skipSpace(); + wordStart = fChar; + this->skipToNonName(); + } while (this->anyOf(wordStart, prefixes, SK_ARRAY_COUNT(prefixes))); + if ('*' == this->peek()) { + this->next(); + } + return this->className(MarkType::kMember); +} + +string BmhParser::methodName() { + if (this->hasEndToken()) { + if (!fParent || !fParent->fName.length()) { + return this->reportError<string>("missing parent method name"); + } + SkASSERT(fMC == this->peek()); + this->next(); + SkASSERT(fMC == this->peek()); + this->next(); + SkASSERT(fMC != this->peek()); + return fParent->fName; + } + string builder; + const char* end = this->lineEnd(); + const char* paren = this->strnchr('(', end); + if (!paren) { + return this->reportError<string>("missing method name and reference"); + } + { + TextParserSave endCheck(this); + while (end < fEnd && !this->strnchr(')', end)) { + fChar = end + 1; + end = this->lineEnd(); + } + if (end >= fEnd) { + return this->reportError<string>("missing method end paren"); + } + endCheck.restore(); + } + const char* nameStart = paren; + char ch; + bool expectOperator = false; + bool isConstructor = false; + const char* nameEnd = nullptr; + while (nameStart > fChar && ' ' != (ch = *--nameStart)) { + if (!isalnum(ch) && '_' != ch) { + if (nameEnd) { + break; + } + expectOperator = true; + continue; + } + if (!nameEnd) { + nameEnd = nameStart + 1; + } + } + if (!nameEnd) { + return this->reportError<string>("unexpected method name char"); + } + if (' ' == nameStart[0]) { + ++nameStart; + } + if (nameEnd <= nameStart) { + return this->reportError<string>("missing method name"); + } + if (nameStart >= paren) { + return this->reportError<string>("missing method name length"); + } + string name(nameStart, nameEnd - nameStart); + bool allLower = true; + for (int index = 0; index < (int) (nameEnd - nameStart); ++index) { + if (!islower(nameStart[index])) { + allLower = false; + break; + } + } + if (expectOperator && "operator" != name) { + return this->reportError<string>("expected operator"); + } + const Definition* parent = this->parentSpace(); + if (parent && parent->fName.length() > 0) { + size_t parentNameIndex = parent->fName.rfind(':'); + parentNameIndex = string::npos == parentNameIndex ? 0 : parentNameIndex + 1; + string parentName = parent->fName.substr(parentNameIndex); + if (parentName == name) { + isConstructor = true; + } else if ('~' == name[0]) { + if (parentName != name.substr(1)) { + return this->reportError<string>("expected destructor"); + } + isConstructor = true; + } + builder = parent->fName + "::"; + } + bool addConst = false; + if (isConstructor || expectOperator) { + paren = this->strnchr(')', end) + 1; + TextParserSave saveState(this); + this->skipTo(paren); + if (this->skipExact("_const")) { + addConst = true; + } + saveState.restore(); + } + builder.append(nameStart, paren - nameStart); + if (addConst) { + builder.append("_const"); + } + if (!expectOperator && allLower) { + builder.append("()"); + } + int parens = 0; + while (fChar < end || parens > 0) { + if ('(' == this->peek()) { + ++parens; + } else if (')' == this->peek()) { + --parens; + } + this->next(); + } + TextParserSave saveState(this); + this->skipWhiteSpace(); + if (this->startsWith("const")) { + this->skipName("const"); + } else { + saveState.restore(); + } +// this->next(); + if (string::npos != builder.find('\n')) { + builder.erase(std::remove(builder.begin(), builder.end(), '\n'), builder.end()); + } + return uniqueRootName(builder, MarkType::kMethod); +} + +const Definition* BmhParser::parentSpace() const { + Definition* parent = nullptr; + Definition* test = fParent; + while (test) { + if (MarkType::kClass == test->fMarkType || + MarkType::kEnumClass == test->fMarkType || + MarkType::kStruct == test->fMarkType) { + parent = test; + break; + } + test = test->fParent; + } + return parent; +} + +// A full terminal statement is in the form: +// \n optional-white-space #MarkType white-space #[# white-space] +// \n optional-white-space #MarkType white-space Name white-space #[# white-space] +// MarkType must match definition->fMarkType +const char* BmhParser::checkForFullTerminal(const char* end, const Definition* definition) const { + const char* start = end; + while ('\n' != start[0] && start > fStart) { + --start; + } + SkASSERT (start < end); + // if end is preceeeded by \n#MarkType ## backup to there + TextParser parser(fFileName, start, fChar, fLineCount); + parser.skipWhiteSpace(); + if (parser.eof() || fMC != parser.next()) { + return end; + } + const char* markName = kMarkProps[(int) definition->fMarkType].fName; + if (!parser.skipExact(markName)) { + return end; + } + parser.skipWhiteSpace(); + TextParser startName(fFileName, definition->fStart, definition->fContentStart, + definition->fLineCount); + if ('#' == startName.next()) { + startName.skipToSpace(); + if (!startName.eof() && startName.skipSpace()) { + const char* nameBegin = startName.fChar; + startName.skipToWhiteSpace(); + string name(nameBegin, (int) (startName.fChar - nameBegin)); + if (fMC != parser.peek() && !parser.skipExact(name.c_str())) { + return end; + } + parser.skipSpace(); + } + } + if (parser.eof() || fMC != parser.next()) { + return end; + } + if (!parser.eof() && fMC != parser.next()) { + return end; + } + SkASSERT(parser.eof()); + return start; +} + +void BmhParser::parseHashAnchor(Definition* definition) { + this->skipToEndBracket(fMC); + fMarkup.emplace_front(MarkType::kLink, fChar, fLineCount, definition, fMC); + SkAssertResult(fMC == this->next()); + this->skipWhiteSpace(); + Definition* link = &fMarkup.front(); + link->fContentStart = fChar; + link->fContentEnd = this->trimmedBracketEnd(fMC); + this->skipToEndBracket(fMC); + SkAssertResult(fMC == this->next()); + SkAssertResult(fMC == this->next()); + link->fTerminator = fChar; + definition->fContentEnd = link->fContentEnd; + definition->fTerminator = fChar; + definition->fChildren.emplace_back(link); +} + +void BmhParser::parseHashFormula(Definition* definition) { + const char* start = definition->fContentStart; + definition->trimEnd(); + const char* end = definition->fContentEnd; + fMarkup.emplace_front(MarkType::kText, start, fLineCount, definition, fMC); + Definition* text = &fMarkup.front(); + text->fContentStart = start; + text->fContentEnd = end; + text->fTerminator = definition->fTerminator; + definition->fChildren.emplace_back(text); +} + +void BmhParser::parseHashLine(Definition* definition) { + const char* nextLF = this->strnchr('\n', this->fEnd); + const char* start = fChar; + const char* end = this->trimmedBracketEnd(fMC); + this->skipToEndBracket(fMC, nextLF); + if (fMC != this->next() || fMC != this->next()) { + return this->reportError<void>("expected ## to delineate line"); + } + fMarkup.emplace_front(MarkType::kText, start, fLineCount, definition, fMC); + Definition* text = &fMarkup.front(); + if (!islower(start[0]) && (!isdigit(start[0]) + || MarkType::kConst != definition->fParent->fMarkType)) { + return this->reportError<void>("expect lower case start"); + } + string contents = string(start, end - start); + if (string::npos != contents.find('.')) { + return this->reportError<void>("expect phrase, not sentence"); + } + size_t firstSpace = contents.find(' '); + if (string::npos == firstSpace || 0 == firstSpace || 's' != start[firstSpace - 1]) { + if (MarkType::kMethod == fParent->fMarkType && "experimental" != contents + && "incomplete" != contents) { + return this->reportError<void>( "expect phrase in third person present" + " tense (1st word should end in 's'"); + } + } + text->fContentStart = start; + text->fContentEnd = end; + text->fTerminator = fChar; + definition->fContentEnd = text->fContentEnd; + definition->fTerminator = fChar; + definition->fChildren.emplace_back(text); +} + +bool BmhParser::popParentStack(Definition* definition) { + if (!fParent) { + return this->reportError<bool>("missing parent"); + } + if (definition != fParent) { + return this->reportError<bool>("definition end is not parent"); + } + if (!definition->fStart) { + return this->reportError<bool>("definition missing start"); + } + if (definition->fContentEnd) { + return this->reportError<bool>("definition already ended"); + } + // more to figure out to handle table columns, at minimum + const char* end = fChar; + if (fMC != end[0]) { + while (end > definition->fContentStart && ' ' >= end[-1]) { + --end; + } + SkASSERT(&end[-1] >= definition->fContentStart && fMC == end[-1] + && (MarkType::kColumn == definition->fMarkType + || (&end[-2] >= definition->fContentStart && fMC == end[-2]))); + end -= 2; + } + end = checkForFullTerminal(end, definition); + definition->fContentEnd = end; + definition->fTerminator = fChar; + fParent = definition->fParent; + if (!fParent || (MarkType::kTopic == fParent->fMarkType && !fParent->fParent)) { + fRoot = nullptr; + } + return true; +} + + + +bool BmhParser::skipNoName() { + if ('\n' == this->peek()) { + this->next(); + return true; + } + this->skipWhiteSpace(); + if (fMC != this->peek()) { + return this->reportError<bool>("expected end mark 1"); + } + this->next(); + if (fMC != this->peek()) { + return this->reportError<bool>("expected end mark 2"); + } + this->next(); + return true; +} + +bool BmhParser::skipToDefinitionEnd(MarkType markType) { + if (this->eof()) { + return this->reportError<bool>("missing end"); + } + const char* start = fLine; + int startLineCount = fLineCount; + int stack = 1; + ptrdiff_t lineLen; + bool foundEnd = false; + do { + lineLen = this->lineLength(); + if (fMC != *fChar++) { + continue; + } + if (fMC == *fChar) { + continue; + } + if (' ' == *fChar) { + continue; + } + MarkType nextType = this->getMarkType(MarkLookup::kAllowUnknown); + if (markType != nextType) { + continue; + } + bool hasEnd = this->hasEndToken(); + if (hasEnd) { + if (!--stack) { + foundEnd = true; + continue; + } + } else { + ++stack; + } + } while ((void) ++fLineCount, (void) (fLine += lineLen), (void) (fChar = fLine), + !this->eof() && !foundEnd); + if (foundEnd) { + return true; + } + fLineCount = startLineCount; + fLine = start; + fChar = start; + return this->reportError<bool>("unbalanced stack"); +} + +bool BmhParser::skipToString() { + this->skipSpace(); + if (fMC != this->peek()) { + return this->reportError<bool>("expected end mark 3"); + } + this->next(); + this->skipSpace(); + // body is text from here to double fMC + // no single fMC allowed, no linefeed allowed + return true; +} + +vector<string> BmhParser::topicName() { + vector<string> result; + this->skipWhiteSpace(); + const char* lineEnd = fLine + this->lineLength(); + const char* nameStart = fChar; + while (fChar < lineEnd) { + char ch = this->next(); + SkASSERT(',' != ch); + if ('\n' == ch) { + break; + } + if (fMC == ch) { + break; + } + } + if (fChar - 1 > nameStart) { + string builder(nameStart, fChar - nameStart - 1); + trim_start_end(builder); + result.push_back(builder); + } + if (fChar < lineEnd && fMC == this->peek()) { + this->next(); + } + return result; +} + +// typeName parsing rules depend on mark type +vector<string> BmhParser::typeName(MarkType markType, bool* checkEnd) { + fAnonymous = false; + fCloned = false; + vector<string> result; + string builder; + if (fParent) { + builder = fParent->fName; + } + switch (markType) { + case MarkType::kDefine: + case MarkType::kEnum: + // enums may be nameless + case MarkType::kConst: + case MarkType::kEnumClass: + case MarkType::kClass: + case MarkType::kStruct: + // expect name + builder = this->className(markType); + break; + case MarkType::kExample: + // check to see if one already exists -- if so, number this one + builder = this->uniqueName(string(), markType); + this->skipNoName(); + break; + case MarkType::kCode: + case MarkType::kDescription: + case MarkType::kExternal: + case MarkType::kFunction: + case MarkType::kLegend: + case MarkType::kList: + case MarkType::kNoExample: + case MarkType::kPrivate: + this->skipNoName(); + break; + case MarkType::kFormula: + case MarkType::kLine: + this->skipToString(); + break; + case MarkType::kAlias: + case MarkType::kAnchor: + case MarkType::kBug: // fixme: expect number + case MarkType::kDeprecated: + case MarkType::kDetails: + case MarkType::kDuration: + case MarkType::kExperimental: + case MarkType::kFile: + case MarkType::kFilter: + case MarkType::kHeight: + case MarkType::kIllustration: + case MarkType::kImage: + case MarkType::kIn: + case MarkType::kLiteral: + case MarkType::kNoJustify: + case MarkType::kOutdent: + case MarkType::kPlatform: + case MarkType::kPopulate: + case MarkType::kReturn: + case MarkType::kSeeAlso: + case MarkType::kSet: + case MarkType::kSubstitute: + case MarkType::kToDo: + case MarkType::kVolatile: + case MarkType::kWidth: + *checkEnd = false; // no name, may have text body + break; + case MarkType::kStdOut: + this->skipNoName(); + break; // unnamed + case MarkType::kMember: + builder = this->memberName(); + break; + case MarkType::kMethod: + builder = this->methodName(); + break; + case MarkType::kTypedef: + builder = this->typedefName(); + break; + case MarkType::kParam: + // fixme: expect camelCase for param + builder = this->word("", ""); + this->skipSpace(); + *checkEnd = false; + break; + case MarkType::kPhraseDef: { + const char* nameEnd = this->anyOf("(\n"); + builder = string(fChar, nameEnd - fChar); + this->skipLower(); + if (fChar != nameEnd) { + this->reportError("expect lower case only"); + break; + } + this->skipTo(nameEnd); + *checkEnd = false; + } break; + case MarkType::kTable: + this->skipNoName(); + break; // unnamed + case MarkType::kSubtopic: + case MarkType::kTopic: + // fixme: start with cap, allow space, hyphen, stop on comma + // one topic can have multiple type names delineated by comma + result = this->topicName(); + if (result.size() == 0 && this->hasEndToken()) { + break; + } + return result; + default: + // fixme: don't allow silent failures + SkASSERT(0); + } + result.push_back(builder); + return result; +} + +string BmhParser::typedefName() { + if (this->hasEndToken()) { + if (!fParent || !fParent->fName.length()) { + return this->reportError<string>("missing parent typedef name"); + } + SkASSERT(fMC == this->peek()); + this->next(); + SkASSERT(fMC == this->peek()); + this->next(); + SkASSERT(fMC != this->peek()); + return fParent->fName; + } + string builder; + const Definition* parent = this->parentSpace(); + if (parent && parent->fName.length() > 0) { + builder = parent->fName + "::"; + } + builder += TextParser::typedefName(); + return uniqueRootName(builder, MarkType::kTypedef); +} + +string BmhParser::uniqueName(string base, MarkType markType) { + string builder(base); + if (!builder.length()) { + builder = fParent->fName; + } + if (!fParent) { + return builder; + } + int number = 2; + string numBuilder(builder); + do { + for (auto& iter : fParent->fChildren) { + if (markType == iter->fMarkType) { + if (iter->fName == numBuilder) { + if (iter->fDeprecated) { + iter->fClone = true; + } else { + fCloned = true; + } + numBuilder = builder + '_' + to_string(number); + goto tryNext; + } + } + } + break; +tryNext: ; + } while (++number); + return numBuilder; +} + +string BmhParser::uniqueRootName(string base, MarkType markType) { + auto checkName = [markType](const Definition& def, string numBuilder) -> bool { + return markType == def.fMarkType && def.fName == numBuilder; + }; + + string builder(base); + if (!builder.length()) { + builder = fParent->fName; + } + int number = 2; + string numBuilder(builder); + Definition* cloned = nullptr; + do { + if (fRoot) { + for (auto& iter : fRoot->fBranches) { + if (checkName(*iter.second, numBuilder)) { + cloned = iter.second; + goto tryNext; + } + } + for (auto& iter : fRoot->fLeaves) { + if (checkName(iter.second, numBuilder)) { + cloned = &iter.second; + goto tryNext; + } + } + } else if (fParent) { + for (auto& iter : fParent->fChildren) { + if (checkName(*iter, numBuilder)) { + cloned = &*iter; + goto tryNext; + } + } + } + break; +tryNext: ; + if ("()" == builder.substr(builder.length() - 2)) { + builder = builder.substr(0, builder.length() - 2); + } + if (MarkType::kMethod == markType) { + cloned->fCloned = true; + if (cloned->fDeprecated) { + cloned->fClone = true; + } else { + fCloned = true; + } + } else { + fCloned = true; + } + numBuilder = builder + '_' + to_string(number); + } while (++number); + return numBuilder; +} + +void BmhParser::validate() const { + for (int index = 0; index <= (int) Last_MarkType; ++index) { + SkASSERT(kMarkProps[index].fMarkType == (MarkType) index); + } + const char* last = ""; + for (int index = 0; index <= (int) Last_MarkType; ++index) { + const char* next = kMarkProps[index].fName; + if (!last[0]) { + last = next; + continue; + } + if (!next[0]) { + continue; + } + SkASSERT(strcmp(last, next) < 0); + last = next; + } +} + +string BmhParser::word(string prefix, string delimiter) { + string builder(prefix); + this->skipWhiteSpace(); + const char* lineEnd = fLine + this->lineLength(); + const char* nameStart = fChar; + while (fChar < lineEnd) { + char ch = this->next(); + if (' ' >= ch) { + break; + } + if (',' == ch) { + return this->reportError<string>("no comma needed"); + break; + } + if (fMC == ch) { + return builder; + } + if (!isalnum(ch) && '_' != ch && ':' != ch && '-' != ch) { + return this->reportError<string>("unexpected char"); + } + if (':' == ch) { + // expect pair, and expect word to start with Sk + if (nameStart[0] != 'S' || nameStart[1] != 'k') { + return this->reportError<string>("expected Sk"); + } + if (':' != this->peek()) { + return this->reportError<string>("expected ::"); + } + this->next(); + } else if ('-' == ch) { + // expect word not to start with Sk or kX where X is A-Z + if (nameStart[0] == 'k' && nameStart[1] >= 'A' && nameStart[1] <= 'Z') { + return this->reportError<string>("didn't expected kX"); + } + if (nameStart[0] == 'S' && nameStart[1] == 'k') { + return this->reportError<string>("expected Sk"); + } + } + } + if (prefix.size()) { + builder += delimiter; + } + builder.append(nameStart, fChar - nameStart - 1); + return builder; +} diff --git a/tools/bookmaker/bmhParser.h b/tools/bookmaker/bmhParser.h new file mode 100644 index 0000000000..2eb0053477 --- /dev/null +++ b/tools/bookmaker/bmhParser.h @@ -0,0 +1,194 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef bmhParser_DEFINED +#define bmhParser_DEFINED + +#include "SkCommandLineFlags.h" + +#include "definition.h" +#include "parserCommon.h" + +class BmhParser : public ParserCommon { +public: + enum class MarkLookup { + kRequire, + kAllowUnknown, + }; + + enum class ExampleOptions { + kText, + kPng, + kAll + }; + + enum class Exemplary { + kNo, + kYes, + kOptional, + }; + + enum class TableState { + kNone, + kColumnStart, + kColumnEnd, + }; + + enum class HasTag { + kNo, + kYes, + }; + + enum class TrimExtract { + kNo, + kYes, + }; + + BmhParser(bool skip) : ParserCommon() + , fMaps { + { &fClassMap, MarkType::kClass } + , { &fConstMap, MarkType::kConst } + , { &fDefineMap, MarkType::kDefine } + , { &fEnumMap, MarkType::kEnum } + , { &fClassMap, MarkType::kEnumClass } + , { &fMethodMap, MarkType::kMethod } + , { &fClassMap, MarkType::kStruct } + , { &fTypedefMap, MarkType::kTypedef } + } + , fSkip(skip) { + this->reset(); + } + + ~BmhParser() override {} + + bool addDefinition(const char* defStart, bool hasEnd, MarkType markType, + const vector<string>& typeNameBuilder, HasTag hasTag); + bool checkEndMarker(MarkType markType, string name) const; + bool checkExamples() const; + const char* checkForFullTerminal(const char* end, const Definition* ) const; + bool checkParamReturn(const Definition* definition) const; + bool dumpExamples(FILE* fiddleOut, Definition& def, bool* continuation) const; + bool dumpExamples(const char* fiddleJsonFileName) const; + bool checkExampleHashes() const; + bool childOf(MarkType markType) const; + string className(MarkType markType); + bool collectExternals(); + int endHashCount() const; + bool endTableColumn(const char* end, const char* terminator); + bool exampleToScript(Definition*, ExampleOptions, string* result ) const; + string extractText(const Definition* , TrimExtract ) const; + RootDefinition* findBmhObject(MarkType markType, string typeName); + bool findDefinitions(); + Definition* findExample(string name) const; + MarkType getMarkType(MarkLookup lookup) const; + bool hasEndToken() const; + static bool IsExemplary(const Definition* ); + string loweredTopic(string name, Definition* def); + 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(); + } + + void parseHashAnchor(Definition* ); + void parseHashFormula(Definition* ); + void parseHashLine(Definition* ); + bool popParentStack(Definition* ); + void reportDuplicates(const Definition& , string dup) const; + void resetExampleHashes(); + + void reset() override { + INHERITED::resetCommon(); + fRoot = nullptr; + fWorkingColumn = nullptr; + fRow = nullptr; + fTableState = TableState::kNone; + fMC = '#'; + fInChar = false; + fInCharCommentString = false; + fInComment = false; + fInEnum = false; + fInString = false; + fCheckMethods = false; + } + + void setUpGlobalSubstitutes(); + void setUpPartialSubstitute(string name); + void setUpSubstitute(string name, Definition* def); + void setUpSubstitutes(const Definition* parent, NameMap* ); + void setWrapper(Definition* def) const; + bool skipNoName(); + bool skipToDefinitionEnd(MarkType markType); + bool skipToString(); + void spellCheck(const char* match, SkCommandLineFlags::StringArray report) const; + void spellStatus(const char* match, SkCommandLineFlags::StringArray report) const; + vector<string> topicName(); + vector<string> typeName(MarkType markType, bool* expectEnd); + string typedefName() override; + string uniqueName(string base, MarkType markType); + string uniqueRootName(string base, MarkType markType); + void validate() const; + string word(string prefix, string delimiter); + +public: + struct MarkProps { + const char* fName; + MarkType fMarkType; + Resolvable fResolve; + Exemplary fExemplary; // worthy of an example + uint64_t fParentMask; + }; + + struct DefinitionMap { + unordered_map<string, RootDefinition>* fMap; + MarkType fMarkType; + }; + + vector<DefinitionMap> fMaps; + + static MarkProps kMarkProps[Last_MarkType + 1]; + forward_list<RootDefinition> fTopics; + forward_list<Definition> fMarkup; + forward_list<RootDefinition> fExternals; + vector<string> fInputFiles; + unordered_map<string, RootDefinition> fClassMap; + unordered_map<string, RootDefinition> fConstMap; + unordered_map<string, RootDefinition> fDefineMap; + unordered_map<string, RootDefinition> fEnumMap; + unordered_map<string, RootDefinition> fMethodMap; + unordered_map<string, RootDefinition> fTypedefMap; + unordered_map<string, Definition*> fTopicMap; + unordered_map<string, Definition*> fAliasMap; + unordered_map<string, Definition*> fPhraseMap; + NameMap fGlobalNames; + RootDefinition* fRoot; + Definition* fWorkingColumn; + Definition* fRow; + const char* fColStart; + TableState fTableState; + mutable char fMC; // markup character + bool fAnonymous; + bool fCloned; + bool fInChar; + bool fInCharCommentString; + bool fInEnum; + bool fInComment; + bool fInString; + bool fCheckMethods; + bool fSkip = false; + bool fWroteOut = false; +private: + typedef ParserCommon INHERITED; +}; + +#endif diff --git a/tools/bookmaker/bookmaker.cpp b/tools/bookmaker/bookmaker.cpp index 5b282146ad..bbbcb1b70f 100644 --- a/tools/bookmaker/bookmaker.cpp +++ b/tools/bookmaker/bookmaker.cpp @@ -5,14 +5,13 @@ * found in the LICENSE file. */ -#include "bookmaker.h" #include "SkOSPath.h" -#ifdef SK_BUILD_FOR_WIN -#include <Windows.h> -#endif - -const string kSpellingFileName("spelling.txt"); +#include "bmhParser.h" +#include "fiddleParser.h" +#include "mdOut.h" +#include "includeWriter.h" +#include "selfCheck.h" DEFINE_string2(status, a, "", "File containing status of documentation. (Use in place of -b -i)"); DEFINE_string2(bmh, b, "", "Path to a *.bmh file or a directory."); @@ -81,2594 +80,6 @@ see head of spellCheck.cpp for additional todos parameters may be reused in other methods */ -#define M(mt) (1LL << (int) MarkType::k##mt) -#define M_D M(Description) -#define M_CS M(Class) | M(Struct) -#define M_MD M(Method) | M(Define) -#define M_MDCM M_MD | M(Const) | M(Member) -#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 R_K Resolvable::kCode -#define R_F Resolvable::kFormula -#define R_C Resolvable::kClone - -#define E_Y Exemplary::kYes -#define E_N Exemplary::kNo -#define E_O Exemplary::kOptional - -// ToDo: add column to denote which marks are one-liners -BmhParser::MarkProps BmhParser::kMarkProps[] = { -// names without formal definitions (e.g. Column) aren't included - { "", MarkType::kNone, R_Y, E_N, 0 } -, { "A", MarkType::kAnchor, R_N, E_N, 0 } -, { "Alias", MarkType::kAlias, R_N, E_N, M_ST | M(Const) } -, { "Bug", MarkType::kBug, R_N, E_N, M_CSST | M_MDCM | M_E - | M(Example) | M(NoExample) } -, { "Class", MarkType::kClass, R_Y, E_O, M_CSST } -, { "Code", MarkType::kCode, R_K, E_N, M_CSST | M_E | M_MD | M(Typedef) } -, { "", MarkType::kColumn, R_Y, E_N, M(Row) } -, { "", MarkType::kComment, R_N, E_N, 0 } -, { "Const", MarkType::kConst, R_Y, E_O, M_E | M_CSST } -, { "Define", MarkType::kDefine, R_O, E_Y, M_ST } -, { "Deprecated", MarkType::kDeprecated, R_Y, E_N, M_CS | M_MDCM | M_E } -, { "Description", MarkType::kDescription, R_Y, E_N, M(Example) | M(NoExample) } -, { "Details", MarkType::kDetails, R_N, E_N, M(Const) } -, { "Duration", MarkType::kDuration, R_N, E_N, M(Example) | M(NoExample) } -, { "Enum", MarkType::kEnum, R_Y, E_O, M_CSST } -, { "EnumClass", MarkType::kEnumClass, R_Y, E_O, M_CSST } -, { "Example", MarkType::kExample, R_O, E_N, M_CSST | M_E | M_MD | M(Const) } -, { "Experimental", MarkType::kExperimental, R_Y, E_N, M_CS | M_MDCM | M_E } -, { "External", MarkType::kExternal, R_Y, E_N, 0 } -, { "File", MarkType::kFile, R_Y, E_N, M(Topic) } -, { "Filter", MarkType::kFilter, R_N, E_N, M(Subtopic) | M(Code) } -, { "Formula", MarkType::kFormula, R_F, E_N, M(Column) | M(Description) - | M_E | M_ST | M_MDCM } -, { "Function", MarkType::kFunction, R_O, E_N, M(Example) | M(NoExample) } -, { "Height", MarkType::kHeight, R_N, E_N, M(Example) | M(NoExample) } -, { "Illustration", MarkType::kIllustration, R_N, E_N, M_CSST | M_MD } -, { "Image", MarkType::kImage, R_N, E_N, M(Example) | M(NoExample) } -, { "In", MarkType::kIn, R_N, E_N, M_CSST | M_E | M(Method) | M(Typedef) | M(Code) } -, { "Legend", MarkType::kLegend, R_Y, E_N, M(Table) } -, { "Line", MarkType::kLine, R_N, E_N, M_CSST | M_E | M(Method) | M(Typedef) } -, { "", MarkType::kLink, R_N, E_N, M(Anchor) } -, { "List", MarkType::kList, R_Y, E_N, M(Method) | M_CSST | M_E | M_D } -, { "Literal", MarkType::kLiteral, R_N, E_N, M(Code) } -, { "", MarkType::kMarkChar, R_N, E_N, 0 } -, { "Member", MarkType::kMember, R_Y, E_O, M_CSST } -, { "Method", MarkType::kMethod, R_Y, E_Y, M_CSST } -, { "NoExample", MarkType::kNoExample, R_N, E_N, M_CSST | M_E | M_MD } -, { "NoJustify", MarkType::kNoJustify, R_N, E_N, M(Const) | M(Member) } -, { "Outdent", MarkType::kOutdent, R_N, E_N, M(Code) } -, { "Param", MarkType::kParam, R_Y, E_N, M(Method) | M(Define) } -, { "PhraseDef", MarkType::kPhraseDef, R_Y, E_N, M_ST } -, { "", MarkType::kPhraseParam, R_Y, E_N, 0 } -, { "", MarkType::kPhraseRef, R_N, E_N, 0 } -, { "Platform", MarkType::kPlatform, R_N, E_N, M(Example) | M(NoExample) } -, { "Populate", MarkType::kPopulate, R_N, E_N, M(Code) | M(Method) } -, { "Private", MarkType::kPrivate, R_N, E_N, M_CSST | M_MDCM | M_E } -, { "Return", MarkType::kReturn, R_Y, E_N, M(Method) } -, { "", MarkType::kRow, R_Y, E_N, M(Table) | M(List) } -, { "SeeAlso", MarkType::kSeeAlso, R_C, E_N, M_CSST | M_E | M_MD | M(Typedef) } -, { "Set", MarkType::kSet, R_N, E_N, M(Example) | M(NoExample) } -, { "StdOut", MarkType::kStdOut, R_N, E_N, M(Example) | M(NoExample) } -, { "Struct", MarkType::kStruct, R_Y, E_O, M(Class) | M_ST } -, { "Substitute", MarkType::kSubstitute, R_N, E_N, M(Alias) | M_ST } -, { "Subtopic", MarkType::kSubtopic, R_Y, E_Y, M_CSST | M_E } -, { "Table", MarkType::kTable, R_Y, E_N, M(Method) | M_CSST | M_E } -, { "Template", MarkType::kTemplate, R_Y, E_N, M_CSST } -, { "", MarkType::kText, R_N, E_N, 0 } -, { "ToDo", MarkType::kToDo, R_N, E_N, 0 } -, { "Topic", MarkType::kTopic, R_Y, E_Y, 0 } -, { "Typedef", MarkType::kTypedef, R_Y, E_O, M_CSST | M_E } -, { "Union", MarkType::kUnion, R_Y, E_N, M_CSST } -, { "Using", MarkType::kUsing, R_Y, E_O, M_CSST } -, { "Volatile", MarkType::kVolatile, R_N, E_N, M(StdOut) } -, { "Width", MarkType::kWidth, R_N, E_N, M(Example) | M(NoExample) } -}; - -#undef R_O -#undef R_N -#undef R_Y -#undef R_K -#undef R_F -#undef R_C - -#undef M_E -#undef M_CSST -#undef M_ST -#undef M_CS -#undef M_MCD -#undef M_D -#undef M - -#undef E_Y -#undef E_N -#undef E_O - -bool BmhParser::addDefinition(const char* defStart, bool hasEnd, MarkType markType, - const vector<string>& typeNameBuilder, HasTag hasTag) { - Definition* definition = nullptr; - switch (markType) { - case MarkType::kComment: - if (!this->skipToDefinitionEnd(markType)) { - return false; - } - return true; - // these types may be referred to by name - case MarkType::kClass: - case MarkType::kStruct: - case MarkType::kConst: - case MarkType::kDefine: - case MarkType::kEnum: - case MarkType::kEnumClass: - case MarkType::kMember: - case MarkType::kMethod: - case MarkType::kTypedef: { - if (!typeNameBuilder.size()) { - return this->reportError<bool>("unnamed markup"); - } - if (typeNameBuilder.size() > 1) { - return this->reportError<bool>("expected one name only"); - } - string name = typeNameBuilder[0]; - if (nullptr == fRoot) { - fRoot = this->findBmhObject(markType, name); - fRoot->fFileName = fFileName; - fRoot->fName = name; - fRoot->fNames.fName = name; - fRoot->fNames.fParent = &fGlobalNames; - definition = fRoot; - } else { - if (nullptr == fParent) { - return this->reportError<bool>("expected parent"); - } - if (fParent == fRoot && hasEnd) { - RootDefinition* rootParent = fRoot->rootParent(); - if (rootParent) { - fRoot = rootParent; - } - definition = fParent; - } else { - if (!hasEnd && fRoot->find(name, RootDefinition::AllowParens::kNo)) { - return this->reportError<bool>("duplicate symbol"); - } - if (MarkType::kStruct == markType || MarkType::kClass == markType - || MarkType::kEnumClass == markType) { - // if class or struct, build fRoot hierarchy - // and change isDefined to search all parents of fRoot - SkASSERT(!hasEnd); - RootDefinition* childRoot = new RootDefinition; - (fRoot->fBranches)[name] = childRoot; - childRoot->setRootParent(fRoot); - childRoot->fFileName = fFileName; - SkASSERT(MarkType::kSubtopic != fRoot->fMarkType - && MarkType::kTopic != fRoot->fMarkType); - childRoot->fNames.fName = name; - childRoot->fNames.fParent = &fRoot->fNames; - fRoot = childRoot; - definition = fRoot; - } else { - definition = &fRoot->fLeaves[name]; - } - } - } - if (hasEnd) { - Exemplary hasExample = Exemplary::kNo; - bool hasExcluder = false; - for (auto child : definition->fChildren) { - if (MarkType::kExample == child->fMarkType) { - hasExample = Exemplary::kYes; - } - hasExcluder |= MarkType::kPrivate == child->fMarkType - || MarkType::kDeprecated == child->fMarkType - || MarkType::kExperimental == child->fMarkType - || MarkType::kNoExample == child->fMarkType; - } - if (kMarkProps[(int) markType].fExemplary != hasExample - && kMarkProps[(int) markType].fExemplary != Exemplary::kOptional) { - if (string::npos == fFileName.find("undocumented") - && !hasExcluder) { - hasExample == Exemplary::kNo ? - this->reportWarning("missing example") : - this->reportWarning("unexpected example"); - } - - } - if (MarkType::kMethod == markType) { - if (fCheckMethods && !definition->checkMethod()) { - return false; - } - } - if (HasTag::kYes == hasTag) { - if (!this->checkEndMarker(markType, definition->fName)) { - return false; - } - } - if (!this->popParentStack(definition)) { - return false; - } - if (fRoot == definition) { - fRoot = nullptr; - } - } else { - definition->fStart = defStart; - this->skipSpace(); - definition->fFileName = fFileName; - definition->fContentStart = fChar; - definition->fLineCount = fLineCount; - definition->fClone = fCloned; - if (MarkType::kConst == markType) { - // todo: require that fChar points to def on same line as markup - // additionally add definition to class children if it is not already there - if (definition->fParent != fRoot) { -// fRoot->fChildren.push_back(definition); - } - } - SkASSERT(string::npos == name.find('\n')); - definition->fName = name; - if (MarkType::kMethod == markType) { - if (string::npos != name.find(':', 0)) { - definition->setCanonicalFiddle(); - } else { - definition->fFiddle = name; - } - } else { - definition->fFiddle = Definition::NormalizedName(name); - } - definition->fMarkType = markType; - definition->fAnonymous = fAnonymous; - this->setAsParent(definition); - } - } break; - case MarkType::kTopic: - case MarkType::kSubtopic: - SkASSERT(1 == typeNameBuilder.size()); - if (!hasEnd) { - if (!typeNameBuilder.size()) { - return this->reportError<bool>("unnamed topic"); - } - fTopics.emplace_front(markType, defStart, fLineCount, fParent, fMC); - RootDefinition* rootDefinition = &fTopics.front(); - definition = rootDefinition; - definition->fFileName = fFileName; - definition->fContentStart = fChar; - if (MarkType::kTopic == markType) { - if (fParent) { - return this->reportError<bool>("#Topic must be root"); - } - // topic name is unappended - definition->fName = typeNameBuilder[0]; - } else { - if (!fParent) { - return this->reportError<bool>("#Subtopic may not be root"); - } - Definition* parent = fParent; - while (MarkType::kTopic != parent->fMarkType && MarkType::kSubtopic != parent->fMarkType) { - parent = parent->fParent; - if (!parent) { - // subtopic must have subtopic or topic in parent chain - return this->reportError<bool>("#Subtopic missing parent"); - } - } - if (MarkType::kSubtopic == parent->fMarkType) { - // subtopic prepends parent subtopic name, but not parent topic name - definition->fName = parent->fName + '_'; - } - definition->fName += typeNameBuilder[0]; - definition->fFiddle = parent->fFiddle + '_'; - } - rootDefinition->fNames.fName = rootDefinition->fName; - definition->fFiddle += Definition::NormalizedName(typeNameBuilder[0]); - this->setAsParent(definition); - } - { - SkASSERT(hasEnd ? fParent : definition); - string fullTopic = hasEnd ? fParent->fFiddle : definition->fFiddle; - Definition* defPtr = fTopicMap[fullTopic]; - if (hasEnd) { - if (HasTag::kYes == hasTag && !this->checkEndMarker(markType, fullTopic)) { - return false; - } - if (!definition) { - definition = defPtr; - } else if (definition != defPtr) { - return this->reportError<bool>("mismatched topic"); - } - } else { - if (nullptr != defPtr) { - return this->reportError<bool>("already declared topic"); - } - fTopicMap[fullTopic] = definition; - } - } - if (hasEnd) { - if (!this->popParentStack(definition)) { - return false; - } - } - break; - case MarkType::kFormula: - // hasEnd : single line / multiple line - if (!fParent || MarkType::kFormula != fParent->fMarkType) { - SkASSERT(!definition || MarkType::kFormula == definition->fMarkType); - fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC); - definition = &fMarkup.front(); - definition->fContentStart = fChar; - definition->fName = typeNameBuilder[0]; - definition->fFiddle = fParent->fFiddle; - fParent = definition; - } else { - SkASSERT(fParent && MarkType::kFormula == fParent->fMarkType); - SkASSERT(fMC == defStart[0]); - SkASSERT(fMC == defStart[-1]); - definition = fParent; - definition->fTerminator = fChar; - if (!this->popParentStack(definition)) { - return false; - } - this->parseHashFormula(definition); - fParent->fChildren.push_back(definition); - } - break; - // these types are children of parents, but are not in named maps - case MarkType::kDescription: - case MarkType::kStdOut: - // may be one-liner - case MarkType::kAlias: - case MarkType::kNoExample: - case MarkType::kParam: - case MarkType::kPhraseDef: - case MarkType::kReturn: - case MarkType::kToDo: - if (hasEnd) { - if (markType == fParent->fMarkType) { - definition = fParent; - if (MarkType::kBug == markType || MarkType::kReturn == markType - || MarkType::kToDo == markType) { - this->skipNoName(); - } - if (!this->popParentStack(fParent)) { // if not one liner, pop - return false; - } - if (MarkType::kParam == markType || MarkType::kReturn == markType - || MarkType::kPhraseDef == markType) { - if (!this->checkParamReturn(definition)) { - return false; - } - } - if (MarkType::kPhraseDef == markType) { - string key = definition->fName; - if (fPhraseMap.end() != fPhraseMap.find(key)) { - this->reportError<bool>("duplicate phrase key"); - } - fPhraseMap[key] = definition; - } - } else { - fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC); - definition = &fMarkup.front(); - definition->fName = typeNameBuilder[0]; - definition->fFiddle = fParent->fFiddle; - definition->fContentStart = fChar; - string endBracket; - endBracket += fMC; - endBracket += fMC; - definition->fContentEnd = this->trimmedBracketEnd(endBracket); - this->skipToEndBracket(endBracket.c_str()); - SkAssertResult(fMC == this->next()); - SkAssertResult(fMC == this->next()); - definition->fTerminator = fChar; - TextParser checkForChildren(definition); - if (checkForChildren.strnchr(fMC, definition->fContentEnd)) { - this->reportError<bool>("put ## on separate line"); - } - fParent->fChildren.push_back(definition); - } - if (MarkType::kAlias == markType) { - const char* end = definition->fChildren.size() > 0 ? - definition->fChildren[0]->fStart : definition->fContentEnd; - TextParser parser(definition->fFileName, definition->fContentStart, end, - definition->fLineCount); - parser.trimEnd(); - string key = string(parser.fStart, parser.lineLength()); - if (fAliasMap.end() != fAliasMap.find(key)) { - return this->reportError<bool>("duplicate alias"); - } - fAliasMap[key] = definition; - definition->fFiddle = definition->fParent->fFiddle; - } - break; - } else if (MarkType::kPhraseDef == markType) { - bool hasParams = '(' == this->next(); - fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC); - definition = &fMarkup.front(); - definition->fName = typeNameBuilder[0]; - definition->fFiddle = fParent->fFiddle; - definition->fContentStart = fChar; - if (hasParams) { - char lastChar; - do { - const char* subEnd = this->anyOf(",)\n"); - if (!subEnd || '\n' == *subEnd) { - return this->reportError<bool>("unexpected phrase list end"); - } - fMarkup.emplace_front(MarkType::kPhraseParam, fChar, fLineCount, fParent, - fMC); - Definition* phraseParam = &fMarkup.front(); - phraseParam->fContentStart = fChar; - phraseParam->fContentEnd = subEnd; - phraseParam->fName = string(fChar, subEnd - fChar); - definition->fChildren.push_back(phraseParam); - this->skipTo(subEnd); - lastChar = this->next(); - phraseParam->fTerminator = fChar; - } while (')' != lastChar); - this->skipWhiteSpace(); - definition->fContentStart = fChar; - } - this->setAsParent(definition); - break; - } - // not one-liners - case MarkType::kCode: - case MarkType::kExample: - case MarkType::kFile: - case MarkType::kFunction: - case MarkType::kLegend: - case MarkType::kList: - case MarkType::kPrivate: - case MarkType::kTable: - if (hasEnd) { - definition = fParent; - if (markType != fParent->fMarkType) { - return this->reportError<bool>("end element mismatch"); - } else if (!this->popParentStack(fParent)) { - return false; - } - if (MarkType::kExample == markType) { - if (definition->fChildren.size() == 0) { - TextParser emptyCheck(definition); - if (emptyCheck.eof() || !emptyCheck.skipWhiteSpace()) { - return this->reportError<bool>("missing example body"); - } - } -// can't do this here; phrase refs may not have been defined yet -// this->setWrapper(definition); - } - } else { - fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC); - definition = &fMarkup.front(); - definition->fContentStart = fChar; - definition->fName = typeNameBuilder[0]; - definition->fFiddle = fParent->fFiddle; - char suffix = '\0'; - bool tryAgain; - do { - tryAgain = false; - for (const auto& child : fParent->fChildren) { - if (child->fFiddle == definition->fFiddle) { - if (MarkType::kExample != child->fMarkType) { - continue; - } - if ('\0' == suffix) { - suffix = 'a'; - } else if (++suffix > 'z') { - return reportError<bool>("too many examples"); - } - definition->fFiddle = fParent->fFiddle + '_'; - definition->fFiddle += suffix; - tryAgain = true; - break; - } - } - } while (tryAgain); - this->setAsParent(definition); - } - break; - // always treated as one-liners (can't detect misuse easily) - case MarkType::kAnchor: - case MarkType::kBug: - case MarkType::kDeprecated: - case MarkType::kDetails: - case MarkType::kDuration: - case MarkType::kExperimental: - case MarkType::kFilter: - case MarkType::kHeight: - case MarkType::kIllustration: - case MarkType::kImage: - case MarkType::kIn: - case MarkType::kLine: - case MarkType::kLiteral: - case MarkType::kNoJustify: - case MarkType::kOutdent: - case MarkType::kPlatform: - case MarkType::kPopulate: - case MarkType::kSeeAlso: - case MarkType::kSet: - case MarkType::kSubstitute: - case MarkType::kVolatile: - case MarkType::kWidth: - // todo : add check disallowing children? - if (hasEnd && MarkType::kAnchor != markType && MarkType::kLine != markType) { - return this->reportError<bool>("one liners omit end element"); - } else if (!hasEnd && MarkType::kAnchor == markType) { - return this->reportError<bool>("anchor line must have end element last"); - } - fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC); - definition = &fMarkup.front(); - definition->fName = typeNameBuilder[0]; - definition->fFiddle = Definition::NormalizedName(typeNameBuilder[0]); - definition->fContentStart = fChar; - definition->fContentEnd = this->trimmedBracketEnd('\n'); - definition->fTerminator = this->lineEnd() - 1; - fParent->fChildren.push_back(definition); - if (MarkType::kAnchor == markType) { - this->parseHashAnchor(definition); - } else if (MarkType::kLine == markType) { - this->parseHashLine(definition); - } else if (IncompleteAllowed(markType)) { - this->skipSpace(); - fParent->fDeprecated = true; - fParent->fDetails = - this->skipExact("soon") ? Definition::Details::kSoonToBe_Deprecated : - this->skipExact("testing") ? Definition::Details::kTestingOnly_Experiment : - this->skipExact("do not use") ? Definition::Details::kDoNotUse_Experiment : - this->skipExact("not ready") ? Definition::Details::kNotReady_Experiment : - Definition::Details::kNone; - this->skipSpace(); - if ('\n' != this->peek()) { - return this->reportError<bool>("unexpected text after #Deprecated"); - } - } - break; - case MarkType::kExternal: - (void) this->collectExternals(); // FIXME: detect errors in external defs? - break; - default: - SkASSERT(0); // fixme : don't let any types be invisible - return true; - } - if (fParent) { - SkASSERT(definition); - SkASSERT(definition->fName.length() > 0); - } - return true; -} - -void BmhParser::reportDuplicates(const Definition& def, string dup) const { - if (MarkType::kExample == def.fMarkType && dup == def.fFiddle) { - TextParser reporter(&def); - reporter.reportError("duplicate example name"); - } - for (auto& child : def.fChildren ) { - reportDuplicates(*child, dup); - } -} - - -static Definition* find_fiddle(Definition* def, string name) { - if (MarkType::kExample == def->fMarkType && name == def->fFiddle) { - return def; - } - for (auto& child : def->fChildren) { - Definition* result = find_fiddle(child, name); - if (result) { - return result; - } - } - return nullptr; -} - -Definition* BmhParser::findExample(string name) const { - for (const auto& topic : fTopicMap) { - if (topic.second->fParent) { - continue; - } - Definition* def = find_fiddle(topic.second, name); - if (def) { - return def; - } - } - return nullptr; -} - -static bool check_example_hashes(Definition* def) { - if (MarkType::kExample == def->fMarkType) { - if (def->fHash.length()) { - return true; - } - for (auto child : def->fChildren) { - if (MarkType::kPlatform == child->fMarkType) { - if (string::npos != string(child->fContentStart, child->length()).find("!fiddle")) { - return true; - } - } - } - return def->reportError<bool>("missing hash"); - } - for (auto& child : def->fChildren) { - if (!check_example_hashes(child)) { - return false; - } - } - return true; -} - -bool BmhParser::checkExampleHashes() const { - for (const auto& topic : fTopicMap) { - if (!topic.second->fParent && !check_example_hashes(topic.second)) { - return false; - } - } - return true; -} - -static void reset_example_hashes(Definition* def) { - if (MarkType::kExample == def->fMarkType) { - def->fHash.clear(); - return; - } - for (auto& child : def->fChildren) { - reset_example_hashes(child); - } -} - -void BmhParser::resetExampleHashes() { - for (const auto& topic : fTopicMap) { - if (!topic.second->fParent) { - reset_example_hashes(topic.second); - } - } -} - -static void find_examples(const Definition& def, vector<string>* exampleNames) { - if (MarkType::kExample == def.fMarkType) { - exampleNames->push_back(def.fFiddle); - } - for (auto& child : def.fChildren ) { - find_examples(*child, exampleNames); - } -} - -bool BmhParser::checkEndMarker(MarkType markType, string match) const { - TextParser tp(fFileName, fLine, fChar, fLineCount); - tp.skipSpace(); - if (fMC != tp.next()) { - return this->reportError<bool>("mismatched end marker expect #"); - } - const char* nameStart = tp.fChar; - tp.skipToNonName(); - string markName(nameStart, tp.fChar - nameStart); - if (kMarkProps[(int) markType].fName != markName) { - return this->reportError<bool>("expected #XXX ## to match"); - } - tp.skipSpace(); - nameStart = tp.fChar; - tp.skipToNonName(); - markName = string(nameStart, tp.fChar - nameStart); - if ("" == markName) { - if (fMC != tp.next() || fMC != tp.next()) { - return this->reportError<bool>("expected ##"); - } - return true; - } - std::replace(markName.begin(), markName.end(), '-', '_'); - auto defPos = match.rfind(markName); - if (string::npos == defPos) { - return this->reportError<bool>("mismatched end marker v1"); - } - if (markName.size() != match.size() - defPos) { - return this->reportError<bool>("mismatched end marker v2"); - } - return true; -} - -bool BmhParser::checkExamples() const { - vector<string> exampleNames; - for (const auto& topic : fTopicMap) { - if (topic.second->fParent) { - continue; - } - find_examples(*topic.second, &exampleNames); - } - std::sort(exampleNames.begin(), exampleNames.end()); - string* last = nullptr; - string reported; - bool checkOK = true; - for (auto& nameIter : exampleNames) { - if (last && *last == nameIter && reported != *last) { - reported = *last; - SkDebugf("%s\n", reported.c_str()); - for (const auto& topic : fTopicMap) { - if (topic.second->fParent) { - continue; - } - this->reportDuplicates(*topic.second, reported); - } - checkOK = false; - } - last = &nameIter; - } - return checkOK; -} - -bool BmhParser::checkParamReturn(const Definition* definition) const { - const char* parmEndCheck = definition->fContentEnd; - while (parmEndCheck < definition->fTerminator) { - if (fMC == parmEndCheck[0]) { - break; - } - if (' ' < parmEndCheck[0]) { - this->reportError<bool>( - "use full end marker on multiline #Param and #Return"); - } - ++parmEndCheck; - } - return true; -} - -bool BmhParser::childOf(MarkType markType) const { - auto childError = [this](MarkType markType) -> bool { - string errStr = "expected "; - errStr += kMarkProps[(int) markType].fName; - errStr += " parent"; - return this->reportError<bool>(errStr.c_str()); - }; - - if (markType == fParent->fMarkType) { - return true; - } - if (this->hasEndToken()) { - if (!fParent->fParent) { - return this->reportError<bool>("expected grandparent"); - } - if (markType == fParent->fParent->fMarkType) { - return true; - } - } - return childError(markType); -} - -string BmhParser::className(MarkType markType) { - const char* end = this->lineEnd(); - const char* mc = this->strnchr(fMC, end); - string classID; - TextParserSave savePlace(this); - this->skipSpace(); - const char* wordStart = fChar; - this->skipToNonName(); - const char* wordEnd = fChar; - classID = string(wordStart, wordEnd - wordStart); - if (!mc) { - savePlace.restore(); - } - string builder; - const Definition* parent = this->parentSpace(); - if (parent && parent->fName != classID) { - builder += parent->fName; - } - if (mc) { - if (mc + 1 < fEnd && fMC == mc[1]) { // if ## - if (markType != fParent->fMarkType) { - return this->reportError<string>("unbalanced method"); - } - if (builder.length() > 0 && classID.size() > 0) { - if (builder != fParent->fName) { - builder += "::"; - builder += classID; - if (builder != fParent->fName) { - return this->reportError<string>("name mismatch"); - } - } - } - this->skipLine(); - return fParent->fName; - } - fChar = mc; - this->next(); - } - this->skipWhiteSpace(); - if (MarkType::kEnum == markType && fChar >= end) { - fAnonymous = true; - builder += "::_anonymous"; - return uniqueRootName(builder, markType); - } - builder = this->word(builder, "::"); - return builder; -} - -bool BmhParser::collectExternals() { - do { - this->skipWhiteSpace(); - if (this->eof()) { - break; - } - if (fMC == this->peek()) { - this->next(); - if (this->eof()) { - break; - } - if (fMC == this->peek()) { - this->skipLine(); - break; - } - if (' ' >= this->peek()) { - this->skipLine(); - continue; - } - if (this->startsWith(kMarkProps[(int) MarkType::kExternal].fName)) { - this->skipToNonName(); - continue; - } - } - this->skipToAlpha(); - const char* wordStart = fChar; - this->skipToWhiteSpace(); - if (fChar - wordStart > 0) { - fExternals.emplace_front(MarkType::kExternal, wordStart, fChar, fLineCount, fParent, - fMC); - RootDefinition* definition = &fExternals.front(); - definition->fFileName = fFileName; - definition->fName = string(wordStart ,fChar - wordStart); - definition->fFiddle = Definition::NormalizedName(definition->fName); - } - } while (!this->eof()); - return true; -} - -bool BmhParser::dumpExamples(FILE* fiddleOut, Definition& def, bool* continuation) const { - if (MarkType::kExample == def.fMarkType) { - string result; - if (!this->exampleToScript(&def, BmhParser::ExampleOptions::kAll, &result)) { - return false; - } - if (result.length() > 0) { - result += "\n"; - result += "}"; - if (*continuation) { - fprintf(fiddleOut, ",\n"); - } else { - *continuation = true; - } - fprintf(fiddleOut, "%s", result.c_str()); - } - return true; - } - for (auto& child : def.fChildren ) { - if (!this->dumpExamples(fiddleOut, *child, continuation)) { - return false; - } - } - return true; -} - -bool BmhParser::dumpExamples(const char* fiddleJsonFileName) const { - string oldFiddle(fiddleJsonFileName); - string newFiddle(fiddleJsonFileName); - newFiddle += "_new"; - FILE* fiddleOut = fopen(newFiddle.c_str(), "wb"); - if (!fiddleOut) { - SkDebugf("could not open output file %s\n", newFiddle.c_str()); - return false; - } - fprintf(fiddleOut, "{\n"); - bool continuation = false; - for (const auto& topic : fTopicMap) { - if (topic.second->fParent) { - continue; - } - this->dumpExamples(fiddleOut, *topic.second, &continuation); - } - fprintf(fiddleOut, "\n}\n"); - fclose(fiddleOut); - if (ParserCommon::WrittenFileDiffers(oldFiddle, newFiddle)) { - ParserCommon::CopyToFile(oldFiddle, newFiddle); - SkDebugf("wrote %s\n", fiddleJsonFileName); - } else { - remove(newFiddle.c_str()); - } - return true; -} - -int BmhParser::endHashCount() const { - const char* end = fLine + this->lineLength(); - int count = 0; - while (fLine < end && fMC == *--end) { - count++; - } - return count; -} - -bool BmhParser::endTableColumn(const char* end, const char* terminator) { - if (!this->popParentStack(fParent)) { - return false; - } - fWorkingColumn->fContentEnd = end; - fWorkingColumn->fTerminator = terminator; - fColStart = fChar - 1; - this->skipSpace(); - fTableState = TableState::kColumnStart; - return true; -} - -static size_t count_indent(string text, size_t test, size_t end) { - size_t result = test; - while (test < end) { - if (' ' != text[test]) { - break; - } - ++test; - } - return test - result; -} - -static void add_code(string text, int pos, int end, - size_t outIndent, size_t textIndent, string& example) { - do { - // fix this to move whole paragraph in, out, but preserve doc indent - int nextIndent = count_indent(text, pos, end); - size_t len = text.find('\n', pos); - if (string::npos == len) { - len = end; - } - if ((size_t) (pos + nextIndent) < len) { - size_t indent = outIndent + nextIndent; - SkASSERT(indent >= textIndent); - indent -= textIndent; - for (size_t index = 0; index < indent; ++index) { - example += ' '; - } - pos += nextIndent; - while ((size_t) pos < len) { - example += '"' == text[pos] ? "\\\"" : - '\\' == text[pos] ? "\\\\" : - text.substr(pos, 1); - ++pos; - } - example += "\\n"; - } else { - pos += nextIndent; - } - if ('\n' == text[pos]) { - ++pos; - } - } while (pos < end); -} - -bool BmhParser::IsExemplary(const Definition* def) { - return kMarkProps[(int) def->fMarkType].fExemplary != Exemplary::kNo; -} - -bool BmhParser::exampleToScript(Definition* def, ExampleOptions exampleOptions, - string* result) const { - bool hasFiddle = true; - const Definition* platform = def->hasChild(MarkType::kPlatform); - if (platform) { - TextParser platParse(platform); - hasFiddle = !platParse.strnstr("!fiddle", platParse.fEnd); - } - if (!hasFiddle) { - *result = ""; - return true; - } - string text = this->extractText(def, TrimExtract::kNo); - bool textOut = string::npos != text.find("SkDebugf(") - || string::npos != text.find("dump(") - || string::npos != text.find("dumpHex("); - string heightStr = "256"; - string widthStr = "256"; - string normalizedName(def->fFiddle); - string code; - string imageStr = "0"; - string srgbStr = "false"; - string durationStr = "0"; - for (auto iter : def->fChildren) { - switch (iter->fMarkType) { - case MarkType::kDuration: - durationStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart); - break; - case MarkType::kHeight: - heightStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart); - break; - case MarkType::kWidth: - widthStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart); - break; - case MarkType::kDescription: - // ignore for now - break; - case MarkType::kFunction: { - // emit this, but don't wrap this in draw() - string funcText = this->extractText(&*iter, TrimExtract::kNo); - size_t pos = 0; - while (pos < funcText.length() && ' ' > funcText[pos]) { - ++pos; - } - size_t indent = count_indent(funcText, pos, funcText.length()); - add_code(funcText, pos, funcText.length(), 0, indent, code); - code += "\\n"; - } break; - case MarkType::kComment: - break; - case MarkType::kImage: - imageStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart); - break; - case MarkType::kToDo: - break; - case MarkType::kBug: - case MarkType::kMarkChar: - case MarkType::kPlatform: - case MarkType::kPhraseRef: - // ignore for now - break; - case MarkType::kSet: - if ("sRGB" == string(iter->fContentStart, - iter->fContentEnd - iter->fContentStart)) { - srgbStr = "true"; - } else { - SkASSERT(0); // more work to do - return false; - } - break; - case MarkType::kStdOut: - textOut = true; - break; - default: - SkASSERT(0); // more coding to do - } - } - string animatedStr = "0" != durationStr ? "true" : "false"; - string textOutStr = textOut ? "true" : "false"; - size_t pos = 0; - while (pos < text.length() && ' ' > text[pos]) { - ++pos; - } - size_t end = text.length(); - size_t outIndent = 0; - size_t textIndent = count_indent(text, pos, end); - if ("" == def->fWrapper) { - this->setWrapper(def); - } - if (def->fWrapper.length() > 0) { - code += def->fWrapper; - code += "\\n"; - outIndent = 4; - } - add_code(text, pos, end, outIndent, textIndent, code); - if (def->fWrapper.length() > 0) { - code += "}"; - } - string example = "\"" + normalizedName + "\": {\n"; - string filename = def->fileName(); - string baseFile = filename.substr(0, filename.length() - 4); - if (ExampleOptions::kText == exampleOptions) { - example += " \"code\": \"" + code + "\",\n"; - example += " \"hash\": \"" + def->fHash + "\",\n"; - example += " \"file\": \"" + baseFile + "\",\n"; - example += " \"name\": \"" + def->fName + "\","; - } else { - example += " \"code\": \"" + code + "\",\n"; - if (ExampleOptions::kPng == exampleOptions) { - example += " \"width\": " + widthStr + ",\n"; - example += " \"height\": " + heightStr + ",\n"; - example += " \"hash\": \"" + def->fHash + "\",\n"; - example += " \"file\": \"" + baseFile + "\",\n"; - example += " \"name\": \"" + def->fName + "\"\n"; - example += "}"; - } else { - example += " \"options\": {\n"; - example += " \"width\": " + widthStr + ",\n"; - example += " \"height\": " + heightStr + ",\n"; - example += " \"source\": " + imageStr + ",\n"; - example += " \"srgb\": " + srgbStr + ",\n"; - example += " \"f16\": false,\n"; - example += " \"textOnly\": " + textOutStr + ",\n"; - example += " \"animated\": " + animatedStr + ",\n"; - example += " \"duration\": " + durationStr + "\n"; - example += " },\n"; - example += " \"fast\": true"; - } - } - *result = example; - return true; -} - -string BmhParser::extractText(const Definition* def, TrimExtract trimExtract) const { - string result; - TextParser parser(def); - auto childIter = def->fChildren.begin(); - while (!parser.eof()) { - const char* end = def->fChildren.end() == childIter ? parser.fEnd : (*childIter)->fStart; - 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); - if (def->fChildren.end() != childIter) { - Definition* child = *childIter; - if (MarkType::kPhraseRef == child->fMarkType) { - auto phraseIter = fPhraseMap.find(child->fName); - if (fPhraseMap.end() == phraseIter) { - return def->reportError<string>("missing phrase definition"); - } - Definition* phrase = phraseIter->second; - // count indent of last line in result - size_t lastLF = result.rfind('\n'); - size_t startPos = string::npos == lastLF ? 0 : lastLF; - size_t lastLen = result.length() - startPos; - size_t indent = count_indent(result, startPos, result.length()) + 4; - string phraseStr = this->extractText(phrase, TrimExtract::kNo); - startPos = 0; - bool firstTime = true; - size_t endPos; - do { - endPos = phraseStr.find('\n', startPos); - size_t len = (string::npos != endPos ? endPos : phraseStr.length()) - startPos; - if (firstTime && lastLen + len + 1 < 100) { // FIXME: make 100 global const or something - result += ' '; - } else { - result += '\n'; - result += string(indent, ' '); - } - firstTime = false; - string tmp = phraseStr.substr(startPos, len); - result += tmp; - startPos = endPos + 1; - } while (string::npos != endPos); - result += '\n'; - } - parser.skipTo(child->fTerminator); - std::advance(childIter, 1); - } - } - return result; -} - -string BmhParser::loweredTopic(string name, Definition* def) { - string lowered; - SkASSERT('_' != name[0]); - char last = '_'; - for (char c : name) { - SkASSERT(' ' != c); - if (isupper(last)) { - lowered += islower(c) ? tolower(last) : last; - last = '\0'; - } - if ('_' == c) { - last = c; - c = ' '; - } else if ('_' == last && isupper(c)) { - last = c; - continue; - } - lowered += c; - if (' ' == c) { - this->setUpPartialSubstitute(lowered); - } - } - if (isupper(last)) { - lowered += tolower(last); - } - return lowered; -} - -void BmhParser::setUpGlobalSubstitutes() { - for (auto& entry : fExternals) { - string externalName = entry.fName; - SkASSERT(fGlobalNames.fRefMap.end() == fGlobalNames.fRefMap.find(externalName)); - fGlobalNames.fRefMap[externalName] = nullptr; - } - for (auto bMap : { &fClassMap, &fConstMap, &fDefineMap, &fEnumMap, &fMethodMap, - &fTypedefMap } ) { - for (auto& entry : *bMap) { - Definition* parent = (Definition*) &entry.second; - string name = parent->fName; - SkASSERT(fGlobalNames.fLinkMap.end() == fGlobalNames.fLinkMap.find(name)); - string ref = ParserCommon::HtmlFileName(parent->fFileName) + '#' + parent->fFiddle; - fGlobalNames.fLinkMap[name] = ref; - SkASSERT(fGlobalNames.fRefMap.end() == fGlobalNames.fRefMap.find(name)); - fGlobalNames.fRefMap[name] = const_cast<Definition*>(parent); - NameMap* names = MarkType::kClass == parent->fMarkType - || MarkType::kStruct == parent->fMarkType - || MarkType::kEnumClass == parent->fMarkType ? &parent->asRoot()->fNames : - &fGlobalNames; - this->setUpSubstitutes(parent, names); - if (names != &fGlobalNames) { - names->copyToParent(&fGlobalNames); - } - } - } - for (auto& topic : fTopicMap) { - bool hasSubstitute = false; - for (auto& child : topic.second->fChildren) { - bool isAlias = MarkType::kAlias == child->fMarkType; - bool isSubstitute = MarkType::kSubstitute == child->fMarkType; - if (!isAlias && !isSubstitute) { - continue; - } - hasSubstitute |= isSubstitute; - string name(child->fContentStart, child->length()); - if (isAlias) { - name = ParserCommon::ConvertRef(name, false); - for (auto aliasChild : child->fChildren) { - if (MarkType::kSubstitute == aliasChild->fMarkType) { - string sub(aliasChild->fContentStart, aliasChild->length()); - this->setUpSubstitute(sub, topic.second); - } - } - } - this->setUpSubstitute(name, topic.second); - } - if (hasSubstitute) { - continue; - } - string lowered = this->loweredTopic(topic.first, topic.second); - SkDEBUGCODE(auto globalIter = fGlobalNames.fLinkMap.find(lowered)); - SkASSERT(fGlobalNames.fLinkMap.end() == globalIter); - fGlobalNames.fLinkMap[lowered] = - ParserCommon::HtmlFileName(topic.second->fFileName) + '#' + topic.first; - SkASSERT(fGlobalNames.fRefMap.end() == fGlobalNames.fRefMap.find(lowered)); - fGlobalNames.fRefMap[lowered] = topic.second; - } - size_t slash = fRawFilePathDir.rfind('/'); - size_t bslash = fRawFilePathDir.rfind('\\'); - string spellFile; - if (string::npos == slash && string::npos == bslash) { - spellFile = fRawFilePathDir; - } else { - if (string::npos != bslash && bslash > slash) { - slash = bslash; - } - spellFile = fRawFilePathDir.substr(0, slash); - } - spellFile += '/'; - spellFile += kSpellingFileName; - FILE* file = fopen(spellFile.c_str(), "r"); - if (!file) { - SkDebugf("missing %s\n", spellFile.c_str()); - return; - } - fseek(file, 0L, SEEK_END); - int sz = (int) ftell(file); - rewind(file); - char* buffer = new char[sz]; - memset(buffer, ' ', sz); - int read = (int)fread(buffer, 1, sz, file); - SkAssertResult(read > 0); - sz = read; // FIXME: ? why are sz and read different? - fclose(file); - int i = 0; - int start = i; - string word; - do { - if (' ' < buffer[i]) { - ++i; - continue; - } - word = string(&buffer[start], i - start); - if (fGlobalNames.fRefMap.end() == fGlobalNames.fRefMap.find(word)) { - fGlobalNames.fRefMap[word] = nullptr; - } else { - SkDebugf("%s ", word.c_str()); // debugging: word missing from spelling list - } - do { - ++i; - } while (i < sz && ' ' >= buffer[i]); - start = i; - } while (i < sz); - delete[] buffer; -} - -void BmhParser::setUpSubstitutes(const Definition* parent, NameMap* names) { - for (const auto& child : parent->fChildren) { - MarkType markType = child->fMarkType; - if (MarkType::kAlias == markType) { - continue; - } - if (MarkType::kSubstitute == markType) { - continue; - } - string name(child->fName); - if (&fGlobalNames != names) { - size_t lastDC = name.rfind("::"); - if (string::npos != lastDC) { - name = name.substr(lastDC + 2); - } - if ("" == name) { - continue; - } - } - size_t lastUnder = name.rfind('_'); - if (string::npos != lastUnder && ++lastUnder < name.length()) { - bool numbers = true; - for (size_t index = lastUnder; index < name.length(); ++index) { - numbers &= (bool) isdigit(name[index]); - } - if (numbers) { - continue; - } - } - string ref; - if (&fGlobalNames == names) { - ref = ParserCommon::HtmlFileName(child->fFileName); - } - ref += '#' + child->fFiddle; - if (MarkType::kClass == markType || MarkType::kStruct == markType - || MarkType::kMethod == markType || MarkType::kEnum == markType - || MarkType::kEnumClass == markType || MarkType::kConst == markType - || MarkType::kMember == markType || MarkType::kDefine == markType - || MarkType::kTypedef == markType) { - SkASSERT(names->fLinkMap.end() == names->fLinkMap.find(name)); - names->fLinkMap[name] = ref; - SkASSERT(names->fRefMap.end() == names->fRefMap.find(name)); - names->fRefMap[name] = child; - } - if (MarkType::kClass == markType || MarkType::kStruct == markType - || MarkType::kEnumClass == markType) { - RootDefinition* rootDef = child->asRoot(); - NameMap* nameMap = &rootDef->fNames; - this->setUpSubstitutes(child, nameMap); - nameMap->copyToParent(names); - } - if (MarkType::kEnum == markType) { - this->setUpSubstitutes(child, names); - } - if (MarkType::kSubtopic == markType) { - if (&fGlobalNames != names && string::npos != child->fName.find('_')) { - string lowered = this->loweredTopic(child->fName, child); - SkDEBUGCODE(auto refIter = names->fRefMap.find(lowered)); - SkDEBUGCODE(auto iter = names->fLinkMap.find(lowered)); - SkASSERT(names->fLinkMap.end() == iter); - names->fLinkMap[lowered] = '#' + child->fName; - SkASSERT(names->fRefMap.end() == refIter); - names->fRefMap[lowered] = child; - } - this->setUpSubstitutes(child, names); - } - } -} - -void BmhParser::setUpPartialSubstitute(string name) { - auto iter = fGlobalNames.fRefMap.find(name); - if (fGlobalNames.fRefMap.end() != iter) { - SkASSERT(nullptr == iter->second); - return; - } - fGlobalNames.fRefMap[name] = nullptr; -} - -void BmhParser::setUpSubstitute(string name, Definition* def) { - SkASSERT(fGlobalNames.fRefMap.end() == fGlobalNames.fRefMap.find(name)); - fGlobalNames.fRefMap[name] = def; - SkASSERT(fGlobalNames.fLinkMap.end() == fGlobalNames.fLinkMap.find(name)); - string str = ParserCommon::HtmlFileName(def->fFileName) + '#' + def->fName; - fGlobalNames.fLinkMap[name] = str; - size_t stop = name.length(); - do { - size_t space = name.rfind(' ', stop); - if (string::npos == space) { - break; - } - string partial = name.substr(0, space + 1); - stop = space - 1; - this->setUpPartialSubstitute(partial); - } while (true); -} - -void BmhParser::setWrapper(Definition* def) const { - const char drawWrapper[] = "void draw(SkCanvas* canvas) {"; - const char drawNoCanvas[] = "void draw(SkCanvas* ) {"; - string text = this->extractText(def, TrimExtract::kNo); - size_t nonSpace = 0; - while (nonSpace < text.length() && ' ' >= text[nonSpace]) { - ++nonSpace; - } - bool hasFunc = !text.compare(nonSpace, sizeof(drawWrapper) - 1, drawWrapper); - bool noCanvas = !text.compare(nonSpace, sizeof(drawNoCanvas) - 1, drawNoCanvas); - bool hasCanvas = string::npos != text.find("SkCanvas canvas"); - SkASSERT(!hasFunc || !noCanvas); - bool preprocessor = text[0] == '#'; - bool wrapCode = !hasFunc && !noCanvas && !preprocessor; - if (wrapCode) { - def->fWrapper = hasCanvas ? string(drawNoCanvas) : string(drawWrapper); - } -} - -RootDefinition* BmhParser::findBmhObject(MarkType markType, string typeName) { - const auto& mapIter = std::find_if(fMaps.begin(), fMaps.end(), - [markType](DefinitionMap& defMap){ return markType == defMap.fMarkType; } ); - if (mapIter == fMaps.end()) { - return nullptr; - } - return &(*mapIter->fMap)[typeName]; -} - -// FIXME: some examples may produce different output on different platforms -// if the text output can be different, think of how to author that - -bool BmhParser::findDefinitions() { - bool lineStart = true; - const char* lastChar = nullptr; - const char* lastMC = nullptr; - fParent = nullptr; - while (!this->eof()) { - if (this->peek() == fMC) { - lastMC = fChar; - this->next(); - if (this->peek() == fMC) { - this->next(); - if (!lineStart && ' ' < this->peek()) { - if (!fParent || MarkType::kFormula != fParent->fMarkType) { - return this->reportError<bool>("expected definition"); - } - } - if (this->peek() != fMC) { - if (MarkType::kColumn == fParent->fMarkType) { - SkASSERT(TableState::kColumnEnd == fTableState); - if (!this->endTableColumn(lastChar, lastMC)) { - return false; - } - SkASSERT(fRow); - if (!this->popParentStack(fParent)) { - return false; - } - fRow->fContentEnd = fWorkingColumn->fContentEnd; - fWorkingColumn = nullptr; - fRow = nullptr; - fTableState = TableState::kNone; - } else { - vector<string> parentName; - parentName.push_back(fParent->fName); - if (!this->addDefinition(fChar - 1, true, fParent->fMarkType, parentName, - HasTag::kNo)) { - return false; - } - } - } else { - SkAssertResult(this->next() == fMC); - fMC = this->next(); // change markup character - if (' ' >= fMC) { - return this->reportError<bool>("illegal markup character"); - } - fMarkup.emplace_front(MarkType::kMarkChar, fChar - 4, fLineCount, fParent, fMC); - Definition* markChar = &fMarkup.front(); - markChar->fContentStart = fChar - 1; - this->skipToEndBracket('\n'); - markChar->fContentEnd = fChar; - markChar->fTerminator = fChar; - fParent->fChildren.push_back(markChar); - } - } else if (this->peek() >= 'A' && this->peek() <= 'Z') { - const char* defStart = fChar - 1; - MarkType markType = this->getMarkType(MarkLookup::kRequire); - bool hasEnd = this->hasEndToken(); - if (!hasEnd && fParent) { - MarkType parentType = fParent->fMarkType; - uint64_t parentMask = kMarkProps[(int) markType].fParentMask; - if (parentMask && !(parentMask & (1LL << (int) parentType))) { - return this->reportError<bool>("invalid parent"); - } - } - if (!this->skipName(kMarkProps[(int) markType].fName)) { - return this->reportError<bool>("illegal markup character"); - } - if (!this->skipSpace()) { - return this->reportError<bool>("unexpected end"); - } - lineStart = '\n' == this->peek(); - bool expectEnd = true; - vector<string> typeNameBuilder = this->typeName(markType, &expectEnd); - if (fCloned && MarkType::kMethod != markType && MarkType::kExample != markType - && !fAnonymous) { - return this->reportError<bool>("duplicate name"); - } - if (hasEnd && expectEnd) { - if (fMC == this->peek()) { - return this->reportError<bool>("missing body"); - } - } - if (!this->addDefinition(defStart, hasEnd, markType, typeNameBuilder, - HasTag::kYes)) { - return false; - } - continue; - } else if (this->peek() == ' ') { - if (!fParent || (MarkType::kFormula != fParent->fMarkType - && MarkType::kLegend != fParent->fMarkType - && MarkType::kList != fParent->fMarkType - && MarkType::kLine != fParent->fMarkType - && MarkType::kTable != fParent->fMarkType)) { - int endHashes = this->endHashCount(); - if (endHashes <= 1) { - if (fParent) { - if (TableState::kColumnEnd == fTableState) { - if (!this->endTableColumn(lastChar, lastMC)) { - return false; - } - } else { // one line comment - fMarkup.emplace_front(MarkType::kComment, fChar - 1, fLineCount, - fParent, fMC); - Definition* comment = &fMarkup.front(); - comment->fContentStart = fChar - 1; - this->skipToEndBracket('\n'); - comment->fContentEnd = fChar; - comment->fTerminator = fChar; - fParent->fChildren.push_back(comment); - } - } else { - fChar = fLine + this->lineLength() - 1; - } - } else { // table row - if (2 != endHashes) { - string errorStr = "expect "; - errorStr += fMC; - errorStr += fMC; - return this->reportError<bool>(errorStr.c_str()); - } - if (!fParent || MarkType::kTable != fParent->fMarkType) { - return this->reportError<bool>("missing table"); - } - } - } else if (TableState::kNone == fTableState) { - // fixme? no nested tables for now - fColStart = fChar - 1; - fMarkup.emplace_front(MarkType::kRow, fColStart, fLineCount, fParent, fMC); - fRow = &fMarkup.front(); - fRow->fName = fParent->fName; - this->skipWhiteSpace(); - fRow->fContentStart = fChar; - this->setAsParent(fRow); - fTableState = TableState::kColumnStart; - } - if (TableState::kColumnStart == fTableState) { - fMarkup.emplace_front(MarkType::kColumn, fColStart, fLineCount, fParent, fMC); - fWorkingColumn = &fMarkup.front(); - fWorkingColumn->fName = fParent->fName; - fWorkingColumn->fContentStart = fChar; - this->setAsParent(fWorkingColumn); - fTableState = TableState::kColumnEnd; - continue; - } - } else if (this->peek() >= 'a' && this->peek() <= 'z') { - // expect zero or more letters and underscores (no spaces) then hash - const char* phraseNameStart = fChar; - this->skipPhraseName(); - string phraseKey = string(phraseNameStart, fChar - phraseNameStart); - char delimiter = this->next(); - vector<string> params; - vector<const char*> paramsLoc; - if (fMC != delimiter) { - if ('(' != delimiter) { - return this->reportError<bool>("expect # after phrase name"); - } - // phrase may take comma delimited parameter list - do { - const char* subEnd = this->anyOf(",)\n"); - if (!subEnd || '\n' == *subEnd) { - return this->reportError<bool>("unexpected phrase list end"); - } - params.push_back(string(fChar, subEnd - fChar)); - paramsLoc.push_back(fChar); - this->skipTo(subEnd); - - } while (')' != this->next()); - } - const char* start = phraseNameStart; - SkASSERT('#' == start[-1]); - --start; - if (start > fStart && ' ' >= start[-1]) { - --start; // preserve whether to add whitespace before substitution - } - fMarkup.emplace_front(MarkType::kPhraseRef, start, fLineCount, fParent, fMC); - Definition* markChar = &fMarkup.front(); - this->skipExact("#"); - markChar->fContentStart = fChar; - markChar->fContentEnd = fChar; - markChar->fTerminator = fChar; - markChar->fName = phraseKey; - fParent->fChildren.push_back(markChar); - int paramLocIndex = 0; - for (auto param : params) { - const char* paramLoc = paramsLoc[paramLocIndex++]; - fMarkup.emplace_front(MarkType::kPhraseParam, paramLoc, fLineCount, fParent, - fMC); - Definition* phraseParam = &fMarkup.front(); - phraseParam->fContentStart = paramLoc; - phraseParam->fContentEnd = paramLoc + param.length(); - phraseParam->fTerminator = paramLoc + param.length(); - phraseParam->fName = param; - markChar->fChildren.push_back(phraseParam); - } - } - } - char nextChar = this->next(); - if (' ' < nextChar) { - lastChar = fChar; - lineStart = false; - } else if (nextChar == '\n') { - lineStart = true; - } - } - if (fParent) { - return fParent->reportError<bool>("mismatched end"); - } - return true; -} - -MarkType BmhParser::getMarkType(MarkLookup lookup) const { - for (int index = 0; index <= Last_MarkType; ++index) { - int typeLen = strlen(kMarkProps[index].fName); - if (typeLen == 0) { - continue; - } - if (fChar + typeLen >= fEnd || fChar[typeLen] > ' ') { - continue; - } - int chCompare = strncmp(fChar, kMarkProps[index].fName, typeLen); - if (chCompare < 0) { - goto fail; - } - if (chCompare == 0) { - return (MarkType) index; - } - } -fail: - if (MarkLookup::kRequire == lookup) { - return this->reportError<MarkType>("unknown mark type"); - } - return MarkType::kNone; -} - - // replace #Method description, #Param, #Return with #Populate - // if description, params, return are free of phrase refs -bool HackParser::hackFiles() { - string filename(fFileName); - size_t len = filename.length() - 1; - while (len > 0 && (isalnum(filename[len]) || '_' == filename[len] || '.' == filename[len])) { - --len; - } - filename = filename.substr(len + 1); - if (filename.substr(0, 2) != "Sk") { - return true; - } - size_t under = filename.find('_'); - SkASSERT(under); - string className = filename.substr(0, under); - fOut = fopen(filename.c_str(), "wb"); - if (!fOut) { - SkDebugf("could not open output file %s\n", filename.c_str()); - return false; - } - auto mapEntry = fBmhParser.fClassMap.find(className); - if (fBmhParser.fClassMap.end() == mapEntry) { - remove(filename.c_str()); - return true; - } - const Definition* classMarkup = &mapEntry->second; - const Definition* root = classMarkup->fParent; - SkASSERT(root); - SkASSERT(root->fTerminator); - SkASSERT('\n' == root->fTerminator[0]); - SkASSERT(!root->fParent); - fStart = root->fStart; - fChar = fStart; - fEnd = root->fTerminator; - this->replaceWithPop(root); - FPRINTF("%.*s", (int) (fEnd - fChar), fChar); - if ('\n' != fEnd[-1]) { - FPRINTF("\n"); - } - fclose(fOut); - if (ParserCommon::WrittenFileDiffers(filename, root->fFileName)) { - SkDebugf("wrote %s\n", filename.c_str()); - } else { - remove(filename.c_str()); - } - return true; -} - -// returns true if topic has method -void HackParser::replaceWithPop(const Definition* root) { - for (auto child : root->fChildren) { - if (MarkType::kClass == child->fMarkType || MarkType::kStruct == child->fMarkType - || MarkType::kSubtopic == child->fMarkType) { - this->replaceWithPop(child); - } - if (MarkType::kMethod != child->fMarkType) { - continue; - } - auto& grans = child->fChildren; - if (grans.end() != std::find_if(grans.begin(), grans.end(), - [](const Definition* def) { - return MarkType::kPopulate == def->fMarkType - || MarkType::kPhraseRef == def->fMarkType - || MarkType::kFormula == def->fMarkType - || MarkType::kAnchor == def->fMarkType - || MarkType::kList == def->fMarkType - || MarkType::kTable == def->fMarkType - || MarkType::kDeprecated == def->fMarkType - || MarkType::kExperimental == def->fMarkType - || MarkType::kPrivate == def->fMarkType; - } )) { - continue; - } - // write #Populate in place of description, #Param(s), #Return (if present) - const char* keep = child->fContentStart; - const char* next = nullptr; - for (auto gran : grans) { - if (MarkType::kIn == gran->fMarkType || MarkType::kLine == gran->fMarkType) { - keep = gran->fTerminator; - continue; - } - if (MarkType::kExample == gran->fMarkType - || MarkType::kNoExample == gran->fMarkType) { - next = gran->fStart; - break; - } - if (MarkType::kParam == gran->fMarkType - || MarkType::kReturn == gran->fMarkType - || MarkType::kToDo == gran->fMarkType - || MarkType::kComment == gran->fMarkType) { - continue; - } - SkDebugf(""); // convenient place to set a breakpoint - } - SkASSERT(next); - FPRINTF("%.*s", (int) (keep - fChar), fChar); - if ('\n' != keep[-1]) { - FPRINTF("\n"); - } - FPRINTF("#Populate\n\n"); - fChar = next; - } -} - -bool BmhParser::hasEndToken() const { - const char* ptr = fLine; - char test; - do { - if (ptr >= fEnd) { - return false; - } - test = *ptr++; - if ('\n' == test) { - return false; - } - } while (fMC != test || fMC != *ptr); - return true; -} - -string BmhParser::memberName() { - const char* wordStart; - const char* prefixes[] = { "static", "const" }; - do { - this->skipSpace(); - wordStart = fChar; - this->skipToNonName(); - } while (this->anyOf(wordStart, prefixes, SK_ARRAY_COUNT(prefixes))); - if ('*' == this->peek()) { - this->next(); - } - return this->className(MarkType::kMember); -} - -string BmhParser::methodName() { - if (this->hasEndToken()) { - if (!fParent || !fParent->fName.length()) { - return this->reportError<string>("missing parent method name"); - } - SkASSERT(fMC == this->peek()); - this->next(); - SkASSERT(fMC == this->peek()); - this->next(); - SkASSERT(fMC != this->peek()); - return fParent->fName; - } - string builder; - const char* end = this->lineEnd(); - const char* paren = this->strnchr('(', end); - if (!paren) { - return this->reportError<string>("missing method name and reference"); - } - { - TextParserSave endCheck(this); - while (end < fEnd && !this->strnchr(')', end)) { - fChar = end + 1; - end = this->lineEnd(); - } - if (end >= fEnd) { - return this->reportError<string>("missing method end paren"); - } - endCheck.restore(); - } - const char* nameStart = paren; - char ch; - bool expectOperator = false; - bool isConstructor = false; - const char* nameEnd = nullptr; - while (nameStart > fChar && ' ' != (ch = *--nameStart)) { - if (!isalnum(ch) && '_' != ch) { - if (nameEnd) { - break; - } - expectOperator = true; - continue; - } - if (!nameEnd) { - nameEnd = nameStart + 1; - } - } - if (!nameEnd) { - return this->reportError<string>("unexpected method name char"); - } - if (' ' == nameStart[0]) { - ++nameStart; - } - if (nameEnd <= nameStart) { - return this->reportError<string>("missing method name"); - } - if (nameStart >= paren) { - return this->reportError<string>("missing method name length"); - } - string name(nameStart, nameEnd - nameStart); - bool allLower = true; - for (int index = 0; index < (int) (nameEnd - nameStart); ++index) { - if (!islower(nameStart[index])) { - allLower = false; - break; - } - } - if (expectOperator && "operator" != name) { - return this->reportError<string>("expected operator"); - } - const Definition* parent = this->parentSpace(); - if (parent && parent->fName.length() > 0) { - size_t parentNameIndex = parent->fName.rfind(':'); - parentNameIndex = string::npos == parentNameIndex ? 0 : parentNameIndex + 1; - string parentName = parent->fName.substr(parentNameIndex); - if (parentName == name) { - isConstructor = true; - } else if ('~' == name[0]) { - if (parentName != name.substr(1)) { - return this->reportError<string>("expected destructor"); - } - isConstructor = true; - } - builder = parent->fName + "::"; - } - bool addConst = false; - if (isConstructor || expectOperator) { - paren = this->strnchr(')', end) + 1; - TextParserSave saveState(this); - this->skipTo(paren); - if (this->skipExact("_const")) { - addConst = true; - } - saveState.restore(); - } - builder.append(nameStart, paren - nameStart); - if (addConst) { - builder.append("_const"); - } - if (!expectOperator && allLower) { - builder.append("()"); - } - int parens = 0; - while (fChar < end || parens > 0) { - if ('(' == this->peek()) { - ++parens; - } else if (')' == this->peek()) { - --parens; - } - this->next(); - } - TextParserSave saveState(this); - this->skipWhiteSpace(); - if (this->startsWith("const")) { - this->skipName("const"); - } else { - saveState.restore(); - } -// this->next(); - if (string::npos != builder.find('\n')) { - builder.erase(std::remove(builder.begin(), builder.end(), '\n'), builder.end()); - } - return uniqueRootName(builder, MarkType::kMethod); -} - -const Definition* BmhParser::parentSpace() const { - Definition* parent = nullptr; - Definition* test = fParent; - while (test) { - if (MarkType::kClass == test->fMarkType || - MarkType::kEnumClass == test->fMarkType || - MarkType::kStruct == test->fMarkType) { - parent = test; - break; - } - test = test->fParent; - } - return parent; -} - -// A full terminal statement is in the form: -// \n optional-white-space #MarkType white-space #[# white-space] -// \n optional-white-space #MarkType white-space Name white-space #[# white-space] -// MarkType must match definition->fMarkType -const char* BmhParser::checkForFullTerminal(const char* end, const Definition* definition) const { - const char* start = end; - while ('\n' != start[0] && start > fStart) { - --start; - } - SkASSERT (start < end); - // if end is preceeeded by \n#MarkType ## backup to there - TextParser parser(fFileName, start, fChar, fLineCount); - parser.skipWhiteSpace(); - if (parser.eof() || fMC != parser.next()) { - return end; - } - const char* markName = kMarkProps[(int) definition->fMarkType].fName; - if (!parser.skipExact(markName)) { - return end; - } - parser.skipWhiteSpace(); - TextParser startName(fFileName, definition->fStart, definition->fContentStart, - definition->fLineCount); - if ('#' == startName.next()) { - startName.skipToSpace(); - if (!startName.eof() && startName.skipSpace()) { - const char* nameBegin = startName.fChar; - startName.skipToWhiteSpace(); - string name(nameBegin, (int) (startName.fChar - nameBegin)); - if (fMC != parser.peek() && !parser.skipExact(name.c_str())) { - return end; - } - parser.skipSpace(); - } - } - if (parser.eof() || fMC != parser.next()) { - return end; - } - if (!parser.eof() && fMC != parser.next()) { - return end; - } - SkASSERT(parser.eof()); - return start; -} - -void BmhParser::parseHashAnchor(Definition* definition) { - this->skipToEndBracket(fMC); - fMarkup.emplace_front(MarkType::kLink, fChar, fLineCount, definition, fMC); - SkAssertResult(fMC == this->next()); - this->skipWhiteSpace(); - Definition* link = &fMarkup.front(); - link->fContentStart = fChar; - link->fContentEnd = this->trimmedBracketEnd(fMC); - this->skipToEndBracket(fMC); - SkAssertResult(fMC == this->next()); - SkAssertResult(fMC == this->next()); - link->fTerminator = fChar; - definition->fContentEnd = link->fContentEnd; - definition->fTerminator = fChar; - definition->fChildren.emplace_back(link); -} - -void BmhParser::parseHashFormula(Definition* definition) { - const char* start = definition->fContentStart; - definition->trimEnd(); - const char* end = definition->fContentEnd; - fMarkup.emplace_front(MarkType::kText, start, fLineCount, definition, fMC); - Definition* text = &fMarkup.front(); - text->fContentStart = start; - text->fContentEnd = end; - text->fTerminator = definition->fTerminator; - definition->fChildren.emplace_back(text); -} - -void BmhParser::parseHashLine(Definition* definition) { - const char* nextLF = this->strnchr('\n', this->fEnd); - const char* start = fChar; - const char* end = this->trimmedBracketEnd(fMC); - this->skipToEndBracket(fMC, nextLF); - if (fMC != this->next() || fMC != this->next()) { - return this->reportError<void>("expected ## to delineate line"); - } - fMarkup.emplace_front(MarkType::kText, start, fLineCount, definition, fMC); - Definition* text = &fMarkup.front(); - if (!islower(start[0]) && (!isdigit(start[0]) - || MarkType::kConst != definition->fParent->fMarkType)) { - return this->reportError<void>("expect lower case start"); - } - string contents = string(start, end - start); - if (string::npos != contents.find('.')) { - return this->reportError<void>("expect phrase, not sentence"); - } - size_t firstSpace = contents.find(' '); - if (string::npos == firstSpace || 0 == firstSpace || 's' != start[firstSpace - 1]) { - if (MarkType::kMethod == fParent->fMarkType && "experimental" != contents - && "incomplete" != contents) { - return this->reportError<void>( "expect phrase in third person present" - " tense (1st word should end in 's'"); - } - } - text->fContentStart = start; - text->fContentEnd = end; - text->fTerminator = fChar; - definition->fContentEnd = text->fContentEnd; - definition->fTerminator = fChar; - definition->fChildren.emplace_back(text); -} - -bool BmhParser::popParentStack(Definition* definition) { - if (!fParent) { - return this->reportError<bool>("missing parent"); - } - if (definition != fParent) { - return this->reportError<bool>("definition end is not parent"); - } - if (!definition->fStart) { - return this->reportError<bool>("definition missing start"); - } - if (definition->fContentEnd) { - return this->reportError<bool>("definition already ended"); - } - // more to figure out to handle table columns, at minimum - const char* end = fChar; - if (fMC != end[0]) { - while (end > definition->fContentStart && ' ' >= end[-1]) { - --end; - } - SkASSERT(&end[-1] >= definition->fContentStart && fMC == end[-1] - && (MarkType::kColumn == definition->fMarkType - || (&end[-2] >= definition->fContentStart && fMC == end[-2]))); - end -= 2; - } - end = checkForFullTerminal(end, definition); - definition->fContentEnd = end; - definition->fTerminator = fChar; - fParent = definition->fParent; - if (!fParent || (MarkType::kTopic == fParent->fMarkType && !fParent->fParent)) { - fRoot = nullptr; - } - return true; -} - -TextParser::TextParser(const Definition* definition) : - TextParser(definition->fFileName, definition->fContentStart, definition->fContentEnd, - definition->fLineCount) { -} - -string TextParser::ReportFilename(string file) { - string fullName; -#ifdef SK_BUILD_FOR_WIN - TCHAR pathChars[MAX_PATH]; - DWORD pathLen = GetCurrentDirectory(MAX_PATH, pathChars); - for (DWORD index = 0; index < pathLen; ++index) { - fullName += pathChars[index] == (char)pathChars[index] ? (char)pathChars[index] : '?'; - } - fullName += '\\'; -#endif - fullName += file; - return fullName; -} - -void TextParser::reportError(const char* errorStr) const { - this->reportWarning(errorStr); - SkDebugf(""); // convenient place to set a breakpoint -} - -void TextParser::reportWarning(const char* errorStr) const { - const char* lineStart = fLine; - if (lineStart >= fEnd) { - lineStart = fChar; - } - SkASSERT(lineStart < fEnd); - TextParser err(fFileName, lineStart, fEnd, fLineCount); - size_t lineLen = this->lineLength(); - ptrdiff_t spaces = fChar - lineStart; - while (spaces > 0 && (size_t) spaces > lineLen) { - ++err.fLineCount; - err.fLine += lineLen; - spaces -= lineLen; - lineLen = err.lineLength(); - } - string fullName = this->ReportFilename(fFileName); - SkDebugf("\n%s(%zd): error: %s\n", fullName.c_str(), err.fLineCount, errorStr); - if (0 == lineLen) { - SkDebugf("[blank line]\n"); - } else { - while (lineLen > 0 && '\n' == err.fLine[lineLen - 1]) { - --lineLen; - } - SkDebugf("%.*s\n", (int) lineLen, err.fLine); - SkDebugf("%*s^\n", (int) spaces, ""); - } -} - -void TextParser::setForErrorReporting(const Definition* definition, const char* str) { - fFileName = definition->fFileName; - fStart = definition->fContentStart; - fLine = str; - while (fLine > fStart && fLine[-1] != '\n') { - --fLine; - } - fChar = str; - fEnd = definition->fContentEnd; - fLineCount = definition->fLineCount; - const char* lineInc = fStart; - while (lineInc < str) { - fLineCount += '\n' == *lineInc++; - } -} - -string TextParser::typedefName() { - // look for typedef as one of three forms: - // typedef return-type (*NAME)(params); - // typedef alias NAME; - // typedef std::function<alias> NAME; - string builder; - const char* end = this->doubleLF(); - if (!end) { - end = fEnd; - } - const char* altEnd = this->strnstr("#Typedef ##", end); - if (altEnd) { - end = this->strnchr('\n', end); - } - if (!end) { - return this->reportError<string>("missing typedef std::function end bracket >"); - } - bool stdFunction = this->startsWith("std::function"); - if (stdFunction) { - if (!this->skipToEndBracket('>')) { - return this->reportError<string>("missing typedef std::function end bracket >"); - } - this->next(); - this->skipWhiteSpace(); - builder += string(fChar, end - fChar); - } else { - const char* paren = this->strnchr('(', end); - if (!paren) { - const char* lastWord = nullptr; - do { - this->skipToWhiteSpace(); - if (fChar < end && isspace(fChar[0])) { - const char* whiteStart = fChar; - this->skipWhiteSpace(); - // FIXME: test should be for fMC - if ('#' == fChar[0]) { - end = whiteStart; - break; - } - lastWord = fChar; - } else { - break; - } - } while (true); - if (!lastWord) { - return this->reportError<string>("missing typedef name"); - } - builder += string(lastWord, end - lastWord); - } else { - this->skipTo(paren); - this->next(); - if ('*' != this->next()) { - return this->reportError<string>("missing typedef function asterisk"); - } - const char* nameStart = fChar; - if (!this->skipToEndBracket(')')) { - return this->reportError<string>("missing typedef function )"); - } - builder += string(nameStart, fChar - nameStart); - if (!this->skipToEndBracket('(')) { - return this->reportError<string>("missing typedef params ("); - } - if (! this->skipToEndBracket(')')) { - return this->reportError<string>("missing typedef params )"); - } - this->skipTo(end); - } - } - return builder; -} - -bool BmhParser::skipNoName() { - if ('\n' == this->peek()) { - this->next(); - return true; - } - this->skipWhiteSpace(); - if (fMC != this->peek()) { - return this->reportError<bool>("expected end mark 1"); - } - this->next(); - if (fMC != this->peek()) { - return this->reportError<bool>("expected end mark 2"); - } - this->next(); - return true; -} - -bool BmhParser::skipToDefinitionEnd(MarkType markType) { - if (this->eof()) { - return this->reportError<bool>("missing end"); - } - const char* start = fLine; - int startLineCount = fLineCount; - int stack = 1; - ptrdiff_t lineLen; - bool foundEnd = false; - do { - lineLen = this->lineLength(); - if (fMC != *fChar++) { - continue; - } - if (fMC == *fChar) { - continue; - } - if (' ' == *fChar) { - continue; - } - MarkType nextType = this->getMarkType(MarkLookup::kAllowUnknown); - if (markType != nextType) { - continue; - } - bool hasEnd = this->hasEndToken(); - if (hasEnd) { - if (!--stack) { - foundEnd = true; - continue; - } - } else { - ++stack; - } - } while ((void) ++fLineCount, (void) (fLine += lineLen), (void) (fChar = fLine), - !this->eof() && !foundEnd); - if (foundEnd) { - return true; - } - fLineCount = startLineCount; - fLine = start; - fChar = start; - return this->reportError<bool>("unbalanced stack"); -} - -bool BmhParser::skipToString() { - this->skipSpace(); - if (fMC != this->peek()) { - return this->reportError<bool>("expected end mark 3"); - } - this->next(); - this->skipSpace(); - // body is text from here to double fMC - // no single fMC allowed, no linefeed allowed - return true; -} - -vector<string> BmhParser::topicName() { - vector<string> result; - this->skipWhiteSpace(); - const char* lineEnd = fLine + this->lineLength(); - const char* nameStart = fChar; - while (fChar < lineEnd) { - char ch = this->next(); - SkASSERT(',' != ch); - if ('\n' == ch) { - break; - } - if (fMC == ch) { - break; - } - } - if (fChar - 1 > nameStart) { - string builder(nameStart, fChar - nameStart - 1); - trim_start_end(builder); - result.push_back(builder); - } - if (fChar < lineEnd && fMC == this->peek()) { - this->next(); - } - return result; -} - -// typeName parsing rules depend on mark type -vector<string> BmhParser::typeName(MarkType markType, bool* checkEnd) { - fAnonymous = false; - fCloned = false; - vector<string> result; - string builder; - if (fParent) { - builder = fParent->fName; - } - switch (markType) { - case MarkType::kDefine: - case MarkType::kEnum: - // enums may be nameless - case MarkType::kConst: - case MarkType::kEnumClass: - case MarkType::kClass: - case MarkType::kStruct: - // expect name - builder = this->className(markType); - break; - case MarkType::kExample: - // check to see if one already exists -- if so, number this one - builder = this->uniqueName(string(), markType); - this->skipNoName(); - break; - case MarkType::kCode: - case MarkType::kDescription: - case MarkType::kExternal: - case MarkType::kFunction: - case MarkType::kLegend: - case MarkType::kList: - case MarkType::kNoExample: - case MarkType::kPrivate: - this->skipNoName(); - break; - case MarkType::kFormula: - case MarkType::kLine: - this->skipToString(); - break; - case MarkType::kAlias: - case MarkType::kAnchor: - case MarkType::kBug: // fixme: expect number - case MarkType::kDeprecated: - case MarkType::kDetails: - case MarkType::kDuration: - case MarkType::kExperimental: - case MarkType::kFile: - case MarkType::kFilter: - case MarkType::kHeight: - case MarkType::kIllustration: - case MarkType::kImage: - case MarkType::kIn: - case MarkType::kLiteral: - case MarkType::kNoJustify: - case MarkType::kOutdent: - case MarkType::kPlatform: - case MarkType::kPopulate: - case MarkType::kReturn: - case MarkType::kSeeAlso: - case MarkType::kSet: - case MarkType::kSubstitute: - case MarkType::kToDo: - case MarkType::kVolatile: - case MarkType::kWidth: - *checkEnd = false; // no name, may have text body - break; - case MarkType::kStdOut: - this->skipNoName(); - break; // unnamed - case MarkType::kMember: - builder = this->memberName(); - break; - case MarkType::kMethod: - builder = this->methodName(); - break; - case MarkType::kTypedef: - builder = this->typedefName(); - break; - case MarkType::kParam: - // fixme: expect camelCase for param - builder = this->word("", ""); - this->skipSpace(); - *checkEnd = false; - break; - case MarkType::kPhraseDef: { - const char* nameEnd = this->anyOf("(\n"); - builder = string(fChar, nameEnd - fChar); - this->skipLower(); - if (fChar != nameEnd) { - this->reportError("expect lower case only"); - break; - } - this->skipTo(nameEnd); - *checkEnd = false; - } break; - case MarkType::kTable: - this->skipNoName(); - break; // unnamed - case MarkType::kSubtopic: - case MarkType::kTopic: - // fixme: start with cap, allow space, hyphen, stop on comma - // one topic can have multiple type names delineated by comma - result = this->topicName(); - if (result.size() == 0 && this->hasEndToken()) { - break; - } - return result; - default: - // fixme: don't allow silent failures - SkASSERT(0); - } - result.push_back(builder); - return result; -} - -string BmhParser::typedefName() { - if (this->hasEndToken()) { - if (!fParent || !fParent->fName.length()) { - return this->reportError<string>("missing parent typedef name"); - } - SkASSERT(fMC == this->peek()); - this->next(); - SkASSERT(fMC == this->peek()); - this->next(); - SkASSERT(fMC != this->peek()); - return fParent->fName; - } - string builder; - const Definition* parent = this->parentSpace(); - if (parent && parent->fName.length() > 0) { - builder = parent->fName + "::"; - } - builder += TextParser::typedefName(); - return uniqueRootName(builder, MarkType::kTypedef); -} - -string BmhParser::uniqueName(string base, MarkType markType) { - string builder(base); - if (!builder.length()) { - builder = fParent->fName; - } - if (!fParent) { - return builder; - } - int number = 2; - string numBuilder(builder); - do { - for (auto& iter : fParent->fChildren) { - if (markType == iter->fMarkType) { - if (iter->fName == numBuilder) { - if (iter->fDeprecated) { - iter->fClone = true; - } else { - fCloned = true; - } - numBuilder = builder + '_' + to_string(number); - goto tryNext; - } - } - } - break; -tryNext: ; - } while (++number); - return numBuilder; -} - -string BmhParser::uniqueRootName(string base, MarkType markType) { - auto checkName = [markType](const Definition& def, string numBuilder) -> bool { - return markType == def.fMarkType && def.fName == numBuilder; - }; - - string builder(base); - if (!builder.length()) { - builder = fParent->fName; - } - int number = 2; - string numBuilder(builder); - Definition* cloned = nullptr; - do { - if (fRoot) { - for (auto& iter : fRoot->fBranches) { - if (checkName(*iter.second, numBuilder)) { - cloned = iter.second; - goto tryNext; - } - } - for (auto& iter : fRoot->fLeaves) { - if (checkName(iter.second, numBuilder)) { - cloned = &iter.second; - goto tryNext; - } - } - } else if (fParent) { - for (auto& iter : fParent->fChildren) { - if (checkName(*iter, numBuilder)) { - cloned = &*iter; - goto tryNext; - } - } - } - break; -tryNext: ; - if ("()" == builder.substr(builder.length() - 2)) { - builder = builder.substr(0, builder.length() - 2); - } - if (MarkType::kMethod == markType) { - cloned->fCloned = true; - if (cloned->fDeprecated) { - cloned->fClone = true; - } else { - fCloned = true; - } - } else { - fCloned = true; - } - numBuilder = builder + '_' + to_string(number); - } while (++number); - return numBuilder; -} - -void BmhParser::validate() const { - for (int index = 0; index <= (int) Last_MarkType; ++index) { - SkASSERT(kMarkProps[index].fMarkType == (MarkType) index); - } - const char* last = ""; - for (int index = 0; index <= (int) Last_MarkType; ++index) { - const char* next = kMarkProps[index].fName; - if (!last[0]) { - last = next; - continue; - } - if (!next[0]) { - continue; - } - SkASSERT(strcmp(last, next) < 0); - last = next; - } -} - -string BmhParser::word(string prefix, string delimiter) { - string builder(prefix); - this->skipWhiteSpace(); - const char* lineEnd = fLine + this->lineLength(); - const char* nameStart = fChar; - while (fChar < lineEnd) { - char ch = this->next(); - if (' ' >= ch) { - break; - } - if (',' == ch) { - return this->reportError<string>("no comma needed"); - break; - } - if (fMC == ch) { - return builder; - } - if (!isalnum(ch) && '_' != ch && ':' != ch && '-' != ch) { - return this->reportError<string>("unexpected char"); - } - if (':' == ch) { - // expect pair, and expect word to start with Sk - if (nameStart[0] != 'S' || nameStart[1] != 'k') { - return this->reportError<string>("expected Sk"); - } - if (':' != this->peek()) { - return this->reportError<string>("expected ::"); - } - this->next(); - } else if ('-' == ch) { - // expect word not to start with Sk or kX where X is A-Z - if (nameStart[0] == 'k' && nameStart[1] >= 'A' && nameStart[1] <= 'Z') { - return this->reportError<string>("didn't expected kX"); - } - if (nameStart[0] == 'S' && nameStart[1] == 'k') { - return this->reportError<string>("expected Sk"); - } - } - } - if (prefix.size()) { - builder += delimiter; - } - builder.append(nameStart, fChar - nameStart - 1); - return builder; -} // pass one: parse text, collect definitions // pass two: lookup references @@ -2848,7 +259,7 @@ int main(int argc, char** const argv) { mdOut.checkAnchors(); } } - if (runAll || (FLAGS_catalog && FLAGS_ref.isEmpty())) { + if (runAll || (FLAGS_catalog && !FLAGS_ref.isEmpty())) { Catalog cparser(&bmhParser); cparser.fDebugOut = FLAGS_stdout; if (!FLAGS_bmh.isEmpty() && !cparser.openCatalog(FLAGS_bmh[0])) { diff --git a/tools/bookmaker/bookmaker.h b/tools/bookmaker/bookmaker.h index 5a72315bf9..59083a75c4 100644 --- a/tools/bookmaker/bookmaker.h +++ b/tools/bookmaker/bookmaker.h @@ -8,43 +8,49 @@ #ifndef bookmaker_DEFINED #define bookmaker_DEFINED -#include "SkCommandLineFlags.h" -#include "SkData.h" -#include "SkJSONCPP.h" - #include <algorithm> #include <cmath> #include <cctype> +#include <cstring> #include <forward_list> #include <list> +#include <sstream> #include <string> #include <unordered_map> #include <vector> +#include "SkTypes.h" + +using std::forward_list; +using std::list; +using std::string; +using std::unordered_map; +using std::vector; + +class Definition; + +class NonAssignable { +public: + NonAssignable(NonAssignable const&) = delete; + NonAssignable& operator=(NonAssignable const&) = delete; + NonAssignable() {} +}; + #define FPRINTF(...) \ if (fDebugOut) { \ SkDebugf(__VA_ARGS__); \ } \ fprintf(fOut, __VA_ARGS__) - // std::to_string isn't implemented on android -#include <sstream> - template <typename T> -std::string to_string(T value) +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, kSK_API, @@ -199,12 +205,6 @@ enum class KeyProperty { kPreprocessor, }; -enum class StatusFilter { - kCompleted, - kInProgress, - kUnknown, -}; - struct IncludeKey { const char* fName; KeyWord fKeyWord; @@ -213,937 +213,6 @@ struct IncludeKey { extern const IncludeKey kKeyWords[]; -static inline bool has_nonwhitespace(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<int, int>(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<int, int>(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, TextParserSave - friend class ParserCommon; - friend class TextParserSave; -public: - virtual ~TextParser() {} - - TextParser(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; - } - - bool back(const char* pattern) { - size_t len = strlen(pattern); - const char* start = fChar - len; - if (start <= fStart) { - return false; - } - if (strncmp(start, pattern, len)) { - return false; - } - fChar = start; - return true; - } - - 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]; - } - - void backupWord() { - while (fChar > fStart && isalpha(fChar[-1])) { - --fChar; - } - } - - bool contains(const char* match, const char* lineEnd, const char** loc) const { - const char* result = this->strnstr(match, lineEnd); - if (loc) { - *loc = result; - } - return result; - } - - bool containsWord(const char* match, const char* lineEnd, const char** loc) { - size_t len = strlen(match); - do { - const char* result = this->strnstr(match, lineEnd); - if (!result) { - return false; - } - if ((result > fStart && isalnum(result[-1])) || (result + len < fEnd - && isalnum(result[len]))) { - fChar = result + len; - continue; - } - if (loc) { - *loc = result; - } - return true; - } while (true); - } - - // either /n/n or /n# will stop parsing a typedef - const char* doubleLF() const { - const char* ptr = fChar - 1; - const char* doubleStart = nullptr; - while (++ptr < fEnd) { - if (!doubleStart) { - if ('\n' == ptr[0]) { - doubleStart = ptr; - } - continue; - } - if ('\n' == ptr[0] || '#' == ptr[0]) { - return doubleStart; - } - if (' ' < ptr[0]) { - doubleStart = nullptr; - } - } - return nullptr; - } - - bool endsWith(const char* match) { - int matchLen = strlen(match); - if (matchLen > fChar - fLine) { - return false; - } - return !strncmp(fChar - matchLen, match, matchLen); - } - - 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; - static string ReportFilename(string file); - void reportWarning(const char* errorStr) const; - - template <typename T> 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; - } - - void setForErrorReporting(const Definition* , const char* ); - - bool skipToBalancedEndBracket(char startB, char endB) { - SkASSERT(fChar < fEnd); - SkASSERT(startB == fChar[0]); - int startCount = 0; - do { - char test = this->next(); - startCount += startB == test; - startCount -= endB == test; - } while (startCount && fChar < fEnd); - return !startCount; - } - - 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++; - } - } - - // returns true if saw close brace - bool skipToAlphaNum() { - bool sawCloseBrace = false; - while (fChar < fEnd && !isalnum(fChar[0])) { - sawCloseBrace |= '}' == *fChar++; - } - return sawCloseBrace; - } - - 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() { - do { - char last = '\0'; - 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++; - } - last = fChar[0]; - fChar++; - } - if (fChar + 1 >= fEnd || '/' != fChar[0] || !isalpha(last) || !isalpha(fChar[1])) { - break; // stop unless pattern is xxx/xxx as in I/O - } - fChar++; // skip slash - } while (true); - } - - int skipToLineBalance(char open, char close) { - int match = 0; - while (!this->eof() && '\n' != fChar[0]) { - match += open == this->peek(); - match -= close == this->next(); - } - return match; - } - - bool skipToLineStart() { - if (!this->skipLine()) { - return false; - } - if (!this->eof()) { - return this->skipWhiteSpace(); - } - return true; - } - - void skipToLineStart(int* indent, bool* sawReturn) { - SkAssertResult(this->skipLine()); - this->skipWhiteSpace(indent, sawReturn); - } - - void skipLower() { - while (fChar < fEnd && (islower(fChar[0]) || '_' == fChar[0])) { - fChar++; - } - } - - void skipToNonAlphaNum() { - while (fChar < fEnd && (isalnum(fChar[0]) || '_' == fChar[0])) { - fChar++; - } - } - - void skipToNonName() { - 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 skipPhraseName() { - while (fChar < fEnd && (islower(fChar[0]) || '_' == fChar[0])) { - fChar++; - } - } - - void skipToSpace() { - while (fChar < fEnd && ' ' != fChar[0]) { - fChar++; - } - } - - void skipToWhiteSpace() { - 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)) { - for (size_t i = 0; i < len; ++i) { - this->next(); - } - } - 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; - } - - void skipWhiteSpace(int* indent, bool* skippedReturn) { - while (' ' >= this->peek()) { - *indent = *skippedReturn ? *indent + 1 : 1; - if ('\n' == this->peek()) { - *skippedReturn |= true; - *indent = 0; - } - (void) this->next(); - SkASSERT(fChar < fEnd); - } - } - - bool startsWith(const char* str) const { - size_t len = strlen(str); - ptrdiff_t lineLen = fEnd - fChar; - 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) const { - int max = (int) (this->lineLength()); - 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* trimmedBracketEnd(string bracket) const { - size_t max = (size_t) (this->lineLength()); - string line(fChar, max); - size_t index = line.find(bracket); - SkASSERT(index < max); - while (index > 0 && ' ' >= fChar[index - 1]) { - --index; - } - return fChar + index; - } - - const char* trimmedBracketNoEnd(const char bracket) const { - int max = (int) (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; - } - } - - // FIXME: nothing else in TextParser knows from C++ -- - // there could be a class between TextParser and ParserCommon - virtual string typedefName(); - - 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 TextParserSave { -public: - TextParserSave(TextParser* parser) { - fParser = parser; - fSave.fFileName = parser->fFileName; - fSave.fStart = parser->fStart; - fSave.fLine = parser->fLine; - fSave.fChar = parser->fChar; - fSave.fEnd = parser->fEnd; - fSave.fLineCount = parser->fLineCount; - } - - void restore() const { - fParser->fFileName = fSave.fFileName; - fParser->fStart = fSave.fStart; - fParser->fLine = fSave.fLine; - fParser->fChar = fSave.fChar; - fParser->fEnd = fSave.fEnd; - fParser->fLineCount = fSave.fLineCount; - } - -private: - TextParser* fParser; - TextParser fSave; -}; - - -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') || - (reader[0] >= 'a' && reader[0] <= 'f')); - int nibble = *reader++ - '0'; - if (nibble > 9) { - nibble = (nibble & ~('a' - 'A')) - 'A' + '9' + 1; - } - unicode |= nibble; - } - SkASSERT(unicode < 256); - *writer++ = (unsigned char) unicode; - } else { - SkASSERT(ctrl == 'n'); - *writer++ = '\n'; - } - } - } - fStart = fLine = fChar = fStorage; - fEnd = writer; - } - - ~EscapeParser() override { - delete fStorage; - } -private: - char* fStorage; -}; - -class RootDefinition; - -class Definition : public NonAssignable { -public: - enum Type { - kNone, - kWord, - kMark, - kKeyWord, - kBracket, - kPunctuation, - kFileType, - }; - - enum class MethodType { - kNone, - kConstructor, - kDestructor, - kOperator, - }; - - enum class Operator { - kUnknown, - kAdd, - kAddTo, - kArray, - kCast, - kCopy, - kDelete, - kDereference, - kEqual, - kMinus, - kMove, - kMultiply, - kMultiplyBy, - kNew, - kNotEqual, - kSubtract, - kSubtractFrom, - }; - - enum class Format { - kIncludeReturn, - kOmitReturn, - }; - - enum class Details { - kNone, - kSoonToBe_Deprecated, - kTestingOnly_Experiment, - kDoNotUse_Experiment, - kNotReady_Experiment, - }; - - enum class DetailsType { - kPhrase, - kSentence, - }; - - Definition() {} - - Definition(const char* start, const char* end, int line, Definition* parent, char mc) - : fStart(start) - , fContentStart(start) - , fContentEnd(end) - , fParent(parent) - , fLineCount(line) - , fType(Type::kWord) - , fMC(mc) { - if (parent) { - SkASSERT(parent->fFileName.length() > 0); - fFileName = parent->fFileName; - } - this->setParentIndex(); - } - - Definition(MarkType markType, const char* start, int line, Definition* parent, char mc) - : Definition(markType, start, nullptr, line, parent, mc) { - } - - Definition(MarkType markType, const char* start, const char* end, int line, Definition* parent, char mc) - : Definition(start, end, line, parent, mc) { - fMarkType = markType; - fType = Type::kMark; - } - - Definition(Bracket bracket, const char* start, int lineCount, Definition* parent, char mc) - : Definition(start, nullptr, lineCount, parent, mc) { - fBracket = bracket; - fType = Type::kBracket; - } - - Definition(KeyWord keyWord, const char* start, const char* end, int lineCount, - Definition* parent, char mc) - : Definition(start, end, lineCount, parent, mc) { - fKeyWord = keyWord; - fType = Type::kKeyWord; - } - - Definition(Punctuation punctuation, const char* start, int lineCount, Definition* parent, char mc) - : Definition(start, nullptr, lineCount, parent, mc) { - fPunctuation = punctuation; - fType = Type::kPunctuation; - } - - virtual ~Definition() {} - - virtual RootDefinition* asRoot() { SkASSERT(0); return nullptr; } - bool boilerplateIfDef(); - - bool boilerplateEndIf() { - return true; - } - - bool checkMethod() const; - bool crossCheck2(const Definition& includeToken) const; - bool crossCheck(const Definition& includeToken) const; - bool crossCheckInside(const char* start, const char* end, const Definition& includeToken) const; - - Definition* csParent() { - Definition* test = fParent; - while (test) { - if (MarkType::kStruct == test->fMarkType || MarkType::kClass == test->fMarkType) { - return test; - } - test = test->fParent; - } - return nullptr; - } - - string fiddleName() const; - string fileName() const; - const Definition* findClone(string match) const; - string formatFunction(Format format) const; - const Definition* hasChild(MarkType markType) const; - bool hasMatch(string name) const; - Definition* hasParam(string ref); - string incompleteMessage(DetailsType ) const; - bool isClone() const { return fClone; } - - const Definition* iRootParent() const { - const Definition* test = fParent; - while (test) { - if (KeyWord::kClass == test->fKeyWord || KeyWord::kStruct == test->fKeyWord) { - return test; - } - test = test->fParent; - } - return nullptr; - } - - virtual bool isRoot() const { return false; } - bool isStructOrClass() const; - - int length() const { - return (int) (fContentEnd - fContentStart); - } - - const char* methodEnd() const; - bool methodHasReturn(string name, TextParser* methodParser) const; - string methodName() const; - bool nextMethodParam(TextParser* methodParser, const char** nextEndPtr, - string* paramName) const; - static string NormalizedName(string name); - bool paramsMatch(string fullRef, string name) const; - bool parseOperator(size_t doubleColons, string& result); - - string printableName() const { - string result(fName); - std::replace(result.begin(), result.end(), '_', ' '); - return result; - } - - template <typename T> T reportError(const char* errorStr) const { - TextParser tp(this); - tp.reportError(errorStr); - return T(); - } - - virtual RootDefinition* rootParent() { SkASSERT(0); return nullptr; } - virtual const RootDefinition* rootParent() const { SkASSERT(0); return nullptr; } - void setCanonicalFiddle(); - - void setParentIndex() { - fParentIndex = fParent ? (int) fParent->fTokens.size() : -1; - } - - string simpleName() { - size_t doubleColon = fName.rfind("::"); - return string::npos == doubleColon ? fName : fName.substr(doubleColon + 2); - } - - const Definition* subtopicParent() const { - Definition* test = fParent; - while (test) { - if (MarkType::kTopic == test->fMarkType || MarkType::kSubtopic == test->fMarkType) { - return test; - } - test = test->fParent; - } - return nullptr; - } - - const Definition* topicParent() const { - Definition* test = fParent; - while (test) { - if (MarkType::kTopic == test->fMarkType) { - return test; - } - test = test->fParent; - } - return nullptr; - } - - void trimEnd(); - - 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 - string fCode; // suitable for autogeneration of #Code blocks in bmh - 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<Definition> fTokens; - vector<Definition*> fChildren; - string fHash; // generated by fiddle - string fFileName; - mutable string fWrapper; // used by Example to wrap into proper function - 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; - Operator fOperator = Operator::kUnknown; - Type fType = Type::kNone; - char fMC = '#'; - bool fClone = false; - bool fCloned = false; - bool fDeprecated = false; - bool fOperatorConst = false; - bool fPrivate = false; - Details fDetails = Details::kNone; - bool fMemberStart = false; - bool fAnonymous = false; - mutable bool fVisited = false; -}; - -class SubtopicKeys { -public: - static constexpr const char* kClasses = "Classes"; - static constexpr const char* kConstants = "Constants"; - static constexpr const char* kConstructors = "Constructors"; - static constexpr const char* kDefines = "Defines"; - static constexpr const char* kMemberFunctions = "Member_Functions"; - static constexpr const char* kMembers = "Members"; - static constexpr const char* kOperators = "Operators"; - static constexpr const char* kOverview = "Overview"; - static constexpr const char* kRelatedFunctions = "Related_Functions"; - static constexpr const char* kStructs = "Structs"; - static constexpr const char* kTypedefs = "Typedefs"; - - static const char* kGeneratedSubtopics[]; -}; - struct NameMap { void copyToParent(NameMap* parent) const; @@ -1154,1568 +223,16 @@ struct NameMap { unordered_map<string, Definition*> fRefMap; // e.g., from #Substitute entry to #Topic entry }; -class RootDefinition : public Definition { -public: - enum class AllowParens { - kNo, - kYes, - }; - - struct SubtopicContents { - SubtopicContents() - : fShowClones(false) { - } - - vector<Definition*> fMembers; - bool fShowClones; - }; - - RootDefinition() { - } - - RootDefinition(MarkType markType, const char* start, int line, Definition* parent, char mc) - : Definition(markType, start, line, parent, mc) { - if (MarkType::kSubtopic != markType && MarkType::kTopic != markType) { - if (parent) { - fNames.fName = parent->fName; - fNames.fParent = &parent->asRoot()->fNames; - } - } - } - - RootDefinition(MarkType markType, const char* start, const char* end, int line, - Definition* parent, char mc) : Definition(markType, start, end, line, parent, mc) { - } - - ~RootDefinition() override { - for (auto& iter : fBranches) { - delete iter.second; - } - } - - RootDefinition* asRoot() override { return this; } - void clearVisited(); - bool dumpUnVisited(); - Definition* find(string ref, AllowParens ); - bool isRoot() const override { return true; } - - SubtopicContents& populator(const char* key) { - return fPopulators[key]; - } - - RootDefinition* rootParent() override { return fRootParent; } - const RootDefinition* rootParent() const override { return fRootParent; } - void setRootParent(RootDefinition* rootParent) { fRootParent = rootParent; } - - unordered_map<string, RootDefinition*> fBranches; - unordered_map<string, Definition> fLeaves; - unordered_map<string, SubtopicContents> fPopulators; - NameMap fNames; -private: - RootDefinition* fRootParent = nullptr; -}; - -struct IClassDefinition : public Definition { - unordered_map<string, Definition*> fConsts; - unordered_map<string, Definition*> fDefines; - unordered_map<string, Definition*> fEnums; - unordered_map<string, Definition*> fMembers; - unordered_map<string, Definition*> fMethods; - unordered_map<string, Definition*> fStructs; - unordered_map<string, Definition*> fTypedefs; -}; - -struct Reference { - Reference() - : fLocation(nullptr) - , fDefinition(nullptr) { - } - - const char* fLocation; // .. in original text file - const Definition* fDefinition; -}; - -struct TypeNames { - const char* fName; - MarkType fMarkType; +enum class Resolvable { + kNo, // neither resolved nor output + kYes, // resolved, output + kOut, // mostly resolved, output (FIXME: is this really different from kYes?) + kCode, // resolve methods as they are used, not as they are prototyped + kFormula, // kCode, plus make most spaces non-breaking + kLiteral, // output untouched + kClone, // resolved, output, with references to clones as well + kSimple, // resolve simple words (used to resolve method declarations) + kInclude, // like simple, plus reverse resolve SkXXX to XXX }; -class ParserCommon : public TextParser { -public: - enum class OneFile { - kNo, - kYes, - }; - - enum class OneLine { - kNo, - kYes, - }; - - enum class IndentKind { - kConstOut, - kEnumChild, - kEnumChild2, - kEnumHeader, - kEnumHeader2, - kMethodOut, - kStructMember, - }; - - struct IndentState { - IndentState(IndentKind kind, int indent) - : fKind(kind) - , fIndent(indent) { - } - - IndentKind fKind; - int fIndent; - }; - - ParserCommon() : TextParser() - , fParent(nullptr) - , fDebugOut(false) - , fValidate(false) - , fReturnOnWrite(false) - { - } - - ~ParserCommon() override { - } - - void addDefinition(Definition* def) { - fParent->fChildren.push_back(def); - fParent = def; - } - - void checkLineLength(size_t len, const char* str); - static string ConvertRef(const string str, bool first); - static void CopyToFile(string oldFile, string newFile); - static char* FindDateTime(char* buffer, int size); - static string HtmlFileName(string bmhFileName); - - void indentIn(IndentKind kind) { - fIndent += 4; - fIndentStack.emplace_back(kind, fIndent); - } - - void indentOut() { - SkASSERT(fIndent >= 4); - SkASSERT(fIndentStack.back().fIndent == fIndent); - fIndent -= 4; - fIndentStack.pop_back(); - } - - void indentToColumn(int column) { - SkASSERT(column >= fColumn); - SkASSERT(!fReturnOnWrite); - SkASSERT(column < 80); - FPRINTF("%*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() { - SkASSERT(!fReturnOnWrite); - fLinefeeds = 0; - fSpaces = 0; - fColumn = 0; - fPendingSpace = 0; - } - - bool parseFile(const char* file, const char* suffix, OneFile ); - bool parseStatus(const char* file, const char* suffix, StatusFilter filter); - virtual bool parseFromFile(const char* path) = 0; - bool parseSetup(const char* path); - - void popObject() { - fParent->fContentEnd = fParent->fTerminator = fChar; - fParent = fParent->fParent; - } - - static char* ReadToBuffer(string filename, int* size); - - virtual void reset() = 0; - - void resetCommon() { - fLine = fChar = fStart; - fLineCount = 0; - fLinesWritten = 1; - fParent = nullptr; - fIndent = 0; - fOut = nullptr; - fMaxLF = 2; - fPendingLF = 0; - fPendingSpace = 0; - fOutdentNext = false; - fWritingIncludes = false; - fDebugWriteCodeBlock = false; - nl(); - } - - void setAsParent(Definition* definition) { - if (fParent) { - fParent->fChildren.push_back(definition); - definition->fParent = fParent; - } - fParent = definition; - } - - void singleLF() { - fMaxLF = 1; - } - - void stringAppend(string& result, char ch) const; - void stringAppend(string& result, string str) const; - void stringAppend(string& result, const Definition* ) const; - - void writeBlock(int size, const char* data) { - SkAssertResult(writeBlockTrim(size, data)); - } - - bool writeBlockIndent(int size, const char* data, bool ignoreIndent); - - void writeBlockSeparator() { - this->writeString( - "# ------------------------------------------------------------------------------"); - this->lf(2); - } - - bool writeBlockTrim(int size, const char* data); - - void writeCommentHeader() { - this->lf(2); - this->writeString("/**"); - this->writeSpace(); - } - - void writeCommentTrailer(OneLine oneLine) { - if (OneLine::kNo == oneLine) { - this->lf(1); - } else { - this->writeSpace(); - } - this->writeString("*/"); - this->lfcr(); - } - - void writePending(); - - // write a pending space, so that two consecutive calls - // don't double write, and trailing spaces on lines aren't written - void writeSpace(int count = 1) { - SkASSERT(!fReturnOnWrite); - SkASSERT(!fPendingLF); - SkASSERT(!fLinefeeds); - SkASSERT(fColumn > 0); - SkASSERT(!fSpaces); - fPendingSpace = count; - } - - void writeString(const char* str); - - void writeString(string str) { - this->writeString(str.c_str()); - } - - static bool WrittenFileDiffers(string filename, string readname); - - unordered_map<string, sk_sp<SkData>> fRawData; - unordered_map<string, vector<char>> fLFOnly; - vector<IndentState> fIndentStack; - Definition* fParent; - FILE* fOut; - string fRawFilePathDir; - 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 - int fPendingSpace; // one or two spaces should preceed the next string or block - size_t fLinesWritten; // as opposed to fLineCount, number of lines read - char fLastChar; // last written - bool fDebugOut; // set true to write to std out - bool fValidate; // set true to check anchor defs and refs - bool fOutdentNext; // set at end of embedded struct to prevent premature outdent - bool fWroteSomething; // used to detect empty content; an alternative source is preferable - bool fReturnOnWrite; // used to detect non-empty content; allowing early return - bool fWritingIncludes; // set true when writing includes to check >100 columns - mutable bool fDebugWriteCodeBlock; - -private: - typedef TextParser INHERITED; -}; - -struct JsonStatus { - const Json::Value& fObject; - Json::Value::iterator fIter; - string fName; - StatusFilter fStatusFilter; -}; - -class JsonCommon : public ParserCommon { -public: - bool empty() { return fStack.empty(); } - bool parseFromFile(const char* path) override; - - void reset() override { - fStack.clear(); - INHERITED::resetCommon(); - } - - vector<JsonStatus> fStack; - Json::Value fRoot; -private: - typedef ParserCommon INHERITED; -}; - -class StatusIter : public JsonCommon { -public: - StatusIter(const char* statusFile, const char* suffix, StatusFilter); - ~StatusIter() override {} - string baseDir(); - bool next(string* file, StatusFilter* filter); -private: - const char* fSuffix; - StatusFilter fFilter; -}; - -class BmhParser : public ParserCommon { -public: - enum class MarkLookup { - kRequire, - kAllowUnknown, - }; - - enum class Resolvable { - kNo, // neither resolved nor output - kYes, // resolved, output - kOut, // mostly resolved, output (FIXME: is this really different from kYes?) - kCode, // resolve methods as they are used, not as they are prototyped - kFormula, // kCode, plus make most spaces non-breaking - kLiteral, // output untouched - kClone, // resolved, output, with references to clones as well - kSimple, // resolve simple words (used to resolve method declarations) - kInclude, // like simple, plus reverse resolve SkXXX to XXX - }; - - enum class ExampleOptions { - kText, - kPng, - kAll - }; - - enum class Exemplary { - kNo, - kYes, - kOptional, - }; - - enum class TableState { - kNone, - kColumnStart, - kColumnEnd, - }; - - enum class HasTag { - kNo, - kYes, - }; - - enum class TrimExtract { - kNo, - kYes, - }; - - BmhParser(bool skip) : ParserCommon() - , fMaps { - { &fClassMap, MarkType::kClass } - , { &fConstMap, MarkType::kConst } - , { &fDefineMap, MarkType::kDefine } - , { &fEnumMap, MarkType::kEnum } - , { &fClassMap, MarkType::kEnumClass } - , { &fMethodMap, MarkType::kMethod } - , { &fClassMap, MarkType::kStruct } - , { &fTypedefMap, MarkType::kTypedef } - } - , fSkip(skip) { - this->reset(); - } - - ~BmhParser() override {} - - bool addDefinition(const char* defStart, bool hasEnd, MarkType markType, - const vector<string>& typeNameBuilder, HasTag hasTag); - bool checkEndMarker(MarkType markType, string name) const; - bool checkExamples() const; - const char* checkForFullTerminal(const char* end, const Definition* ) const; - bool checkParamReturn(const Definition* definition) const; - bool dumpExamples(FILE* fiddleOut, Definition& def, bool* continuation) const; - bool dumpExamples(const char* fiddleJsonFileName) const; - bool checkExampleHashes() const; - bool childOf(MarkType markType) const; - string className(MarkType markType); - bool collectExternals(); - int endHashCount() const; - bool endTableColumn(const char* end, const char* terminator); - bool exampleToScript(Definition*, ExampleOptions, string* result ) const; - string extractText(const Definition* , TrimExtract ) const; - RootDefinition* findBmhObject(MarkType markType, string typeName); - bool findDefinitions(); - Definition* findExample(string name) const; - MarkType getMarkType(MarkLookup lookup) const; - bool hasEndToken() const; - static bool IsExemplary(const Definition* ); - string loweredTopic(string name, Definition* def); - 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(); - } - - void parseHashAnchor(Definition* ); - void parseHashFormula(Definition* ); - void parseHashLine(Definition* ); - bool popParentStack(Definition* ); - void reportDuplicates(const Definition& , string dup) const; - void resetExampleHashes(); - - void reset() override { - INHERITED::resetCommon(); - fRoot = nullptr; - fWorkingColumn = nullptr; - fRow = nullptr; - fTableState = TableState::kNone; - fMC = '#'; - fInChar = false; - fInCharCommentString = false; - fInComment = false; - fInEnum = false; - fInString = false; - fCheckMethods = false; - } - - void setUpGlobalSubstitutes(); - void setUpPartialSubstitute(string name); - void setUpSubstitute(string name, Definition* def); - void setUpSubstitutes(const Definition* parent, NameMap* ); - void setWrapper(Definition* def) const; - bool skipNoName(); - bool skipToDefinitionEnd(MarkType markType); - bool skipToString(); - void spellCheck(const char* match, SkCommandLineFlags::StringArray report) const; - void spellStatus(const char* match, SkCommandLineFlags::StringArray report) const; - vector<string> topicName(); - vector<string> typeName(MarkType markType, bool* expectEnd); - string typedefName() override; - string uniqueName(string base, MarkType markType); - string uniqueRootName(string base, MarkType markType); - void validate() const; - string word(string prefix, string delimiter); - -public: - struct MarkProps { - const char* fName; - MarkType fMarkType; - Resolvable fResolve; - Exemplary fExemplary; // worthy of an example - uint64_t fParentMask; - }; - - struct DefinitionMap { - unordered_map<string, RootDefinition>* fMap; - MarkType fMarkType; - }; - - vector<DefinitionMap> fMaps; - - static MarkProps kMarkProps[Last_MarkType + 1]; - forward_list<RootDefinition> fTopics; - forward_list<Definition> fMarkup; - forward_list<RootDefinition> fExternals; - vector<string> fInputFiles; - unordered_map<string, RootDefinition> fClassMap; - unordered_map<string, RootDefinition> fConstMap; - unordered_map<string, RootDefinition> fDefineMap; - unordered_map<string, RootDefinition> fEnumMap; - unordered_map<string, RootDefinition> fMethodMap; - unordered_map<string, RootDefinition> fTypedefMap; - unordered_map<string, Definition*> fTopicMap; - unordered_map<string, Definition*> fAliasMap; - unordered_map<string, Definition*> fPhraseMap; - NameMap fGlobalNames; - RootDefinition* fRoot; - Definition* fWorkingColumn; - Definition* fRow; - const char* fColStart; - TableState fTableState; - mutable char fMC; // markup character - bool fAnonymous; - bool fCloned; - bool fInChar; - bool fInCharCommentString; - bool fInEnum; - bool fInComment; - bool fInString; - bool fCheckMethods; - bool fSkip = false; - bool fWroteOut = false; -private: - typedef ParserCommon INHERITED; -}; - -class IncludeParser : public ParserCommon { -public: - enum class IsStruct { - kNo, - kYes, - }; - - enum class Elided { - kNo, - kYes, - }; - - struct CheckCode { - enum class State { - kNone, - kClassDeclaration, - kConstructor, - kForwardDeclaration, - kMethod, - }; - - void reset() { - fInDebugCode = nullptr; - fPrivateBrace = 0; - fBraceCount = 0; - fIndent = 0; - fDoubleReturn = 0; - fState = State::kNone; - fPrivateProtected = false; - fTypedefReturn = false; - fSkipAPI = false; - fSkipInline = false; - fSkipWarnUnused = false; - fWriteReturn = false; - } - - const char* fInDebugCode; - int fPrivateBrace; - int fBraceCount; - int fIndent; - int fDoubleReturn; - State fState; - bool fPrivateProtected; - bool fTypedefReturn; - bool fSkipAPI; - bool fSkipInline; - bool fSkipWarnUnused; - bool fWriteReturn; - }; - - IncludeParser() : ParserCommon() - , fMaps { - { &fIConstMap, MarkType::kConst } - , { &fIDefineMap, MarkType::kDefine } - , { &fIEnumMap, MarkType::kEnum } - , { &fIEnumMap, MarkType::kEnumClass } - , { &fIStructMap, MarkType::kStruct } - , { &fITemplateMap, MarkType::kTemplate } - , { &fITypedefMap, MarkType::kTypedef } - , { &fIUnionMap, MarkType::kUnion } - } - { - this->reset(); - } - - ~IncludeParser() override {} - - void addKeyword(KeyWord keyWord); - - void addPunctuation(Punctuation punctuation) { - fParent->fTokens.emplace_back(punctuation, fChar, fLineCount, fParent, '\0'); - } - - void addWord() { - fParent->fTokens.emplace_back(fIncludeWord, fChar, fLineCount, fParent, '\0'); - fIncludeWord = nullptr; - } - - bool advanceInclude(TextParser& i); - bool inAlignAs() const; - void checkForMissingParams(const vector<string>& methodParams, - const vector<string>& foundParams); - bool checkForWord(); - string className() const; - - string codeBlock(const Definition& def, bool inProgress) const { - return codeBlock(def.fMarkType, def.fName, inProgress); - } - - string codeBlock(MarkType markType, string name, bool inProgress) const { - if (MarkType::kClass == markType || MarkType::kStruct == markType) { - auto map = fIClassMap.find(name); - SkASSERT(fIClassMap.end() != map || inProgress); - return fIClassMap.end() != map ? map->second.fCode : ""; - } - if (MarkType::kConst == markType) { - auto map = fIConstMap.find(name); - SkASSERT(fIConstMap.end() != map); - return map->second->fCode; - } - if (MarkType::kDefine == markType) { - auto map = fIDefineMap.find(name); - SkASSERT(fIDefineMap.end() != map); - return map->second->fCode; - } - if (MarkType::kEnum == markType || MarkType::kEnumClass == markType) { - auto map = fIEnumMap.find(name); - SkASSERT(fIEnumMap.end() != map); - return map->second->fCode; - } - if (MarkType::kTypedef == markType) { - auto map = fITypedefMap.find(name); - SkASSERT(fITypedefMap.end() != map); - return map->second->fCode; - } - SkASSERT(0); - return ""; - } - - void codeBlockAppend(string& result, char ch) const; - void codeBlockSpaces(string& result, int indent) const; - - bool crossCheck(BmhParser& ); - IClassDefinition* defineClass(const Definition& includeDef, string className); - void dumpClassTokens(IClassDefinition& classDef); - void dumpComment(const Definition& ); - void dumpCommonTail(const Definition& ); - void dumpConst(const Definition& , string className); - void dumpDefine(const Definition& ); - void dumpEnum(const Definition& , string name); - bool dumpGlobals(string* globalFileName, long int* globalTell); - void dumpMethod(const Definition& , string className); - void dumpMember(const Definition& ); - bool dumpTokens(); - bool dumpTokens(string skClassName, string globalFileName, long int* globalTell); - void dumpTypedef(const Definition& , string className); - - string elidedCodeBlock(const Definition& ); - string filteredBlock(string inContents, string filterContents); - bool findComments(const Definition& includeDef, Definition* markupDef); - Definition* findIncludeObject(const Definition& includeDef, MarkType markType, - string typeName); - static KeyWord FindKey(const char* start, const char* end); - Definition* findMethod(const Definition& bmhDef); - Bracket grandParentBracket() const; - const Definition* include(string ) const; - bool isClone(const Definition& token); - bool isConstructor(const Definition& token, string className); - bool isInternalName(const Definition& token); - bool isMember(const Definition& token) const; - bool isOperator(const Definition& token); - Definition* parentBracket(Definition* parent) const; - bool parseChar(); - bool parseComment(string filename, const char* start, const char* end, int lineCount, - Definition* markupDef); - bool parseClass(Definition* def, IsStruct); - bool parseConst(Definition* child, Definition* markupDef); - bool parseDefine(Definition* child, Definition* markupDef); - bool parseEnum(Definition* child, Definition* markupDef); - - bool parseFromFile(const char* path) override { - this->reset(); - if (!INHERITED::parseSetup(path)) { - return false; - } - string name(path); - return this->parseInclude(name); - } - - bool parseInclude(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(Definition* child, Definition* markupDef); - bool parseTypedef(Definition* child, Definition* markupDef); - bool parseUsing(); - bool parseUnion(); - - void popBracket() { - if (Definition::Type::kKeyWord == fParent->fType - && KeyWord::kTypename == fParent->fKeyWord) { - this->popObject(); - } - 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, '\0'); - Definition* container = &fParent->fTokens.back(); - this->addDefinition(container); - } - - bool references(const SkString& file) const; - - static void RemoveFile(const char* docs, const char* includes); - static void RemoveOneFile(const char* docs, const char* includesFileOrPath); - - void reset() override { - INHERITED::resetCommon(); - fRootTopic = nullptr; - fConstExpr = nullptr; - fInBrace = nullptr; - fIncludeWord = nullptr; - fLastObject = nullptr; - fPriorEnum = nullptr; - fPriorObject = nullptr; - fPrev = '\0'; - fInChar = false; - fInCharCommentString = false; - fInComment = false; - fInDefine = false; - fInEnum = false; - fInFunction = false; - fInString = false; - fFailed = 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; - - template <typename T> - string uniqueName(const unordered_map<string, T>& m, 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; - void writeCodeBlock(); - string writeCodeBlock(const Definition&, MarkType ); - string writeCodeBlock(TextParser& i, MarkType , int indent); - - void writeDefinition(const Definition& def) { - if (def.length() > 1) { - this->writeBlock((int) (def.fContentEnd - def.fContentStart), def.fContentStart); - this->lf(1); - } - } - - void writeDefinition(const Definition& def, string name, int spaces) { - this->writeBlock((int) (def.fContentEnd - def.fContentStart), def.fContentStart); - this->writeSpace(spaces); - this->writeString(name); - this->lf(1); - } - - void writeEndTag() { - this->lf(1); - this->writeString("##"); - this->lf(1); - } - - void writeEndTag(const char* tagType) { - this->lf(1); - this->writeString(string("#") + tagType + " ##"); - this->lf(1); - } - - void writeEndTag(const char* tagType, const char* tagID, int spaces = 1) { - this->lf(1); - this->writeString(string("#") + tagType + " " + tagID); - this->writeSpace(spaces); - this->writeString("##"); - this->lf(1); - } - - void writeEndTag(const char* tagType, string tagID, int spaces = 1) { - this->writeEndTag(tagType, tagID.c_str(), spaces); - } - - void writeIncompleteTag(const char* tagType, string tagID, int spaces = 1) { - this->writeString(string("#") + tagType + " " + tagID); - this->writeSpace(spaces); - this->writeString("incomplete"); - this->writeSpace(); - this->writeString("##"); - this->lf(1); - } - - void writeIncompleteTag(const char* tagType) { - this->writeString(string("#") + tagType + " incomplete ##"); - this->lf(1); - } - - void writeTableHeader(const char* col1, size_t pad, const char* col2) { - this->lf(1); - this->writeString("#Table"); - this->lf(1); - this->writeString("#Legend"); - this->lf(1); - string legend = "# "; - legend += col1; - if (pad > strlen(col1)) { - legend += string(pad - strlen(col1), ' '); - } - legend += " # "; - legend += col2; - legend += " ##"; - this->writeString(legend); - this->lf(1); - this->writeString("#Legend ##"); - this->lf(1); - } - - void writeTableRow(size_t pad, string col1) { - this->lf(1); - string row = "# " + col1 + string(pad - col1.length(), ' ') + " # ##"; - this->writeString(row); - this->lf(1); - } - - void writeTableRow(size_t pad1, string col1, size_t pad2, string col2) { - this->lf(1); - string row = "# " + col1 + string(pad1 - col1.length(), ' ') + " # " + - col2 + string(pad2 - col2.length(), ' ') + " ##"; - this->writeString(row); - this->lf(1); - } - - void writeTableTrailer() { - this->lf(1); - this->writeString("#Table ##"); - this->lf(1); - } - - void writeTag(const char* tagType) { - this->lf(1); - this->writeString("#"); - this->writeString(tagType); - } - - void writeTagNoLF(const char* tagType, const char* tagID) { - this->writeString("#"); - this->writeString(tagType); - this->writeSpace(); - this->writeString(tagID); - } - - void writeTagNoLF(const char* tagType, string tagID) { - this->writeTagNoLF(tagType, tagID.c_str()); - } - - void writeTag(const char* tagType, const char* tagID) { - this->lf(1); - this->writeTagNoLF(tagType, tagID); - } - - void writeTag(const char* tagType, string tagID) { - this->writeTag(tagType, tagID.c_str()); - } - - void writeTagTable(string tagType, string body) { - this->writeTag(tagType.c_str()); - this->writeSpace(1); - this->writeString("#"); - this->writeSpace(1); - this->writeString(body); - this->writeSpace(1); - this->writeString("##"); - } - -protected: - static void ValidateKeyWords(); - - struct DefinitionMap { - unordered_map<string, Definition*>* fInclude; - MarkType fMarkType; - }; - - vector<DefinitionMap> fMaps; - unordered_map<string, Definition> fIncludeMap; - list<Definition> fGlobals; - unordered_map<string, IClassDefinition> fIClassMap; - unordered_map<string, Definition*> fIConstMap; - unordered_map<string, Definition*> fIDefineMap; - unordered_map<string, Definition*> fIEnumMap; - unordered_map<string, Definition*> fIFunctionMap; - unordered_map<string, Definition*> fIStructMap; - unordered_map<string, Definition*> fITemplateMap; - unordered_map<string, Definition*> fITypedefMap; - unordered_map<string, Definition*> fIUnionMap; - CheckCode fCheck; - Definition* fRootTopic; - Definition* fConstExpr; - Definition* fInBrace; - Definition* fLastObject; - Definition* fPriorEnum; - Definition* fPriorObject; - int fPriorIndex; - const char* fIncludeWord; - Elided fElided; - char fPrev; - bool fInChar; - bool fInCharCommentString; - bool fInComment; - bool fInDefine; - bool fInEnum; - bool fInFunction; - bool fInString; - bool fFailed; - - typedef ParserCommon INHERITED; -}; - -class IncludeWriter : public IncludeParser { -public: - enum class Word { - kStart, - kCap, - kFirst, - kUnderline, - kMixed, - }; - - enum class Phrase { - kNo, - kYes, - }; - - enum class PunctuationState { - kStart, - kDelimiter, - kParen, // treated as a delimiter unless following a space, and followed by word - kPeriod, - kSpace, - }; - - enum class RefType { - kUndefined, - kNormal, - kExternal, - }; - - enum class SkipFirstLine { - kNo, - kYes, - }; - - enum class Wrote { - kNone, - kLF, - kChars, - }; - - enum class MemberPass { - kCount, - kOut, - }; - - enum class ItemState { - kNone, - kName, - kValue, - kComment, - }; - - struct IterState { - IterState (list<Definition>::iterator tIter, list<Definition>::iterator tIterEnd) - : fDefIter(tIter) - , fDefEnd(tIterEnd) { - } - list<Definition>::iterator fDefIter; - list<Definition>::iterator fDefEnd; - }; - - struct ParentPair { - const Definition* fParent; - const ParentPair* fPrev; - }; - - struct Preprocessor { - Preprocessor() { - reset(); - } - - void reset() { - fDefinition = nullptr; - fStart = nullptr; - fEnd = nullptr; - fWord = false; - } - - const Definition* fDefinition; - const char* fStart; - const char* fEnd; - bool fWord; - }; - - struct Item { - void reset() { - fName = ""; - fValue = ""; - } - - string fName; - string fValue; - }; - - struct LastItem { - const char* fStart; - const char* fEnd; - }; - - struct ItemLength { - int fCurName; - int fCurValue; - int fLongestName; - int fLongestValue; - }; - - IncludeWriter() : IncludeParser() { - this->reset(); - } - - ~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; - } - - bool checkChildCommentLength(const Definition* parent, MarkType childType) const; - void checkEnumLengths(const Definition& child, string enumName, ItemLength* length) const; - void constOut(const Definition* memberStart, const Definition* bmhConst); - void constSizeMembers(const RootDefinition* root); - bool defineOut(const Definition& ); - bool descriptionOut(const Definition* def, SkipFirstLine , Phrase ); - void enumHeaderOut(RootDefinition* root, const Definition& child); - string enumMemberComment(const Definition* currentEnumItem, const Definition& child) const; - const Definition* enumMemberForComment(const Definition* currentEnumItem) const; - ItemState enumMemberName(const Definition& child, - const Definition* token, Item* , LastItem* , const Definition** enumItem); - void enumMemberOut(const Definition* currentEnumItem, const Definition& child, - const Item& , Preprocessor& ); - void enumMembersOut(Definition& child); - bool enumPreprocessor(Definition* token, MemberPass pass, - vector<IterState>& iterStack, IterState** iterState, Preprocessor* ); - void enumSizeItems(const Definition& child); - bool findEnumSubtopic(string undername, const Definition** ) const; - void firstBlock(int size, const char* data); - bool firstBlockTrim(int size, const char* data); - Definition* findMemberCommentBlock(const vector<Definition*>& bmhChildren, string name) const; - Definition* findMethod(string name, RootDefinition* ) const; - - void indentDeferred(IndentKind kind) { - if (fIndentNext) { - this->indentIn(kind); - fIndentNext = false; - } - } - - int lookupMethod(const PunctuationState punctuation, const Word word, - const int start, const int run, int lastWrite, - const char* data, bool hasIndirection); - int lookupReference(const PunctuationState punctuation, const Word word, - const int start, const int run, int lastWrite, const char last, - const char* data); - const Definition* matchMemberName(string matchName, const Definition& child) const; - void methodOut(Definition* method, const Definition& child); - bool populate(Definition* def, ParentPair* parentPair, RootDefinition* root); - bool populate(BmhParser& bmhParser); - - void reset() override { - INHERITED::resetCommon(); - fBmhParser = nullptr; - fDeferComment = nullptr; - fBmhMethod = nullptr; - fEnumDef = nullptr; - fMethodDef = nullptr; - fBmhConst = nullptr; - fConstDef = nullptr; - fLastDescription = nullptr; - fStartSetter = nullptr; - fBmhStructDef = nullptr; - fContinuation = nullptr; - fInStruct = false; - fWroteMethod = false; - fIndentNext = false; - fPendingMethod = false; - fFirstWrite = false; - fStructEnded = false; - fWritingIncludes = true; - } - - string resolveAlias(const Definition* ); - string resolveMethod(const char* start, const char* end, bool first); - string resolveRef(const char* start, const char* end, bool first, RefType* refType); - Wrote rewriteBlock(int size, const char* data, Phrase phrase); - void setStart(const char* start, const Definition * ); - void setStartBack(const char* start, const Definition * ); - Definition* structMemberOut(const Definition* memberStart, const Definition& child); - void structOut(const Definition* root, const Definition& child, - const char* commentStart, const char* commentEnd); - void structSizeMembers(const Definition& child); - bool writeHeader(std::pair<const string, Definition>& ); -private: - vector<const Definition* > fICSStack; - BmhParser* fBmhParser; - Definition* fDeferComment; - const Definition* fBmhMethod; - const Definition* fEnumDef; - const Definition* fMethodDef; - const Definition* fBmhConst; - const Definition* fConstDef; - const Definition* fLastDescription; - const Definition* fStartSetter; - Definition* fBmhStructDef; - const char* fContinuation; // used to construct paren-qualified method name - int fAnonymousEnumCount; - int fEnumItemValueTab; - int fEnumItemCommentTab; - int fStructMemberTab; - int fStructValueTab; - int fStructCommentTab; - int fStructMemberLength; - int fConstValueTab; - int fConstCommentTab; - int fConstLength; - bool fInStruct; // set if struct is inside class - bool fWroteMethod; - bool fIndentNext; - bool fPendingMethod; - bool fFirstWrite; // set to write file information just after includes - bool fStructEnded; // allow resetting indent after struct is complete - - typedef IncludeParser INHERITED; -}; - -class FiddleBase : public JsonCommon { -protected: - FiddleBase(BmhParser* bmh) - : fBmhParser(bmh) - , fContinuation(false) - , fTextOut(false) - , fPngOut(false) - { - this->reset(); - } - - void reset() override { - INHERITED::reset(); - } - - Definition* findExample(string name) const { return fBmhParser->findExample(name); } - bool parseFiddles(); - virtual bool pngOut(Definition* example) = 0; - virtual bool textOut(Definition* example, const char* stdOutStart, - const char* stdOutEnd) = 0; - - BmhParser* fBmhParser; // must be writable; writes example hash - string fFullName; - bool fContinuation; - bool fTextOut; - bool fPngOut; -private: - typedef JsonCommon INHERITED; -}; - -class FiddleParser : public FiddleBase { -public: - FiddleParser(BmhParser* bmh) : FiddleBase(bmh) { - fTextOut = true; - } - - bool parseFromFile(const char* path) override { - if (!INHERITED::parseFromFile(path)) { - return false; - } - fBmhParser->resetExampleHashes(); - if (!INHERITED::parseFiddles()) { - return false; - } - return fBmhParser->checkExampleHashes(); - } - -private: - bool pngOut(Definition* example) override { - return true; - } - - bool textOut(Definition* example, const char* stdOutStart, - const char* stdOutEnd) override; - - typedef FiddleBase INHERITED; -}; - -class Catalog : public FiddleBase { -public: - Catalog(BmhParser* bmh) : FiddleBase(bmh) {} - - bool appendFile(string path); - bool closeCatalog(const char* outDir); - bool openCatalog(const char* inDir); - bool openStatus(const char* inDir); - - bool parseFromFile(const char* path) override ; -private: - bool pngOut(Definition* example) override; - bool textOut(Definition* example, const char* stdOutStart, - const char* stdOutEnd) override; - - string fDocsDir; - - typedef FiddleBase INHERITED; -}; - -class HackParser : public ParserCommon { -public: - HackParser(const BmhParser& bmhParser) - : ParserCommon() - , fBmhParser(bmhParser) { - this->reset(); - } - - bool parseFromFile(const char* path) override { - if (!INHERITED::parseSetup(path)) { - return false; - } - return hackFiles(); - } - - void reset() override { - INHERITED::resetCommon(); - } - - void replaceWithPop(const Definition* ); - -private: - const BmhParser& fBmhParser; - bool hackFiles(); - - typedef ParserCommon INHERITED; -}; - -class MdOut : public ParserCommon { -public: - struct SubtopicDescriptions { - string fSingular; - string fPlural; - string fOneLiner; - string fDetails; - }; - - MdOut(BmhParser& bmh, IncludeParser& inc) : ParserCommon() - , fBmhParser(bmh) - , fIncludeParser(inc) { - this->reset(); - this->addPopulators(); - fBmhParser.setUpGlobalSubstitutes(); - } - - bool buildReferences(const char* docDir, const char* mdOutDirOrFile); - bool buildStatus(const char* docDir, const char* mdOutDir); - void checkAnchors(); - -private: - enum class TableState { - kNone, - kRow, - kColumn, - }; - - struct AnchorDef { - string fDef; - MarkType fMarkType; - }; - - void addCodeBlock(const Definition* def, string& str) const; - void addPopulators(); - string addIncludeReferences(const char* refStart, const char* refEnd); - string addReferences(const char* start, const char* end, BmhParser::Resolvable ); - string anchorDef(string def, string name); - string anchorLocalRef(string ref, string name); - string anchorRef(string def, string name); - bool buildRefFromFile(const char* fileName, const char* outDir); - bool checkParamReturnBody(const Definition* def); - Definition* checkParentsForMatch(Definition* test, string ref) const; - void childrenOut(Definition* def, const char* contentStart); - Definition* csParent(); - bool findLink(string ref, string* link); - Definition* findParamType(); - string getMemberTypeName(const Definition* def, string* memberType); - static bool HasDetails(const Definition* def); - bool hasWordSpace(string ) const; - void htmlOut(string ); - Definition* isDefined(const TextParser& , string ref, BmhParser::Resolvable ); - Definition* isDefinedByParent(RootDefinition* root, string ref); - string linkName(const Definition* ) const; - string linkRef(string leadingSpaces, Definition*, string ref, BmhParser::Resolvable ); - void markTypeOut(Definition* , const Definition** prior); - void mdHeaderOut(int depth) { mdHeaderOutLF(depth, 2); } - void mdHeaderOutLF(int depth, int lf); - void parameterHeaderOut(TextParser& paramParser, const Definition** prior, Definition* def); - void parameterTrailerOut(); - bool parseFromFile(const char* path) override { return true; } - bool phraseContinues(string phrase, string* priorWord, string* priorLink) const; - void populateOne(Definition* def, - unordered_map<string, RootDefinition::SubtopicContents>& populator); - void populateTables(const Definition* def, RootDefinition* ); - - SubtopicDescriptions& populator(string key) { - auto entry = fPopulators.find(key); - // FIXME: this should have been detected earlier - SkASSERT(fPopulators.end() != entry); - return entry->second; - } - - void reset() override { - INHERITED::resetCommon(); - fEnumClass = nullptr; - fMethod = nullptr; - fRoot = nullptr; - fSubtopic = nullptr; - fLastParam = nullptr; - fTableState = TableState::kNone; - fAddRefFailed = false; - fHasFiddle = false; - fInDescription = false; - fInList = false; - fResolveAndIndent = false; - fLiteralAndIndent = false; - fLastDef = nullptr; - fParamEnd = nullptr; - fInProgress = false; - } - - BmhParser::Resolvable resolvable(const Definition* definition) const { - MarkType markType = definition->fMarkType; - if (MarkType::kCode == markType) { - for (auto child : definition->fChildren) { - if (MarkType::kLiteral == child->fMarkType) { - return BmhParser::Resolvable::kLiteral; - } - } - } - if ((MarkType::kExample == markType - || MarkType::kFunction == markType) && fHasFiddle) { - return BmhParser::Resolvable::kNo; - } - return BmhParser::kMarkProps[(int) markType].fResolve; - } - - void resolveOut(const char* start, const char* end, BmhParser::Resolvable ); - void returnHeaderOut(const Definition** prior, Definition* def); - void rowOut(string col1, const Definition* col2); - void rowOut(const char * name, string description, bool literalName); - - void subtopicOut(string name); - void subtopicsOut(Definition* def); - void subtopicOut(string key, const vector<Definition*>& data, const Definition* csParent, - const Definition* topicParent, bool showClones); - bool subtopicRowOut(string keyName, const Definition* entry); - void summaryOut(const Definition* def, MarkType , string name); - string tableDataCodeDef(const Definition* def); - string tableDataCodeDef(string def, string name); - string tableDataCodeLocalRef(string name); - string tableDataCodeLocalRef(string ref, string name); - string tableDataCodeRef(const Definition* ref); - string tableDataCodeRef(string ref, string name); - void writeSubtopicTableHeader(string key); - - vector<const Definition*> fClassStack; - unordered_map<string, vector<AnchorDef> > fAllAnchorDefs; - unordered_map<string, vector<string> > fAllAnchorRefs; - NameMap* fNames; - BmhParser& fBmhParser; - IncludeParser& fIncludeParser; - const Definition* fEnumClass; - const Definition* fLastDef; - Definition* fMethod; - RootDefinition* fRoot; // used in generating populated tables; always struct or class - RootDefinition* fSubtopic; // used in resolving symbols - const Definition* fLastParam; - TableState fTableState; - unordered_map<string, SubtopicDescriptions> fPopulators; - unordered_map<string, string> fPhraseParams; - const char* fParamEnd; - bool fAddRefFailed; - 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; - bool fLiteralAndIndent; - bool fResolveAndIndent; - bool fOddRow; - bool fHasDetails; - bool fInProgress; - typedef ParserCommon INHERITED; -}; - - -// some methods cannot be trivially parsed; look for class-name / ~ / operator -class MethodParser : public TextParser { -public: - MethodParser(string className, string fileName, - const char* start, const char* end, int lineCount) - : TextParser(fileName, start, end, lineCount) - , fClassName(className) { - size_t doubleColons = className.find_last_of("::"); - if (string::npos != doubleColons) { - fLocalName = className.substr(doubleColons + 1); - SkASSERT(fLocalName.length() > 0); - } - } - - ~MethodParser() override {} - - string localName() const { - return fLocalName; - } - - void setLocalName(string name) { - if (name == fClassName) { - fLocalName = ""; - } else { - fLocalName = name; - } - } - - // returns true if close brace was skipped - int skipToMethodStart() { - if (!fClassName.length()) { - return this->skipToAlphaNum(); - } - int braceCount = 0; - while (!this->eof() && !isalnum(this->peek()) && '~' != this->peek()) { - braceCount += '{' == this->peek(); - braceCount -= '}' == this->peek(); - this->next(); - } - return braceCount; - } - - void skipToMethodEnd(BmhParser::Resolvable resolvable) { - if (this->eof()) { - return; - } - string name = fLocalName.length() ? fLocalName : fClassName; - if ('~' == this->peek()) { - this->next(); - if (!this->startsWith(name.c_str())) { - --fChar; - return; - } - } - if (BmhParser::Resolvable::kSimple != resolvable - && BmhParser::Resolvable::kInclude != resolvable - && (this->startsWith(name.c_str()) || this->startsWith("operator"))) { - const char* ptr = this->anyOf("\n ("); - if (ptr && '(' == *ptr && strncmp(ptr, "(...", 4)) { - this->skipToEndBracket(')'); - SkAssertResult(')' == this->next()); - this->skipExact("_const") || (BmhParser::Resolvable::kCode == resolvable - && this->skipExact(" const")); - return; - } - } - if (this->startsWith("Sk") && this->wordEndsWith(".h")) { // allow include refs - this->skipToNonName(); - } else { - this->skipFullName(); - if (this->endsWith("operator")) { - const char* ptr = this->anyOf("\n ("); - if (ptr && '(' == *ptr) { - this->skipToEndBracket(')'); - SkAssertResult(')' == this->next()); - this->skipExact("_const"); - } - } - } - } - - 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; - string fLocalName; - typedef TextParser INHERITED; -}; - -bool SelfCheck(const BmhParser& ); - #endif - diff --git a/tools/bookmaker/cataloger.cpp b/tools/bookmaker/cataloger.cpp index fe0084d5bf..cad7505f4e 100644 --- a/tools/bookmaker/cataloger.cpp +++ b/tools/bookmaker/cataloger.cpp @@ -5,7 +5,8 @@ * found in the LICENSE file. */ -#include "bookmaker.h" +#include "bmhParser.h" +#include "fiddleParser.h" #include "SkOSFile.h" #include "SkOSPath.h" diff --git a/tools/bookmaker/definition.cpp b/tools/bookmaker/definition.cpp index fb96ed19a4..e6707f6c55 100644 --- a/tools/bookmaker/definition.cpp +++ b/tools/bookmaker/definition.cpp @@ -5,9 +5,11 @@ * found in the LICENSE file. */ -#include "bookmaker.h" #include "SkOSPath.h" +#include "definition.h" +#include "textParser.h" + #ifdef CONST #undef CONST #endif diff --git a/tools/bookmaker/definition.h b/tools/bookmaker/definition.h new file mode 100644 index 0000000000..632135b77b --- /dev/null +++ b/tools/bookmaker/definition.h @@ -0,0 +1,326 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef definition_DEFINED +#define definition_DEFINED + +#include "textParser.h" + +class RootDefinition; +class TextParser; + +class Definition : public NonAssignable { +public: + enum Type { + kNone, + kWord, + kMark, + kKeyWord, + kBracket, + kPunctuation, + kFileType, + }; + + enum class MethodType { + kNone, + kConstructor, + kDestructor, + kOperator, + }; + + enum class Operator { + kUnknown, + kAdd, + kAddTo, + kArray, + kCast, + kCopy, + kDelete, + kDereference, + kEqual, + kMinus, + kMove, + kMultiply, + kMultiplyBy, + kNew, + kNotEqual, + kSubtract, + kSubtractFrom, + }; + + enum class Format { + kIncludeReturn, + kOmitReturn, + }; + + enum class Details { + kNone, + kSoonToBe_Deprecated, + kTestingOnly_Experiment, + kDoNotUse_Experiment, + kNotReady_Experiment, + }; + + enum class DetailsType { + kPhrase, + kSentence, + }; + + Definition() {} + + Definition(const char* start, const char* end, int line, Definition* parent, char mc) + : fStart(start) + , fContentStart(start) + , fContentEnd(end) + , fParent(parent) + , fLineCount(line) + , fType(Type::kWord) + , fMC(mc) { + if (parent) { + SkASSERT(parent->fFileName.length() > 0); + fFileName = parent->fFileName; + } + this->setParentIndex(); + } + + Definition(MarkType markType, const char* start, int line, Definition* parent, char mc) + : Definition(markType, start, nullptr, line, parent, mc) { + } + + Definition(MarkType markType, const char* start, const char* end, int line, Definition* parent, char mc) + : Definition(start, end, line, parent, mc) { + fMarkType = markType; + fType = Type::kMark; + } + + Definition(Bracket bracket, const char* start, int lineCount, Definition* parent, char mc) + : Definition(start, nullptr, lineCount, parent, mc) { + fBracket = bracket; + fType = Type::kBracket; + } + + Definition(KeyWord keyWord, const char* start, const char* end, int lineCount, + Definition* parent, char mc) + : Definition(start, end, lineCount, parent, mc) { + fKeyWord = keyWord; + fType = Type::kKeyWord; + } + + Definition(Punctuation punctuation, const char* start, int lineCount, Definition* parent, char mc) + : Definition(start, nullptr, lineCount, parent, mc) { + fPunctuation = punctuation; + fType = Type::kPunctuation; + } + + virtual ~Definition() {} + + virtual RootDefinition* asRoot() { SkASSERT(0); return nullptr; } + bool boilerplateIfDef(); + + bool boilerplateEndIf() { + return true; + } + + bool checkMethod() const; + bool crossCheck2(const Definition& includeToken) const; + bool crossCheck(const Definition& includeToken) const; + bool crossCheckInside(const char* start, const char* end, const Definition& includeToken) const; + + Definition* csParent() { + Definition* test = fParent; + while (test) { + if (MarkType::kStruct == test->fMarkType || MarkType::kClass == test->fMarkType) { + return test; + } + test = test->fParent; + } + return nullptr; + } + + string fiddleName() const; + string fileName() const; + const Definition* findClone(string match) const; + string formatFunction(Format format) const; + const Definition* hasChild(MarkType markType) const; + bool hasMatch(string name) const; + Definition* hasParam(string ref); + string incompleteMessage(DetailsType ) const; + bool isClone() const { return fClone; } + + const Definition* iRootParent() const { + const Definition* test = fParent; + while (test) { + if (KeyWord::kClass == test->fKeyWord || KeyWord::kStruct == test->fKeyWord) { + return test; + } + test = test->fParent; + } + return nullptr; + } + + virtual bool isRoot() const { return false; } + bool isStructOrClass() const; + + int length() const { + return (int) (fContentEnd - fContentStart); + } + + const char* methodEnd() const; + bool methodHasReturn(string name, TextParser* methodParser) const; + string methodName() const; + bool nextMethodParam(TextParser* methodParser, const char** nextEndPtr, + string* paramName) const; + static string NormalizedName(string name); + bool paramsMatch(string fullRef, string name) const; + bool parseOperator(size_t doubleColons, string& result); + + string printableName() const { + string result(fName); + std::replace(result.begin(), result.end(), '_', ' '); + return result; + } + + template <typename T> T reportError(const char* errorStr) const { + TextParser tp(this); + tp.reportError(errorStr); + return T(); + } + + virtual RootDefinition* rootParent() { SkASSERT(0); return nullptr; } + virtual const RootDefinition* rootParent() const { SkASSERT(0); return nullptr; } + void setCanonicalFiddle(); + + void setParentIndex() { + fParentIndex = fParent ? (int) fParent->fTokens.size() : -1; + } + + string simpleName() { + size_t doubleColon = fName.rfind("::"); + return string::npos == doubleColon ? fName : fName.substr(doubleColon + 2); + } + + const Definition* subtopicParent() const { + Definition* test = fParent; + while (test) { + if (MarkType::kTopic == test->fMarkType || MarkType::kSubtopic == test->fMarkType) { + return test; + } + test = test->fParent; + } + return nullptr; + } + + const Definition* topicParent() const { + Definition* test = fParent; + while (test) { + if (MarkType::kTopic == test->fMarkType) { + return test; + } + test = test->fParent; + } + return nullptr; + } + + void trimEnd(); + + 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 + string fCode; // suitable for autogeneration of #Code blocks in bmh + 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<Definition> fTokens; + vector<Definition*> fChildren; + string fHash; // generated by fiddle + string fFileName; + mutable string fWrapper; // used by Example to wrap into proper function + 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; + Operator fOperator = Operator::kUnknown; + Type fType = Type::kNone; + char fMC = '#'; + bool fClone = false; + bool fCloned = false; + bool fDeprecated = false; + bool fOperatorConst = false; + bool fPrivate = false; + Details fDetails = Details::kNone; + bool fMemberStart = false; + bool fAnonymous = false; + mutable bool fVisited = false; +}; + +class RootDefinition : public Definition { +public: + enum class AllowParens { + kNo, + kYes, + }; + + struct SubtopicContents { + SubtopicContents() + : fShowClones(false) { + } + + vector<Definition*> fMembers; + bool fShowClones; + }; + + RootDefinition() { + } + + RootDefinition(MarkType markType, const char* start, int line, Definition* parent, char mc) + : Definition(markType, start, line, parent, mc) { + if (MarkType::kSubtopic != markType && MarkType::kTopic != markType) { + if (parent) { + fNames.fName = parent->fName; + fNames.fParent = &parent->asRoot()->fNames; + } + } + } + + RootDefinition(MarkType markType, const char* start, const char* end, int line, + Definition* parent, char mc) : Definition(markType, start, end, line, parent, mc) { + } + + ~RootDefinition() override { + for (auto& iter : fBranches) { + delete iter.second; + } + } + + RootDefinition* asRoot() override { return this; } + void clearVisited(); + bool dumpUnVisited(); + Definition* find(string ref, AllowParens ); + bool isRoot() const override { return true; } + + SubtopicContents& populator(const char* key) { + return fPopulators[key]; + } + + RootDefinition* rootParent() override { return fRootParent; } + const RootDefinition* rootParent() const override { return fRootParent; } + void setRootParent(RootDefinition* rootParent) { fRootParent = rootParent; } + + unordered_map<string, RootDefinition*> fBranches; + unordered_map<string, Definition> fLeaves; + unordered_map<string, SubtopicContents> fPopulators; + NameMap fNames; +private: + RootDefinition* fRootParent = nullptr; +}; + +#endif diff --git a/tools/bookmaker/fiddleParser.cpp b/tools/bookmaker/fiddleParser.cpp index ec36e4b564..79c4a70252 100644 --- a/tools/bookmaker/fiddleParser.cpp +++ b/tools/bookmaker/fiddleParser.cpp @@ -5,7 +5,8 @@ * found in the LICENSE file. */ -#include "bookmaker.h" +#include "bmhParser.h" +#include "fiddleParser.h" // could make this more elaborate and look up the example definition in the bmh file; // see if a simpler hint provided is sufficient @@ -14,6 +15,10 @@ static bool report_error(const char* blockName, const char* errorMessage) { return false; } +Definition* FiddleBase::findExample(string name) const { + return fBmhParser->findExample(name); +} + bool FiddleBase::parseFiddles() { if (fStack.empty()) { return false; @@ -86,6 +91,17 @@ bool FiddleBase::parseFiddles() { return true; } +bool FiddleParser::parseFromFile(const char* path) { + if (!INHERITED::parseFromFile(path)) { + return false; + } + fBmhParser->resetExampleHashes(); + if (!INHERITED::parseFiddles()) { + return false; + } + return fBmhParser->checkExampleHashes(); +} + bool FiddleParser::textOut(Definition* example, const char* stdOutStart, const char* stdOutEnd) { bool foundStdOut = false; diff --git a/tools/bookmaker/fiddleParser.h b/tools/bookmaker/fiddleParser.h new file mode 100644 index 0000000000..e6fa9a405d --- /dev/null +++ b/tools/bookmaker/fiddleParser.h @@ -0,0 +1,84 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef fiddleParser_DEFINED +#define fiddleParser_DEFINED + +#include "parserCommon.h" + +class BmhParser; + +class FiddleBase : public JsonCommon { +protected: + FiddleBase(BmhParser* bmh) + : fBmhParser(bmh) + , fContinuation(false) + , fTextOut(false) + , fPngOut(false) + { + this->reset(); + } + + void reset() override { + INHERITED::reset(); + } + + Definition* findExample(string name) const; + bool parseFiddles(); + virtual bool pngOut(Definition* example) = 0; + virtual bool textOut(Definition* example, const char* stdOutStart, + const char* stdOutEnd) = 0; + + BmhParser* fBmhParser; // must be writable; writes example hash + string fFullName; + bool fContinuation; + bool fTextOut; + bool fPngOut; +private: + typedef JsonCommon INHERITED; +}; + +class FiddleParser : public FiddleBase { +public: + FiddleParser(BmhParser* bmh) : FiddleBase(bmh) { + fTextOut = true; + } + + bool parseFromFile(const char* path) override; + +private: + bool pngOut(Definition* example) override { + return true; + } + + bool textOut(Definition* example, const char* stdOutStart, + const char* stdOutEnd) override; + + typedef FiddleBase INHERITED; +}; + +class Catalog : public FiddleBase { +public: + Catalog(BmhParser* bmh) : FiddleBase(bmh) {} + + bool appendFile(string path); + bool closeCatalog(const char* outDir); + bool openCatalog(const char* inDir); + bool openStatus(const char* inDir); + + bool parseFromFile(const char* path) override ; +private: + bool pngOut(Definition* example) override; + bool textOut(Definition* example, const char* stdOutStart, + const char* stdOutEnd) override; + + string fDocsDir; + + typedef FiddleBase INHERITED; +}; + +#endif diff --git a/tools/bookmaker/hackParser.cpp b/tools/bookmaker/hackParser.cpp new file mode 100644 index 0000000000..03823a3f71 --- /dev/null +++ b/tools/bookmaker/hackParser.cpp @@ -0,0 +1,112 @@ +/* + * 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 "bmhParser.h" + + // replace #Method description, #Param, #Return with #Populate + // if description, params, return are free of phrase refs +bool HackParser::hackFiles() { + string filename(fFileName); + size_t len = filename.length() - 1; + while (len > 0 && (isalnum(filename[len]) || '_' == filename[len] || '.' == filename[len])) { + --len; + } + filename = filename.substr(len + 1); + if (filename.substr(0, 2) != "Sk") { + return true; + } + size_t under = filename.find('_'); + SkASSERT(under); + string className = filename.substr(0, under); + fOut = fopen(filename.c_str(), "wb"); + if (!fOut) { + SkDebugf("could not open output file %s\n", filename.c_str()); + return false; + } + auto mapEntry = fBmhParser.fClassMap.find(className); + if (fBmhParser.fClassMap.end() == mapEntry) { + remove(filename.c_str()); + return true; + } + const Definition* classMarkup = &mapEntry->second; + const Definition* root = classMarkup->fParent; + SkASSERT(root); + SkASSERT(root->fTerminator); + SkASSERT('\n' == root->fTerminator[0]); + SkASSERT(!root->fParent); + fStart = root->fStart; + fChar = fStart; + fEnd = root->fTerminator; + this->replaceWithPop(root); + FPRINTF("%.*s", (int) (fEnd - fChar), fChar); + if ('\n' != fEnd[-1]) { + FPRINTF("\n"); + } + fclose(fOut); + if (ParserCommon::WrittenFileDiffers(filename, root->fFileName)) { + SkDebugf("wrote %s\n", filename.c_str()); + } else { + remove(filename.c_str()); + } + return true; +} + +// returns true if topic has method +void HackParser::replaceWithPop(const Definition* root) { + for (auto child : root->fChildren) { + if (MarkType::kClass == child->fMarkType || MarkType::kStruct == child->fMarkType + || MarkType::kSubtopic == child->fMarkType) { + this->replaceWithPop(child); + } + if (MarkType::kMethod != child->fMarkType) { + continue; + } + auto& grans = child->fChildren; + if (grans.end() != std::find_if(grans.begin(), grans.end(), + [](const Definition* def) { + return MarkType::kPopulate == def->fMarkType + || MarkType::kPhraseRef == def->fMarkType + || MarkType::kFormula == def->fMarkType + || MarkType::kAnchor == def->fMarkType + || MarkType::kList == def->fMarkType + || MarkType::kTable == def->fMarkType + || MarkType::kDeprecated == def->fMarkType + || MarkType::kExperimental == def->fMarkType + || MarkType::kPrivate == def->fMarkType; + } )) { + continue; + } + // write #Populate in place of description, #Param(s), #Return (if present) + const char* keep = child->fContentStart; + const char* next = nullptr; + for (auto gran : grans) { + if (MarkType::kIn == gran->fMarkType || MarkType::kLine == gran->fMarkType) { + keep = gran->fTerminator; + continue; + } + if (MarkType::kExample == gran->fMarkType + || MarkType::kNoExample == gran->fMarkType) { + next = gran->fStart; + break; + } + if (MarkType::kParam == gran->fMarkType + || MarkType::kReturn == gran->fMarkType + || MarkType::kToDo == gran->fMarkType + || MarkType::kComment == gran->fMarkType) { + continue; + } + SkDebugf(""); // convenient place to set a breakpoint + } + SkASSERT(next); + FPRINTF("%.*s", (int) (keep - fChar), fChar); + if ('\n' != keep[-1]) { + FPRINTF("\n"); + } + FPRINTF("#Populate\n\n"); + fChar = next; + } +} diff --git a/tools/bookmaker/includeParser.cpp b/tools/bookmaker/includeParser.cpp index 2fdd7ae5da..5662bcfdbc 100644 --- a/tools/bookmaker/includeParser.cpp +++ b/tools/bookmaker/includeParser.cpp @@ -5,10 +5,12 @@ * found in the LICENSE file. */ -#include "bookmaker.h" #include "SkOSFile.h" #include "SkOSPath.h" +#include "bmhParser.h" +#include "includeParser.h" + const IncludeKey kKeyWords[] = { { "", KeyWord::kNone, KeyProperty::kNone }, { "SK_API", KeyWord::kSK_API, KeyProperty::kModifier }, diff --git a/tools/bookmaker/includeParser.h b/tools/bookmaker/includeParser.h new file mode 100644 index 0000000000..cd01687f5d --- /dev/null +++ b/tools/bookmaker/includeParser.h @@ -0,0 +1,452 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef includeParser_DEFINED +#define includeParser_DEFINED + +#include "SkString.h" + +#include "parserCommon.h" + +class BmhParser; + +struct IClassDefinition : public Definition { + unordered_map<string, Definition*> fConsts; + unordered_map<string, Definition*> fDefines; + unordered_map<string, Definition*> fEnums; + unordered_map<string, Definition*> fMembers; + unordered_map<string, Definition*> fMethods; + unordered_map<string, Definition*> fStructs; + unordered_map<string, Definition*> fTypedefs; +}; + +class IncludeParser : public ParserCommon { +public: + enum class IsStruct { + kNo, + kYes, + }; + + enum class Elided { + kNo, + kYes, + }; + + struct CheckCode { + enum class State { + kNone, + kClassDeclaration, + kConstructor, + kForwardDeclaration, + kMethod, + }; + + void reset() { + fInDebugCode = nullptr; + fPrivateBrace = 0; + fBraceCount = 0; + fIndent = 0; + fDoubleReturn = 0; + fState = State::kNone; + fPrivateProtected = false; + fTypedefReturn = false; + fSkipAPI = false; + fSkipInline = false; + fSkipWarnUnused = false; + fWriteReturn = false; + } + + const char* fInDebugCode; + int fPrivateBrace; + int fBraceCount; + int fIndent; + int fDoubleReturn; + State fState; + bool fPrivateProtected; + bool fTypedefReturn; + bool fSkipAPI; + bool fSkipInline; + bool fSkipWarnUnused; + bool fWriteReturn; + }; + + IncludeParser() : ParserCommon() + , fMaps { + { &fIConstMap, MarkType::kConst } + , { &fIDefineMap, MarkType::kDefine } + , { &fIEnumMap, MarkType::kEnum } + , { &fIEnumMap, MarkType::kEnumClass } + , { &fIStructMap, MarkType::kStruct } + , { &fITemplateMap, MarkType::kTemplate } + , { &fITypedefMap, MarkType::kTypedef } + , { &fIUnionMap, MarkType::kUnion } + } + { + this->reset(); + } + + ~IncludeParser() override {} + + void addKeyword(KeyWord keyWord); + + void addPunctuation(Punctuation punctuation) { + fParent->fTokens.emplace_back(punctuation, fChar, fLineCount, fParent, '\0'); + } + + void addWord() { + fParent->fTokens.emplace_back(fIncludeWord, fChar, fLineCount, fParent, '\0'); + fIncludeWord = nullptr; + } + + bool advanceInclude(TextParser& i); + bool inAlignAs() const; + void checkForMissingParams(const vector<string>& methodParams, + const vector<string>& foundParams); + bool checkForWord(); + string className() const; + + string codeBlock(const Definition& def, bool inProgress) const { + return codeBlock(def.fMarkType, def.fName, inProgress); + } + + string codeBlock(MarkType markType, string name, bool inProgress) const { + if (MarkType::kClass == markType || MarkType::kStruct == markType) { + auto map = fIClassMap.find(name); + SkASSERT(fIClassMap.end() != map || inProgress); + return fIClassMap.end() != map ? map->second.fCode : ""; + } + if (MarkType::kConst == markType) { + auto map = fIConstMap.find(name); + SkASSERT(fIConstMap.end() != map); + return map->second->fCode; + } + if (MarkType::kDefine == markType) { + auto map = fIDefineMap.find(name); + SkASSERT(fIDefineMap.end() != map); + return map->second->fCode; + } + if (MarkType::kEnum == markType || MarkType::kEnumClass == markType) { + auto map = fIEnumMap.find(name); + SkASSERT(fIEnumMap.end() != map); + return map->second->fCode; + } + if (MarkType::kTypedef == markType) { + auto map = fITypedefMap.find(name); + SkASSERT(fITypedefMap.end() != map); + return map->second->fCode; + } + SkASSERT(0); + return ""; + } + + void codeBlockAppend(string& result, char ch) const; + void codeBlockSpaces(string& result, int indent) const; + + bool crossCheck(BmhParser& ); + IClassDefinition* defineClass(const Definition& includeDef, string className); + void dumpClassTokens(IClassDefinition& classDef); + void dumpComment(const Definition& ); + void dumpCommonTail(const Definition& ); + void dumpConst(const Definition& , string className); + void dumpDefine(const Definition& ); + void dumpEnum(const Definition& , string name); + bool dumpGlobals(string* globalFileName, long int* globalTell); + void dumpMethod(const Definition& , string className); + void dumpMember(const Definition& ); + bool dumpTokens(); + bool dumpTokens(string skClassName, string globalFileName, long int* globalTell); + void dumpTypedef(const Definition& , string className); + + string elidedCodeBlock(const Definition& ); + string filteredBlock(string inContents, string filterContents); + bool findComments(const Definition& includeDef, Definition* markupDef); + Definition* findIncludeObject(const Definition& includeDef, MarkType markType, + string typeName); + static KeyWord FindKey(const char* start, const char* end); + Definition* findMethod(const Definition& bmhDef); + Bracket grandParentBracket() const; + const Definition* include(string ) const; + bool isClone(const Definition& token); + bool isConstructor(const Definition& token, string className); + bool isInternalName(const Definition& token); + bool isMember(const Definition& token) const; + bool isOperator(const Definition& token); + Definition* parentBracket(Definition* parent) const; + bool parseChar(); + bool parseComment(string filename, const char* start, const char* end, int lineCount, + Definition* markupDef); + bool parseClass(Definition* def, IsStruct); + bool parseConst(Definition* child, Definition* markupDef); + bool parseDefine(Definition* child, Definition* markupDef); + bool parseEnum(Definition* child, Definition* markupDef); + + bool parseFromFile(const char* path) override { + this->reset(); + if (!INHERITED::parseSetup(path)) { + return false; + } + string name(path); + return this->parseInclude(name); + } + + bool parseInclude(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(Definition* child, Definition* markupDef); + bool parseTypedef(Definition* child, Definition* markupDef); + bool parseUsing(); + bool parseUnion(); + + void popBracket() { + if (Definition::Type::kKeyWord == fParent->fType + && KeyWord::kTypename == fParent->fKeyWord) { + this->popObject(); + } + 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, '\0'); + Definition* container = &fParent->fTokens.back(); + this->addDefinition(container); + } + + bool references(const SkString& file) const; + + static void RemoveFile(const char* docs, const char* includes); + static void RemoveOneFile(const char* docs, const char* includesFileOrPath); + + void reset() override { + INHERITED::resetCommon(); + fRootTopic = nullptr; + fConstExpr = nullptr; + fInBrace = nullptr; + fIncludeWord = nullptr; + fLastObject = nullptr; + fPriorEnum = nullptr; + fPriorObject = nullptr; + fPrev = '\0'; + fInChar = false; + fInCharCommentString = false; + fInComment = false; + fInDefine = false; + fInEnum = false; + fInFunction = false; + fInString = false; + fFailed = 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; + + template <typename T> + string uniqueName(const unordered_map<string, T>& m, 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; + void writeCodeBlock(); + string writeCodeBlock(const Definition&, MarkType ); + string writeCodeBlock(TextParser& i, MarkType , int indent); + + void writeDefinition(const Definition& def) { + if (def.length() > 1) { + this->writeBlock((int) (def.fContentEnd - def.fContentStart), def.fContentStart); + this->lf(1); + } + } + + void writeDefinition(const Definition& def, string name, int spaces) { + this->writeBlock((int) (def.fContentEnd - def.fContentStart), def.fContentStart); + this->writeSpace(spaces); + this->writeString(name); + this->lf(1); + } + + void writeEndTag() { + this->lf(1); + this->writeString("##"); + this->lf(1); + } + + void writeEndTag(const char* tagType) { + this->lf(1); + this->writeString(string("#") + tagType + " ##"); + this->lf(1); + } + + void writeEndTag(const char* tagType, const char* tagID, int spaces = 1) { + this->lf(1); + this->writeString(string("#") + tagType + " " + tagID); + this->writeSpace(spaces); + this->writeString("##"); + this->lf(1); + } + + void writeEndTag(const char* tagType, string tagID, int spaces = 1) { + this->writeEndTag(tagType, tagID.c_str(), spaces); + } + + void writeIncompleteTag(const char* tagType, string tagID, int spaces = 1) { + this->writeString(string("#") + tagType + " " + tagID); + this->writeSpace(spaces); + this->writeString("incomplete"); + this->writeSpace(); + this->writeString("##"); + this->lf(1); + } + + void writeIncompleteTag(const char* tagType) { + this->writeString(string("#") + tagType + " incomplete ##"); + this->lf(1); + } + + void writeTableHeader(const char* col1, size_t pad, const char* col2) { + this->lf(1); + this->writeString("#Table"); + this->lf(1); + this->writeString("#Legend"); + this->lf(1); + string legend = "# "; + legend += col1; + if (pad > strlen(col1)) { + legend += string(pad - strlen(col1), ' '); + } + legend += " # "; + legend += col2; + legend += " ##"; + this->writeString(legend); + this->lf(1); + this->writeString("#Legend ##"); + this->lf(1); + } + + void writeTableRow(size_t pad, string col1) { + this->lf(1); + string row = "# " + col1 + string(pad - col1.length(), ' ') + " # ##"; + this->writeString(row); + this->lf(1); + } + + void writeTableRow(size_t pad1, string col1, size_t pad2, string col2) { + this->lf(1); + string row = "# " + col1 + string(pad1 - col1.length(), ' ') + " # " + + col2 + string(pad2 - col2.length(), ' ') + " ##"; + this->writeString(row); + this->lf(1); + } + + void writeTableTrailer() { + this->lf(1); + this->writeString("#Table ##"); + this->lf(1); + } + + void writeTag(const char* tagType) { + this->lf(1); + this->writeString("#"); + this->writeString(tagType); + } + + void writeTagNoLF(const char* tagType, const char* tagID) { + this->writeString("#"); + this->writeString(tagType); + this->writeSpace(); + this->writeString(tagID); + } + + void writeTagNoLF(const char* tagType, string tagID) { + this->writeTagNoLF(tagType, tagID.c_str()); + } + + void writeTag(const char* tagType, const char* tagID) { + this->lf(1); + this->writeTagNoLF(tagType, tagID); + } + + void writeTag(const char* tagType, string tagID) { + this->writeTag(tagType, tagID.c_str()); + } + + void writeTagTable(string tagType, string body) { + this->writeTag(tagType.c_str()); + this->writeSpace(1); + this->writeString("#"); + this->writeSpace(1); + this->writeString(body); + this->writeSpace(1); + this->writeString("##"); + } + +protected: + static void ValidateKeyWords(); + + struct DefinitionMap { + unordered_map<string, Definition*>* fInclude; + MarkType fMarkType; + }; + + vector<DefinitionMap> fMaps; + unordered_map<string, Definition> fIncludeMap; + list<Definition> fGlobals; + unordered_map<string, IClassDefinition> fIClassMap; + unordered_map<string, Definition*> fIConstMap; + unordered_map<string, Definition*> fIDefineMap; + unordered_map<string, Definition*> fIEnumMap; + unordered_map<string, Definition*> fIFunctionMap; + unordered_map<string, Definition*> fIStructMap; + unordered_map<string, Definition*> fITemplateMap; + unordered_map<string, Definition*> fITypedefMap; + unordered_map<string, Definition*> fIUnionMap; + CheckCode fCheck; + Definition* fRootTopic; + Definition* fConstExpr; + Definition* fInBrace; + Definition* fLastObject; + Definition* fPriorEnum; + Definition* fPriorObject; + int fPriorIndex; + const char* fIncludeWord; + Elided fElided; + char fPrev; + bool fInChar; + bool fInCharCommentString; + bool fInComment; + bool fInDefine; + bool fInEnum; + bool fInFunction; + bool fInString; + bool fFailed; + + typedef ParserCommon INHERITED; +}; + +#endif diff --git a/tools/bookmaker/includeWriter.cpp b/tools/bookmaker/includeWriter.cpp index ef854a72aa..17336bf772 100644 --- a/tools/bookmaker/includeWriter.cpp +++ b/tools/bookmaker/includeWriter.cpp @@ -5,10 +5,11 @@ * found in the LICENSE file. */ -#include "bookmaker.h" #include <chrono> #include <ctime> -#include <string> + +#include "bmhParser.h" +#include "includeWriter.h" bool IncludeWriter::checkChildCommentLength(const Definition* parent, MarkType childType) const { bool oneMember = false; diff --git a/tools/bookmaker/includeWriter.h b/tools/bookmaker/includeWriter.h new file mode 100644 index 0000000000..6271ac7425 --- /dev/null +++ b/tools/bookmaker/includeWriter.h @@ -0,0 +1,243 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef includeWriter_DEFINED +#define includeWriter_DEFINED + +#include "includeParser.h" + +class IncludeWriter : public IncludeParser { +public: + enum class Word { + kStart, + kCap, + kFirst, + kUnderline, + kMixed, + }; + + enum class Phrase { + kNo, + kYes, + }; + + enum class PunctuationState { + kStart, + kDelimiter, + kParen, // treated as a delimiter unless following a space, and followed by word + kPeriod, + kSpace, + }; + + enum class RefType { + kUndefined, + kNormal, + kExternal, + }; + + enum class SkipFirstLine { + kNo, + kYes, + }; + + enum class Wrote { + kNone, + kLF, + kChars, + }; + + enum class MemberPass { + kCount, + kOut, + }; + + enum class ItemState { + kNone, + kName, + kValue, + kComment, + }; + + struct IterState { + IterState (list<Definition>::iterator tIter, list<Definition>::iterator tIterEnd) + : fDefIter(tIter) + , fDefEnd(tIterEnd) { + } + list<Definition>::iterator fDefIter; + list<Definition>::iterator fDefEnd; + }; + + struct ParentPair { + const Definition* fParent; + const ParentPair* fPrev; + }; + + struct Preprocessor { + Preprocessor() { + reset(); + } + + void reset() { + fDefinition = nullptr; + fStart = nullptr; + fEnd = nullptr; + fWord = false; + } + + const Definition* fDefinition; + const char* fStart; + const char* fEnd; + bool fWord; + }; + + struct Item { + void reset() { + fName = ""; + fValue = ""; + } + + string fName; + string fValue; + }; + + struct LastItem { + const char* fStart; + const char* fEnd; + }; + + struct ItemLength { + int fCurName; + int fCurValue; + int fLongestName; + int fLongestValue; + }; + + IncludeWriter() : IncludeParser() { + this->reset(); + } + + ~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; + } + + bool checkChildCommentLength(const Definition* parent, MarkType childType) const; + void checkEnumLengths(const Definition& child, string enumName, ItemLength* length) const; + void constOut(const Definition* memberStart, const Definition* bmhConst); + void constSizeMembers(const RootDefinition* root); + bool defineOut(const Definition& ); + bool descriptionOut(const Definition* def, SkipFirstLine , Phrase ); + void enumHeaderOut(RootDefinition* root, const Definition& child); + string enumMemberComment(const Definition* currentEnumItem, const Definition& child) const; + const Definition* enumMemberForComment(const Definition* currentEnumItem) const; + ItemState enumMemberName(const Definition& child, + const Definition* token, Item* , LastItem* , const Definition** enumItem); + void enumMemberOut(const Definition* currentEnumItem, const Definition& child, + const Item& , Preprocessor& ); + void enumMembersOut(Definition& child); + bool enumPreprocessor(Definition* token, MemberPass pass, + vector<IterState>& iterStack, IterState** iterState, Preprocessor* ); + void enumSizeItems(const Definition& child); + bool findEnumSubtopic(string undername, const Definition** ) const; + void firstBlock(int size, const char* data); + bool firstBlockTrim(int size, const char* data); + Definition* findMemberCommentBlock(const vector<Definition*>& bmhChildren, string name) const; + Definition* findMethod(string name, RootDefinition* ) const; + + void indentDeferred(IndentKind kind) { + if (fIndentNext) { + this->indentIn(kind); + fIndentNext = false; + } + } + + int lookupMethod(const PunctuationState punctuation, const Word word, + const int start, const int run, int lastWrite, + const char* data, bool hasIndirection); + int lookupReference(const PunctuationState punctuation, const Word word, + const int start, const int run, int lastWrite, const char last, + const char* data); + const Definition* matchMemberName(string matchName, const Definition& child) const; + void methodOut(Definition* method, const Definition& child); + bool populate(Definition* def, ParentPair* parentPair, RootDefinition* root); + bool populate(BmhParser& bmhParser); + + void reset() override { + INHERITED::resetCommon(); + fBmhParser = nullptr; + fDeferComment = nullptr; + fBmhMethod = nullptr; + fEnumDef = nullptr; + fMethodDef = nullptr; + fBmhConst = nullptr; + fConstDef = nullptr; + fLastDescription = nullptr; + fStartSetter = nullptr; + fBmhStructDef = nullptr; + fContinuation = nullptr; + fInStruct = false; + fWroteMethod = false; + fIndentNext = false; + fPendingMethod = false; + fFirstWrite = false; + fStructEnded = false; + fWritingIncludes = true; + } + + string resolveAlias(const Definition* ); + string resolveMethod(const char* start, const char* end, bool first); + string resolveRef(const char* start, const char* end, bool first, RefType* refType); + Wrote rewriteBlock(int size, const char* data, Phrase phrase); + void setStart(const char* start, const Definition * ); + void setStartBack(const char* start, const Definition * ); + Definition* structMemberOut(const Definition* memberStart, const Definition& child); + void structOut(const Definition* root, const Definition& child, + const char* commentStart, const char* commentEnd); + void structSizeMembers(const Definition& child); + bool writeHeader(std::pair<const string, Definition>& ); +private: + vector<const Definition* > fICSStack; + BmhParser* fBmhParser; + Definition* fDeferComment; + const Definition* fBmhMethod; + const Definition* fEnumDef; + const Definition* fMethodDef; + const Definition* fBmhConst; + const Definition* fConstDef; + const Definition* fLastDescription; + const Definition* fStartSetter; + Definition* fBmhStructDef; + const char* fContinuation; // used to construct paren-qualified method name + int fAnonymousEnumCount; + int fEnumItemValueTab; + int fEnumItemCommentTab; + int fStructMemberTab; + int fStructValueTab; + int fStructCommentTab; + int fStructMemberLength; + int fConstValueTab; + int fConstCommentTab; + int fConstLength; + bool fInStruct; // set if struct is inside class + bool fWroteMethod; + bool fIndentNext; + bool fPendingMethod; + bool fFirstWrite; // set to write file information just after includes + bool fStructEnded; // allow resetting indent after struct is complete + + typedef IncludeParser INHERITED; +}; + +#endif diff --git a/tools/bookmaker/mdOut.cpp b/tools/bookmaker/mdOut.cpp index 22b830b69d..49f8b86ecf 100644 --- a/tools/bookmaker/mdOut.cpp +++ b/tools/bookmaker/mdOut.cpp @@ -5,11 +5,30 @@ * found in the LICENSE file. */ -#include "bookmaker.h" +#include "bmhParser.h" +#include "includeParser.h" +#include "mdOut.h" #include "SkOSFile.h" #include "SkOSPath.h" +class SubtopicKeys { +public: + static constexpr const char* kClasses = "Classes"; + static constexpr const char* kConstants = "Constants"; + static constexpr const char* kConstructors = "Constructors"; + static constexpr const char* kDefines = "Defines"; + static constexpr const char* kMemberFunctions = "Member_Functions"; + static constexpr const char* kMembers = "Members"; + static constexpr const char* kOperators = "Operators"; + static constexpr const char* kOverview = "Overview"; + static constexpr const char* kRelatedFunctions = "Related_Functions"; + static constexpr const char* kStructs = "Structs"; + static constexpr const char* kTypedefs = "Typedefs"; + + static const char* kGeneratedSubtopics[]; +}; + const char* SubtopicKeys::kGeneratedSubtopics[] = { kConstants, kDefines, kTypedefs, kMembers, kClasses, kStructs, kConstructors, kOperators, kMemberFunctions, kRelatedFunctions @@ -257,9 +276,9 @@ Definition* MdOut::checkParentsForMatch(Definition* test, string ref) const { return nullptr; } -static bool formula_or_code(BmhParser::Resolvable resolvable) { - return BmhParser::Resolvable::kFormula == resolvable - || BmhParser::Resolvable::kCode == resolvable; +static bool formula_or_code(Resolvable resolvable) { + return Resolvable::kFormula == resolvable + || Resolvable::kCode == resolvable; } static void fixup_const_function_name(string* ref) { @@ -507,8 +526,8 @@ string MdOut::addIncludeReferences(const char* refStart, const char* refEnd) { // FIXME: preserve inter-line spaces and don't add new ones string MdOut::addReferences(const char* refStart, const char* refEnd, - BmhParser::Resolvable resolvable) { - if (BmhParser::Resolvable::kInclude == resolvable) { // test include resolving + Resolvable resolvable) { + if (Resolvable::kInclude == resolvable) { // test include resolving return this->addIncludeReferences(refStart, refEnd); } string result; @@ -522,7 +541,7 @@ string MdOut::addReferences(const char* refStart, const char* refEnd, vector<BraceState> braceStack; KeyWord lastKeyWord = KeyWord::kNone; KeyWord keyWord = KeyWord::kNone; - if (BmhParser::Resolvable::kCode == resolvable) { + if (Resolvable::kCode == resolvable) { braceStack.push_back({fRoot, fRoot->fName, t.fChar, lastKeyWord, keyWord, 0}); } do { @@ -536,7 +555,7 @@ string MdOut::addReferences(const char* refStart, const char* refEnd, t.next(); continue; } - if (BmhParser::Resolvable::kCode == resolvable) { + if (Resolvable::kCode == resolvable) { int priorBrace = braceStack.back().fBraceCount; int braceCount = priorBrace + t.skipToMethodStart(); if (braceCount > priorBrace) { @@ -590,7 +609,7 @@ string MdOut::addReferences(const char* refStart, const char* refEnd, wordStart = base; } string nonWord = string(wordStart, start - wordStart); - if (BmhParser::Resolvable::kFormula == resolvable) { + if (Resolvable::kFormula == resolvable) { string unbreakable; bool comma = false; for (char c : nonWord) { @@ -642,7 +661,7 @@ string MdOut::addReferences(const char* refStart, const char* refEnd, result += ref; continue; } - if (BmhParser::Resolvable::kCode == resolvable) { + if (Resolvable::kCode == resolvable) { fixup_const_function_name(&ref); } if (Definition* def = this->isDefined(t, ref, resolvable)) { @@ -652,8 +671,8 @@ string MdOut::addReferences(const char* refStart, const char* refEnd, continue; } SkASSERT(def->fFiddle.length()); - if (BmhParser::Resolvable::kSimple != resolvable - && BmhParser::Resolvable::kInclude != resolvable + if (Resolvable::kSimple != resolvable + && Resolvable::kInclude != resolvable && !t.eof() && '(' == t.peek() && t.strnchr(')', t.fEnd)) { TextParserSave tSave(&t); if (!t.skipToBalancedEndBracket('(', ')')) { @@ -680,7 +699,7 @@ string MdOut::addReferences(const char* refStart, const char* refEnd, } string altTest = ref + '_'; altTest += suffix++; - altDef = this->isDefined(t, altTest, BmhParser::Resolvable::kOut); + altDef = this->isDefined(t, altTest, Resolvable::kOut); } if (suffix > '9') { t.reportError("too many alts"); @@ -705,7 +724,7 @@ string MdOut::addReferences(const char* refStart, const char* refEnd, } ref = ref.substr(0, ref.find('(')); tSave.restore(); - } else if (BmhParser::Resolvable::kClone != resolvable && + } else if (Resolvable::kClone != resolvable && all_lower(ref) && (t.eof() || '(' != t.peek())) { add_ref(leadingSpaces, ref, &result); continue; @@ -717,7 +736,7 @@ string MdOut::addReferences(const char* refStart, const char* refEnd, } result += linkRef(leadingSpaces, def, ref, resolvable); if (!t.eof() && '(' == t.peek()) { - if (BmhParser::Resolvable::kInclude == resolvable + if (Resolvable::kInclude == resolvable && std::any_of(ref.begin(), ref.end(), [](char c){ return !islower(c); } )) { t.next(); // skip open paren SkAssertResult(')' == t.next()); // skip close paren @@ -735,7 +754,7 @@ string MdOut::addReferences(const char* refStart, const char* refEnd, } t.next(); ref = string(start, t.fChar - start); - if (Definition* def = this->isDefined(t, ref, BmhParser::Resolvable::kYes)) { + if (Definition* def = this->isDefined(t, ref, Resolvable::kYes)) { SkASSERT(def->fFiddle.length()); result += linkRef(leadingSpaces, def, ref, resolvable); continue; @@ -749,14 +768,14 @@ string MdOut::addReferences(const char* refStart, const char* refEnd, // look for Sk / sk / SK .. if (!ref.compare(0, 2, "Sk") && ref != "Skew" && ref != "Skews" && ref != "Skewing" && ref != "Skip" && ref != "Skips") { - if (BmhParser::Resolvable::kOut != resolvable && !formula_or_code(resolvable)) { + if (Resolvable::kOut != resolvable && !formula_or_code(resolvable)) { t.reportError("missed Sk prefixed"); fAddRefFailed = true; return result; } } if (!ref.compare(0, 2, "SK")) { - if (BmhParser::Resolvable::kOut != resolvable && !formula_or_code(resolvable)) { + if (Resolvable::kOut != resolvable && !formula_or_code(resolvable)) { t.reportError("missed SK prefixed"); fAddRefFailed = true; return result; @@ -788,9 +807,9 @@ string MdOut::addReferences(const char* refStart, const char* refEnd, } } } - if (BmhParser::Resolvable::kSimple != resolvable - && BmhParser::Resolvable::kInclude != resolvable - && BmhParser::Resolvable::kOut != resolvable + if (Resolvable::kSimple != resolvable + && Resolvable::kInclude != resolvable + && Resolvable::kOut != resolvable && !formula_or_code(resolvable)) { t.reportError("missed camelCase"); fAddRefFailed = true; @@ -804,7 +823,7 @@ string MdOut::addReferences(const char* refStart, const char* refEnd, && KeyWord::kPublic != newKeyWord) { lastKeyWord = keyWord; keyWord = newKeyWord; - } else if (BmhParser::Resolvable::kCode == resolvable && ':' != t.peek()) { + } else if (Resolvable::kCode == resolvable && ':' != t.peek()) { lastLine = t.fLine; lastKeyWord = keyWord; keyWord = newKeyWord; @@ -829,7 +848,7 @@ string MdOut::addReferences(const char* refStart, const char* refEnd, if (isupper(t.fChar[1]) && startsSentence) { TextParser next(t.fFileName, &t.fChar[1], t.fEnd, t.fLineCount); string nextWord(next.fChar, next.wordEnd() - next.fChar); - if (this->isDefined(t, nextWord, BmhParser::Resolvable::kYes)) { + if (this->isDefined(t, nextWord, Resolvable::kYes)) { add_ref(leadingSpaces, ref, &result); continue; } @@ -842,7 +861,7 @@ string MdOut::addReferences(const char* refStart, const char* refEnd, result += this->linkRef(leadingSpaces, def, ref, resolvable); continue; } - if (BmhParser::Resolvable::kOut != resolvable && + if (Resolvable::kOut != resolvable && !formula_or_code(resolvable)) { t.reportError("undefined reference"); fAddRefFailed = true; @@ -1063,7 +1082,7 @@ bool MdOut::checkParamReturnBody(const Definition* def) { if (!islower(descriptionStart[0]) && !isdigit(descriptionStart[0])) { paramBody.skipToNonName(); string ref = string(descriptionStart, paramBody.fChar - descriptionStart); - if (!this->isDefined(paramBody, ref, BmhParser::Resolvable::kYes)) { + if (!this->isDefined(paramBody, ref, Resolvable::kYes)) { string errorStr = MarkType::kReturn == def->fMarkType ? "return" : "param"; errorStr += " description must start with lower case"; paramBody.reportError(errorStr.c_str()); @@ -1088,20 +1107,20 @@ void MdOut::childrenOut(Definition* def, const char* start) { if (MarkType::kEnumClass == def->fMarkType) { fEnumClass = def; } - BmhParser::Resolvable resolvable = this->resolvable(def); + Resolvable resolvable = this->resolvable(def); const Definition* prior = nullptr; for (auto& child : def->fChildren) { if (MarkType::kPhraseParam == child->fMarkType) { continue; } end = child->fStart; - if (BmhParser::Resolvable::kNo != resolvable) { + if (Resolvable::kNo != resolvable) { this->resolveOut(start, end, resolvable); } this->markTypeOut(child, &prior); start = child->fTerminator; } - if (BmhParser::Resolvable::kNo != resolvable) { + if (Resolvable::kNo != resolvable) { end = def->fContentEnd; if (MarkType::kFormula == def->fMarkType && ' ' == start[0]) { this->writeSpace(); @@ -1226,7 +1245,7 @@ Definition* MdOut::findParamType() { string name = string(word, parser.fChar - word); if (fLastParam->fName == name) { Definition* paramType = this->isDefined(parser, lastFull, - BmhParser::Resolvable::kOut); + Resolvable::kOut); return paramType; } if (isupper(name[0])) { @@ -1337,7 +1356,7 @@ Definition* MdOut::isDefinedByParent(RootDefinition* root, string ref) { } Definition* MdOut::isDefined(const TextParser& parser, string ref, - BmhParser::Resolvable resolvable) { + Resolvable resolvable) { auto rootIter = fBmhParser.fClassMap.find(ref); if (rootIter != fBmhParser.fClassMap.end()) { return &rootIter->second; @@ -1423,7 +1442,7 @@ Definition* MdOut::isDefined(const TextParser& parser, string ref, return nullptr; } } else { - if (BmhParser::Resolvable::kOut != resolvable && + if (Resolvable::kOut != resolvable && !formula_or_code(resolvable)) { parser.reportError("SK undefined"); fAddRefFailed = true; @@ -1454,7 +1473,7 @@ Definition* MdOut::isDefined(const TextParser& parser, string ref, return definition; } } - if (BmhParser::Resolvable::kOut != resolvable && + if (Resolvable::kOut != resolvable && !formula_or_code(resolvable)) { parser.reportError("_ undefined"); fAddRefFailed = true; @@ -1483,8 +1502,8 @@ string MdOut::linkName(const Definition* ref) const { // for now, hard-code to html links // def should not include SkXXX_ string MdOut::linkRef(string leadingSpaces, Definition* def, - string ref, BmhParser::Resolvable resolvable) { - bool trimRef = BmhParser::Resolvable::kInclude == resolvable && "Sk" == ref.substr(0, 2); + string ref, Resolvable resolvable) { + bool trimRef = Resolvable::kInclude == resolvable && "Sk" == ref.substr(0, 2); if (trimRef) { #ifdef SK_DEBUG for (auto c : ref) { @@ -1576,15 +1595,15 @@ string MdOut::linkRef(string leadingSpaces, Definition* def, buildup = '#' + parent->fFiddle + '_' + ref; } string refOut(ref); - if (!globalEnumMember && BmhParser::Resolvable::kCode != resolvable) { + if (!globalEnumMember && Resolvable::kCode != resolvable) { std::replace(refOut.begin(), refOut.end(), '_', ' '); } if (ref.length() > 2 && islower(ref[0]) && "()" == ref.substr(ref.length() - 2) - && BmhParser::Resolvable::kCode != resolvable) { + && Resolvable::kCode != resolvable) { refOut = refOut.substr(0, refOut.length() - 2); } string result = leadingSpaces + this->anchorRef(buildup, refOut); - if (BmhParser::Resolvable::kClone == resolvable && MarkType::kMethod == def->fMarkType && + if (Resolvable::kClone == resolvable && MarkType::kMethod == def->fMarkType && def->fCloned && !def->fClone) { bool found = false; string match = def->fName; @@ -2015,7 +2034,7 @@ void MdOut::markTypeOut(Definition* def, const Definition** prior) { string formattedStr = def->formatFunction(Definition::Format::kIncludeReturn); string preformattedStr = preformat(formattedStr); string references = this->addReferences(&preformattedStr.front(), - &preformattedStr.back() + 1, BmhParser::Resolvable::kSimple); + &preformattedStr.back() + 1, Resolvable::kSimple); preformattedStr = references; this->htmlOut("<pre style=\"padding: 1em 1em 1em 1em; width: 62.5em;" "background-color: #f0f0f0\">\n" + preformattedStr + "\n" + "</pre>"); @@ -2208,7 +2227,7 @@ void MdOut::markTypeOut(Definition* def, const Definition** prior) { if (parser.skipExact("@param ")) { // write parameters, if any this->parameterHeaderOut(parser, prior, def); this->resolveOut(parser.fChar, parser.fEnd, - BmhParser::Resolvable::kInclude); + Resolvable::kInclude); this->parameterTrailerOut(); wroteParam = true; continue; @@ -2223,7 +2242,7 @@ void MdOut::markTypeOut(Definition* def, const Definition** prior) { if (parser.skipExact("@return ")) { // write return, if any this->returnHeaderOut(prior, def); this->resolveOut(parser.fChar, parser.fEnd, - BmhParser::Resolvable::kInclude); + Resolvable::kInclude); this->lf(2); continue; } @@ -2240,7 +2259,7 @@ void MdOut::markTypeOut(Definition* def, const Definition** prior) { this->lf(2); } this->resolveOut(entry.fContentStart, entry.fContentEnd, - BmhParser::Resolvable::kInclude); // write description + Resolvable::kInclude); // write description this->lf(1); } fMethod = nullptr; @@ -2332,7 +2351,7 @@ void MdOut::markTypeOut(Definition* def, const Definition** prior) { this->writePending(); this->htmlOut("<code>"); this->resolveOut(def->fContentStart, def->fContentEnd, - BmhParser::Resolvable::kFormula); + Resolvable::kFormula); this->htmlOut("</code>"); } break; @@ -2665,8 +2684,8 @@ void MdOut::populateTables(const Definition* def, RootDefinition* root) { } } -void MdOut::resolveOut(const char* start, const char* end, BmhParser::Resolvable resolvable) { - if ((BmhParser::Resolvable::kLiteral == resolvable || fLiteralAndIndent || +void MdOut::resolveOut(const char* start, const char* end, Resolvable resolvable) { + if ((Resolvable::kLiteral == resolvable || fLiteralAndIndent || fResolveAndIndent) && end > start) { int linefeeds = 0; while ('\n' == *start) { @@ -2684,7 +2703,7 @@ void MdOut::resolveOut(const char* start, const char* end, BmhParser::Resolvable fIndent = start - spaceStart; } } - if (BmhParser::Resolvable::kLiteral == resolvable || fLiteralAndIndent) { + if (Resolvable::kLiteral == resolvable || fLiteralAndIndent) { this->writeBlockTrim(end - start, start); if ('\n' == end[-1]) { this->lf(1); @@ -2784,12 +2803,12 @@ void MdOut::rowOut(const char* name, string description, bool literalName) { this->writeString(name); } } else { - this->resolveOut(name, name + strlen(name), BmhParser::Resolvable::kYes); + this->resolveOut(name, name + strlen(name), Resolvable::kYes); } FPRINTF("</td>"); this->lfAlways(1); FPRINTF("%s", kTD_Left.c_str()); - this->resolveOut(&description.front(), &description.back() + 1, BmhParser::Resolvable::kYes); + this->resolveOut(&description.front(), &description.back() + 1, Resolvable::kYes); FPRINTF("</td>"); this->lfAlways(1); FPRINTF(" </tr>"); @@ -2943,7 +2962,7 @@ bool MdOut::subtopicRowOut(string keyName, const Definition* entry) { return parser.reportError<bool>("missing #Line"); } TextParser dummy(entry); // for reporting errors, which we won't do - if (!this->isDefined(dummy, keyName, BmhParser::Resolvable::kOut)) { + if (!this->isDefined(dummy, keyName, Resolvable::kOut)) { keyName = entry->fName; size_t doubleColon = keyName.find("::"); SkASSERT(string::npos != doubleColon); diff --git a/tools/bookmaker/mdOut.h b/tools/bookmaker/mdOut.h new file mode 100644 index 0000000000..30b48d8e55 --- /dev/null +++ b/tools/bookmaker/mdOut.h @@ -0,0 +1,171 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef mdOut_DEFINED +#define mdOut_DEFINED + +#include "parserCommon.h" + +class IncludeParser; + +class MdOut : public ParserCommon { +public: + struct SubtopicDescriptions { + string fSingular; + string fPlural; + string fOneLiner; + string fDetails; + }; + + MdOut(BmhParser& bmh, IncludeParser& inc) : ParserCommon() + , fBmhParser(bmh) + , fIncludeParser(inc) { + this->reset(); + this->addPopulators(); + fBmhParser.setUpGlobalSubstitutes(); + } + + bool buildReferences(const char* docDir, const char* mdOutDirOrFile); + bool buildStatus(const char* docDir, const char* mdOutDir); + void checkAnchors(); + +private: + enum class TableState { + kNone, + kRow, + kColumn, + }; + + struct AnchorDef { + string fDef; + MarkType fMarkType; + }; + + void addCodeBlock(const Definition* def, string& str) const; + void addPopulators(); + string addIncludeReferences(const char* refStart, const char* refEnd); + string addReferences(const char* start, const char* end, Resolvable ); + string anchorDef(string def, string name); + string anchorLocalRef(string ref, string name); + string anchorRef(string def, string name); + bool buildRefFromFile(const char* fileName, const char* outDir); + bool checkParamReturnBody(const Definition* def); + Definition* checkParentsForMatch(Definition* test, string ref) const; + void childrenOut(Definition* def, const char* contentStart); + Definition* csParent(); + bool findLink(string ref, string* link); + Definition* findParamType(); + string getMemberTypeName(const Definition* def, string* memberType); + static bool HasDetails(const Definition* def); + bool hasWordSpace(string ) const; + void htmlOut(string ); + Definition* isDefined(const TextParser& , string ref, Resolvable ); + Definition* isDefinedByParent(RootDefinition* root, string ref); + string linkName(const Definition* ) const; + string linkRef(string leadingSpaces, Definition*, string ref, Resolvable ); + void markTypeOut(Definition* , const Definition** prior); + void mdHeaderOut(int depth) { mdHeaderOutLF(depth, 2); } + void mdHeaderOutLF(int depth, int lf); + void parameterHeaderOut(TextParser& paramParser, const Definition** prior, Definition* def); + void parameterTrailerOut(); + bool parseFromFile(const char* path) override { return true; } + bool phraseContinues(string phrase, string* priorWord, string* priorLink) const; + void populateOne(Definition* def, + unordered_map<string, RootDefinition::SubtopicContents>& populator); + void populateTables(const Definition* def, RootDefinition* ); + + SubtopicDescriptions& populator(string key) { + auto entry = fPopulators.find(key); + // FIXME: this should have been detected earlier + SkASSERT(fPopulators.end() != entry); + return entry->second; + } + + void reset() override { + INHERITED::resetCommon(); + fEnumClass = nullptr; + fMethod = nullptr; + fRoot = nullptr; + fSubtopic = nullptr; + fLastParam = nullptr; + fTableState = TableState::kNone; + fAddRefFailed = false; + fHasFiddle = false; + fInDescription = false; + fInList = false; + fResolveAndIndent = false; + fLiteralAndIndent = false; + fLastDef = nullptr; + fParamEnd = nullptr; + fInProgress = false; + } + + Resolvable resolvable(const Definition* definition) const { + MarkType markType = definition->fMarkType; + if (MarkType::kCode == markType) { + for (auto child : definition->fChildren) { + if (MarkType::kLiteral == child->fMarkType) { + return Resolvable::kLiteral; + } + } + } + if ((MarkType::kExample == markType + || MarkType::kFunction == markType) && fHasFiddle) { + return Resolvable::kNo; + } + return BmhParser::kMarkProps[(int) markType].fResolve; + } + + void resolveOut(const char* start, const char* end, Resolvable ); + void returnHeaderOut(const Definition** prior, Definition* def); + void rowOut(string col1, const Definition* col2); + void rowOut(const char * name, string description, bool literalName); + + void subtopicOut(string name); + void subtopicsOut(Definition* def); + void subtopicOut(string key, const vector<Definition*>& data, const Definition* csParent, + const Definition* topicParent, bool showClones); + bool subtopicRowOut(string keyName, const Definition* entry); + void summaryOut(const Definition* def, MarkType , string name); + string tableDataCodeDef(const Definition* def); + string tableDataCodeDef(string def, string name); + string tableDataCodeLocalRef(string name); + string tableDataCodeLocalRef(string ref, string name); + string tableDataCodeRef(const Definition* ref); + string tableDataCodeRef(string ref, string name); + void writeSubtopicTableHeader(string key); + + vector<const Definition*> fClassStack; + unordered_map<string, vector<AnchorDef> > fAllAnchorDefs; + unordered_map<string, vector<string> > fAllAnchorRefs; + NameMap* fNames; + BmhParser& fBmhParser; + IncludeParser& fIncludeParser; + const Definition* fEnumClass; + const Definition* fLastDef; + Definition* fMethod; + RootDefinition* fRoot; // used in generating populated tables; always struct or class + RootDefinition* fSubtopic; // used in resolving symbols + const Definition* fLastParam; + TableState fTableState; + unordered_map<string, SubtopicDescriptions> fPopulators; + unordered_map<string, string> fPhraseParams; + const char* fParamEnd; + bool fAddRefFailed; + 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; + bool fLiteralAndIndent; + bool fResolveAndIndent; + bool fOddRow; + bool fHasDetails; + bool fInProgress; + typedef ParserCommon INHERITED; +}; + +#endif diff --git a/tools/bookmaker/parserCommon.cpp b/tools/bookmaker/parserCommon.cpp index c3bab1a4da..271551bd6f 100644 --- a/tools/bookmaker/parserCommon.cpp +++ b/tools/bookmaker/parserCommon.cpp @@ -5,10 +5,11 @@ * found in the LICENSE file. */ -#include "bookmaker.h" #include "SkOSFile.h" #include "SkOSPath.h" +#include "parserCommon.h" + void ParserCommon::checkLineLength(size_t len, const char* str) { if (!fWritingIncludes) { return; diff --git a/tools/bookmaker/parserCommon.h b/tools/bookmaker/parserCommon.h new file mode 100644 index 0000000000..f31cbd63cd --- /dev/null +++ b/tools/bookmaker/parserCommon.h @@ -0,0 +1,319 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef parserCommon_DEFINED +#define parserCommon_DEFINED + +#include "SkData.h" +#include "SkJSONCPP.h" + +#include "definition.h" +#include "textParser.h" + +enum class StatusFilter { + kCompleted, + kInProgress, + kUnknown, +}; + +class ParserCommon : public TextParser { +public: + enum class OneFile { + kNo, + kYes, + }; + + enum class OneLine { + kNo, + kYes, + }; + + enum class IndentKind { + kConstOut, + kEnumChild, + kEnumChild2, + kEnumHeader, + kEnumHeader2, + kMethodOut, + kStructMember, + }; + + struct IndentState { + IndentState(IndentKind kind, int indent) + : fKind(kind) + , fIndent(indent) { + } + + IndentKind fKind; + int fIndent; + }; + + ParserCommon() : TextParser() + , fParent(nullptr) + , fDebugOut(false) + , fValidate(false) + , fReturnOnWrite(false) + { + } + + ~ParserCommon() override { + } + + void addDefinition(Definition* def) { + fParent->fChildren.push_back(def); + fParent = def; + } + + void checkLineLength(size_t len, const char* str); + static string ConvertRef(const string str, bool first); + static void CopyToFile(string oldFile, string newFile); + static char* FindDateTime(char* buffer, int size); + static string HtmlFileName(string bmhFileName); + + void indentIn(IndentKind kind) { + fIndent += 4; + fIndentStack.emplace_back(kind, fIndent); + } + + void indentOut() { + SkASSERT(fIndent >= 4); + SkASSERT(fIndentStack.back().fIndent == fIndent); + fIndent -= 4; + fIndentStack.pop_back(); + } + + void indentToColumn(int column) { + SkASSERT(column >= fColumn); + SkASSERT(!fReturnOnWrite); + SkASSERT(column < 80); + FPRINTF("%*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() { + SkASSERT(!fReturnOnWrite); + fLinefeeds = 0; + fSpaces = 0; + fColumn = 0; + fPendingSpace = 0; + } + + bool parseFile(const char* file, const char* suffix, OneFile ); + bool parseStatus(const char* file, const char* suffix, StatusFilter filter); + virtual bool parseFromFile(const char* path) = 0; + bool parseSetup(const char* path); + + void popObject() { + fParent->fContentEnd = fParent->fTerminator = fChar; + fParent = fParent->fParent; + } + + static char* ReadToBuffer(string filename, int* size); + + virtual void reset() = 0; + + void resetCommon() { + fLine = fChar = fStart; + fLineCount = 0; + fLinesWritten = 1; + fParent = nullptr; + fIndent = 0; + fOut = nullptr; + fMaxLF = 2; + fPendingLF = 0; + fPendingSpace = 0; + fOutdentNext = false; + fWritingIncludes = false; + fDebugWriteCodeBlock = false; + nl(); + } + + void setAsParent(Definition* definition) { + if (fParent) { + fParent->fChildren.push_back(definition); + definition->fParent = fParent; + } + fParent = definition; + } + + void singleLF() { + fMaxLF = 1; + } + + void stringAppend(string& result, char ch) const; + void stringAppend(string& result, string str) const; + void stringAppend(string& result, const Definition* ) const; + + void writeBlock(int size, const char* data) { + SkAssertResult(writeBlockTrim(size, data)); + } + + bool writeBlockIndent(int size, const char* data, bool ignoreIndent); + + void writeBlockSeparator() { + this->writeString( + "# ------------------------------------------------------------------------------"); + this->lf(2); + } + + bool writeBlockTrim(int size, const char* data); + + void writeCommentHeader() { + this->lf(2); + this->writeString("/**"); + this->writeSpace(); + } + + void writeCommentTrailer(OneLine oneLine) { + if (OneLine::kNo == oneLine) { + this->lf(1); + } else { + this->writeSpace(); + } + this->writeString("*/"); + this->lfcr(); + } + + void writePending(); + + // write a pending space, so that two consecutive calls + // don't double write, and trailing spaces on lines aren't written + void writeSpace(int count = 1) { + SkASSERT(!fReturnOnWrite); + SkASSERT(!fPendingLF); + SkASSERT(!fLinefeeds); + SkASSERT(fColumn > 0); + SkASSERT(!fSpaces); + fPendingSpace = count; + } + + void writeString(const char* str); + + void writeString(string str) { + this->writeString(str.c_str()); + } + + static bool WrittenFileDiffers(string filename, string readname); + + unordered_map<string, sk_sp<SkData>> fRawData; + unordered_map<string, vector<char>> fLFOnly; + vector<IndentState> fIndentStack; + Definition* fParent; + FILE* fOut; + string fRawFilePathDir; + 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 + int fPendingSpace; // one or two spaces should preceed the next string or block + size_t fLinesWritten; // as opposed to fLineCount, number of lines read + char fLastChar; // last written + bool fDebugOut; // set true to write to std out + bool fValidate; // set true to check anchor defs and refs + bool fOutdentNext; // set at end of embedded struct to prevent premature outdent + bool fWroteSomething; // used to detect empty content; an alternative source is preferable + bool fReturnOnWrite; // used to detect non-empty content; allowing early return + bool fWritingIncludes; // set true when writing includes to check >100 columns + mutable bool fDebugWriteCodeBlock; + +private: + typedef TextParser INHERITED; +}; + +struct JsonStatus { + const Json::Value& fObject; + Json::Value::iterator fIter; + string fName; + StatusFilter fStatusFilter; +}; + +class JsonCommon : public ParserCommon { +public: + bool empty() { return fStack.empty(); } + bool parseFromFile(const char* path) override; + + void reset() override { + fStack.clear(); + INHERITED::resetCommon(); + } + + vector<JsonStatus> fStack; + Json::Value fRoot; +private: + typedef ParserCommon INHERITED; +}; + +class StatusIter : public JsonCommon { +public: + StatusIter(const char* statusFile, const char* suffix, StatusFilter); + ~StatusIter() override {} + string baseDir(); + bool next(string* file, StatusFilter* filter); +private: + const char* fSuffix; + StatusFilter fFilter; +}; + +class HackParser : public ParserCommon { +public: + HackParser(const BmhParser& bmhParser) + : ParserCommon() + , fBmhParser(bmhParser) { + this->reset(); + } + + bool parseFromFile(const char* path) override { + if (!INHERITED::parseSetup(path)) { + return false; + } + return hackFiles(); + } + + void reset() override { + INHERITED::resetCommon(); + } + + void replaceWithPop(const Definition* ); + +private: + const BmhParser& fBmhParser; + bool hackFiles(); + + typedef ParserCommon INHERITED; +}; + +#endif diff --git a/tools/bookmaker/selfCheck.cpp b/tools/bookmaker/selfCheck.cpp index 5f1eb38ece..1c822c2d05 100644 --- a/tools/bookmaker/selfCheck.cpp +++ b/tools/bookmaker/selfCheck.cpp @@ -5,7 +5,8 @@ * found in the LICENSE file. */ -#include "bookmaker.h" +#include "bmhParser.h" +#include "selfCheck.h" #ifdef SK_BUILD_FOR_WIN #include <windows.h> diff --git a/tools/bookmaker/selfCheck.h b/tools/bookmaker/selfCheck.h new file mode 100644 index 0000000000..6e7bcf038b --- /dev/null +++ b/tools/bookmaker/selfCheck.h @@ -0,0 +1,15 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef selfCheck_DEFINED +#define selfCheck_DEFINED + +class BmhParser; + +bool SelfCheck(const BmhParser& ); + +#endif diff --git a/tools/bookmaker/spellCheck.cpp b/tools/bookmaker/spellCheck.cpp index 75a01f78dc..c6cfe5d079 100644 --- a/tools/bookmaker/spellCheck.cpp +++ b/tools/bookmaker/spellCheck.cpp @@ -5,8 +5,9 @@ * found in the LICENSE file. */ -#include "bookmaker.h" +#include "bmhParser.h" +#include "SkCommandLineFlags.h" #include "SkOSFile.h" #include "SkOSPath.h" @@ -369,7 +370,7 @@ bool SpellCheck::check(Definition* def) { } bool SpellCheck::checkable(MarkType markType) { - return BmhParser::Resolvable::kYes == fBmhParser.kMarkProps[(int) markType].fResolve; + return Resolvable::kYes == fBmhParser.kMarkProps[(int) markType].fResolve; } void SpellCheck::childCheck(Definition* def, const char* start) { diff --git a/tools/bookmaker/textParser.cpp b/tools/bookmaker/textParser.cpp new file mode 100644 index 0000000000..6ea4a2485e --- /dev/null +++ b/tools/bookmaker/textParser.cpp @@ -0,0 +1,190 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "definition.h" + +#ifdef SK_BUILD_FOR_WIN +#include <Windows.h> +#endif + +TextParser::TextParser(const Definition* definition) : + TextParser(definition->fFileName, definition->fContentStart, definition->fContentEnd, + definition->fLineCount) { +} + +string TextParser::ReportFilename(string file) { + string fullName; +#ifdef SK_BUILD_FOR_WIN + TCHAR pathChars[MAX_PATH]; + DWORD pathLen = GetCurrentDirectory(MAX_PATH, pathChars); + for (DWORD index = 0; index < pathLen; ++index) { + fullName += pathChars[index] == (char)pathChars[index] ? (char)pathChars[index] : '?'; + } + fullName += '\\'; +#endif + fullName += file; + return fullName; +} + +void TextParser::reportError(const char* errorStr) const { + this->reportWarning(errorStr); + SkDebugf(""); // convenient place to set a breakpoint +} + +void TextParser::reportWarning(const char* errorStr) const { + const char* lineStart = fLine; + if (lineStart >= fEnd) { + lineStart = fChar; + } + SkASSERT(lineStart < fEnd); + TextParser err(fFileName, lineStart, fEnd, fLineCount); + size_t lineLen = this->lineLength(); + ptrdiff_t spaces = fChar - lineStart; + while (spaces > 0 && (size_t) spaces > lineLen) { + ++err.fLineCount; + err.fLine += lineLen; + spaces -= lineLen; + lineLen = err.lineLength(); + } + string fullName = this->ReportFilename(fFileName); + SkDebugf("\n%s(%zd): error: %s\n", fullName.c_str(), err.fLineCount, errorStr); + if (0 == lineLen) { + SkDebugf("[blank line]\n"); + } else { + while (lineLen > 0 && '\n' == err.fLine[lineLen - 1]) { + --lineLen; + } + SkDebugf("%.*s\n", (int) lineLen, err.fLine); + SkDebugf("%*s^\n", (int) spaces, ""); + } +} + +void TextParser::setForErrorReporting(const Definition* definition, const char* str) { + fFileName = definition->fFileName; + fStart = definition->fContentStart; + fLine = str; + while (fLine > fStart && fLine[-1] != '\n') { + --fLine; + } + fChar = str; + fEnd = definition->fContentEnd; + fLineCount = definition->fLineCount; + const char* lineInc = fStart; + while (lineInc < str) { + fLineCount += '\n' == *lineInc++; + } +} + +string TextParser::typedefName() { + // look for typedef as one of three forms: + // typedef return-type (*NAME)(params); + // typedef alias NAME; + // typedef std::function<alias> NAME; + string builder; + const char* end = this->doubleLF(); + if (!end) { + end = fEnd; + } + const char* altEnd = this->strnstr("#Typedef ##", end); + if (altEnd) { + end = this->strnchr('\n', end); + } + if (!end) { + return this->reportError<string>("missing typedef std::function end bracket >"); + } + bool stdFunction = this->startsWith("std::function"); + if (stdFunction) { + if (!this->skipToEndBracket('>')) { + return this->reportError<string>("missing typedef std::function end bracket >"); + } + this->next(); + this->skipWhiteSpace(); + builder += string(fChar, end - fChar); + } else { + const char* paren = this->strnchr('(', end); + if (!paren) { + const char* lastWord = nullptr; + do { + this->skipToWhiteSpace(); + if (fChar < end && isspace(fChar[0])) { + const char* whiteStart = fChar; + this->skipWhiteSpace(); + // FIXME: test should be for fMC + if ('#' == fChar[0]) { + end = whiteStart; + break; + } + lastWord = fChar; + } else { + break; + } + } while (true); + if (!lastWord) { + return this->reportError<string>("missing typedef name"); + } + builder += string(lastWord, end - lastWord); + } else { + this->skipTo(paren); + this->next(); + if ('*' != this->next()) { + return this->reportError<string>("missing typedef function asterisk"); + } + const char* nameStart = fChar; + if (!this->skipToEndBracket(')')) { + return this->reportError<string>("missing typedef function )"); + } + builder += string(nameStart, fChar - nameStart); + if (!this->skipToEndBracket('(')) { + return this->reportError<string>("missing typedef params ("); + } + if (! this->skipToEndBracket(')')) { + return this->reportError<string>("missing typedef params )"); + } + this->skipTo(end); + } + } + return builder; +} + +void MethodParser::skipToMethodEnd(Resolvable resolvable) { + if (this->eof()) { + return; + } + string name = fLocalName.length() ? fLocalName : fClassName; + if ('~' == this->peek()) { + this->next(); + if (!this->startsWith(name.c_str())) { + --fChar; + return; + } + } + if (Resolvable::kSimple != resolvable + && Resolvable::kInclude != resolvable + && (this->startsWith(name.c_str()) || this->startsWith("operator"))) { + const char* ptr = this->anyOf("\n ("); + if (ptr && '(' == *ptr && strncmp(ptr, "(...", 4)) { + this->skipToEndBracket(')'); + SkAssertResult(')' == this->next()); + this->skipExact("_const") || (Resolvable::kCode == resolvable + && this->skipExact(" const")); + return; + } + } + if (this->startsWith("Sk") && this->wordEndsWith(".h")) { // allow include refs + this->skipToNonName(); + } else { + this->skipFullName(); + if (this->endsWith("operator")) { + const char* ptr = this->anyOf("\n ("); + if (ptr && '(' == *ptr) { + this->skipToEndBracket(')'); + SkAssertResult(')' == this->next()); + this->skipExact("_const"); + } + } + } +} diff --git a/tools/bookmaker/textParser.h b/tools/bookmaker/textParser.h new file mode 100644 index 0000000000..eee1f7cfad --- /dev/null +++ b/tools/bookmaker/textParser.h @@ -0,0 +1,733 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef textParser_DEFINED +#define textParser_DEFINED + +#include <functional> + +#include "bookmaker.h" + +class BmhParser; +class Definition; + +class TextParser : public NonAssignable { + TextParser() {} // only for ParserCommon, TextParserSave + friend class ParserCommon; + friend class TextParserSave; +public: + virtual ~TextParser() {} + + TextParser(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; + } + + bool back(const char* pattern) { + size_t len = strlen(pattern); + const char* start = fChar - len; + if (start <= fStart) { + return false; + } + if (strncmp(start, pattern, len)) { + return false; + } + fChar = start; + return true; + } + + 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]; + } + + void backupWord() { + while (fChar > fStart && isalpha(fChar[-1])) { + --fChar; + } + } + + bool contains(const char* match, const char* lineEnd, const char** loc) const { + const char* result = this->strnstr(match, lineEnd); + if (loc) { + *loc = result; + } + return result; + } + + bool containsWord(const char* match, const char* lineEnd, const char** loc) { + size_t len = strlen(match); + do { + const char* result = this->strnstr(match, lineEnd); + if (!result) { + return false; + } + if ((result > fStart && isalnum(result[-1])) || (result + len < fEnd + && isalnum(result[len]))) { + fChar = result + len; + continue; + } + if (loc) { + *loc = result; + } + return true; + } while (true); + } + + // either /n/n or /n# will stop parsing a typedef + const char* doubleLF() const { + const char* ptr = fChar - 1; + const char* doubleStart = nullptr; + while (++ptr < fEnd) { + if (!doubleStart) { + if ('\n' == ptr[0]) { + doubleStart = ptr; + } + continue; + } + if ('\n' == ptr[0] || '#' == ptr[0]) { + return doubleStart; + } + if (' ' < ptr[0]) { + doubleStart = nullptr; + } + } + return nullptr; + } + + bool endsWith(const char* match) { + int matchLen = strlen(match); + if (matchLen > fChar - fLine) { + return false; + } + return !strncmp(fChar - matchLen, match, matchLen); + } + + 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; + static string ReportFilename(string file); + void reportWarning(const char* errorStr) const; + + template <typename T> 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; + } + + void setForErrorReporting(const Definition* , const char* ); + + bool skipToBalancedEndBracket(char startB, char endB) { + SkASSERT(fChar < fEnd); + SkASSERT(startB == fChar[0]); + int startCount = 0; + do { + char test = this->next(); + startCount += startB == test; + startCount -= endB == test; + } while (startCount && fChar < fEnd); + return !startCount; + } + + 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++; + } + } + + // returns true if saw close brace + bool skipToAlphaNum() { + bool sawCloseBrace = false; + while (fChar < fEnd && !isalnum(fChar[0])) { + sawCloseBrace |= '}' == *fChar++; + } + return sawCloseBrace; + } + + 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() { + do { + char last = '\0'; + 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++; + } + last = fChar[0]; + fChar++; + } + if (fChar + 1 >= fEnd || '/' != fChar[0] || !isalpha(last) || !isalpha(fChar[1])) { + break; // stop unless pattern is xxx/xxx as in I/O + } + fChar++; // skip slash + } while (true); + } + + int skipToLineBalance(char open, char close) { + int match = 0; + while (!this->eof() && '\n' != fChar[0]) { + match += open == this->peek(); + match -= close == this->next(); + } + return match; + } + + bool skipToLineStart() { + if (!this->skipLine()) { + return false; + } + if (!this->eof()) { + return this->skipWhiteSpace(); + } + return true; + } + + void skipToLineStart(int* indent, bool* sawReturn) { + SkAssertResult(this->skipLine()); + this->skipWhiteSpace(indent, sawReturn); + } + + void skipLower() { + while (fChar < fEnd && (islower(fChar[0]) || '_' == fChar[0])) { + fChar++; + } + } + + void skipToNonAlphaNum() { + while (fChar < fEnd && (isalnum(fChar[0]) || '_' == fChar[0])) { + fChar++; + } + } + + void skipToNonName() { + 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 skipPhraseName() { + while (fChar < fEnd && (islower(fChar[0]) || '_' == fChar[0])) { + fChar++; + } + } + + void skipToSpace() { + while (fChar < fEnd && ' ' != fChar[0]) { + fChar++; + } + } + + void skipToWhiteSpace() { + 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)) { + for (size_t i = 0; i < len; ++i) { + this->next(); + } + } + 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; + } + + void skipWhiteSpace(int* indent, bool* skippedReturn) { + while (' ' >= this->peek()) { + *indent = *skippedReturn ? *indent + 1 : 1; + if ('\n' == this->peek()) { + *skippedReturn |= true; + *indent = 0; + } + (void) this->next(); + SkASSERT(fChar < fEnd); + } + } + + bool startsWith(const char* str) const { + size_t len = strlen(str); + ptrdiff_t lineLen = fEnd - fChar; + 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) const { + int max = (int) (this->lineLength()); + 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* trimmedBracketEnd(string bracket) const { + size_t max = (size_t) (this->lineLength()); + string line(fChar, max); + size_t index = line.find(bracket); + SkASSERT(index < max); + while (index > 0 && ' ' >= fChar[index - 1]) { + --index; + } + return fChar + index; + } + + const char* trimmedBracketNoEnd(const char bracket) const { + int max = (int) (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; + } + } + + // FIXME: nothing else in TextParser knows from C++ -- + // there could be a class between TextParser and ParserCommon + virtual string typedefName(); + + 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 TextParserSave { +public: + TextParserSave(TextParser* parser) { + fParser = parser; + fSave.fFileName = parser->fFileName; + fSave.fStart = parser->fStart; + fSave.fLine = parser->fLine; + fSave.fChar = parser->fChar; + fSave.fEnd = parser->fEnd; + fSave.fLineCount = parser->fLineCount; + } + + void restore() const { + fParser->fFileName = fSave.fFileName; + fParser->fStart = fSave.fStart; + fParser->fLine = fSave.fLine; + fParser->fChar = fSave.fChar; + fParser->fEnd = fSave.fEnd; + fParser->fLineCount = fSave.fLineCount; + } + +private: + TextParser* fParser; + TextParser fSave; +}; + +static inline bool has_nonwhitespace(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<int, int>(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<int, int>(std::isspace)))); +} + +static inline void trim_start_end(string& s) { + trim_start(s); + trim_end(s); +} + +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') || + (reader[0] >= 'a' && reader[0] <= 'f')); + int nibble = *reader++ - '0'; + if (nibble > 9) { + nibble = (nibble & ~('a' - 'A')) - 'A' + '9' + 1; + } + unicode |= nibble; + } + SkASSERT(unicode < 256); + *writer++ = (unsigned char) unicode; + } else { + SkASSERT(ctrl == 'n'); + *writer++ = '\n'; + } + } + } + fStart = fLine = fChar = fStorage; + fEnd = writer; + } + + ~EscapeParser() override { + delete fStorage; + } +private: + char* fStorage; +}; + +// some methods cannot be trivially parsed; look for class-name / ~ / operator +class MethodParser : public TextParser { +public: + MethodParser(string className, string fileName, + const char* start, const char* end, int lineCount) + : TextParser(fileName, start, end, lineCount) + , fClassName(className) { + size_t doubleColons = className.find_last_of("::"); + if (string::npos != doubleColons) { + fLocalName = className.substr(doubleColons + 1); + SkASSERT(fLocalName.length() > 0); + } + } + + ~MethodParser() override {} + + string localName() const { + return fLocalName; + } + + void setLocalName(string name) { + if (name == fClassName) { + fLocalName = ""; + } else { + fLocalName = name; + } + } + + // returns true if close brace was skipped + int skipToMethodStart() { + if (!fClassName.length()) { + return this->skipToAlphaNum(); + } + int braceCount = 0; + while (!this->eof() && !isalnum(this->peek()) && '~' != this->peek()) { + braceCount += '{' == this->peek(); + braceCount -= '}' == this->peek(); + this->next(); + } + return braceCount; + } + + void skipToMethodEnd(Resolvable resolvable); + + 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; + string fLocalName; + typedef TextParser INHERITED; +}; + +#endif |