• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2018 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "bmhParser.h"
9 
10 const string kSpellingFileName("spelling.txt");
11 
12 #define M(mt) (1LL << (int) MarkType::k##mt)
13 #define M_D M(Description)
14 #define M_CS M(Class) | M(Struct)
15 #define M_MD M(Method) | M(Define)
16 #define M_MDCM M_MD | M(Const) | M(Member)
17 #define M_ST M(Subtopic) | M(Topic)
18 #define M_CSST M_CS | M_ST
19 #ifdef M_E
20 #undef M_E
21 #endif
22 #define M_E M(Enum) | M(EnumClass)
23 
24 #define R_Y Resolvable::kYes
25 #define R_N Resolvable::kNo
26 #define R_O Resolvable::kOut
27 #define R_K Resolvable::kCode
28 #define R_F Resolvable::kFormula
29 #define R_C Resolvable::kClone
30 
31 #define E_Y Exemplary::kYes
32 #define E_N Exemplary::kNo
33 #define E_O Exemplary::kOptional
34 
35 // ToDo: add column to denote which marks are one-liners
36 BmhParser::MarkProps BmhParser::kMarkProps[] = {
37 // names without formal definitions (e.g. Column) aren't included
38   { "",             MarkType::kNone,         R_Y, E_N, 0 }
39 , { "A",            MarkType::kAnchor,       R_N, E_N, 0 }
40 , { "Alias",        MarkType::kAlias,        R_N, E_N, M_ST | M(Const) }
41 , { "Bug",          MarkType::kBug,          R_N, E_N, M_CSST | M_MDCM | M_E
42                                                      | M(Example) | M(NoExample) }
43 , { "Class",        MarkType::kClass,        R_Y, E_O, M_CSST }
44 , { "Code",         MarkType::kCode,         R_K, E_N, M_CSST | M_E | M_MD | M(Typedef) }
45 , { "",             MarkType::kColumn,       R_Y, E_N, M(Row) }
46 , { "",             MarkType::kComment,      R_N, E_N, 0 }
47 , { "Const",        MarkType::kConst,        R_Y, E_O, M_E | M_CSST  }
48 , { "Define",       MarkType::kDefine,       R_O, E_Y, M_ST }
49 , { "Description",  MarkType::kDescription,  R_Y, E_N, M(Example) | M(NoExample) }
50 , { "Details",      MarkType::kDetails,      R_N, E_N, M(Const) }
51 , { "Duration",     MarkType::kDuration,     R_N, E_N, M(Example) | M(NoExample) }
52 , { "Enum",         MarkType::kEnum,         R_Y, E_O, M_CSST }
53 , { "EnumClass",    MarkType::kEnumClass,    R_Y, E_O, M_CSST }
54 , { "Example",      MarkType::kExample,      R_O, E_N, M_CSST | M_E | M_MD | M(Const) }
55 , { "External",     MarkType::kExternal,     R_Y, E_N, 0 }
56 , { "File",         MarkType::kFile,         R_Y, E_N, M(Topic) }
57 , { "Filter",       MarkType::kFilter,       R_N, E_N, M(Subtopic) | M(Code) }
58 , { "Formula",      MarkType::kFormula,      R_F, E_N, M(Column) | M(Description)
59                                                      | M_E | M_ST | M_MDCM }
60 , { "Function",     MarkType::kFunction,     R_O, E_N, M(Example) | M(NoExample) }
61 , { "Height",       MarkType::kHeight,       R_N, E_N, M(Example) | M(NoExample) }
62 , { "Illustration", MarkType::kIllustration, R_N, E_N, M_CSST | M_MD }
63 , { "Image",        MarkType::kImage,        R_N, E_N, M(Example) | M(NoExample) }
64 , { "In",           MarkType::kIn,           R_N, E_N, M_CSST | M_E | M(Method) | M(Typedef) | M(Code) }
65 , { "Legend",       MarkType::kLegend,       R_Y, E_N, M(Table) }
66 , { "Line",         MarkType::kLine,         R_N, E_N, M_CSST | M_E | M(Method) | M(Typedef) }
67 , { "",             MarkType::kLink,         R_N, E_N, M(Anchor) }
68 , { "List",         MarkType::kList,         R_Y, E_N, M(Method) | M_CSST | M_E | M_D }
69 , { "Literal",      MarkType::kLiteral,      R_N, E_N, M(Code) }
70 , { "",             MarkType::kMarkChar,     R_N, E_N, 0 }
71 , { "Member",       MarkType::kMember,       R_Y, E_O, M_CSST }
72 , { "Method",       MarkType::kMethod,       R_Y, E_Y, M_CSST }
73 , { "NoExample",    MarkType::kNoExample,    R_N, E_N, M_CSST | M_E | M_MD }
74 , { "NoJustify",    MarkType::kNoJustify,    R_N, E_N, M(Const) | M(Member) }
75 , { "Outdent",      MarkType::kOutdent,      R_N, E_N, M(Code) }
76 , { "Param",        MarkType::kParam,        R_Y, E_N, M(Method) | M(Define) }
77 , { "PhraseDef",    MarkType::kPhraseDef,    R_Y, E_N, M_ST }
78 , { "",             MarkType::kPhraseParam,  R_Y, E_N, 0 }
79 , { "",             MarkType::kPhraseRef,    R_N, E_N, 0 }
80 , { "Platform",     MarkType::kPlatform,     R_N, E_N, M(Example) | M(NoExample) }
81 , { "Populate",     MarkType::kPopulate,     R_N, E_N, M(Code) | M(Method) }
82 , { "Return",       MarkType::kReturn,       R_Y, E_N, M(Method) }
83 , { "",             MarkType::kRow,          R_Y, E_N, M(Table) | M(List) }
84 , { "SeeAlso",      MarkType::kSeeAlso,      R_C, E_N, M_CSST | M_E | M_MD | M(Typedef) }
85 , { "Set",          MarkType::kSet,          R_N, E_N, M(Example) | M(NoExample) }
86 , { "StdOut",       MarkType::kStdOut,       R_N, E_N, M(Example) | M(NoExample) }
87 , { "Struct",       MarkType::kStruct,       R_Y, E_O, M(Class) | M_ST }
88 , { "Substitute",   MarkType::kSubstitute,   R_N, E_N, M(Alias) | M_ST }
89 , { "Subtopic",     MarkType::kSubtopic,     R_Y, E_Y, M_CSST | M_E }
90 , { "Table",        MarkType::kTable,        R_Y, E_N, M(Method) | M_CSST | M_E }
91 , { "Template",     MarkType::kTemplate,     R_Y, E_N, M_CSST }
92 , { "",             MarkType::kText,         R_N, E_N, 0 }
93 , { "ToDo",         MarkType::kToDo,         R_N, E_N, 0 }
94 , { "Topic",        MarkType::kTopic,        R_Y, E_Y, 0 }
95 , { "Typedef",      MarkType::kTypedef,      R_Y, E_O, M_CSST | M_E }
96 , { "Union",        MarkType::kUnion,        R_Y, E_N, M_CSST }
97 , { "Using",        MarkType::kUsing,        R_Y, E_O, M_CSST }
98 , { "Volatile",     MarkType::kVolatile,     R_N, E_N, M(StdOut) }
99 , { "Width",        MarkType::kWidth,        R_N, E_N, M(Example) | M(NoExample) }
100 };
101 
102 #undef R_O
103 #undef R_N
104 #undef R_Y
105 #undef R_K
106 #undef R_F
107 #undef R_C
108 
109 #undef M_E
110 #undef M_CSST
111 #undef M_ST
112 #undef M_CS
113 #undef M_MCD
114 #undef M_D
115 #undef M
116 
117 #undef E_Y
118 #undef E_N
119 #undef E_O
120 
addDefinition(const char * defStart,bool hasEnd,MarkType markType,const vector<string> & typeNameBuilder,HasTag hasTag)121 bool BmhParser::addDefinition(const char* defStart, bool hasEnd, MarkType markType,
122         const vector<string>& typeNameBuilder, HasTag hasTag) {
123     Definition* definition = nullptr;
124     switch (markType) {
125         case MarkType::kComment:
126             if (!this->skipToDefinitionEnd(markType)) {
127                 return false;
128             }
129             return true;
130         // these types may be referred to by name
131         case MarkType::kClass:
132         case MarkType::kStruct:
133         case MarkType::kConst:
134         case MarkType::kDefine:
135         case MarkType::kEnum:
136         case MarkType::kEnumClass:
137         case MarkType::kMember:
138         case MarkType::kMethod:
139         case MarkType::kTemplate:
140         case MarkType::kTypedef: {
141             if (!typeNameBuilder.size()) {
142                 return this->reportError<bool>("unnamed markup");
143             }
144             if (typeNameBuilder.size() > 1) {
145                 return this->reportError<bool>("expected one name only");
146             }
147             string name = typeNameBuilder[0];
148             if (nullptr == fRoot) {
149                 fRoot = this->findBmhObject(markType, name);
150                 fRoot->fFileName = fFileName;
151                 fRoot->fName = name;
152                 fRoot->fNames.fName = name;
153                 fRoot->fNames.fParent = &fGlobalNames;
154                 definition = fRoot;
155             } else {
156                 if (nullptr == fParent) {
157                     return this->reportError<bool>("expected parent");
158                 }
159                 if (fParent == fRoot && hasEnd) {
160                     RootDefinition* rootParent = fRoot->rootParent();
161                     if (rootParent) {
162                         fRoot = rootParent;
163                     }
164                     definition = fParent;
165                 } else {
166                     if (!hasEnd && fRoot->find(name, RootDefinition::AllowParens::kNo)) {
167                         return this->reportError<bool>("duplicate symbol");
168                     }
169                     if (MarkType::kStruct == markType || MarkType::kClass == markType
170                             || MarkType::kEnumClass == markType) {
171                         // if class or struct, build fRoot hierarchy
172                         // and change isDefined to search all parents of fRoot
173                         SkASSERT(!hasEnd);
174                         RootDefinition* childRoot = new RootDefinition;
175                         (fRoot->fBranches)[name] = childRoot;
176                         childRoot->setRootParent(fRoot);
177                         childRoot->fFileName = fFileName;
178                         SkASSERT(MarkType::kSubtopic != fRoot->fMarkType
179                                 && MarkType::kTopic != fRoot->fMarkType);
180                         childRoot->fNames.fName = name;
181                         childRoot->fNames.fParent = &fRoot->fNames;
182                         fRoot = childRoot;
183                         definition = fRoot;
184                     } else {
185                         definition = &fRoot->fLeaves[name];
186                     }
187                 }
188             }
189             if (hasEnd) {
190                 Exemplary hasExample = Exemplary::kNo;
191                 bool hasExcluder = false;
192                 for (auto child : definition->fChildren) {
193                      if (MarkType::kExample == child->fMarkType) {
194                         hasExample = Exemplary::kYes;
195                      }
196                      hasExcluder |= MarkType::kNoExample == child->fMarkType;
197                 }
198                 if (kMarkProps[(int) markType].fExemplary != hasExample
199                         && kMarkProps[(int) markType].fExemplary != Exemplary::kOptional) {
200                     if (string::npos == fFileName.find("undocumented")
201                             && !hasExcluder) {
202                         hasExample == Exemplary::kNo ?
203                                 this->reportWarning("missing example") :
204                                 this->reportWarning("unexpected example");
205                     }
206 
207                 }
208                 if (MarkType::kMethod == markType) {
209                     if (fCheckMethods && !definition->checkMethod()) {
210                         return false;
211                     }
212                 }
213                 if (HasTag::kYes == hasTag) {
214                     if (!this->checkEndMarker(markType, definition->fName)) {
215                         return false;
216                     }
217                 }
218                 if (!this->popParentStack(definition)) {
219                     return false;
220                 }
221                 if (fRoot == definition) {
222                     fRoot = nullptr;
223                 }
224             } else {
225                 definition->fStart = defStart;
226                 this->skipSpace();
227                 definition->fFileName = fFileName;
228                 definition->fContentStart = fChar;
229                 definition->fLineCount = fLineCount;
230                 definition->fClone = fCloned;
231                 if (MarkType::kConst == markType) {
232                     // todo: require that fChar points to def on same line as markup
233                     // additionally add definition to class children if it is not already there
234                     if (definition->fParent != fRoot) {
235 //                        fRoot->fChildren.push_back(definition);
236                     }
237                 }
238                 SkASSERT(string::npos == name.find('\n'));
239                 definition->fName = name;
240                 if (MarkType::kMethod == markType) {
241                     if (string::npos != name.find(':', 0)) {
242                         definition->setCanonicalFiddle();
243                     } else {
244                         definition->fFiddle = name;
245                     }
246                 } else {
247                     definition->fFiddle = Definition::NormalizedName(name);
248                 }
249                 definition->fMarkType = markType;
250                 definition->fAnonymous = fAnonymous;
251                 this->setAsParent(definition);
252             }
253             } break;
254         case MarkType::kTopic:
255         case MarkType::kSubtopic:
256             SkASSERT(1 == typeNameBuilder.size());
257             if (!hasEnd) {
258                 if (!typeNameBuilder.size()) {
259                     return this->reportError<bool>("unnamed topic");
260                 }
261                 fTopics.emplace_front(markType, defStart, fLineCount, fParent, fMC);
262                 RootDefinition* rootDefinition = &fTopics.front();
263                 definition = rootDefinition;
264                 definition->fFileName = fFileName;
265                 definition->fContentStart = fChar;
266                 if (MarkType::kTopic == markType) {
267                     if (fParent) {
268                         return this->reportError<bool>("#Topic must be root");
269                     }
270                     // topic name is unappended
271                     definition->fName = typeNameBuilder[0];
272                 } else {
273                     if (!fParent) {
274                         return this->reportError<bool>("#Subtopic may not be root");
275                     }
276                     Definition* parent = fParent;
277                     while (MarkType::kTopic != parent->fMarkType && MarkType::kSubtopic != parent->fMarkType) {
278                         parent = parent->fParent;
279                         if (!parent) {
280                             // subtopic must have subtopic or topic in parent chain
281                             return this->reportError<bool>("#Subtopic missing parent");
282                         }
283                     }
284                     if (MarkType::kSubtopic == parent->fMarkType) {
285                         // subtopic prepends parent subtopic name, but not parent topic name
286                         definition->fName = parent->fName + '_';
287                     }
288                     definition->fName += typeNameBuilder[0];
289                     definition->fFiddle = parent->fFiddle + '_';
290                 }
291                 rootDefinition->fNames.fName = rootDefinition->fName;
292                 rootDefinition->fNames.fParent = &fGlobalNames;
293                 definition->fFiddle += Definition::NormalizedName(typeNameBuilder[0]);
294                 this->setAsParent(definition);
295             }
296             {
297                 SkASSERT(hasEnd ? fParent : definition);
298                 string fullTopic = hasEnd ? fParent->fFiddle : definition->fFiddle;
299                 Definition* defPtr = fTopicMap[fullTopic];
300                 if (hasEnd) {
301                     if (HasTag::kYes == hasTag && !this->checkEndMarker(markType, fullTopic)) {
302                         return false;
303                     }
304                     if (!definition) {
305                         definition = defPtr;
306                     } else if (definition != defPtr) {
307                         return this->reportError<bool>("mismatched topic");
308                     }
309                 } else {
310                     if (nullptr != defPtr) {
311                         return this->reportError<bool>("already declared topic");
312                     }
313                     fTopicMap[fullTopic] = definition;
314                 }
315             }
316             if (hasEnd) {
317                 if (!this->popParentStack(definition)) {
318                     return false;
319                 }
320             }
321             break;
322         case MarkType::kFormula:
323             // hasEnd : single line / multiple line
324             if (!fParent || MarkType::kFormula != fParent->fMarkType) {
325                 SkASSERT(!definition || MarkType::kFormula == definition->fMarkType);
326                 fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC);
327                 definition = &fMarkup.front();
328                 definition->fContentStart = fChar;
329                 definition->fName = typeNameBuilder[0];
330                 definition->fFiddle = fParent->fFiddle;
331                 fParent = definition;
332             } else {
333                 SkASSERT(fParent && MarkType::kFormula == fParent->fMarkType);
334                 SkASSERT(fMC == defStart[0]);
335                 SkASSERT(fMC == defStart[-1]);
336                 definition = fParent;
337                 definition->fTerminator = fChar;
338                 if (!this->popParentStack(definition)) {
339                     return false;
340                 }
341                 this->parseHashFormula(definition);
342                 fParent->fChildren.push_back(definition);
343             }
344             break;
345         // these types are children of parents, but are not in named maps
346         case MarkType::kDescription:
347         case MarkType::kStdOut:
348         // may be one-liner
349         case MarkType::kAlias:
350         case MarkType::kNoExample:
351         case MarkType::kParam:
352         case MarkType::kPhraseDef:
353         case MarkType::kReturn:
354         case MarkType::kToDo:
355             if (hasEnd) {
356                 if (markType == fParent->fMarkType) {
357                     definition = fParent;
358                     if (MarkType::kBug == markType || MarkType::kReturn == markType
359                             || MarkType::kToDo == markType) {
360                         this->skipNoName();
361                     }
362                     if (!this->popParentStack(fParent)) { // if not one liner, pop
363                         return false;
364                     }
365                     if (MarkType::kParam == markType || MarkType::kReturn == markType
366                             || MarkType::kPhraseDef == markType) {
367                         if (!this->checkParamReturn(definition)) {
368                             return false;
369                         }
370                     }
371                     if (MarkType::kPhraseDef == markType) {
372                         string key = definition->fName;
373                         if (fPhraseMap.end() != fPhraseMap.find(key)) {
374                             this->reportError<bool>("duplicate phrase key");
375                         }
376                         fPhraseMap[key] = definition;
377                     }
378                 } else {
379                     fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC);
380                     definition = &fMarkup.front();
381                     definition->fName = typeNameBuilder[0];
382                     definition->fFiddle = fParent->fFiddle;
383                     definition->fContentStart = fChar;
384                     string endBracket;
385                     endBracket += fMC;
386                     endBracket += fMC;
387                     definition->fContentEnd = this->trimmedBracketEnd(endBracket);
388                     this->skipToEndBracket(endBracket.c_str());
389                     SkAssertResult(fMC == this->next());
390                     SkAssertResult(fMC == this->next());
391                     definition->fTerminator = fChar;
392                     TextParser checkForChildren(definition);
393                     if (checkForChildren.strnchr(fMC, definition->fContentEnd)) {
394                         this->reportError<bool>("put ## on separate line");
395                     }
396                     fParent->fChildren.push_back(definition);
397                 }
398                 if (MarkType::kAlias == markType) {
399                     const char* end = definition->fChildren.size() > 0 ?
400                             definition->fChildren[0]->fStart : definition->fContentEnd;
401                     TextParser parser(definition->fFileName, definition->fContentStart, end,
402                             definition->fLineCount);
403                     parser.trimEnd();
404                     string key = string(parser.fStart, parser.lineLength());
405                     if (fAliasMap.end() != fAliasMap.find(key)) {
406                         return this->reportError<bool>("duplicate alias");
407                     }
408                     fAliasMap[key] = definition;
409                     definition->fFiddle = definition->fParent->fFiddle;
410                 }
411                 break;
412             } else if (MarkType::kPhraseDef == markType) {
413                 bool hasParams = '(' == this->next();
414                 fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC);
415                 definition = &fMarkup.front();
416                 definition->fName = typeNameBuilder[0];
417                 definition->fFiddle = fParent->fFiddle;
418                 definition->fContentStart = fChar;
419                 if (hasParams) {
420                     char lastChar;
421                     do {
422                         const char* subEnd = this->anyOf(",)\n");
423                         if (!subEnd || '\n' == *subEnd) {
424                             return this->reportError<bool>("unexpected phrase list end");
425                         }
426                         fMarkup.emplace_front(MarkType::kPhraseParam, fChar, fLineCount, fParent,
427                                 fMC);
428                         Definition* phraseParam = &fMarkup.front();
429                         phraseParam->fContentStart = fChar;
430                         phraseParam->fContentEnd = subEnd;
431                         phraseParam->fName = string(fChar, subEnd - fChar);
432                         definition->fChildren.push_back(phraseParam);
433                         this->skipTo(subEnd);
434                         lastChar = this->next();
435                         phraseParam->fTerminator = fChar;
436                     } while (')' != lastChar);
437                     this->skipWhiteSpace();
438                     definition->fContentStart = fChar;
439                 }
440                 this->setAsParent(definition);
441                 break;
442             }
443         // not one-liners
444         case MarkType::kCode:
445         case MarkType::kExample:
446         case MarkType::kFile:
447         case MarkType::kFunction:
448         case MarkType::kLegend:
449         case MarkType::kList:
450         case MarkType::kTable:
451             if (hasEnd) {
452                 definition = fParent;
453                 if (markType != fParent->fMarkType) {
454                     return this->reportError<bool>("end element mismatch");
455                 } else if (!this->popParentStack(fParent)) {
456                     return false;
457                 }
458                 if (MarkType::kExample == markType) {
459                     if (definition->fChildren.size() == 0) {
460                         TextParser emptyCheck(definition);
461                         if (emptyCheck.eof() || !emptyCheck.skipWhiteSpace()) {
462                             return this->reportError<bool>("missing example body");
463                         }
464                     }
465 // can't do this here; phrase refs may not have been defined yet
466 //                    this->setWrapper(definition);
467                 }
468             } else {
469                 fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC);
470                 definition = &fMarkup.front();
471                 definition->fContentStart = fChar;
472                 definition->fName = typeNameBuilder[0];
473                 definition->fFiddle = fParent->fFiddle;
474                 char suffix = '\0';
475                 bool tryAgain;
476                 do {
477                     tryAgain = false;
478                     for (const auto& child : fParent->fChildren) {
479                         if (child->fFiddle == definition->fFiddle) {
480                             if (MarkType::kExample != child->fMarkType) {
481                                 continue;
482                             }
483                             if ('\0' == suffix) {
484                                 suffix = 'a';
485                             } else if (++suffix > 'z') {
486                                 return reportError<bool>("too many examples");
487                             }
488                             definition->fFiddle = fParent->fFiddle + '_';
489                             definition->fFiddle += suffix;
490                             tryAgain = true;
491                             break;
492                         }
493                     }
494                 } while (tryAgain);
495                 this->setAsParent(definition);
496             }
497             break;
498             // always treated as one-liners (can't detect misuse easily)
499         case MarkType::kAnchor:
500         case MarkType::kBug:
501         case MarkType::kDetails:
502         case MarkType::kDuration:
503         case MarkType::kFilter:
504         case MarkType::kHeight:
505         case MarkType::kIllustration:
506         case MarkType::kImage:
507 		case MarkType::kIn:
508 		case MarkType::kLine:
509 		case MarkType::kLiteral:
510         case MarkType::kNoJustify:
511         case MarkType::kOutdent:
512         case MarkType::kPlatform:
513         case MarkType::kPopulate:
514         case MarkType::kSeeAlso:
515         case MarkType::kSet:
516         case MarkType::kSubstitute:
517         case MarkType::kVolatile:
518         case MarkType::kWidth:
519             // todo : add check disallowing children?
520             if (hasEnd && MarkType::kAnchor != markType && MarkType::kLine != markType) {
521                 return this->reportError<bool>("one liners omit end element");
522             } else if (!hasEnd && MarkType::kAnchor == markType) {
523                 return this->reportError<bool>("anchor line must have end element last");
524             }
525             fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC);
526             definition = &fMarkup.front();
527             definition->fName = typeNameBuilder[0];
528             definition->fFiddle = Definition::NormalizedName(typeNameBuilder[0]);
529             definition->fContentStart = fChar;
530             definition->fContentEnd = this->trimmedBracketEnd('\n');
531             definition->fTerminator = this->lineEnd() - 1;
532             fParent->fChildren.push_back(definition);
533             if (MarkType::kAnchor == markType) {
534                 this->parseHashAnchor(definition);
535 			} else if (MarkType::kLine == markType) {
536                 this->parseHashLine(definition);
537 			}
538             break;
539         case MarkType::kExternal:
540             (void) this->collectExternals();  // FIXME: detect errors in external defs?
541             break;
542         default:
543             SkASSERT(0);  // fixme : don't let any types be invisible
544             return true;
545     }
546     if (fParent) {
547         SkASSERT(definition);
548         SkASSERT(definition->fName.length() > 0);
549     }
550     return true;
551 }
552 
reportDuplicates(const Definition & def,string dup) const553 void BmhParser::reportDuplicates(const Definition& def, string dup) const {
554     if (MarkType::kExample == def.fMarkType && dup == def.fFiddle) {
555         TextParser reporter(&def);
556         reporter.reportError("duplicate example name");
557     }
558     for (auto& child : def.fChildren ) {
559         reportDuplicates(*child, dup);
560     }
561 }
562 
563 
find_fiddle(Definition * def,string name)564 static Definition* find_fiddle(Definition* def, string name) {
565     if (MarkType::kExample == def->fMarkType && name == def->fFiddle) {
566         return def;
567     }
568     for (auto& child : def->fChildren) {
569         Definition* result = find_fiddle(child, name);
570         if (result) {
571             return result;
572         }
573     }
574     return nullptr;
575 }
576 
findExample(string name) const577 Definition* BmhParser::findExample(string name) const {
578     for (const auto& topic : fTopicMap) {
579         if (topic.second->fParent) {
580             continue;
581         }
582         Definition* def = find_fiddle(topic.second, name);
583         if (def) {
584             return def;
585         }
586     }
587     return nullptr;
588 }
589 
check_example_hashes(Definition * def)590 static bool check_example_hashes(Definition* def) {
591     if (MarkType::kExample == def->fMarkType) {
592         if (def->fHash.length()) {
593             return true;
594         }
595         for (auto child : def->fChildren) {
596             if (MarkType::kPlatform == child->fMarkType) {
597                 if (string::npos != string(child->fContentStart, child->length()).find("!fiddle")) {
598                     return true;
599                 }
600             }
601         }
602         return def->reportError<bool>("missing hash");
603     }
604     for (auto& child : def->fChildren) {
605         if (!check_example_hashes(child)) {
606             return false;
607         }
608     }
609     return true;
610 }
611 
checkExampleHashes() const612 bool BmhParser::checkExampleHashes() const {
613     for (const auto& topic : fTopicMap) {
614         if (!topic.second->fParent && !check_example_hashes(topic.second)) {
615             return false;
616         }
617     }
618     return true;
619 }
620 
reset_example_hashes(Definition * def)621 static void reset_example_hashes(Definition* def) {
622     if (MarkType::kExample == def->fMarkType) {
623         def->fHash.clear();
624         return;
625     }
626     for (auto& child : def->fChildren) {
627         reset_example_hashes(child);
628     }
629 }
630 
resetExampleHashes()631 void BmhParser::resetExampleHashes() {
632     for (const auto& topic : fTopicMap) {
633         if (!topic.second->fParent) {
634             reset_example_hashes(topic.second);
635         }
636     }
637 }
638 
find_examples(const Definition & def,vector<string> * exampleNames)639 static void find_examples(const Definition& def, vector<string>* exampleNames) {
640     if (MarkType::kExample == def.fMarkType) {
641         exampleNames->push_back(def.fFiddle);
642     }
643     for (auto& child : def.fChildren ) {
644         find_examples(*child, exampleNames);
645     }
646 }
647 
checkEndMarker(MarkType markType,string match) const648 bool BmhParser::checkEndMarker(MarkType markType, string match) const {
649     TextParser tp(fFileName, fLine, fChar, fLineCount);
650     tp.skipSpace();
651     if (fMC != tp.next()) {
652         return this->reportError<bool>("mismatched end marker expect #");
653     }
654     const char* nameStart = tp.fChar;
655     tp.skipToNonName();
656     string markName(nameStart, tp.fChar - nameStart);
657     if (kMarkProps[(int) markType].fName != markName) {
658         return this->reportError<bool>("expected #XXX ## to match");
659     }
660     tp.skipSpace();
661     nameStart = tp.fChar;
662     tp.skipToNonName();
663     markName = string(nameStart, tp.fChar - nameStart);
664     if ("" == markName) {
665         if (fMC != tp.next() || fMC != tp.next()) {
666             return this->reportError<bool>("expected ##");
667         }
668         return true;
669     }
670     std::replace(markName.begin(), markName.end(), '-', '_');
671     auto defPos = match.rfind(markName);
672     if (string::npos == defPos) {
673         return this->reportError<bool>("mismatched end marker v1");
674     }
675     if (markName.size() != match.size() - defPos) {
676         return this->reportError<bool>("mismatched end marker v2");
677     }
678     return true;
679 }
680 
checkExamples() const681 bool BmhParser::checkExamples() const {
682     vector<string> exampleNames;
683     for (const auto& topic : fTopicMap) {
684         if (topic.second->fParent) {
685             continue;
686         }
687         find_examples(*topic.second, &exampleNames);
688     }
689     std::sort(exampleNames.begin(), exampleNames.end());
690     string* last = nullptr;
691     string reported;
692     bool checkOK = true;
693     for (auto& nameIter : exampleNames) {
694         if (last && *last == nameIter && reported != *last) {
695             reported = *last;
696             SkDebugf("%s\n", reported.c_str());
697             for (const auto& topic : fTopicMap) {
698                 if (topic.second->fParent) {
699                     continue;
700                 }
701                 this->reportDuplicates(*topic.second, reported);
702             }
703             checkOK = false;
704         }
705         last = &nameIter;
706     }
707     return checkOK;
708 }
709 
checkParamReturn(const Definition * definition) const710 bool BmhParser::checkParamReturn(const Definition* definition) const {
711     const char* parmEndCheck = definition->fContentEnd;
712     while (parmEndCheck < definition->fTerminator) {
713         if (fMC == parmEndCheck[0]) {
714             break;
715         }
716         if (' ' < parmEndCheck[0]) {
717             this->reportError<bool>(
718                     "use full end marker on multiline #Param and #Return");
719         }
720         ++parmEndCheck;
721     }
722     return true;
723 }
724 
childOf(MarkType markType) const725 bool BmhParser::childOf(MarkType markType) const {
726     auto childError = [this](MarkType markType) -> bool {
727         string errStr = "expected ";
728         errStr += kMarkProps[(int) markType].fName;
729         errStr += " parent";
730         return this->reportError<bool>(errStr.c_str());
731     };
732 
733     if (markType == fParent->fMarkType) {
734         return true;
735     }
736     if (this->hasEndToken()) {
737         if (!fParent->fParent) {
738             return this->reportError<bool>("expected grandparent");
739         }
740         if (markType == fParent->fParent->fMarkType) {
741             return true;
742         }
743     }
744     return childError(markType);
745 }
746 
className(MarkType markType)747 string BmhParser::className(MarkType markType) {
748     const char* end = this->lineEnd();
749     const char* mc = this->strnchr(fMC, end);
750     string classID;
751     TextParserSave savePlace(this);
752     this->skipSpace();
753     const char* wordStart = fChar;
754     this->skipToNonName();
755     const char* wordEnd = fChar;
756     classID = string(wordStart, wordEnd - wordStart);
757     if (!mc) {
758         savePlace.restore();
759     }
760     string builder;
761     const Definition* parent = this->parentSpace();
762     if (parent && parent->fName != classID) {
763         builder += parent->fName;
764     }
765     if (mc) {
766         if (mc + 1 < fEnd && fMC == mc[1]) {  // if ##
767             if (markType != fParent->fMarkType) {
768                 return this->reportError<string>("unbalanced method");
769             }
770             if (builder.length() > 0 && classID.size() > 0) {
771                 if (builder != fParent->fName) {
772                     builder += "::";
773                     builder += classID;
774                     if (builder != fParent->fName) {
775                         return this->reportError<string>("name mismatch");
776                     }
777                 }
778             }
779             this->skipLine();
780             return fParent->fName;
781         } else if (' ' ==  mc[1] && MarkType::kConst == markType && fParent
782                 && (MarkType::kEnum == fParent->fMarkType
783                 || MarkType::kEnumClass == fParent->fMarkType)) {
784             this->skipToEndBracket('\n');
785             return builder + "::" + string(wordStart, wordEnd - wordStart);
786         }
787         fChar = mc;
788         this->next();
789     }
790     this->skipWhiteSpace();
791     if (MarkType::kEnum == markType && fChar >= end) {
792         fAnonymous = true;
793         builder += "::_anonymous";
794         return uniqueRootName(builder, markType);
795     }
796     builder = this->word(builder, "::");
797     return builder;
798 }
799 
collectExternals()800 bool BmhParser::collectExternals() {
801     do {
802         this->skipWhiteSpace();
803         if (this->eof()) {
804             break;
805         }
806         if (fMC == this->peek()) {
807             this->next();
808             if (this->eof()) {
809                 break;
810             }
811             if (fMC == this->peek()) {
812                 this->skipLine();
813                 break;
814             }
815             if (' ' >= this->peek()) {
816                 this->skipLine();
817                 continue;
818             }
819             if (this->startsWith(kMarkProps[(int) MarkType::kExternal].fName)) {
820                 this->skipToNonName();
821                 continue;
822             }
823         }
824         this->skipToAlpha();
825         const char* wordStart = fChar;
826         this->skipToWhiteSpace();
827         if (fChar - wordStart > 0) {
828             fExternals.emplace_front(MarkType::kExternal, wordStart, fChar, fLineCount, fParent,
829                     fMC);
830             RootDefinition* definition = &fExternals.front();
831             definition->fFileName = fFileName;
832             definition->fName = string(wordStart ,fChar - wordStart);
833             definition->fFiddle = Definition::NormalizedName(definition->fName);
834         }
835     } while (!this->eof());
836     return true;
837 }
838 
dumpExamples(FILE * fiddleOut,Definition & def,bool * continuation) const839 bool BmhParser::dumpExamples(FILE* fiddleOut, Definition& def, bool* continuation) const {
840     if (MarkType::kExample == def.fMarkType) {
841         string result;
842         if (!this->exampleToScript(&def, BmhParser::ExampleOptions::kAll, &result)) {
843             return false;
844         }
845         if (result.length() > 0) {
846             result += "\n";
847             result += "}";
848             if (*continuation) {
849                 fprintf(fiddleOut, ",\n");
850             } else {
851                 *continuation = true;
852             }
853             fprintf(fiddleOut, "%s", result.c_str());
854         }
855         return true;
856     }
857     for (auto& child : def.fChildren ) {
858         if (!this->dumpExamples(fiddleOut, *child, continuation)) {
859             return false;
860         }
861     }
862     return true;
863 }
864 
dumpExamples(const char * fiddleJsonFileName) const865 bool BmhParser::dumpExamples(const char* fiddleJsonFileName) const {
866     string oldFiddle(fiddleJsonFileName);
867     string newFiddle(fiddleJsonFileName);
868     newFiddle += "_new";
869     FILE* fiddleOut = fopen(newFiddle.c_str(), "wb");
870     if (!fiddleOut) {
871         SkDebugf("could not open output file %s\n", newFiddle.c_str());
872         return false;
873     }
874     fprintf(fiddleOut, "{\n");
875     bool continuation = false;
876     for (const auto& topic : fTopicMap) {
877         if (topic.second->fParent) {
878             continue;
879         }
880         this->dumpExamples(fiddleOut, *topic.second, &continuation);
881     }
882     fprintf(fiddleOut, "\n}\n");
883     fclose(fiddleOut);
884     if (ParserCommon::WrittenFileDiffers(oldFiddle, newFiddle)) {
885         ParserCommon::CopyToFile(oldFiddle, newFiddle);
886         SkDebugf("wrote %s\n", fiddleJsonFileName);
887     } else {
888         remove(newFiddle.c_str());
889     }
890     return true;
891 }
892 
endHashCount() const893 int BmhParser::endHashCount() const {
894     const char* end = fLine + this->lineLength();
895     int count = 0;
896     while (fLine < end && fMC == *--end) {
897         count++;
898     }
899     return count;
900 }
901 
endTableColumn(const char * end,const char * terminator)902 bool BmhParser::endTableColumn(const char* end, const char* terminator) {
903     if (!this->popParentStack(fParent)) {
904         return false;
905     }
906     fWorkingColumn->fContentEnd = end;
907     fWorkingColumn->fTerminator = terminator;
908     fColStart = fChar - 1;
909     this->skipSpace();
910     fTableState = TableState::kColumnStart;
911     return true;
912 }
913 
count_indent(string text,size_t test,size_t end)914 static size_t count_indent(string text, size_t test, size_t end) {
915     size_t result = test;
916     while (test < end) {
917         if (' ' != text[test]) {
918             break;
919         }
920         ++test;
921     }
922     return test - result;
923 }
924 
add_code(string text,int pos,int end,size_t outIndent,size_t textIndent,string & example)925 static void add_code(string text, int pos, int end,
926     size_t outIndent, size_t textIndent, string& example) {
927     do {
928         // fix this to move whole paragraph in, out, but preserve doc indent
929         int nextIndent = count_indent(text, pos, end);
930         size_t len = text.find('\n', pos);
931         if (string::npos == len) {
932             len = end;
933         }
934         if ((size_t) (pos + nextIndent) < len) {
935             size_t indent = outIndent + nextIndent;
936             SkASSERT(indent >= textIndent);
937             indent -= textIndent;
938             for (size_t index = 0; index < indent; ++index) {
939                 example += ' ';
940             }
941             pos += nextIndent;
942             while ((size_t) pos < len) {
943                 example += '"' == text[pos] ? "\\\"" :
944                     '\\' == text[pos] ? "\\\\" :
945                     text.substr(pos, 1);
946                 ++pos;
947             }
948             example += "\\n";
949         } else {
950             pos += nextIndent;
951         }
952         if ('\n' == text[pos]) {
953             ++pos;
954         }
955     } while (pos < end);
956 }
957 
IsExemplary(const Definition * def)958 bool BmhParser::IsExemplary(const Definition* def) {
959     return kMarkProps[(int) def->fMarkType].fExemplary != Exemplary::kNo;
960 }
961 
exampleToScript(Definition * def,ExampleOptions exampleOptions,string * result) const962 bool BmhParser::exampleToScript(Definition* def, ExampleOptions exampleOptions,
963         string* result) const {
964     bool hasFiddle = true;
965     const Definition* platform = def->hasChild(MarkType::kPlatform);
966     if (platform) {
967         TextParser platParse(platform);
968         hasFiddle = !platParse.strnstr("!fiddle", platParse.fEnd);
969     }
970     if (!hasFiddle) {
971         *result = "";
972         return true;
973     }
974     string text = this->extractText(def, TrimExtract::kNo);
975     bool textOut = string::npos != text.find("SkDebugf(")
976         || string::npos != text.find("dump(")
977         || string::npos != text.find("dumpHex(");
978     string heightStr = "256";
979     string widthStr = "256";
980     string normalizedName(def->fFiddle);
981     string code;
982     string imageStr = "0";
983     string srgbStr = "false";
984     string durationStr = "0";
985     for (auto iter : def->fChildren) {
986         switch (iter->fMarkType) {
987         case MarkType::kDuration:
988             durationStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
989             break;
990         case MarkType::kHeight:
991             heightStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
992             break;
993         case MarkType::kWidth:
994             widthStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
995             break;
996         case MarkType::kDescription:
997             // ignore for now
998             break;
999         case MarkType::kFunction: {
1000             // emit this, but don't wrap this in draw()
1001             string funcText = this->extractText(&*iter, TrimExtract::kNo);
1002             size_t pos = 0;
1003             while (pos < funcText.length() && ' ' > funcText[pos]) {
1004                 ++pos;
1005             }
1006             size_t indent = count_indent(funcText, pos, funcText.length());
1007             add_code(funcText, pos, funcText.length(), 0, indent, code);
1008             code += "\\n";
1009         } break;
1010         case MarkType::kComment:
1011             break;
1012         case MarkType::kImage:
1013             imageStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
1014             break;
1015         case MarkType::kToDo:
1016             break;
1017         case MarkType::kBug:
1018         case MarkType::kMarkChar:
1019         case MarkType::kPlatform:
1020         case MarkType::kPhraseRef:
1021             // ignore for now
1022             break;
1023         case MarkType::kSet:
1024             if ("sRGB" == string(iter->fContentStart,
1025                 iter->fContentEnd - iter->fContentStart)) {
1026                 srgbStr = "true";
1027             } else {
1028                 SkASSERT(0);   // more work to do
1029                 return false;
1030             }
1031             break;
1032         case MarkType::kStdOut:
1033             textOut = true;
1034             break;
1035         default:
1036             SkASSERT(0);  // more coding to do
1037         }
1038     }
1039     string animatedStr = "0" != durationStr ? "true" : "false";
1040     string textOutStr = textOut ? "true" : "false";
1041     size_t pos = 0;
1042     while (pos < text.length() && ' ' > text[pos]) {
1043         ++pos;
1044     }
1045     size_t end = text.length();
1046     size_t outIndent = 0;
1047     size_t textIndent = count_indent(text, pos, end);
1048     if ("" == def->fWrapper) {
1049         this->setWrapper(def);
1050     }
1051     if (def->fWrapper.length() > 0) {
1052         code += def->fWrapper;
1053         code += "\\n";
1054         outIndent = 4;
1055     }
1056     add_code(text, pos, end, outIndent, textIndent, code);
1057     if (def->fWrapper.length() > 0) {
1058         code += "}";
1059     }
1060     string example = "\"" + normalizedName + "\": {\n";
1061     string filename = def->fileName();
1062     string baseFile = filename.substr(0, filename.length() - 4);
1063     if (ExampleOptions::kText == exampleOptions) {
1064         example += "    \"code\": \"" + code + "\",\n";
1065         example += "    \"hash\": \"" + def->fHash + "\",\n";
1066         example += "    \"file\": \"" + baseFile + "\",\n";
1067         example += "    \"name\": \"" + def->fName + "\",";
1068     } else {
1069         example += "    \"code\": \"" + code + "\",\n";
1070         if (ExampleOptions::kPng == exampleOptions) {
1071             example += "    \"width\": " + widthStr + ",\n";
1072             example += "    \"height\": " + heightStr + ",\n";
1073             example += "    \"hash\": \"" + def->fHash + "\",\n";
1074             example += "    \"file\": \"" + baseFile + "\",\n";
1075             example += "    \"name\": \"" + def->fName + "\"\n";
1076             example += "}";
1077         } else {
1078             example += "    \"options\": {\n";
1079             example += "        \"width\": " + widthStr + ",\n";
1080             example += "        \"height\": " + heightStr + ",\n";
1081             example += "        \"source\": " + imageStr + ",\n";
1082             example += "        \"srgb\": " + srgbStr + ",\n";
1083             example += "        \"f16\": false,\n";
1084             example += "        \"textOnly\": " + textOutStr + ",\n";
1085             example += "        \"animated\": " + animatedStr + ",\n";
1086             example += "        \"duration\": " + durationStr + "\n";
1087             example += "    },\n";
1088             example += "    \"fast\": true";
1089         }
1090     }
1091     *result = example;
1092     return true;
1093 }
1094 
extractText(const Definition * def,TrimExtract trimExtract) const1095 string BmhParser::extractText(const Definition* def, TrimExtract trimExtract) const {
1096     string result;
1097     TextParser parser(def);
1098     auto childIter = def->fChildren.begin();
1099     while (!parser.eof()) {
1100         const char* end = def->fChildren.end() == childIter ? parser.fEnd : (*childIter)->fStart;
1101         string fragment(parser.fChar, end - parser.fChar);
1102         trim_end(fragment);
1103         if (TrimExtract::kYes == trimExtract) {
1104             trim_start(fragment);
1105             if (result.length()) {
1106                 result += '\n';
1107                 result += '\n';
1108             }
1109         }
1110         if (TrimExtract::kYes == trimExtract || has_nonwhitespace(fragment)) {
1111             result += fragment;
1112         }
1113         parser.skipTo(end);
1114         if (def->fChildren.end() != childIter) {
1115             Definition* child = *childIter;
1116             if (MarkType::kPhraseRef == child->fMarkType) {
1117                 auto phraseIter = fPhraseMap.find(child->fName);
1118                 if (fPhraseMap.end() == phraseIter) {
1119                     return def->reportError<string>("missing phrase definition");
1120                 }
1121                 Definition* phrase = phraseIter->second;
1122                 // count indent of last line in result
1123                 size_t lastLF = result.rfind('\n');
1124                 size_t startPos = string::npos == lastLF ? 0 : lastLF;
1125                 size_t lastLen = result.length() - startPos;
1126                 size_t indent = count_indent(result, startPos, result.length()) + 4;
1127                 string phraseStr = this->extractText(phrase, TrimExtract::kNo);
1128                 startPos = 0;
1129                 bool firstTime = true;
1130                 size_t endPos;
1131                 do {
1132                     endPos = phraseStr.find('\n', startPos);
1133                     size_t len = (string::npos != endPos ? endPos : phraseStr.length()) - startPos;
1134                     if (firstTime && lastLen + len + 1 < 100) {  // FIXME: make 100 global const or something
1135                         result += ' ';
1136                     } else {
1137                         result += '\n';
1138                         result += string(indent, ' ');
1139                     }
1140                     firstTime = false;
1141                     string tmp = phraseStr.substr(startPos, len);
1142                     result += tmp;
1143                     startPos = endPos + 1;
1144                 } while (string::npos != endPos);
1145                 result += '\n';
1146             }
1147             parser.skipTo(child->fTerminator);
1148             std::advance(childIter, 1);
1149         }
1150     }
1151     return result;
1152 }
1153 
loweredTopic(string name,Definition * def)1154 string BmhParser::loweredTopic(string name, Definition* def) {
1155     string lowered;
1156     SkASSERT('_' != name[0]);
1157     char last = '_';
1158     for (char c : name) {
1159         SkASSERT(' ' != c);
1160         if (isupper(last)) {
1161             lowered += islower(c) ? tolower(last) : last;
1162             last = '\0';
1163         }
1164         if ('_' == c) {
1165             last = c;
1166             c = ' ';
1167         } else if ('_' == last && isupper(c)) {
1168             last = c;
1169             continue;
1170         }
1171         lowered += c;
1172         if (' ' == c) {
1173             this->setUpPartialSubstitute(lowered);
1174         }
1175     }
1176     if (isupper(last)) {
1177         lowered += tolower(last);
1178     }
1179     return lowered;
1180 }
1181 
setUpGlobalSubstitutes()1182 void BmhParser::setUpGlobalSubstitutes() {
1183     for (auto& entry : fExternals) {
1184         string externalName = entry.fName;
1185         SkASSERT(fGlobalNames.fRefMap.end() == fGlobalNames.fRefMap.find(externalName));
1186         fGlobalNames.fRefMap[externalName] = nullptr;
1187     }
1188     for (auto bMap : { &fClassMap, &fConstMap, &fDefineMap, &fEnumMap, &fMethodMap,
1189             &fTypedefMap } ) {
1190         for (auto& entry : *bMap) {
1191             Definition* parent = &entry.second;
1192             string name = parent->fName;
1193             SkASSERT(fGlobalNames.fLinkMap.end() == fGlobalNames.fLinkMap.find(name));
1194             string ref = ParserCommon::HtmlFileName(parent->fFileName) + '#' + parent->fFiddle;
1195             fGlobalNames.fLinkMap[name] = ref;
1196             SkASSERT(fGlobalNames.fRefMap.end() == fGlobalNames.fRefMap.find(name));
1197             fGlobalNames.fRefMap[name] = parent;
1198             NameMap* names = MarkType::kClass == parent->fMarkType
1199                     || MarkType::kStruct == parent->fMarkType
1200                     || MarkType::kEnumClass == parent->fMarkType ? &parent->asRoot()->fNames :
1201                     &fGlobalNames;
1202             this->setUpSubstitutes(parent, names);
1203             if (names != &fGlobalNames) {
1204                 names->copyToParent(&fGlobalNames);
1205             }
1206         }
1207     }
1208     for (auto& tEntry : fTypedefMap) {
1209         Definition* typeDef = &tEntry.second;
1210         string defName = typeDef->fName;
1211         TextParser parser(typeDef->fFileName, typeDef->fStart, typeDef->fContentStart,
1212                 typeDef->fLineCount);
1213         parser.skipExact("#Typedef");
1214         parser.skipWhiteSpace();
1215         const char* refStart = parser.fChar;
1216         parser.skipToWhiteSpace();
1217         string refName(refStart, parser.fChar - refStart);
1218         auto structIter = fClassMap.find(refName);
1219         if (fClassMap.end() == structIter) {
1220             continue;
1221         }
1222         for (auto& sEntry : structIter->second.fLeaves) {
1223             Definition* def = &sEntry.second;
1224             string name = def->fName;
1225             size_t colonPos = name.find("::");
1226             SkASSERT(string::npos != colonPos);
1227             string typedName = defName + "::" + name.substr(colonPos + 2);
1228             string ref = ParserCommon::HtmlFileName(def->fFileName) + '#' + def->fFiddle;
1229             SkASSERT(fGlobalNames.fLinkMap.end() == fGlobalNames.fLinkMap.find(typedName));
1230             fGlobalNames.fLinkMap[typedName] = ref;
1231             SkASSERT(fGlobalNames.fRefMap.end() == fGlobalNames.fRefMap.find(typedName));
1232             fGlobalNames.fRefMap[typedName] = def;
1233         }
1234     }
1235     for (auto& topic : fTopicMap) {
1236         bool hasSubstitute = false;
1237         for (auto& child : topic.second->fChildren) {
1238             bool isAlias = MarkType::kAlias == child->fMarkType;
1239             bool isSubstitute = MarkType::kSubstitute == child->fMarkType;
1240             if (!isAlias && !isSubstitute) {
1241                 continue;
1242             }
1243             hasSubstitute |= isSubstitute;
1244             string name(child->fContentStart, child->length());
1245             if (isAlias) {
1246                 name = ParserCommon::ConvertRef(name, false);
1247                 for (auto aliasChild : child->fChildren) {
1248                     if (MarkType::kSubstitute == aliasChild->fMarkType) {
1249                         string sub(aliasChild->fContentStart, aliasChild->length());
1250                         this->setUpSubstitute(sub, topic.second);
1251                     }
1252                 }
1253             }
1254             this->setUpSubstitute(name, topic.second);
1255         }
1256         if (hasSubstitute) {
1257             continue;
1258         }
1259         string lowered = this->loweredTopic(topic.first, topic.second);
1260         SkDEBUGCODE(auto globalIter = fGlobalNames.fLinkMap.find(lowered));
1261         SkASSERT(fGlobalNames.fLinkMap.end() == globalIter);
1262         fGlobalNames.fLinkMap[lowered] =
1263                 ParserCommon::HtmlFileName(topic.second->fFileName) + '#' + topic.first;
1264         SkASSERT(fGlobalNames.fRefMap.end() == fGlobalNames.fRefMap.find(lowered));
1265         fGlobalNames.fRefMap[lowered] = topic.second;
1266     }
1267     size_t slash = fRawFilePathDir.rfind('/');
1268     size_t bslash = fRawFilePathDir.rfind('\\');
1269     string spellFile;
1270     if (string::npos == slash && string::npos == bslash) {
1271         spellFile = fRawFilePathDir;
1272     } else {
1273         if (string::npos != bslash && bslash > slash) {
1274             slash = bslash;
1275         }
1276         spellFile = fRawFilePathDir.substr(0, slash);
1277     }
1278     spellFile += '/';
1279     spellFile += kSpellingFileName;
1280     FILE* file = fopen(spellFile.c_str(), "r");
1281     if (!file) {
1282         SkDebugf("missing %s\n", spellFile.c_str());
1283         return;
1284     }
1285     fseek(file, 0L, SEEK_END);
1286     int sz = (int) ftell(file);
1287     rewind(file);
1288     char* buffer = new char[sz];
1289     memset(buffer, ' ', sz);
1290     int read = (int)fread(buffer, 1, sz, file);
1291     SkAssertResult(read > 0);
1292     sz = read;  // FIXME: ? why are sz and read different?
1293     fclose(file);
1294     int i = 0;
1295     int start = i;
1296     string last = " ";
1297     string word;
1298     do {
1299         if (' ' < buffer[i]) {
1300             ++i;
1301             continue;
1302         }
1303         last = word;
1304         word = string(&buffer[start], i - start);
1305 #ifdef SK_DEBUG
1306         SkASSERT(last.compare(word) < 0);
1307         for (char c : word) {
1308             SkASSERT(islower(c) || '-' == c);
1309         }
1310 #endif
1311         if (fGlobalNames.fRefMap.end() == fGlobalNames.fRefMap.find(word)) {
1312             fGlobalNames.fRefMap[word] = nullptr;
1313         } else {
1314             SkDebugf("%s ", word.c_str());  // debugging: word missing from spelling list
1315         }
1316         do {
1317             ++i;
1318         } while (i < sz && ' ' >= buffer[i]);
1319         start = i;
1320     } while (i < sz);
1321     delete[] buffer;
1322 }
1323 
setUpSubstitutes(const Definition * parent,NameMap * names)1324 void BmhParser::setUpSubstitutes(const Definition* parent, NameMap* names) {
1325     for (const auto& child : parent->fChildren) {
1326         MarkType markType = child->fMarkType;
1327         if (MarkType::kAlias == markType) {
1328             continue;
1329         }
1330         if (MarkType::kSubstitute == markType) {
1331             continue;
1332         }
1333         string name(child->fName);
1334         if (&fGlobalNames != names) {
1335             size_t lastDC = name.rfind("::");
1336             if (string::npos != lastDC) {
1337                 name = name.substr(lastDC + 2);
1338             }
1339             if ("" == name) {
1340                 continue;
1341             }
1342         }
1343         string ref;
1344         if (&fGlobalNames == names) {
1345             ref = ParserCommon::HtmlFileName(child->fFileName);
1346         }
1347         ref += '#' + child->fFiddle;
1348         if (MarkType::kClass == markType || MarkType::kStruct == markType
1349                 || (MarkType::kMethod == markType && !child->fClone)
1350                 || MarkType::kEnum == markType
1351                 || MarkType::kEnumClass == markType || MarkType::kConst == markType
1352                 || MarkType::kMember == markType || MarkType::kDefine == markType
1353                 || MarkType::kTypedef == markType) {
1354             SkASSERT(names->fLinkMap.end() == names->fLinkMap.find(name));
1355             names->fLinkMap[name] = ref;
1356             SkASSERT(names->fRefMap.end() == names->fRefMap.find(name));
1357             names->fRefMap[name] = child;
1358         }
1359         if (MarkType::kClass == markType || MarkType::kStruct == markType
1360                 || MarkType::kEnumClass == markType) {
1361             RootDefinition* rootDef = child->asRoot();
1362             NameMap* nameMap = &rootDef->fNames;
1363             this->setUpSubstitutes(child, nameMap);
1364             nameMap->copyToParent(names);
1365         }
1366         if (MarkType::kEnum == markType) {
1367             this->setUpSubstitutes(child, names);
1368         }
1369         if (MarkType::kMethod == markType) {
1370             if (child->fClone || child->fCloned) {
1371                 TextParser parser(child->fFileName, child->fStart, child->fContentStart,
1372                         child->fLineCount);
1373                 parser.skipExact("#Method");
1374                 parser.skipSpace();
1375                 string name = child->methodName();
1376                 const char* nameInParser = parser.strnstr(name.c_str(), parser.fEnd);
1377                 parser.skipTo(nameInParser);
1378                 const char* paren = parser.strnchr('(', parser.fEnd);
1379                 parser.skipTo(paren);
1380                 parser.skipToBalancedEndBracket('(', ')');
1381                 if ("()" != string(paren, parser.fChar - paren)) {
1382                     string fullName =
1383                             trim_inline_spaces(string(nameInParser, parser.fChar - nameInParser));
1384                     SkASSERT(names->fLinkMap.end() == names->fLinkMap.find(fullName));
1385                     names->fLinkMap[fullName] = ref;
1386                     SkASSERT(names->fRefMap.end() == names->fRefMap.find(fullName));
1387                     names->fRefMap[fullName] = child;
1388                 }
1389             }
1390         }
1391         if (MarkType::kSubtopic == markType) {
1392             if (&fGlobalNames != names && string::npos != child->fName.find('_')) {
1393                 string lowered = this->loweredTopic(child->fName, child);
1394                 SkDEBUGCODE(auto refIter = names->fRefMap.find(lowered));
1395                 SkDEBUGCODE(auto iter = names->fLinkMap.find(lowered));
1396                 SkASSERT(names->fLinkMap.end() == iter);
1397                 names->fLinkMap[lowered] = '#' + child->fName;
1398                 SkASSERT(names->fRefMap.end() == refIter);
1399                 names->fRefMap[lowered] = child;
1400             }
1401             this->setUpSubstitutes(child, names);
1402         }
1403     }
1404 }
1405 
setUpPartialSubstitute(string name)1406 void BmhParser::setUpPartialSubstitute(string name) {
1407     auto iter = fGlobalNames.fRefMap.find(name);
1408     if (fGlobalNames.fRefMap.end() != iter) {
1409         SkASSERT(nullptr == iter->second);
1410         return;
1411     }
1412     fGlobalNames.fRefMap[name] = nullptr;
1413 }
1414 
setUpSubstitute(string name,Definition * def)1415 void BmhParser::setUpSubstitute(string name, Definition* def) {
1416     SkASSERT(fGlobalNames.fRefMap.end() == fGlobalNames.fRefMap.find(name));
1417     fGlobalNames.fRefMap[name] = def;
1418     SkASSERT(fGlobalNames.fLinkMap.end() == fGlobalNames.fLinkMap.find(name));
1419     string str = ParserCommon::HtmlFileName(def->fFileName) + '#' + def->fName;
1420     fGlobalNames.fLinkMap[name] = str;
1421     size_t stop = name.length();
1422     do {
1423         size_t space = name.rfind(' ', stop);
1424         if (string::npos == space) {
1425             break;
1426         }
1427         string partial = name.substr(0, space + 1);
1428         stop = space - 1;
1429         this->setUpPartialSubstitute(partial);
1430     } while (true);
1431 }
1432 
setWrapper(Definition * def) const1433 void BmhParser::setWrapper(Definition* def) const {
1434     const char drawWrapper[] = "void draw(SkCanvas* canvas) {";
1435     const char drawNoCanvas[] = "void draw(SkCanvas* ) {";
1436     string text = this->extractText(def, TrimExtract::kNo);
1437     size_t nonSpace = 0;
1438     while (nonSpace < text.length() && ' ' >= text[nonSpace]) {
1439         ++nonSpace;
1440     }
1441     bool hasFunc = !text.compare(nonSpace, sizeof(drawWrapper) - 1, drawWrapper);
1442     bool noCanvas = !text.compare(nonSpace, sizeof(drawNoCanvas) - 1, drawNoCanvas);
1443     bool hasCanvas = string::npos != text.find("SkCanvas canvas");
1444     SkASSERT(!hasFunc || !noCanvas);
1445     bool preprocessor = text[0] == '#';
1446     bool wrapCode = !hasFunc && !noCanvas && !preprocessor;
1447     if (wrapCode) {
1448         def->fWrapper = hasCanvas ? string(drawNoCanvas) : string(drawWrapper);
1449     }
1450 }
1451 
findBmhObject(MarkType markType,string typeName)1452 RootDefinition* BmhParser::findBmhObject(MarkType markType, string typeName) {
1453     const auto& mapIter = std::find_if(fMaps.begin(), fMaps.end(),
1454             [markType](DefinitionMap& defMap){ return markType == defMap.fMarkType; } );
1455     if (mapIter == fMaps.end()) {
1456         return nullptr;
1457     }
1458     return &(*mapIter->fMap)[typeName];
1459 }
1460 
1461 // FIXME: some examples may produce different output on different platforms
1462 // if the text output can be different, think of how to author that
1463 
findDefinitions()1464 bool BmhParser::findDefinitions() {
1465     bool lineStart = true;
1466     const char* lastChar = nullptr;
1467     const char* lastMC = nullptr;
1468     fParent = nullptr;
1469     while (!this->eof()) {
1470         if (this->peek() == fMC) {
1471             lastMC = fChar;
1472             this->next();
1473             if (this->peek() == fMC) {
1474                 this->next();
1475                 if (!lineStart && ' ' < this->peek()) {
1476                     if (!fParent || MarkType::kFormula != fParent->fMarkType) {
1477                         return this->reportError<bool>("expected definition");
1478                     }
1479                 }
1480                 if (this->peek() != fMC) {
1481                     if (MarkType::kColumn == fParent->fMarkType) {
1482                         SkASSERT(TableState::kColumnEnd == fTableState);
1483                         if (!this->endTableColumn(lastChar, lastMC)) {
1484                             return false;
1485                         }
1486                         SkASSERT(fRow);
1487                         if (!this->popParentStack(fParent)) {
1488                             return false;
1489                         }
1490                         fRow->fContentEnd = fWorkingColumn->fContentEnd;
1491                         fWorkingColumn = nullptr;
1492                         fRow = nullptr;
1493                         fTableState = TableState::kNone;
1494                     } else {
1495                         vector<string> parentName;
1496                         parentName.push_back(fParent->fName);
1497                         if (!this->addDefinition(fChar - 1, true, fParent->fMarkType, parentName,
1498                                 HasTag::kNo)) {
1499                             return false;
1500                         }
1501                     }
1502                 } else {
1503                     SkAssertResult(this->next() == fMC);
1504                     fMC = this->next();  // change markup character
1505                     if (' ' >= fMC) {
1506                         return this->reportError<bool>("illegal markup character");
1507                     }
1508                     fMarkup.emplace_front(MarkType::kMarkChar, fChar - 4, fLineCount, fParent, fMC);
1509                     Definition* markChar = &fMarkup.front();
1510                     markChar->fContentStart = fChar - 1;
1511                     this->skipToEndBracket('\n');
1512                     markChar->fContentEnd = fChar;
1513                     markChar->fTerminator = fChar;
1514                     fParent->fChildren.push_back(markChar);
1515                 }
1516             } else if (this->peek() >= 'A' && this->peek() <= 'Z') {
1517                 const char* defStart = fChar - 1;
1518                 MarkType markType = this->getMarkType(MarkLookup::kRequire);
1519                 bool hasEnd = this->hasEndToken();
1520                 if (!hasEnd && fParent) {
1521                     MarkType parentType = fParent->fMarkType;
1522                     uint64_t parentMask = kMarkProps[(int) markType].fParentMask;
1523                     if (parentMask && !(parentMask & (1LL << (int) parentType))) {
1524                         return this->reportError<bool>("invalid parent");
1525                     }
1526                 }
1527                 if (!this->skipName(kMarkProps[(int) markType].fName)) {
1528                     return this->reportError<bool>("illegal markup character");
1529                 }
1530                 if (!this->skipSpace()) {
1531                     return this->reportError<bool>("unexpected end");
1532                 }
1533                 lineStart = '\n' == this->peek();
1534                 bool expectEnd = true;
1535                 vector<string> typeNameBuilder = this->typeName(markType, &expectEnd);
1536                 if (fCloned && MarkType::kMethod != markType && MarkType::kExample != markType
1537                         && !fAnonymous) {
1538                     return this->reportError<bool>("duplicate name");
1539                 }
1540                 if (hasEnd && expectEnd) {
1541                     if (fMC == this->peek()) {
1542                         return this->reportError<bool>("missing body");
1543                     }
1544                 }
1545                 if (!this->addDefinition(defStart, hasEnd, markType, typeNameBuilder,
1546                         HasTag::kYes)) {
1547                     return false;
1548                 }
1549                 continue;
1550             } else if (this->peek() == ' ') {
1551                 if (!fParent || (MarkType::kFormula != fParent->fMarkType
1552                         && MarkType::kLegend != fParent->fMarkType
1553                         && MarkType::kList != fParent->fMarkType
1554 						&& MarkType::kLine != fParent->fMarkType
1555                         && MarkType::kTable != fParent->fMarkType)) {
1556                     int endHashes = this->endHashCount();
1557                     if (endHashes <= 1) {
1558                         if (fParent) {
1559                             if (TableState::kColumnEnd == fTableState) {
1560                                 if (!this->endTableColumn(lastChar, lastMC)) {
1561                                     return false;
1562                                 }
1563                             } else {  // one line comment
1564                                 fMarkup.emplace_front(MarkType::kComment, fChar - 1, fLineCount,
1565                                         fParent, fMC);
1566                                 Definition* comment = &fMarkup.front();
1567                                 comment->fContentStart = fChar - 1;
1568                                 this->skipToEndBracket('\n');
1569                                 comment->fContentEnd = fChar;
1570                                 comment->fTerminator = fChar;
1571                                 fParent->fChildren.push_back(comment);
1572                             }
1573                         } else {
1574                             fChar = fLine + this->lineLength() - 1;
1575                         }
1576                     } else {  // table row
1577                         if (2 != endHashes) {
1578                             string errorStr = "expect ";
1579                             errorStr += fMC;
1580                             errorStr += fMC;
1581                             return this->reportError<bool>(errorStr.c_str());
1582                         }
1583                         if (!fParent || MarkType::kTable != fParent->fMarkType) {
1584                             return this->reportError<bool>("missing table");
1585                         }
1586                     }
1587                 } else if (TableState::kNone == fTableState) {
1588                     // fixme? no nested tables for now
1589                     fColStart = fChar - 1;
1590                     fMarkup.emplace_front(MarkType::kRow, fColStart, fLineCount, fParent, fMC);
1591                     fRow = &fMarkup.front();
1592                     fRow->fName = fParent->fName;
1593                     this->skipWhiteSpace();
1594                     fRow->fContentStart = fChar;
1595                     this->setAsParent(fRow);
1596                     fTableState = TableState::kColumnStart;
1597                 }
1598                 if (TableState::kColumnStart == fTableState) {
1599                     fMarkup.emplace_front(MarkType::kColumn, fColStart, fLineCount, fParent, fMC);
1600                     fWorkingColumn = &fMarkup.front();
1601                     fWorkingColumn->fName = fParent->fName;
1602                     fWorkingColumn->fContentStart = fChar;
1603                     this->setAsParent(fWorkingColumn);
1604                     fTableState = TableState::kColumnEnd;
1605                     continue;
1606                 }
1607             } else if (this->peek() >= 'a' && this->peek() <= 'z') {
1608                 // expect zero or more letters and underscores (no spaces) then hash
1609                 const char* phraseNameStart = fChar;
1610                 this->skipPhraseName();
1611                 string phraseKey = string(phraseNameStart, fChar - phraseNameStart);
1612                 char delimiter = this->next();
1613                 vector<string> params;
1614                 vector<const char*> paramsLoc;
1615                 if (fMC != delimiter) {
1616                     if ('(' != delimiter) {
1617                         return this->reportError<bool>("expect # after phrase name");
1618                     }
1619                     // phrase may take comma delimited parameter list
1620                     do {
1621                         const char* subEnd = this->anyOf(",)\n");
1622                         if (!subEnd || '\n' == *subEnd) {
1623                             return this->reportError<bool>("unexpected phrase list end");
1624                         }
1625                         params.push_back(string(fChar, subEnd - fChar));
1626                         paramsLoc.push_back(fChar);
1627                         this->skipTo(subEnd);
1628 
1629                     } while (')' != this->next());
1630                 }
1631                 const char* start = phraseNameStart;
1632                 SkASSERT('#' == start[-1]);
1633                 --start;
1634                 if (start > fStart && ' ' >= start[-1]) {
1635                     --start;  // preserve whether to add whitespace before substitution
1636                 }
1637                 fMarkup.emplace_front(MarkType::kPhraseRef, start, fLineCount, fParent, fMC);
1638                 Definition* markChar = &fMarkup.front();
1639                 this->skipExact("#");
1640                 markChar->fContentStart = fChar;
1641                 markChar->fContentEnd = fChar;
1642                 markChar->fTerminator = fChar;
1643                 markChar->fName = phraseKey;
1644                 fParent->fChildren.push_back(markChar);
1645                 int paramLocIndex = 0;
1646                 for (auto param : params) {
1647                     const char* paramLoc = paramsLoc[paramLocIndex++];
1648                     fMarkup.emplace_front(MarkType::kPhraseParam, paramLoc, fLineCount, fParent,
1649                             fMC);
1650                     Definition* phraseParam = &fMarkup.front();
1651                     phraseParam->fContentStart = paramLoc;
1652                     phraseParam->fContentEnd = paramLoc + param.length();
1653                     phraseParam->fTerminator = paramLoc + param.length();
1654                     phraseParam->fName = param;
1655                     markChar->fChildren.push_back(phraseParam);
1656                 }
1657             }
1658         }
1659         char nextChar = this->next();
1660         if (' ' < nextChar) {
1661             lastChar = fChar;
1662             lineStart = false;
1663         } else if (nextChar == '\n') {
1664             lineStart = true;
1665         }
1666     }
1667     if (fParent) {
1668         return fParent->reportError<bool>("mismatched end");
1669     }
1670     return true;
1671 }
1672 
getMarkType(MarkLookup lookup) const1673 MarkType BmhParser::getMarkType(MarkLookup lookup) const {
1674     for (int index = 0; index <= Last_MarkType; ++index) {
1675         int typeLen = strlen(kMarkProps[index].fName);
1676         if (typeLen == 0) {
1677             continue;
1678         }
1679         if (fChar + typeLen >= fEnd || fChar[typeLen] > ' ') {
1680             continue;
1681         }
1682         int chCompare = strncmp(fChar, kMarkProps[index].fName, typeLen);
1683         if (chCompare < 0) {
1684             goto fail;
1685         }
1686         if (chCompare == 0) {
1687             return (MarkType) index;
1688         }
1689     }
1690 fail:
1691     if (MarkLookup::kRequire == lookup) {
1692         return this->reportError<MarkType>("unknown mark type");
1693     }
1694     return MarkType::kNone;
1695 }
1696 
hasEndToken() const1697 bool BmhParser::hasEndToken() const {
1698     const char* ptr = fLine;
1699     char test;
1700     do {
1701         if (ptr >= fEnd) {
1702             return false;
1703         }
1704         test = *ptr++;
1705         if ('\n' == test) {
1706             return false;
1707         }
1708     } while (fMC != test || fMC != *ptr);
1709     return true;
1710 }
1711 
memberName()1712 string BmhParser::memberName() {
1713     const char* wordStart;
1714     const char* prefixes[] = { "static", "const" };
1715     do {
1716         this->skipSpace();
1717         wordStart = fChar;
1718         this->skipToNonName();
1719     } while (this->anyOf(wordStart, prefixes, SK_ARRAY_COUNT(prefixes)));
1720     if ('*' == this->peek()) {
1721         this->next();
1722     }
1723     return this->className(MarkType::kMember);
1724 }
1725 
methodName()1726 string BmhParser::methodName() {
1727     if (this->hasEndToken()) {
1728         if (!fParent || !fParent->fName.length()) {
1729             return this->reportError<string>("missing parent method name");
1730         }
1731         SkASSERT(fMC == this->peek());
1732         this->next();
1733         SkASSERT(fMC == this->peek());
1734         this->next();
1735         SkASSERT(fMC != this->peek());
1736         return fParent->fName;
1737     }
1738     string builder;
1739     const char* end = this->lineEnd();
1740     const char* paren = this->strnchr('(', end);
1741     if (!paren) {
1742         return this->reportError<string>("missing method name and reference");
1743     }
1744     {
1745         TextParserSave endCheck(this);
1746         while (end < fEnd && !this->strnchr(')', end)) {
1747             fChar = end + 1;
1748             end = this->lineEnd();
1749         }
1750         if (end >= fEnd) {
1751             return this->reportError<string>("missing method end paren");
1752         }
1753         endCheck.restore();
1754     }
1755     const char* nameStart = paren;
1756     char ch;
1757     bool expectOperator = false;
1758     bool isConstructor = false;
1759     const char* nameEnd = nullptr;
1760     while (nameStart > fChar && ' ' != (ch = *--nameStart)) {
1761         if (!isalnum(ch) && '_' != ch) {
1762             if (nameEnd) {
1763                 break;
1764             }
1765             expectOperator = true;
1766             continue;
1767         }
1768         if (!nameEnd) {
1769             nameEnd = nameStart + 1;
1770         }
1771     }
1772     if (!nameEnd) {
1773          return this->reportError<string>("unexpected method name char");
1774     }
1775     if (' ' == nameStart[0]) {
1776         ++nameStart;
1777     }
1778     if (nameEnd <= nameStart) {
1779         return this->reportError<string>("missing method name");
1780     }
1781     if (nameStart >= paren) {
1782         return this->reportError<string>("missing method name length");
1783     }
1784     string name(nameStart, nameEnd - nameStart);
1785     bool allLower = true;
1786     for (int index = 0; index < (int) (nameEnd - nameStart); ++index) {
1787         if (!islower(nameStart[index])) {
1788             allLower = false;
1789             break;
1790         }
1791     }
1792     if (expectOperator && "operator" != name) {
1793          return this->reportError<string>("expected operator");
1794     }
1795     const Definition* parent = this->parentSpace();
1796     if (parent && parent->fName.length() > 0) {
1797         size_t parentNameIndex = parent->fName.rfind(':');
1798         parentNameIndex = string::npos == parentNameIndex ? 0 : parentNameIndex + 1;
1799         string parentName = parent->fName.substr(parentNameIndex);
1800         if (parentName == name) {
1801             isConstructor = true;
1802         } else if ('~' == name[0]) {
1803             if (parentName != name.substr(1)) {
1804                  return this->reportError<string>("expected destructor");
1805             }
1806             isConstructor = true;
1807         }
1808         builder = parent->fName + "::";
1809     }
1810     bool addConst = false;
1811     if (isConstructor || expectOperator) {
1812         paren = this->strnchr(')', end) + 1;
1813         TextParserSave saveState(this);
1814         this->skipTo(paren);
1815         if (this->skipExact(" const")) {
1816             addConst = true;
1817         }
1818         saveState.restore();
1819     }
1820     builder.append(nameStart, paren - nameStart);
1821     if (addConst) {
1822         builder.append(" const");
1823     }
1824     if (!expectOperator && allLower) {
1825         builder.append("()");
1826     }
1827     int parens = 0;
1828     while (fChar < end || parens > 0) {
1829         if ('(' == this->peek()) {
1830             ++parens;
1831         } else if (')' == this->peek()) {
1832             --parens;
1833         }
1834         this->next();
1835     }
1836     TextParserSave saveState(this);
1837     this->skipWhiteSpace();
1838     if (this->startsWith("const")) {
1839         this->skipName("const");
1840     } else {
1841         saveState.restore();
1842     }
1843 //    this->next();
1844     if (string::npos != builder.find('\n')) {
1845         builder.erase(std::remove(builder.begin(), builder.end(), '\n'), builder.end());
1846     }
1847     return uniqueRootName(builder, MarkType::kMethod);
1848 }
1849 
parentSpace() const1850 const Definition* BmhParser::parentSpace() const {
1851     Definition* parent = nullptr;
1852     Definition* test = fParent;
1853     while (test) {
1854         if (MarkType::kClass == test->fMarkType ||
1855                 MarkType::kEnumClass == test->fMarkType ||
1856                 MarkType::kStruct == test->fMarkType) {
1857             parent = test;
1858             break;
1859         }
1860         test = test->fParent;
1861     }
1862     return parent;
1863 }
1864 
1865 // A full terminal statement is in the form:
1866 //     \n optional-white-space #MarkType white-space #[# white-space]
1867 //     \n optional-white-space #MarkType white-space Name white-space #[# white-space]
1868 // MarkType must match definition->fMarkType
checkForFullTerminal(const char * end,const Definition * definition) const1869 const char* BmhParser::checkForFullTerminal(const char* end, const Definition* definition) const {
1870     const char* start = end;
1871     while ('\n' != start[0] && start > fStart) {
1872         --start;
1873     }
1874     SkASSERT (start < end);
1875     // if end is preceeeded by \n#MarkType ## backup to there
1876     TextParser parser(fFileName, start, fChar, fLineCount);
1877     parser.skipWhiteSpace();
1878     if (parser.eof() || fMC != parser.next()) {
1879         return end;
1880     }
1881     const char* markName = kMarkProps[(int) definition->fMarkType].fName;
1882     if (!parser.skipExact(markName)) {
1883         return end;
1884     }
1885     parser.skipWhiteSpace();
1886     TextParser startName(fFileName, definition->fStart, definition->fContentStart,
1887             definition->fLineCount);
1888     if ('#' == startName.next()) {
1889         startName.skipToSpace();
1890         if (!startName.eof() && startName.skipSpace()) {
1891             const char* nameBegin = startName.fChar;
1892             startName.skipToWhiteSpace();
1893             string name(nameBegin, (int) (startName.fChar - nameBegin));
1894             if (fMC != parser.peek() && !parser.skipExact(name.c_str())) {
1895                 return end;
1896             }
1897             parser.skipSpace();
1898         }
1899     }
1900     if (parser.eof() || fMC != parser.next()) {
1901         return end;
1902     }
1903     if (!parser.eof() && fMC != parser.next()) {
1904         return end;
1905     }
1906     SkASSERT(parser.eof());
1907     return start;
1908 }
1909 
parseHashAnchor(Definition * definition)1910 void BmhParser::parseHashAnchor(Definition* definition) {
1911     this->skipToEndBracket(fMC);
1912     fMarkup.emplace_front(MarkType::kLink, fChar, fLineCount, definition, fMC);
1913     SkAssertResult(fMC == this->next());
1914     this->skipWhiteSpace();
1915     Definition* link = &fMarkup.front();
1916     link->fContentStart = fChar;
1917     link->fContentEnd = this->trimmedBracketEnd(fMC);
1918     this->skipToEndBracket(fMC);
1919     SkAssertResult(fMC == this->next());
1920     SkAssertResult(fMC == this->next());
1921     link->fTerminator = fChar;
1922     definition->fContentEnd = link->fContentEnd;
1923     definition->fTerminator = fChar;
1924     definition->fChildren.emplace_back(link);
1925 }
1926 
parseHashFormula(Definition * definition)1927 void BmhParser::parseHashFormula(Definition* definition) {
1928     const char* start = definition->fContentStart;
1929     definition->trimEnd();
1930 	const char* end = definition->fContentEnd;
1931 	fMarkup.emplace_front(MarkType::kText, start, fLineCount, definition, fMC);
1932 	Definition* text = &fMarkup.front();
1933 	text->fContentStart = start;
1934 	text->fContentEnd = end;
1935 	text->fTerminator = definition->fTerminator;
1936 	definition->fChildren.emplace_back(text);
1937 }
1938 
parseHashLine(Definition * definition)1939 void BmhParser::parseHashLine(Definition* definition) {
1940 	const char* nextLF = this->strnchr('\n', this->fEnd);
1941 	const char* start = fChar;
1942 	const char* end = this->trimmedBracketEnd(fMC);
1943 	this->skipToEndBracket(fMC, nextLF);
1944 	if (fMC != this->next() || fMC != this->next()) {
1945 		return this->reportError<void>("expected ## to delineate line");
1946 	}
1947 	fMarkup.emplace_front(MarkType::kText, start, fLineCount, definition, fMC);
1948 	Definition* text = &fMarkup.front();
1949     if (!islower(start[0]) && (!isdigit(start[0])
1950             || MarkType::kConst != definition->fParent->fMarkType)) {
1951         return this->reportError<void>("expect lower case start");
1952     }
1953     string contents = string(start, end - start);
1954     if (string::npos != contents.find('.')) {
1955         return this->reportError<void>("expect phrase, not sentence");
1956     }
1957     size_t firstSpace = contents.find(' ');
1958     if (string::npos == firstSpace || 0 == firstSpace || 's' != start[firstSpace - 1]) {
1959         if (MarkType::kMethod == fParent->fMarkType && "experimental" != contents
1960                     && "incomplete" != contents) {
1961             return this->reportError<void>( "expect phrase in third person present"
1962                     " tense (1st word should end in 's'");
1963         }
1964     }
1965 	text->fContentStart = start;
1966 	text->fContentEnd = end;
1967 	text->fTerminator = fChar;
1968 	definition->fContentEnd = text->fContentEnd;
1969 	definition->fTerminator = fChar;
1970 	definition->fChildren.emplace_back(text);
1971 }
1972 
popParentStack(Definition * definition)1973 bool BmhParser::popParentStack(Definition* definition) {
1974     if (!fParent) {
1975         return this->reportError<bool>("missing parent");
1976     }
1977     if (definition != fParent) {
1978         return this->reportError<bool>("definition end is not parent");
1979     }
1980     if (!definition->fStart) {
1981         return this->reportError<bool>("definition missing start");
1982     }
1983     if (definition->fContentEnd) {
1984         return this->reportError<bool>("definition already ended");
1985     }
1986     // more to figure out to handle table columns, at minimum
1987     const char* end = fChar;
1988     if (fMC != end[0]) {
1989         while (end > definition->fContentStart && ' ' >= end[-1]) {
1990             --end;
1991         }
1992         SkASSERT(&end[-1] >= definition->fContentStart && fMC == end[-1]
1993                 && (MarkType::kColumn == definition->fMarkType
1994                 || (&end[-2] >= definition->fContentStart && fMC == end[-2])));
1995         end -= 2;
1996     }
1997     end = checkForFullTerminal(end, definition);
1998     definition->fContentEnd = end;
1999     definition->fTerminator = fChar;
2000     fParent = definition->fParent;
2001     if (!fParent || (MarkType::kTopic == fParent->fMarkType && !fParent->fParent)) {
2002         fRoot = nullptr;
2003     }
2004     return true;
2005 }
2006 
2007 
2008 
skipNoName()2009 bool BmhParser::skipNoName() {
2010     if ('\n' == this->peek()) {
2011         this->next();
2012         return true;
2013     }
2014     this->skipWhiteSpace();
2015     if (fMC != this->peek()) {
2016         return this->reportError<bool>("expected end mark 1");
2017     }
2018     this->next();
2019     if (fMC != this->peek()) {
2020         return this->reportError<bool>("expected end mark 2");
2021     }
2022     this->next();
2023     return true;
2024 }
2025 
skipToDefinitionEnd(MarkType markType)2026 bool BmhParser::skipToDefinitionEnd(MarkType markType) {
2027     if (this->eof()) {
2028         return this->reportError<bool>("missing end");
2029     }
2030     const char* start = fLine;
2031     int startLineCount = fLineCount;
2032     int stack = 1;
2033     ptrdiff_t lineLen;
2034     bool foundEnd = false;
2035     do {
2036         lineLen = this->lineLength();
2037         if (fMC != *fChar++) {
2038             continue;
2039         }
2040         if (fMC == *fChar) {
2041             continue;
2042         }
2043         if (' ' == *fChar) {
2044             continue;
2045         }
2046         MarkType nextType = this->getMarkType(MarkLookup::kAllowUnknown);
2047         if (markType != nextType) {
2048             continue;
2049         }
2050         bool hasEnd = this->hasEndToken();
2051         if (hasEnd) {
2052             if (!--stack) {
2053                 foundEnd = true;
2054                 continue;
2055             }
2056         } else {
2057             ++stack;
2058         }
2059     } while ((void) ++fLineCount, (void) (fLine += lineLen), (void) (fChar = fLine),
2060             !this->eof() && !foundEnd);
2061     if (foundEnd) {
2062         return true;
2063     }
2064     fLineCount = startLineCount;
2065     fLine = start;
2066     fChar = start;
2067     return this->reportError<bool>("unbalanced stack");
2068 }
2069 
skipToString()2070 bool BmhParser::skipToString() {
2071 	this->skipSpace();
2072 	if (fMC != this->peek()) {
2073 		return this->reportError<bool>("expected end mark 3");
2074 	}
2075 	this->next();
2076 	this->skipSpace();
2077 	// body is text from here to double fMC
2078 		// no single fMC allowed, no linefeed allowed
2079 	return true;
2080 }
2081 
topicName()2082 vector<string> BmhParser::topicName() {
2083     vector<string> result;
2084     this->skipWhiteSpace();
2085     const char* lineEnd = fLine + this->lineLength();
2086     const char* nameStart = fChar;
2087     while (fChar < lineEnd) {
2088         char ch = this->next();
2089         SkASSERT(',' != ch);
2090         if ('\n' == ch) {
2091             break;
2092         }
2093         if (fMC == ch) {
2094             break;
2095         }
2096     }
2097     if (fChar - 1 > nameStart) {
2098         string builder(nameStart, fChar - nameStart - 1);
2099         trim_start_end(builder);
2100         result.push_back(builder);
2101     }
2102     if (fChar < lineEnd && fMC == this->peek()) {
2103         this->next();
2104     }
2105     return result;
2106 }
2107 
2108 // typeName parsing rules depend on mark type
typeName(MarkType markType,bool * checkEnd)2109 vector<string> BmhParser::typeName(MarkType markType, bool* checkEnd) {
2110     fAnonymous = false;
2111     fCloned = false;
2112     vector<string> result;
2113     string builder;
2114     if (fParent) {
2115         builder = fParent->fName;
2116     }
2117     switch (markType) {
2118         case MarkType::kDefine:
2119         case MarkType::kEnum:
2120             // enums may be nameless
2121         case MarkType::kConst:
2122         case MarkType::kEnumClass:
2123         case MarkType::kClass:
2124         case MarkType::kStruct:
2125         case MarkType::kTemplate:
2126             // expect name
2127             builder = this->className(markType);
2128             break;
2129         case MarkType::kExample:
2130             // check to see if one already exists -- if so, number this one
2131             builder = this->uniqueName(string(), markType);
2132             this->skipNoName();
2133             break;
2134         case MarkType::kCode:
2135         case MarkType::kDescription:
2136         case MarkType::kExternal:
2137         case MarkType::kFunction:
2138         case MarkType::kLegend:
2139         case MarkType::kList:
2140         case MarkType::kNoExample:
2141             this->skipNoName();
2142             break;
2143         case MarkType::kFormula:
2144 		case MarkType::kLine:
2145 			this->skipToString();
2146 			break;
2147         case MarkType::kAlias:
2148         case MarkType::kAnchor:
2149         case MarkType::kBug:  // fixme: expect number
2150         case MarkType::kDetails:
2151         case MarkType::kDuration:
2152         case MarkType::kFile:
2153         case MarkType::kFilter:
2154         case MarkType::kHeight:
2155         case MarkType::kIllustration:
2156         case MarkType::kImage:
2157 		case MarkType::kIn:
2158         case MarkType::kLiteral:
2159         case MarkType::kNoJustify:
2160         case MarkType::kOutdent:
2161         case MarkType::kPlatform:
2162         case MarkType::kPopulate:
2163         case MarkType::kReturn:
2164         case MarkType::kSeeAlso:
2165         case MarkType::kSet:
2166         case MarkType::kSubstitute:
2167         case MarkType::kToDo:
2168         case MarkType::kVolatile:
2169         case MarkType::kWidth:
2170             *checkEnd = false;  // no name, may have text body
2171             break;
2172         case MarkType::kStdOut:
2173             this->skipNoName();
2174             break;  // unnamed
2175         case MarkType::kMember:
2176             builder = this->memberName();
2177             break;
2178         case MarkType::kMethod:
2179             builder = this->methodName();
2180             break;
2181         case MarkType::kTypedef:
2182             builder = this->typedefName();
2183             break;
2184         case MarkType::kParam:
2185             // fixme: expect camelCase for param
2186             builder = this->word("", "");
2187             this->skipSpace();
2188             *checkEnd = false;
2189             break;
2190         case MarkType::kPhraseDef: {
2191             const char* nameEnd = this->anyOf("(\n");
2192             builder = string(fChar, nameEnd - fChar);
2193             this->skipLower();
2194             if (fChar != nameEnd) {
2195                 this->reportError("expect lower case only");
2196                 break;
2197             }
2198             this->skipTo(nameEnd);
2199             *checkEnd = false;
2200             } break;
2201         case MarkType::kTable:
2202             this->skipNoName();
2203             break;  // unnamed
2204         case MarkType::kSubtopic:
2205         case MarkType::kTopic:
2206             // fixme: start with cap, allow space, hyphen, stop on comma
2207             // one topic can have multiple type names delineated by comma
2208             result = this->topicName();
2209             if (result.size() == 0 && this->hasEndToken()) {
2210                 break;
2211             }
2212             return result;
2213         default:
2214             // fixme: don't allow silent failures
2215             SkASSERT(0);
2216     }
2217     result.push_back(builder);
2218     return result;
2219 }
2220 
typedefName()2221 string BmhParser::typedefName() {
2222     if (this->hasEndToken()) {
2223         if (!fParent || !fParent->fName.length()) {
2224             return this->reportError<string>("missing parent typedef name");
2225         }
2226         SkASSERT(fMC == this->peek());
2227         this->next();
2228         SkASSERT(fMC == this->peek());
2229         this->next();
2230         SkASSERT(fMC != this->peek());
2231         return fParent->fName;
2232     }
2233     string builder;
2234     const Definition* parent = this->parentSpace();
2235     if (parent && parent->fName.length() > 0) {
2236         builder = parent->fName + "::";
2237     }
2238     builder += TextParser::typedefName();
2239     return uniqueRootName(builder, MarkType::kTypedef);
2240 }
2241 
uniqueName(string base,MarkType markType)2242 string BmhParser::uniqueName(string base, MarkType markType) {
2243     string builder(base);
2244     if (!builder.length()) {
2245         builder = fParent->fName;
2246     }
2247     if (!fParent) {
2248         return builder;
2249     }
2250     int number = 2;
2251     string numBuilder(builder);
2252     do {
2253         for (auto& iter : fParent->fChildren) {
2254             if (markType == iter->fMarkType) {
2255                 if (iter->fName == numBuilder) {
2256                     fCloned = true;
2257                     numBuilder = builder + '_' + to_string(number);
2258                     goto tryNext;
2259                 }
2260             }
2261         }
2262         break;
2263 tryNext: ;
2264     } while (++number);
2265     return numBuilder;
2266 }
2267 
uniqueRootName(string base,MarkType markType)2268 string BmhParser::uniqueRootName(string base, MarkType markType) {
2269     auto checkName = [markType](const Definition& def, string numBuilder) -> bool {
2270         return markType == def.fMarkType && def.fName == numBuilder;
2271     };
2272 
2273     string builder(base);
2274     if (!builder.length()) {
2275         builder = fParent->fName;
2276     }
2277     int number = 2;
2278     string numBuilder(builder);
2279     Definition* cloned = nullptr;
2280     do {
2281         if (fRoot) {
2282             for (auto& iter : fRoot->fBranches) {
2283                 if (checkName(*iter.second, numBuilder)) {
2284                     cloned = iter.second;
2285                     goto tryNext;
2286                 }
2287             }
2288             for (auto& iter : fRoot->fLeaves) {
2289                 if (checkName(iter.second, numBuilder)) {
2290                     cloned = &iter.second;
2291                     goto tryNext;
2292                 }
2293             }
2294         } else if (fParent) {
2295             for (auto& iter : fParent->fChildren) {
2296                 if (checkName(*iter, numBuilder)) {
2297                     cloned = &*iter;
2298                     goto tryNext;
2299                 }
2300             }
2301         }
2302         break;
2303 tryNext: ;
2304         if ("()" == builder.substr(builder.length() - 2)) {
2305             builder = builder.substr(0, builder.length() - 2);
2306         }
2307         if (MarkType::kMethod == markType) {
2308             cloned->fCloned = true;
2309         }
2310         fCloned = true;
2311         numBuilder = builder + '_' + to_string(number);
2312     } while (++number);
2313     return numBuilder;
2314 }
2315 
validate() const2316 void BmhParser::validate() const {
2317     for (int index = 0; index <= (int) Last_MarkType; ++index) {
2318         SkASSERT(kMarkProps[index].fMarkType == (MarkType) index);
2319     }
2320     const char* last = "";
2321     for (int index = 0; index <= (int) Last_MarkType; ++index) {
2322         const char* next = kMarkProps[index].fName;
2323         if (!last[0]) {
2324             last = next;
2325             continue;
2326         }
2327         if (!next[0]) {
2328             continue;
2329         }
2330         SkASSERT(strcmp(last, next) < 0);
2331         last = next;
2332     }
2333 }
2334 
word(string prefix,string delimiter)2335 string BmhParser::word(string prefix, string delimiter) {
2336     string builder(prefix);
2337     this->skipWhiteSpace();
2338     const char* lineEnd = fLine + this->lineLength();
2339     const char* nameStart = fChar;
2340     while (fChar < lineEnd) {
2341         char ch = this->next();
2342         if (' ' >= ch) {
2343             break;
2344         }
2345         if (',' == ch) {
2346             return this->reportError<string>("no comma needed");
2347             break;
2348         }
2349         if (fMC == ch) {
2350             return builder;
2351         }
2352         if (!isalnum(ch) && '_' != ch && ':' != ch && '-' != ch) {
2353             return this->reportError<string>("unexpected char");
2354         }
2355         if (':' == ch) {
2356             // expect pair, and expect word to start with Sk
2357             if (nameStart[0] != 'S' || nameStart[1] != 'k') {
2358                 return this->reportError<string>("expected Sk");
2359             }
2360             if (':' != this->peek()) {
2361                 return this->reportError<string>("expected ::");
2362             }
2363             this->next();
2364         } else if ('-' == ch) {
2365             // expect word not to start with Sk or kX where X is A-Z
2366             if (nameStart[0] == 'k' && nameStart[1] >= 'A' && nameStart[1] <= 'Z') {
2367                 return this->reportError<string>("didn't expected kX");
2368             }
2369             if (nameStart[0] == 'S' && nameStart[1] == 'k') {
2370                 return this->reportError<string>("expected Sk");
2371             }
2372         }
2373     }
2374     if (prefix.size()) {
2375         builder += delimiter;
2376     }
2377     builder.append(nameStart, fChar - nameStart - 1);
2378     return builder;
2379 }
2380