blob: beb7dd6116ba283db635665b9d457531e2e641e6 [file] [log] [blame]
Cary Clark8032b982017-07-28 11:04:54 -04001/*
2 * Copyright 2017 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 "bookmaker.h"
Cary Clark1a8d7622018-03-05 13:26:16 -05009#include "SkOSPath.h"
Cary Clark8032b982017-07-28 11:04:54 -040010
Cary Clark2dc84ad2018-01-26 12:56:22 -050011#ifdef SK_BUILD_FOR_WIN
12#include <Windows.h>
13#endif
14
Cary Clark09d80c02018-10-31 12:14:03 -040015const string kSpellingFileName("spelling.txt");
16
Cary Clark2f466242017-12-11 16:03:17 -050017DEFINE_string2(status, a, "", "File containing status of documentation. (Use in place of -b -i)");
Cary Clarkbc5697d2017-10-04 14:31:33 -040018DEFINE_string2(bmh, b, "", "Path to a *.bmh file or a directory.");
Cary Clarkbef063a2017-10-31 15:44:45 -040019DEFINE_bool2(catalog, c, false, "Write example catalog.htm. (Requires -b -f -r)");
Cary Clarkbc5697d2017-10-04 14:31:33 -040020DEFINE_string2(examples, e, "", "File of fiddlecli input, usually fiddle.json (For now, disables -r -f -s)");
21DEFINE_string2(fiddle, f, "", "File of fiddlecli output, usually fiddleout.json.");
Cary Clark5081eed2018-01-22 07:55:48 -050022DEFINE_bool2(hack, H, false, "Do a find/replace hack to update all *.bmh files. (Requires -b)");
23// h is reserved for help
Cary Clarkbc5697d2017-10-04 14:31:33 -040024DEFINE_string2(include, i, "", "Path to a *.h file or a directory.");
Cary Clarkac47b882018-01-11 10:35:44 -050025DEFINE_bool2(selfcheck, k, false, "Check bmh against itself. (Requires -b)");
Cary Clarkbc5697d2017-10-04 14:31:33 -040026DEFINE_bool2(stdout, o, false, "Write file out to standard out.");
27DEFINE_bool2(populate, p, false, "Populate include from bmh. (Requires -b -i)");
Cary Clark5081eed2018-01-22 07:55:48 -050028// q is reserved for quiet
Cary Clark7cfcbca2018-01-04 16:11:51 -050029DEFINE_string2(ref, r, "", "Resolve refs and write *.md files to path. (Requires -b -f)");
Cary Clarkbc5697d2017-10-04 14:31:33 -040030DEFINE_string2(spellcheck, s, "", "Spell-check [once, all, mispelling]. (Requires -b)");
Cary Clarka560c472017-11-27 10:44:06 -050031DEFINE_bool2(tokens, t, false, "Write bmh from include. (Requires -b -i)");
Cary Clarkbc5697d2017-10-04 14:31:33 -040032DEFINE_bool2(crosscheck, x, false, "Check bmh against includes. (Requires -b -i)");
Cary Clark5081eed2018-01-22 07:55:48 -050033// v is reserved for verbose
Cary Clark682c58d2018-05-16 07:07:07 -040034DEFINE_bool2(validate, V, false, "Validate that all anchor references have definitions. (Requires -r)");
Cary Clark884dd7d2017-10-11 10:37:52 -040035DEFINE_bool2(skip, z, false, "Skip degenerate missed in legacy preprocessor.");
Cary Clark8032b982017-07-28 11:04:54 -040036
Cary Clark09d80c02018-10-31 12:14:03 -040037// -b docs -i include/core/SkRect.h -f fiddleout.json -r site/user/api
38// -b docs/SkIRect_Reference.bmh -H
Cary Clark682c58d2018-05-16 07:07:07 -040039/* todos:
Cary Clark8032b982017-07-28 11:04:54 -040040
Cary Clarka90ea222018-10-16 10:30:28 -040041if #Subtopic contains #SeeAlso or #Example generate horizontal rule at end
42constexpr populated with filter inside subtopic does not have definition body
43
Cary Clark8032b982017-07-28 11:04:54 -040044#List needs '# content ##', formatting
Cary Clark186d08f2018-04-03 08:43:27 -040045rewrap text to fit in some number of columns
46#Literal is inflexible, making the entire #Code block link-less (see $Literal in SkImageInfo)
Cary Clark80247e52018-07-11 16:18:41 -040047 would rather keep links for body above #Literal, and/or make it a block and not a one-liner
Cary Clark682c58d2018-05-16 07:07:07 -040048add check to require #Const to contain #Code block if defining const or constexpr (enum consts have
Cary Clark80247e52018-07-11 16:18:41 -040049 #Code blocks inside the #Enum def)
Cary Clarkd2ca79c2018-08-10 13:09:13 -040050subclasses (e.g. Iter in SkPath) need to check for #Line and generate overview
51 subclass methods should also disallow #In
Cary Clark682c58d2018-05-16 07:07:07 -040052
Cary Clark682c58d2018-05-16 07:07:07 -040053It's awkward that phrase param is a child of the phrase def. Since phrase refs may also be children,
54there is special case code to skip phrase def when looking for additional substitutions in the
55phrase def. Could put it in the token list instead I guess, or make a definition subclass used
56by phrase def with an additional slot...
57
Cary Clark682c58d2018-05-16 07:07:07 -040058rearrange const out for md so that const / value / short description comes first in a table,
59followed by more elaborate descriptions, examples, seealso. In md.cpp, look to see if #Subtopic
60has #Const children. If so, generate a summary table first.
61Or, only allow #Line and moderate text description in #Const. Put more verbose text, example,
62seealso, in subsequent #SubTopic. Alpha_Type does this and it looks good.
63
Cary Clark61313f32018-10-08 14:57:48 -040064IPoint is awkward. SkPoint and SkIPoint are named things; Point is a topic, which
65refers to float points or integer points. There needn't be an IPoint topic.
66One way to resolve this would be to combine SkPoint_Reference and SkIPoint_Reference into
67Point_Reference that then contains both structs (or just move SKIPoint into SkPoint_Reference).
68Most Point references would be replaced with SkPoint / SkIPoint (if that's what they mean),
69or remain Point if the text indicates the concept rather one of the C structs.
70
Cary Clarkac47b882018-01-11 10:35:44 -050071see head of selfCheck.cpp for additional todos
Cary Clark80247e52018-07-11 16:18:41 -040072see head of spellCheck.cpp for additional todos
Cary Clark8032b982017-07-28 11:04:54 -040073 */
74
Ben Wagner63fd7602017-10-09 15:45:33 -040075/*
Cary Clark8032b982017-07-28 11:04:54 -040076 class contains named struct, enum, enum-member, method, topic, subtopic
77 everything contained by class is uniquely named
78 contained names may be reused by other classes
79 method contains named parameters
80 parameters may be reused in other methods
81 */
82
Cary Clark2d4bf5f2018-04-16 08:37:38 -040083#define M(mt) (1LL << (int) MarkType::k##mt)
84#define M_D M(Description)
85#define M_CS M(Class) | M(Struct)
Cary Clark682c58d2018-05-16 07:07:07 -040086#define M_MD M(Method) | M(Define)
87#define M_MDCM M_MD | M(Const) | M(Member)
Cary Clark2d4bf5f2018-04-16 08:37:38 -040088#define M_ST M(Subtopic) | M(Topic)
89#define M_CSST M_CS | M_ST
90#ifdef M_E
91#undef M_E
92#endif
93#define M_E M(Enum) | M(EnumClass)
94
95#define R_Y Resolvable::kYes
96#define R_N Resolvable::kNo
97#define R_O Resolvable::kOut
Cary Clark2be81cf2018-09-13 12:04:30 -040098#define R_K Resolvable::kCode
Cary Clark2d4bf5f2018-04-16 08:37:38 -040099#define R_F Resolvable::kFormula
100#define R_C Resolvable::kClone
101
102#define E_Y Exemplary::kYes
103#define E_N Exemplary::kNo
104#define E_O Exemplary::kOptional
105
Cary Clark682c58d2018-05-16 07:07:07 -0400106// ToDo: add column to denote which marks are one-liners
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400107BmhParser::MarkProps BmhParser::kMarkProps[] = {
108// names without formal definitions (e.g. Column) aren't included
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400109 { "", MarkType::kNone, R_Y, E_N, 0 }
110, { "A", MarkType::kAnchor, R_N, E_N, 0 }
Cary Clark682c58d2018-05-16 07:07:07 -0400111, { "Alias", MarkType::kAlias, R_N, E_N, M_ST | M(Const) }
112, { "Bug", MarkType::kBug, R_N, E_N, M_CSST | M_MDCM | M_E
113 | M(Example) | M(NoExample) }
114, { "Class", MarkType::kClass, R_Y, E_O, M_CSST }
Cary Clark2be81cf2018-09-13 12:04:30 -0400115, { "Code", MarkType::kCode, R_K, E_N, M_CSST | M_E | M_MD | M(Typedef) }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400116, { "", MarkType::kColumn, R_Y, E_N, M(Row) }
117, { "", MarkType::kComment, R_N, E_N, 0 }
Cary Clark224c7002018-06-27 11:00:21 -0400118, { "Const", MarkType::kConst, R_Y, E_O, M_E | M_CSST }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400119, { "Define", MarkType::kDefine, R_O, E_Y, M_ST }
Cary Clark682c58d2018-05-16 07:07:07 -0400120, { "Deprecated", MarkType::kDeprecated, R_Y, E_N, M_CS | M_MDCM | M_E }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400121, { "Description", MarkType::kDescription, R_Y, E_N, M(Example) | M(NoExample) }
Cary Clark682c58d2018-05-16 07:07:07 -0400122, { "Details", MarkType::kDetails, R_N, E_N, M(Const) }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400123, { "Duration", MarkType::kDuration, R_N, E_N, M(Example) | M(NoExample) }
Cary Clark682c58d2018-05-16 07:07:07 -0400124, { "Enum", MarkType::kEnum, R_Y, E_O, M_CSST }
125, { "EnumClass", MarkType::kEnumClass, R_Y, E_O, M_CSST }
Cary Clark224c7002018-06-27 11:00:21 -0400126, { "Example", MarkType::kExample, R_O, E_N, M_CSST | M_E | M_MD | M(Const) }
Cary Clark682c58d2018-05-16 07:07:07 -0400127, { "Experimental", MarkType::kExperimental, R_Y, E_N, M_CS | M_MDCM | M_E }
128, { "External", MarkType::kExternal, R_Y, E_N, 0 }
Cary Clark0d225392018-06-07 09:59:07 -0400129, { "File", MarkType::kFile, R_Y, E_N, M(Topic) }
Cary Clarka90ea222018-10-16 10:30:28 -0400130, { "Filter", MarkType::kFilter, R_N, E_N, M(Subtopic) | M(Code) }
Cary Clark682c58d2018-05-16 07:07:07 -0400131, { "Formula", MarkType::kFormula, R_F, E_N, M(Column) | M(Description)
132 | M_E | M_ST | M_MDCM }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400133, { "Function", MarkType::kFunction, R_O, E_N, M(Example) | M(NoExample) }
134, { "Height", MarkType::kHeight, R_N, E_N, M(Example) | M(NoExample) }
Cary Clark682c58d2018-05-16 07:07:07 -0400135, { "Illustration", MarkType::kIllustration, R_N, E_N, M_CSST | M_MD }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400136, { "Image", MarkType::kImage, R_N, E_N, M(Example) | M(NoExample) }
Cary Clarka90ea222018-10-16 10:30:28 -0400137, { "In", MarkType::kIn, R_N, E_N, M_CSST | M_E | M(Method) | M(Typedef) | M(Code) }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400138, { "Legend", MarkType::kLegend, R_Y, E_N, M(Table) }
139, { "Line", MarkType::kLine, R_N, E_N, M_CSST | M_E | M(Method) | M(Typedef) }
140, { "", MarkType::kLink, R_N, E_N, M(Anchor) }
141, { "List", MarkType::kList, R_Y, E_N, M(Method) | M_CSST | M_E | M_D }
142, { "Literal", MarkType::kLiteral, R_N, E_N, M(Code) }
143, { "", MarkType::kMarkChar, R_N, E_N, 0 }
Cary Clark61313f32018-10-08 14:57:48 -0400144, { "Member", MarkType::kMember, R_Y, E_O, M_CSST }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400145, { "Method", MarkType::kMethod, R_Y, E_Y, M_CSST }
Cary Clark682c58d2018-05-16 07:07:07 -0400146, { "NoExample", MarkType::kNoExample, R_N, E_N, M_CSST | M_E | M_MD }
147, { "NoJustify", MarkType::kNoJustify, R_N, E_N, M(Const) | M(Member) }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400148, { "Outdent", MarkType::kOutdent, R_N, E_N, M(Code) }
149, { "Param", MarkType::kParam, R_Y, E_N, M(Method) | M(Define) }
Cary Clark224c7002018-06-27 11:00:21 -0400150, { "PhraseDef", MarkType::kPhraseDef, R_Y, E_N, M_ST }
Cary Clark682c58d2018-05-16 07:07:07 -0400151, { "", MarkType::kPhraseParam, R_Y, E_N, 0 }
152, { "", MarkType::kPhraseRef, R_N, E_N, 0 }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400153, { "Platform", MarkType::kPlatform, R_N, E_N, M(Example) | M(NoExample) }
Cary Clarka64e4ee2018-10-18 08:30:34 -0400154, { "Populate", MarkType::kPopulate, R_N, E_N, M(Code) | M(Method) }
Cary Clark224c7002018-06-27 11:00:21 -0400155, { "Private", MarkType::kPrivate, R_N, E_N, M_CSST | M_MDCM | M_E }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400156, { "Return", MarkType::kReturn, R_Y, E_N, M(Method) }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400157, { "", MarkType::kRow, R_Y, E_N, M(Table) | M(List) }
Cary Clark682c58d2018-05-16 07:07:07 -0400158, { "SeeAlso", MarkType::kSeeAlso, R_C, E_N, M_CSST | M_E | M_MD | M(Typedef) }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400159, { "Set", MarkType::kSet, R_N, E_N, M(Example) | M(NoExample) }
160, { "StdOut", MarkType::kStdOut, R_N, E_N, M(Example) | M(NoExample) }
Cary Clark682c58d2018-05-16 07:07:07 -0400161, { "Struct", MarkType::kStruct, R_Y, E_O, M(Class) | M_ST }
Cary Clark137b8742018-05-30 09:21:49 -0400162, { "Substitute", MarkType::kSubstitute, R_N, E_N, M(Alias) | M_ST }
Cary Clark61313f32018-10-08 14:57:48 -0400163, { "Subtopic", MarkType::kSubtopic, R_Y, E_Y, M_CSST | M_E }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400164, { "Table", MarkType::kTable, R_Y, E_N, M(Method) | M_CSST | M_E }
Cary Clark682c58d2018-05-16 07:07:07 -0400165, { "Template", MarkType::kTemplate, R_Y, E_N, M_CSST }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400166, { "", MarkType::kText, R_N, E_N, 0 }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400167, { "ToDo", MarkType::kToDo, R_N, E_N, 0 }
Cary Clark682c58d2018-05-16 07:07:07 -0400168, { "Topic", MarkType::kTopic, R_Y, E_Y, 0 }
Cary Clark61313f32018-10-08 14:57:48 -0400169, { "Typedef", MarkType::kTypedef, R_Y, E_O, M_CSST | M_E }
Cary Clark682c58d2018-05-16 07:07:07 -0400170, { "Union", MarkType::kUnion, R_Y, E_N, M_CSST }
Cary Clark61313f32018-10-08 14:57:48 -0400171, { "Using", MarkType::kUsing, R_Y, E_O, M_CSST }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400172, { "Volatile", MarkType::kVolatile, R_N, E_N, M(StdOut) }
173, { "Width", MarkType::kWidth, R_N, E_N, M(Example) | M(NoExample) }
174};
175
176#undef R_O
177#undef R_N
178#undef R_Y
Cary Clark2be81cf2018-09-13 12:04:30 -0400179#undef R_K
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400180#undef R_F
181#undef R_C
182
183#undef M_E
184#undef M_CSST
185#undef M_ST
186#undef M_CS
Cary Clark682c58d2018-05-16 07:07:07 -0400187#undef M_MCD
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400188#undef M_D
189#undef M
190
191#undef E_Y
192#undef E_N
193#undef E_O
194
Cary Clark8032b982017-07-28 11:04:54 -0400195bool BmhParser::addDefinition(const char* defStart, bool hasEnd, MarkType markType,
Cary Clarkf895a422018-02-27 09:54:21 -0500196 const vector<string>& typeNameBuilder, HasTag hasTag) {
Cary Clark8032b982017-07-28 11:04:54 -0400197 Definition* definition = nullptr;
198 switch (markType) {
199 case MarkType::kComment:
200 if (!this->skipToDefinitionEnd(markType)) {
201 return false;
202 }
203 return true;
204 // these types may be referred to by name
205 case MarkType::kClass:
206 case MarkType::kStruct:
207 case MarkType::kConst:
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400208 case MarkType::kDefine:
Cary Clark8032b982017-07-28 11:04:54 -0400209 case MarkType::kEnum:
210 case MarkType::kEnumClass:
211 case MarkType::kMember:
212 case MarkType::kMethod:
213 case MarkType::kTypedef: {
214 if (!typeNameBuilder.size()) {
215 return this->reportError<bool>("unnamed markup");
216 }
217 if (typeNameBuilder.size() > 1) {
218 return this->reportError<bool>("expected one name only");
219 }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400220 string name = typeNameBuilder[0];
Cary Clark8032b982017-07-28 11:04:54 -0400221 if (nullptr == fRoot) {
222 fRoot = this->findBmhObject(markType, name);
223 fRoot->fFileName = fFileName;
Cary Clark09d80c02018-10-31 12:14:03 -0400224 fRoot->fName = name;
225 fRoot->fNames.fName = name;
226 fRoot->fNames.fParent = &fGlobalNames;
Cary Clark8032b982017-07-28 11:04:54 -0400227 definition = fRoot;
228 } else {
229 if (nullptr == fParent) {
230 return this->reportError<bool>("expected parent");
231 }
232 if (fParent == fRoot && hasEnd) {
233 RootDefinition* rootParent = fRoot->rootParent();
234 if (rootParent) {
235 fRoot = rootParent;
236 }
237 definition = fParent;
238 } else {
Cary Clarkce101242017-09-01 15:51:02 -0400239 if (!hasEnd && fRoot->find(name, RootDefinition::AllowParens::kNo)) {
Cary Clark8032b982017-07-28 11:04:54 -0400240 return this->reportError<bool>("duplicate symbol");
241 }
Cary Clark61313f32018-10-08 14:57:48 -0400242 if (MarkType::kStruct == markType || MarkType::kClass == markType
243 || MarkType::kEnumClass == markType) {
Cary Clark8032b982017-07-28 11:04:54 -0400244 // if class or struct, build fRoot hierarchy
245 // and change isDefined to search all parents of fRoot
246 SkASSERT(!hasEnd);
247 RootDefinition* childRoot = new RootDefinition;
248 (fRoot->fBranches)[name] = childRoot;
249 childRoot->setRootParent(fRoot);
250 childRoot->fFileName = fFileName;
Cary Clark09d80c02018-10-31 12:14:03 -0400251 SkASSERT(MarkType::kSubtopic != fRoot->fMarkType
252 && MarkType::kTopic != fRoot->fMarkType);
253 childRoot->fNames.fName = name;
254 childRoot->fNames.fParent = &fRoot->fNames;
Cary Clark8032b982017-07-28 11:04:54 -0400255 fRoot = childRoot;
256 definition = fRoot;
257 } else {
258 definition = &fRoot->fLeaves[name];
259 }
260 }
261 }
262 if (hasEnd) {
263 Exemplary hasExample = Exemplary::kNo;
264 bool hasExcluder = false;
265 for (auto child : definition->fChildren) {
266 if (MarkType::kExample == child->fMarkType) {
267 hasExample = Exemplary::kYes;
268 }
269 hasExcluder |= MarkType::kPrivate == child->fMarkType
270 || MarkType::kDeprecated == child->fMarkType
271 || MarkType::kExperimental == child->fMarkType
272 || MarkType::kNoExample == child->fMarkType;
273 }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400274 if (kMarkProps[(int) markType].fExemplary != hasExample
275 && kMarkProps[(int) markType].fExemplary != Exemplary::kOptional) {
Cary Clark8032b982017-07-28 11:04:54 -0400276 if (string::npos == fFileName.find("undocumented")
277 && !hasExcluder) {
Ben Wagner63fd7602017-10-09 15:45:33 -0400278 hasExample == Exemplary::kNo ?
279 this->reportWarning("missing example") :
Cary Clark8032b982017-07-28 11:04:54 -0400280 this->reportWarning("unexpected example");
281 }
282
283 }
284 if (MarkType::kMethod == markType) {
285 if (fCheckMethods && !definition->checkMethod()) {
286 return false;
287 }
288 }
Cary Clarkf895a422018-02-27 09:54:21 -0500289 if (HasTag::kYes == hasTag) {
290 if (!this->checkEndMarker(markType, definition->fName)) {
291 return false;
292 }
293 }
Cary Clark8032b982017-07-28 11:04:54 -0400294 if (!this->popParentStack(definition)) {
295 return false;
296 }
Cary Clark06c20f32018-03-20 15:53:27 -0400297 if (fRoot == definition) {
298 fRoot = nullptr;
299 }
Cary Clark8032b982017-07-28 11:04:54 -0400300 } else {
301 definition->fStart = defStart;
302 this->skipSpace();
303 definition->fFileName = fFileName;
304 definition->fContentStart = fChar;
305 definition->fLineCount = fLineCount;
306 definition->fClone = fCloned;
307 if (MarkType::kConst == markType) {
308 // todo: require that fChar points to def on same line as markup
309 // additionally add definition to class children if it is not already there
310 if (definition->fParent != fRoot) {
311// fRoot->fChildren.push_back(definition);
312 }
313 }
Cary Clark82f1f742018-06-28 08:50:35 -0400314 SkASSERT(string::npos == name.find('\n'));
Cary Clark8032b982017-07-28 11:04:54 -0400315 definition->fName = name;
316 if (MarkType::kMethod == markType) {
317 if (string::npos != name.find(':', 0)) {
318 definition->setCanonicalFiddle();
319 } else {
320 definition->fFiddle = name;
321 }
322 } else {
Cary Clarka560c472017-11-27 10:44:06 -0500323 definition->fFiddle = Definition::NormalizedName(name);
Cary Clark8032b982017-07-28 11:04:54 -0400324 }
325 definition->fMarkType = markType;
Cary Clarkd0530ba2017-09-14 11:25:39 -0400326 definition->fAnonymous = fAnonymous;
Cary Clark8032b982017-07-28 11:04:54 -0400327 this->setAsParent(definition);
328 }
329 } break;
330 case MarkType::kTopic:
331 case MarkType::kSubtopic:
332 SkASSERT(1 == typeNameBuilder.size());
333 if (!hasEnd) {
334 if (!typeNameBuilder.size()) {
335 return this->reportError<bool>("unnamed topic");
336 }
Cary Clark1a8d7622018-03-05 13:26:16 -0500337 fTopics.emplace_front(markType, defStart, fLineCount, fParent, fMC);
Cary Clark8032b982017-07-28 11:04:54 -0400338 RootDefinition* rootDefinition = &fTopics.front();
339 definition = rootDefinition;
340 definition->fFileName = fFileName;
341 definition->fContentStart = fChar;
Cary Clark2a8c48b2018-02-15 17:31:24 -0500342 if (MarkType::kTopic == markType) {
343 if (fParent) {
344 return this->reportError<bool>("#Topic must be root");
345 }
346 // topic name is unappended
347 definition->fName = typeNameBuilder[0];
348 } else {
349 if (!fParent) {
350 return this->reportError<bool>("#Subtopic may not be root");
351 }
352 Definition* parent = fParent;
353 while (MarkType::kTopic != parent->fMarkType && MarkType::kSubtopic != parent->fMarkType) {
354 parent = parent->fParent;
355 if (!parent) {
356 // subtopic must have subtopic or topic in parent chain
357 return this->reportError<bool>("#Subtopic missing parent");
358 }
359 }
360 if (MarkType::kSubtopic == parent->fMarkType) {
361 // subtopic prepends parent subtopic name, but not parent topic name
362 definition->fName = parent->fName + '_';
363 }
364 definition->fName += typeNameBuilder[0];
365 definition->fFiddle = parent->fFiddle + '_';
Cary Clark8032b982017-07-28 11:04:54 -0400366 }
Cary Clark09d80c02018-10-31 12:14:03 -0400367 rootDefinition->fNames.fName = rootDefinition->fName;
Cary Clarka560c472017-11-27 10:44:06 -0500368 definition->fFiddle += Definition::NormalizedName(typeNameBuilder[0]);
Cary Clark8032b982017-07-28 11:04:54 -0400369 this->setAsParent(definition);
370 }
371 {
Cary Clark08895c42018-02-01 09:37:32 -0500372 SkASSERT(hasEnd ? fParent : definition);
373 string fullTopic = hasEnd ? fParent->fFiddle : definition->fFiddle;
Cary Clark8032b982017-07-28 11:04:54 -0400374 Definition* defPtr = fTopicMap[fullTopic];
375 if (hasEnd) {
Cary Clarkf895a422018-02-27 09:54:21 -0500376 if (HasTag::kYes == hasTag && !this->checkEndMarker(markType, fullTopic)) {
377 return false;
378 }
Cary Clark8032b982017-07-28 11:04:54 -0400379 if (!definition) {
380 definition = defPtr;
381 } else if (definition != defPtr) {
382 return this->reportError<bool>("mismatched topic");
383 }
384 } else {
385 if (nullptr != defPtr) {
386 return this->reportError<bool>("already declared topic");
387 }
388 fTopicMap[fullTopic] = definition;
389 }
390 }
391 if (hasEnd) {
392 if (!this->popParentStack(definition)) {
393 return false;
394 }
395 }
396 break;
Cary Clark2be81cf2018-09-13 12:04:30 -0400397 case MarkType::kFormula:
398 // hasEnd : single line / multiple line
399 if (!fParent || MarkType::kFormula != fParent->fMarkType) {
400 SkASSERT(!definition || MarkType::kFormula == definition->fMarkType);
401 fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC);
402 definition = &fMarkup.front();
403 definition->fContentStart = fChar;
404 definition->fName = typeNameBuilder[0];
405 definition->fFiddle = fParent->fFiddle;
406 fParent = definition;
407 } else {
408 SkASSERT(fParent && MarkType::kFormula == fParent->fMarkType);
409 SkASSERT(fMC == defStart[0]);
410 SkASSERT(fMC == defStart[-1]);
411 definition = fParent;
412 definition->fTerminator = fChar;
413 if (!this->popParentStack(definition)) {
414 return false;
415 }
416 this->parseHashFormula(definition);
417 fParent->fChildren.push_back(definition);
418 }
419 break;
Cary Clark8032b982017-07-28 11:04:54 -0400420 // these types are children of parents, but are not in named maps
Cary Clark8032b982017-07-28 11:04:54 -0400421 case MarkType::kDescription:
422 case MarkType::kStdOut:
423 // may be one-liner
Cary Clark137b8742018-05-30 09:21:49 -0400424 case MarkType::kAlias:
Cary Clark8032b982017-07-28 11:04:54 -0400425 case MarkType::kNoExample:
426 case MarkType::kParam:
Cary Clark1a8d7622018-03-05 13:26:16 -0500427 case MarkType::kPhraseDef:
Cary Clark8032b982017-07-28 11:04:54 -0400428 case MarkType::kReturn:
429 case MarkType::kToDo:
430 if (hasEnd) {
431 if (markType == fParent->fMarkType) {
432 definition = fParent;
433 if (MarkType::kBug == markType || MarkType::kReturn == markType
434 || MarkType::kToDo == markType) {
435 this->skipNoName();
436 }
437 if (!this->popParentStack(fParent)) { // if not one liner, pop
438 return false;
439 }
Cary Clark1a8d7622018-03-05 13:26:16 -0500440 if (MarkType::kParam == markType || MarkType::kReturn == markType
441 || MarkType::kPhraseDef == markType) {
Cary Clarka523d2d2017-08-30 08:58:10 -0400442 if (!this->checkParamReturn(definition)) {
443 return false;
Cary Clark579985c2017-07-31 11:48:27 -0400444 }
445 }
Cary Clark1a8d7622018-03-05 13:26:16 -0500446 if (MarkType::kPhraseDef == markType) {
447 string key = definition->fName;
448 if (fPhraseMap.end() != fPhraseMap.find(key)) {
449 this->reportError<bool>("duplicate phrase key");
450 }
451 fPhraseMap[key] = definition;
452 }
Cary Clark8032b982017-07-28 11:04:54 -0400453 } else {
Cary Clark1a8d7622018-03-05 13:26:16 -0500454 fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC);
Cary Clark8032b982017-07-28 11:04:54 -0400455 definition = &fMarkup.front();
456 definition->fName = typeNameBuilder[0];
Cary Clark73fa9722017-08-29 17:36:51 -0400457 definition->fFiddle = fParent->fFiddle;
Cary Clark8032b982017-07-28 11:04:54 -0400458 definition->fContentStart = fChar;
Cary Clark1a8d7622018-03-05 13:26:16 -0500459 string endBracket;
460 endBracket += fMC;
461 endBracket += fMC;
462 definition->fContentEnd = this->trimmedBracketEnd(endBracket);
463 this->skipToEndBracket(endBracket.c_str());
Cary Clark8032b982017-07-28 11:04:54 -0400464 SkAssertResult(fMC == this->next());
465 SkAssertResult(fMC == this->next());
466 definition->fTerminator = fChar;
Cary Clark1a8d7622018-03-05 13:26:16 -0500467 TextParser checkForChildren(definition);
468 if (checkForChildren.strnchr(fMC, definition->fContentEnd)) {
469 this->reportError<bool>("put ## on separate line");
470 }
Cary Clark8032b982017-07-28 11:04:54 -0400471 fParent->fChildren.push_back(definition);
472 }
Cary Clark137b8742018-05-30 09:21:49 -0400473 if (MarkType::kAlias == markType) {
474 const char* end = definition->fChildren.size() > 0 ?
475 definition->fChildren[0]->fStart : definition->fContentEnd;
476 TextParser parser(definition->fFileName, definition->fContentStart, end,
477 definition->fLineCount);
478 parser.trimEnd();
479 string key = string(parser.fStart, parser.lineLength());
480 if (fAliasMap.end() != fAliasMap.find(key)) {
481 return this->reportError<bool>("duplicate alias");
482 }
483 fAliasMap[key] = definition;
484 definition->fFiddle = definition->fParent->fFiddle;
485 }
Cary Clark8032b982017-07-28 11:04:54 -0400486 break;
Cary Clark682c58d2018-05-16 07:07:07 -0400487 } else if (MarkType::kPhraseDef == markType) {
488 bool hasParams = '(' == this->next();
489 fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC);
490 definition = &fMarkup.front();
491 definition->fName = typeNameBuilder[0];
492 definition->fFiddle = fParent->fFiddle;
493 definition->fContentStart = fChar;
494 if (hasParams) {
495 char lastChar;
496 do {
497 const char* subEnd = this->anyOf(",)\n");
498 if (!subEnd || '\n' == *subEnd) {
499 return this->reportError<bool>("unexpected phrase list end");
500 }
501 fMarkup.emplace_front(MarkType::kPhraseParam, fChar, fLineCount, fParent,
502 fMC);
503 Definition* phraseParam = &fMarkup.front();
504 phraseParam->fContentStart = fChar;
505 phraseParam->fContentEnd = subEnd;
506 phraseParam->fName = string(fChar, subEnd - fChar);
507 definition->fChildren.push_back(phraseParam);
508 this->skipTo(subEnd);
509 lastChar = this->next();
510 phraseParam->fTerminator = fChar;
511 } while (')' != lastChar);
512 this->skipWhiteSpace();
513 definition->fContentStart = fChar;
514 }
515 this->setAsParent(definition);
516 break;
Cary Clark8032b982017-07-28 11:04:54 -0400517 }
518 // not one-liners
519 case MarkType::kCode:
Cary Clark8032b982017-07-28 11:04:54 -0400520 case MarkType::kExample:
Cary Clark0d225392018-06-07 09:59:07 -0400521 case MarkType::kFile:
Cary Clark8032b982017-07-28 11:04:54 -0400522 case MarkType::kFunction:
523 case MarkType::kLegend:
524 case MarkType::kList:
525 case MarkType::kPrivate:
526 case MarkType::kTable:
Cary Clark8032b982017-07-28 11:04:54 -0400527 if (hasEnd) {
528 definition = fParent;
529 if (markType != fParent->fMarkType) {
530 return this->reportError<bool>("end element mismatch");
531 } else if (!this->popParentStack(fParent)) {
532 return false;
533 }
534 if (MarkType::kExample == markType) {
535 if (definition->fChildren.size() == 0) {
536 TextParser emptyCheck(definition);
537 if (emptyCheck.eof() || !emptyCheck.skipWhiteSpace()) {
Cary Clark884dd7d2017-10-11 10:37:52 -0400538 return this->reportError<bool>("missing example body");
Cary Clark8032b982017-07-28 11:04:54 -0400539 }
540 }
Cary Clark1a8d7622018-03-05 13:26:16 -0500541// can't do this here; phrase refs may not have been defined yet
542// this->setWrapper(definition);
Cary Clark8032b982017-07-28 11:04:54 -0400543 }
544 } else {
Cary Clark1a8d7622018-03-05 13:26:16 -0500545 fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC);
Cary Clark8032b982017-07-28 11:04:54 -0400546 definition = &fMarkup.front();
547 definition->fContentStart = fChar;
548 definition->fName = typeNameBuilder[0];
549 definition->fFiddle = fParent->fFiddle;
550 char suffix = '\0';
551 bool tryAgain;
552 do {
553 tryAgain = false;
554 for (const auto& child : fParent->fChildren) {
555 if (child->fFiddle == definition->fFiddle) {
556 if (MarkType::kExample != child->fMarkType) {
557 continue;
558 }
559 if ('\0' == suffix) {
560 suffix = 'a';
561 } else if (++suffix > 'z') {
562 return reportError<bool>("too many examples");
563 }
564 definition->fFiddle = fParent->fFiddle + '_';
565 definition->fFiddle += suffix;
566 tryAgain = true;
567 break;
568 }
569 }
570 } while (tryAgain);
571 this->setAsParent(definition);
572 }
573 break;
574 // always treated as one-liners (can't detect misuse easily)
Ben Wagner63fd7602017-10-09 15:45:33 -0400575 case MarkType::kAnchor:
Cary Clark4855f782018-02-06 09:41:53 -0500576 case MarkType::kBug:
Cary Clark4855f782018-02-06 09:41:53 -0500577 case MarkType::kDeprecated:
Cary Clark682c58d2018-05-16 07:07:07 -0400578 case MarkType::kDetails:
Cary Clarkac47b882018-01-11 10:35:44 -0500579 case MarkType::kDuration:
Cary Clark682c58d2018-05-16 07:07:07 -0400580 case MarkType::kExperimental:
Cary Clarka90ea222018-10-16 10:30:28 -0400581 case MarkType::kFilter:
Cary Clark8032b982017-07-28 11:04:54 -0400582 case MarkType::kHeight:
Cary Clarkf895a422018-02-27 09:54:21 -0500583 case MarkType::kIllustration:
Cary Clark8032b982017-07-28 11:04:54 -0400584 case MarkType::kImage:
Cary Clarkab2621d2018-01-30 10:08:57 -0500585 case MarkType::kIn:
586 case MarkType::kLine:
587 case MarkType::kLiteral:
Cary Clark682c58d2018-05-16 07:07:07 -0400588 case MarkType::kNoJustify:
Cary Clark154beea2017-10-26 07:58:48 -0400589 case MarkType::kOutdent:
Cary Clark8032b982017-07-28 11:04:54 -0400590 case MarkType::kPlatform:
Cary Clark08895c42018-02-01 09:37:32 -0500591 case MarkType::kPopulate:
Cary Clark8032b982017-07-28 11:04:54 -0400592 case MarkType::kSeeAlso:
Cary Clark61dfc3a2018-01-03 08:37:53 -0500593 case MarkType::kSet:
Cary Clark8032b982017-07-28 11:04:54 -0400594 case MarkType::kSubstitute:
Cary Clark8032b982017-07-28 11:04:54 -0400595 case MarkType::kVolatile:
596 case MarkType::kWidth:
Cary Clark4855f782018-02-06 09:41:53 -0500597 // todo : add check disallowing children?
Cary Clarkab2621d2018-01-30 10:08:57 -0500598 if (hasEnd && MarkType::kAnchor != markType && MarkType::kLine != markType) {
Cary Clark8032b982017-07-28 11:04:54 -0400599 return this->reportError<bool>("one liners omit end element");
Cary Clark6fc50412017-09-21 12:31:06 -0400600 } else if (!hasEnd && MarkType::kAnchor == markType) {
601 return this->reportError<bool>("anchor line must have end element last");
Cary Clark8032b982017-07-28 11:04:54 -0400602 }
Cary Clark1a8d7622018-03-05 13:26:16 -0500603 fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC);
Cary Clark8032b982017-07-28 11:04:54 -0400604 definition = &fMarkup.front();
605 definition->fName = typeNameBuilder[0];
Cary Clarka560c472017-11-27 10:44:06 -0500606 definition->fFiddle = Definition::NormalizedName(typeNameBuilder[0]);
Cary Clark8032b982017-07-28 11:04:54 -0400607 definition->fContentStart = fChar;
Cary Clarkce101242017-09-01 15:51:02 -0400608 definition->fContentEnd = this->trimmedBracketEnd('\n');
Cary Clark8032b982017-07-28 11:04:54 -0400609 definition->fTerminator = this->lineEnd() - 1;
610 fParent->fChildren.push_back(definition);
611 if (MarkType::kAnchor == markType) {
Cary Clark2be81cf2018-09-13 12:04:30 -0400612 this->parseHashAnchor(definition);
Cary Clark137b8742018-05-30 09:21:49 -0400613 } else if (MarkType::kLine == markType) {
Cary Clark2be81cf2018-09-13 12:04:30 -0400614 this->parseHashLine(definition);
Cary Clark682c58d2018-05-16 07:07:07 -0400615 } else if (IncompleteAllowed(markType)) {
Cary Clark4855f782018-02-06 09:41:53 -0500616 this->skipSpace();
617 fParent->fDeprecated = true;
Cary Clark682c58d2018-05-16 07:07:07 -0400618 fParent->fDetails =
619 this->skipExact("soon") ? Definition::Details::kSoonToBe_Deprecated :
620 this->skipExact("testing") ? Definition::Details::kTestingOnly_Experiment :
Cary Clark137b8742018-05-30 09:21:49 -0400621 this->skipExact("do not use") ? Definition::Details::kDoNotUse_Experiment :
Cary Clark682c58d2018-05-16 07:07:07 -0400622 this->skipExact("not ready") ? Definition::Details::kNotReady_Experiment :
623 Definition::Details::kNone;
Cary Clark4855f782018-02-06 09:41:53 -0500624 this->skipSpace();
625 if ('\n' != this->peek()) {
626 return this->reportError<bool>("unexpected text after #Deprecated");
627 }
628 }
Cary Clark8032b982017-07-28 11:04:54 -0400629 break;
630 case MarkType::kExternal:
631 (void) this->collectExternals(); // FIXME: detect errors in external defs?
632 break;
633 default:
634 SkASSERT(0); // fixme : don't let any types be invisible
635 return true;
636 }
637 if (fParent) {
638 SkASSERT(definition);
639 SkASSERT(definition->fName.length() > 0);
640 }
641 return true;
642}
643
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400644void BmhParser::reportDuplicates(const Definition& def, string dup) const {
Cary Clark73fa9722017-08-29 17:36:51 -0400645 if (MarkType::kExample == def.fMarkType && dup == def.fFiddle) {
646 TextParser reporter(&def);
647 reporter.reportError("duplicate example name");
648 }
649 for (auto& child : def.fChildren ) {
650 reportDuplicates(*child, dup);
651 }
652}
653
Cary Clarkf895a422018-02-27 09:54:21 -0500654
655static Definition* find_fiddle(Definition* def, string name) {
656 if (MarkType::kExample == def->fMarkType && name == def->fFiddle) {
657 return def;
658 }
659 for (auto& child : def->fChildren) {
660 Definition* result = find_fiddle(child, name);
661 if (result) {
662 return result;
663 }
664 }
665 return nullptr;
666}
667
668Definition* BmhParser::findExample(string name) const {
669 for (const auto& topic : fTopicMap) {
670 if (topic.second->fParent) {
671 continue;
672 }
673 Definition* def = find_fiddle(topic.second, name);
674 if (def) {
675 return def;
676 }
677 }
678 return nullptr;
679}
680
Cary Clarkd7895502018-07-18 15:10:08 -0400681static bool check_example_hashes(Definition* def) {
682 if (MarkType::kExample == def->fMarkType) {
683 if (def->fHash.length()) {
684 return true;
685 }
686 for (auto child : def->fChildren) {
687 if (MarkType::kPlatform == child->fMarkType) {
688 if (string::npos != string(child->fContentStart, child->length()).find("!fiddle")) {
689 return true;
690 }
691 }
692 }
693 return def->reportError<bool>("missing hash");
694 }
695 for (auto& child : def->fChildren) {
696 if (!check_example_hashes(child)) {
697 return false;
698 }
699 }
700 return true;
701}
702
703bool BmhParser::checkExampleHashes() const {
704 for (const auto& topic : fTopicMap) {
705 if (!topic.second->fParent && !check_example_hashes(topic.second)) {
706 return false;
707 }
708 }
709 return true;
710}
711
712static void reset_example_hashes(Definition* def) {
713 if (MarkType::kExample == def->fMarkType) {
714 def->fHash.clear();
715 return;
716 }
717 for (auto& child : def->fChildren) {
718 reset_example_hashes(child);
719 }
720}
721
722void BmhParser::resetExampleHashes() {
723 for (const auto& topic : fTopicMap) {
724 if (!topic.second->fParent) {
725 reset_example_hashes(topic.second);
726 }
727 }
728}
729
Cary Clark73fa9722017-08-29 17:36:51 -0400730static void find_examples(const Definition& def, vector<string>* exampleNames) {
731 if (MarkType::kExample == def.fMarkType) {
732 exampleNames->push_back(def.fFiddle);
733 }
734 for (auto& child : def.fChildren ) {
735 find_examples(*child, exampleNames);
736 }
737}
738
Cary Clarkf895a422018-02-27 09:54:21 -0500739bool BmhParser::checkEndMarker(MarkType markType, string match) const {
740 TextParser tp(fFileName, fLine, fChar, fLineCount);
741 tp.skipSpace();
742 if (fMC != tp.next()) {
743 return this->reportError<bool>("mismatched end marker expect #");
744 }
745 const char* nameStart = tp.fChar;
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400746 tp.skipToNonName();
Cary Clarkf895a422018-02-27 09:54:21 -0500747 string markName(nameStart, tp.fChar - nameStart);
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400748 if (kMarkProps[(int) markType].fName != markName) {
Cary Clarkf895a422018-02-27 09:54:21 -0500749 return this->reportError<bool>("expected #XXX ## to match");
750 }
751 tp.skipSpace();
752 nameStart = tp.fChar;
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400753 tp.skipToNonName();
Cary Clarkf895a422018-02-27 09:54:21 -0500754 markName = string(nameStart, tp.fChar - nameStart);
755 if ("" == markName) {
756 if (fMC != tp.next() || fMC != tp.next()) {
757 return this->reportError<bool>("expected ##");
758 }
759 return true;
760 }
761 std::replace(markName.begin(), markName.end(), '-', '_');
762 auto defPos = match.rfind(markName);
763 if (string::npos == defPos) {
764 return this->reportError<bool>("mismatched end marker v1");
765 }
766 if (markName.size() != match.size() - defPos) {
767 return this->reportError<bool>("mismatched end marker v2");
768 }
769 return true;
770}
771
Cary Clark73fa9722017-08-29 17:36:51 -0400772bool BmhParser::checkExamples() const {
773 vector<string> exampleNames;
774 for (const auto& topic : fTopicMap) {
775 if (topic.second->fParent) {
776 continue;
777 }
778 find_examples(*topic.second, &exampleNames);
779 }
780 std::sort(exampleNames.begin(), exampleNames.end());
781 string* last = nullptr;
782 string reported;
783 bool checkOK = true;
784 for (auto& nameIter : exampleNames) {
785 if (last && *last == nameIter && reported != *last) {
786 reported = *last;
787 SkDebugf("%s\n", reported.c_str());
788 for (const auto& topic : fTopicMap) {
789 if (topic.second->fParent) {
790 continue;
791 }
792 this->reportDuplicates(*topic.second, reported);
793 }
794 checkOK = false;
795 }
796 last = &nameIter;
797 }
798 return checkOK;
799}
800
Cary Clarka523d2d2017-08-30 08:58:10 -0400801bool BmhParser::checkParamReturn(const Definition* definition) const {
802 const char* parmEndCheck = definition->fContentEnd;
803 while (parmEndCheck < definition->fTerminator) {
804 if (fMC == parmEndCheck[0]) {
805 break;
806 }
807 if (' ' < parmEndCheck[0]) {
808 this->reportError<bool>(
809 "use full end marker on multiline #Param and #Return");
810 }
811 ++parmEndCheck;
812 }
813 return true;
814}
815
Cary Clark8032b982017-07-28 11:04:54 -0400816bool BmhParser::childOf(MarkType markType) const {
817 auto childError = [this](MarkType markType) -> bool {
818 string errStr = "expected ";
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400819 errStr += kMarkProps[(int) markType].fName;
Cary Clark8032b982017-07-28 11:04:54 -0400820 errStr += " parent";
821 return this->reportError<bool>(errStr.c_str());
822 };
823
824 if (markType == fParent->fMarkType) {
825 return true;
826 }
827 if (this->hasEndToken()) {
828 if (!fParent->fParent) {
829 return this->reportError<bool>("expected grandparent");
830 }
831 if (markType == fParent->fParent->fMarkType) {
832 return true;
833 }
834 }
835 return childError(markType);
836}
837
838string BmhParser::className(MarkType markType) {
Cary Clark8032b982017-07-28 11:04:54 -0400839 const char* end = this->lineEnd();
840 const char* mc = this->strnchr(fMC, end);
Cary Clark73fa9722017-08-29 17:36:51 -0400841 string classID;
Cary Clark186d08f2018-04-03 08:43:27 -0400842 TextParserSave savePlace(this);
Cary Clark73fa9722017-08-29 17:36:51 -0400843 this->skipSpace();
844 const char* wordStart = fChar;
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400845 this->skipToNonName();
Cary Clark73fa9722017-08-29 17:36:51 -0400846 const char* wordEnd = fChar;
847 classID = string(wordStart, wordEnd - wordStart);
848 if (!mc) {
849 savePlace.restore();
850 }
851 string builder;
852 const Definition* parent = this->parentSpace();
853 if (parent && parent->fName != classID) {
854 builder += parent->fName;
855 }
Cary Clark8032b982017-07-28 11:04:54 -0400856 if (mc) {
Cary Clark8032b982017-07-28 11:04:54 -0400857 if (mc + 1 < fEnd && fMC == mc[1]) { // if ##
858 if (markType != fParent->fMarkType) {
859 return this->reportError<string>("unbalanced method");
860 }
Cary Clark73fa9722017-08-29 17:36:51 -0400861 if (builder.length() > 0 && classID.size() > 0) {
Cary Clark8032b982017-07-28 11:04:54 -0400862 if (builder != fParent->fName) {
863 builder += "::";
Cary Clark73fa9722017-08-29 17:36:51 -0400864 builder += classID;
Cary Clark8032b982017-07-28 11:04:54 -0400865 if (builder != fParent->fName) {
866 return this->reportError<string>("name mismatch");
867 }
868 }
869 }
870 this->skipLine();
871 return fParent->fName;
872 }
873 fChar = mc;
874 this->next();
875 }
876 this->skipWhiteSpace();
877 if (MarkType::kEnum == markType && fChar >= end) {
878 fAnonymous = true;
879 builder += "::_anonymous";
880 return uniqueRootName(builder, markType);
881 }
882 builder = this->word(builder, "::");
883 return builder;
884}
885
886bool BmhParser::collectExternals() {
887 do {
888 this->skipWhiteSpace();
889 if (this->eof()) {
890 break;
891 }
892 if (fMC == this->peek()) {
893 this->next();
894 if (this->eof()) {
895 break;
896 }
897 if (fMC == this->peek()) {
898 this->skipLine();
899 break;
900 }
901 if (' ' >= this->peek()) {
902 this->skipLine();
903 continue;
904 }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400905 if (this->startsWith(kMarkProps[(int) MarkType::kExternal].fName)) {
906 this->skipToNonName();
Cary Clark8032b982017-07-28 11:04:54 -0400907 continue;
908 }
909 }
910 this->skipToAlpha();
911 const char* wordStart = fChar;
Cary Clark09d80c02018-10-31 12:14:03 -0400912 this->skipToWhiteSpace();
Cary Clark8032b982017-07-28 11:04:54 -0400913 if (fChar - wordStart > 0) {
Cary Clark1a8d7622018-03-05 13:26:16 -0500914 fExternals.emplace_front(MarkType::kExternal, wordStart, fChar, fLineCount, fParent,
915 fMC);
Cary Clark8032b982017-07-28 11:04:54 -0400916 RootDefinition* definition = &fExternals.front();
917 definition->fFileName = fFileName;
918 definition->fName = string(wordStart ,fChar - wordStart);
Cary Clarka560c472017-11-27 10:44:06 -0500919 definition->fFiddle = Definition::NormalizedName(definition->fName);
Cary Clark8032b982017-07-28 11:04:54 -0400920 }
921 } while (!this->eof());
922 return true;
923}
924
Cary Clark1a8d7622018-03-05 13:26:16 -0500925bool BmhParser::dumpExamples(FILE* fiddleOut, Definition& def, bool* continuation) const {
Cary Clark73fa9722017-08-29 17:36:51 -0400926 if (MarkType::kExample == def.fMarkType) {
927 string result;
Cary Clark1a8d7622018-03-05 13:26:16 -0500928 if (!this->exampleToScript(&def, BmhParser::ExampleOptions::kAll, &result)) {
Cary Clark73fa9722017-08-29 17:36:51 -0400929 return false;
930 }
931 if (result.length() > 0) {
Cary Clarkbef063a2017-10-31 15:44:45 -0400932 result += "\n";
933 result += "}";
Cary Clark73fa9722017-08-29 17:36:51 -0400934 if (*continuation) {
935 fprintf(fiddleOut, ",\n");
936 } else {
937 *continuation = true;
938 }
939 fprintf(fiddleOut, "%s", result.c_str());
940 }
941 return true;
942 }
943 for (auto& child : def.fChildren ) {
Cary Clark1a8d7622018-03-05 13:26:16 -0500944 if (!this->dumpExamples(fiddleOut, *child, continuation)) {
Cary Clark73fa9722017-08-29 17:36:51 -0400945 return false;
946 }
947 }
948 return true;
949}
950
951bool BmhParser::dumpExamples(const char* fiddleJsonFileName) const {
Cary Clark5b1f9532018-08-28 14:53:37 -0400952 string oldFiddle(fiddleJsonFileName);
953 string newFiddle(fiddleJsonFileName);
954 newFiddle += "_new";
955 FILE* fiddleOut = fopen(newFiddle.c_str(), "wb");
Cary Clark73fa9722017-08-29 17:36:51 -0400956 if (!fiddleOut) {
Cary Clark5b1f9532018-08-28 14:53:37 -0400957 SkDebugf("could not open output file %s\n", newFiddle.c_str());
Cary Clark73fa9722017-08-29 17:36:51 -0400958 return false;
959 }
960 fprintf(fiddleOut, "{\n");
961 bool continuation = false;
962 for (const auto& topic : fTopicMap) {
963 if (topic.second->fParent) {
964 continue;
965 }
Cary Clark1a8d7622018-03-05 13:26:16 -0500966 this->dumpExamples(fiddleOut, *topic.second, &continuation);
Cary Clark73fa9722017-08-29 17:36:51 -0400967 }
968 fprintf(fiddleOut, "\n}\n");
969 fclose(fiddleOut);
Cary Clark5b1f9532018-08-28 14:53:37 -0400970 if (ParserCommon::WrittenFileDiffers(oldFiddle, newFiddle)) {
971 ParserCommon::CopyToFile(oldFiddle, newFiddle);
972 SkDebugf("wrote %s\n", fiddleJsonFileName);
Cary Clark6a1185a2018-09-04 16:15:11 -0400973 } else {
974 remove(newFiddle.c_str());
Cary Clark5b1f9532018-08-28 14:53:37 -0400975 }
Cary Clark73fa9722017-08-29 17:36:51 -0400976 return true;
977}
978
Cary Clark8032b982017-07-28 11:04:54 -0400979int BmhParser::endHashCount() const {
980 const char* end = fLine + this->lineLength();
981 int count = 0;
982 while (fLine < end && fMC == *--end) {
983 count++;
984 }
985 return count;
986}
987
Cary Clarkce101242017-09-01 15:51:02 -0400988bool BmhParser::endTableColumn(const char* end, const char* terminator) {
989 if (!this->popParentStack(fParent)) {
990 return false;
991 }
992 fWorkingColumn->fContentEnd = end;
993 fWorkingColumn->fTerminator = terminator;
994 fColStart = fChar - 1;
995 this->skipSpace();
996 fTableState = TableState::kColumnStart;
997 return true;
998}
999
Cary Clark2d4bf5f2018-04-16 08:37:38 -04001000static size_t count_indent(string text, size_t test, size_t end) {
Cary Clark1a8d7622018-03-05 13:26:16 -05001001 size_t result = test;
1002 while (test < end) {
1003 if (' ' != text[test]) {
1004 break;
1005 }
1006 ++test;
1007 }
1008 return test - result;
1009}
1010
Cary Clark2d4bf5f2018-04-16 08:37:38 -04001011static void add_code(string text, int pos, int end,
Cary Clark1a8d7622018-03-05 13:26:16 -05001012 size_t outIndent, size_t textIndent, string& example) {
1013 do {
1014 // fix this to move whole paragraph in, out, but preserve doc indent
1015 int nextIndent = count_indent(text, pos, end);
1016 size_t len = text.find('\n', pos);
1017 if (string::npos == len) {
1018 len = end;
1019 }
1020 if ((size_t) (pos + nextIndent) < len) {
1021 size_t indent = outIndent + nextIndent;
1022 SkASSERT(indent >= textIndent);
1023 indent -= textIndent;
1024 for (size_t index = 0; index < indent; ++index) {
1025 example += ' ';
1026 }
1027 pos += nextIndent;
1028 while ((size_t) pos < len) {
1029 example += '"' == text[pos] ? "\\\"" :
1030 '\\' == text[pos] ? "\\\\" :
1031 text.substr(pos, 1);
1032 ++pos;
1033 }
1034 example += "\\n";
1035 } else {
1036 pos += nextIndent;
1037 }
1038 if ('\n' == text[pos]) {
1039 ++pos;
1040 }
1041 } while (pos < end);
1042}
1043
Cary Clark61313f32018-10-08 14:57:48 -04001044bool BmhParser::IsExemplary(const Definition* def) {
1045 return kMarkProps[(int) def->fMarkType].fExemplary != Exemplary::kNo;
1046}
1047
Cary Clark1a8d7622018-03-05 13:26:16 -05001048bool BmhParser::exampleToScript(Definition* def, ExampleOptions exampleOptions,
1049 string* result) const {
1050 bool hasFiddle = true;
1051 const Definition* platform = def->hasChild(MarkType::kPlatform);
1052 if (platform) {
1053 TextParser platParse(platform);
1054 hasFiddle = !platParse.strnstr("!fiddle", platParse.fEnd);
1055 }
1056 if (!hasFiddle) {
1057 *result = "";
1058 return true;
1059 }
1060 string text = this->extractText(def, TrimExtract::kNo);
1061 bool textOut = string::npos != text.find("SkDebugf(")
1062 || string::npos != text.find("dump(")
1063 || string::npos != text.find("dumpHex(");
1064 string heightStr = "256";
1065 string widthStr = "256";
1066 string normalizedName(def->fFiddle);
1067 string code;
1068 string imageStr = "0";
1069 string srgbStr = "false";
1070 string durationStr = "0";
1071 for (auto iter : def->fChildren) {
1072 switch (iter->fMarkType) {
1073 case MarkType::kDuration:
1074 durationStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
1075 break;
1076 case MarkType::kHeight:
1077 heightStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
1078 break;
1079 case MarkType::kWidth:
1080 widthStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
1081 break;
1082 case MarkType::kDescription:
1083 // ignore for now
1084 break;
1085 case MarkType::kFunction: {
1086 // emit this, but don't wrap this in draw()
1087 string funcText = this->extractText(&*iter, TrimExtract::kNo);
1088 size_t pos = 0;
1089 while (pos < funcText.length() && ' ' > funcText[pos]) {
1090 ++pos;
1091 }
1092 size_t indent = count_indent(funcText, pos, funcText.length());
1093 add_code(funcText, pos, funcText.length(), 0, indent, code);
1094 code += "\\n";
1095 } break;
1096 case MarkType::kComment:
1097 break;
1098 case MarkType::kImage:
1099 imageStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
1100 break;
1101 case MarkType::kToDo:
1102 break;
1103 case MarkType::kBug:
1104 case MarkType::kMarkChar:
1105 case MarkType::kPlatform:
1106 case MarkType::kPhraseRef:
1107 // ignore for now
1108 break;
1109 case MarkType::kSet:
1110 if ("sRGB" == string(iter->fContentStart,
1111 iter->fContentEnd - iter->fContentStart)) {
1112 srgbStr = "true";
1113 } else {
1114 SkASSERT(0); // more work to do
1115 return false;
1116 }
1117 break;
1118 case MarkType::kStdOut:
1119 textOut = true;
1120 break;
1121 default:
1122 SkASSERT(0); // more coding to do
1123 }
1124 }
1125 string animatedStr = "0" != durationStr ? "true" : "false";
1126 string textOutStr = textOut ? "true" : "false";
1127 size_t pos = 0;
1128 while (pos < text.length() && ' ' > text[pos]) {
1129 ++pos;
1130 }
1131 size_t end = text.length();
1132 size_t outIndent = 0;
1133 size_t textIndent = count_indent(text, pos, end);
1134 if ("" == def->fWrapper) {
1135 this->setWrapper(def);
1136 }
1137 if (def->fWrapper.length() > 0) {
1138 code += def->fWrapper;
1139 code += "\\n";
1140 outIndent = 4;
1141 }
1142 add_code(text, pos, end, outIndent, textIndent, code);
1143 if (def->fWrapper.length() > 0) {
1144 code += "}";
1145 }
1146 string example = "\"" + normalizedName + "\": {\n";
Cary Clark2d4bf5f2018-04-16 08:37:38 -04001147 string filename = def->fileName();
1148 string baseFile = filename.substr(0, filename.length() - 4);
Cary Clark1a8d7622018-03-05 13:26:16 -05001149 if (ExampleOptions::kText == exampleOptions) {
1150 example += " \"code\": \"" + code + "\",\n";
1151 example += " \"hash\": \"" + def->fHash + "\",\n";
1152 example += " \"file\": \"" + baseFile + "\",\n";
1153 example += " \"name\": \"" + def->fName + "\",";
1154 } else {
1155 example += " \"code\": \"" + code + "\",\n";
1156 if (ExampleOptions::kPng == exampleOptions) {
1157 example += " \"width\": " + widthStr + ",\n";
1158 example += " \"height\": " + heightStr + ",\n";
1159 example += " \"hash\": \"" + def->fHash + "\",\n";
1160 example += " \"file\": \"" + baseFile + "\",\n";
1161 example += " \"name\": \"" + def->fName + "\"\n";
1162 example += "}";
1163 } else {
1164 example += " \"options\": {\n";
1165 example += " \"width\": " + widthStr + ",\n";
1166 example += " \"height\": " + heightStr + ",\n";
1167 example += " \"source\": " + imageStr + ",\n";
1168 example += " \"srgb\": " + srgbStr + ",\n";
1169 example += " \"f16\": false,\n";
1170 example += " \"textOnly\": " + textOutStr + ",\n";
1171 example += " \"animated\": " + animatedStr + ",\n";
1172 example += " \"duration\": " + durationStr + "\n";
1173 example += " },\n";
1174 example += " \"fast\": true";
1175 }
1176 }
1177 *result = example;
1178 return true;
1179}
1180
1181string BmhParser::extractText(const Definition* def, TrimExtract trimExtract) const {
1182 string result;
1183 TextParser parser(def);
1184 auto childIter = def->fChildren.begin();
1185 while (!parser.eof()) {
1186 const char* end = def->fChildren.end() == childIter ? parser.fEnd : (*childIter)->fStart;
1187 string fragment(parser.fChar, end - parser.fChar);
1188 trim_end(fragment);
1189 if (TrimExtract::kYes == trimExtract) {
1190 trim_start(fragment);
1191 if (result.length()) {
1192 result += '\n';
1193 result += '\n';
1194 }
1195 }
1196 if (TrimExtract::kYes == trimExtract || has_nonwhitespace(fragment)) {
1197 result += fragment;
1198 }
1199 parser.skipTo(end);
1200 if (def->fChildren.end() != childIter) {
1201 Definition* child = *childIter;
1202 if (MarkType::kPhraseRef == child->fMarkType) {
1203 auto phraseIter = fPhraseMap.find(child->fName);
1204 if (fPhraseMap.end() == phraseIter) {
1205 return def->reportError<string>("missing phrase definition");
1206 }
1207 Definition* phrase = phraseIter->second;
1208 // count indent of last line in result
1209 size_t lastLF = result.rfind('\n');
1210 size_t startPos = string::npos == lastLF ? 0 : lastLF;
1211 size_t lastLen = result.length() - startPos;
1212 size_t indent = count_indent(result, startPos, result.length()) + 4;
1213 string phraseStr = this->extractText(phrase, TrimExtract::kNo);
1214 startPos = 0;
1215 bool firstTime = true;
1216 size_t endPos;
1217 do {
1218 endPos = phraseStr.find('\n', startPos);
1219 size_t len = (string::npos != endPos ? endPos : phraseStr.length()) - startPos;
1220 if (firstTime && lastLen + len + 1 < 100) { // FIXME: make 100 global const or something
1221 result += ' ';
1222 } else {
1223 result += '\n';
1224 result += string(indent, ' ');
1225 }
1226 firstTime = false;
1227 string tmp = phraseStr.substr(startPos, len);
1228 result += tmp;
1229 startPos = endPos + 1;
1230 } while (string::npos != endPos);
1231 result += '\n';
1232 }
1233 parser.skipTo(child->fTerminator);
1234 std::advance(childIter, 1);
1235 }
1236 }
1237 return result;
1238}
1239
Cary Clark09d80c02018-10-31 12:14:03 -04001240string BmhParser::loweredTopic(string name, Definition* def) {
1241 string lowered;
1242 SkASSERT('_' != name[0]);
1243 char last = '_';
1244 for (char c : name) {
1245 SkASSERT(' ' != c);
1246 if (isupper(last)) {
1247 lowered += islower(c) ? tolower(last) : last;
1248 last = '\0';
1249 }
1250 if ('_' == c) {
1251 last = c;
1252 c = ' ';
1253 } else if ('_' == last && isupper(c)) {
1254 last = c;
1255 continue;
1256 }
1257 lowered += c;
1258 if (' ' == c) {
1259 this->setUpPartialSubstitute(lowered);
1260 }
1261 }
1262 if (isupper(last)) {
1263 lowered += tolower(last);
1264 }
1265 return lowered;
1266}
1267
1268void BmhParser::setUpGlobalSubstitutes() {
1269 for (auto& entry : fExternals) {
1270 string externalName = entry.fName;
1271 SkASSERT(fGlobalNames.fRefMap.end() == fGlobalNames.fRefMap.find(externalName));
1272 fGlobalNames.fRefMap[externalName] = nullptr;
1273 }
1274 for (auto bMap : { &fClassMap, &fConstMap, &fDefineMap, &fEnumMap, &fMethodMap,
1275 &fTypedefMap } ) {
1276 for (auto& entry : *bMap) {
1277 Definition* parent = (Definition*) &entry.second;
1278 string name = parent->fName;
1279 SkASSERT(fGlobalNames.fLinkMap.end() == fGlobalNames.fLinkMap.find(name));
1280 string ref = ParserCommon::HtmlFileName(parent->fFileName) + '#' + parent->fFiddle;
1281 fGlobalNames.fLinkMap[name] = ref;
1282 SkASSERT(fGlobalNames.fRefMap.end() == fGlobalNames.fRefMap.find(name));
1283 fGlobalNames.fRefMap[name] = const_cast<Definition*>(parent);
1284 NameMap* names = MarkType::kClass == parent->fMarkType
1285 || MarkType::kStruct == parent->fMarkType
1286 || MarkType::kEnumClass == parent->fMarkType ? &parent->asRoot()->fNames :
1287 &fGlobalNames;
1288 this->setUpSubstitutes(parent, names);
1289 if (names != &fGlobalNames) {
1290 names->copyToParent(&fGlobalNames);
1291 }
1292 }
1293 }
1294 for (auto& topic : fTopicMap) {
1295 bool hasSubstitute = false;
1296 for (auto& child : topic.second->fChildren) {
1297 bool isAlias = MarkType::kAlias == child->fMarkType;
1298 bool isSubstitute = MarkType::kSubstitute == child->fMarkType;
1299 if (!isAlias && !isSubstitute) {
1300 continue;
1301 }
1302 hasSubstitute |= isSubstitute;
1303 string name(child->fContentStart, child->length());
1304 if (isAlias) {
1305 name = ParserCommon::ConvertRef(name, false);
1306 for (auto aliasChild : child->fChildren) {
1307 if (MarkType::kSubstitute == aliasChild->fMarkType) {
1308 string sub(aliasChild->fContentStart, aliasChild->length());
1309 this->setUpSubstitute(sub, topic.second);
1310 }
1311 }
1312 }
1313 this->setUpSubstitute(name, topic.second);
1314 }
1315 if (hasSubstitute) {
1316 continue;
1317 }
1318 string lowered = this->loweredTopic(topic.first, topic.second);
1319 SkDEBUGCODE(auto globalIter = fGlobalNames.fLinkMap.find(lowered));
1320 SkASSERT(fGlobalNames.fLinkMap.end() == globalIter);
1321 fGlobalNames.fLinkMap[lowered] =
1322 ParserCommon::HtmlFileName(topic.second->fFileName) + '#' + topic.first;
1323 SkASSERT(fGlobalNames.fRefMap.end() == fGlobalNames.fRefMap.find(lowered));
1324 fGlobalNames.fRefMap[lowered] = topic.second;
1325 }
1326 size_t slash = fRawFilePathDir.rfind('/');
1327 size_t bslash = fRawFilePathDir.rfind('\\');
1328 string spellFile;
1329 if (string::npos == slash && string::npos == bslash) {
1330 spellFile = fRawFilePathDir;
1331 } else {
1332 if (string::npos != bslash && bslash > slash) {
1333 slash = bslash;
1334 }
1335 spellFile = fRawFilePathDir.substr(0, slash);
1336 }
1337 spellFile += '/';
1338 spellFile += kSpellingFileName;
1339 FILE* file = fopen(spellFile.c_str(), "r");
1340 if (!file) {
1341 SkDebugf("missing %s\n", spellFile.c_str());
1342 return;
1343 }
1344 fseek(file, 0L, SEEK_END);
1345 int sz = (int) ftell(file);
1346 rewind(file);
1347 char* buffer = new char[sz];
1348 memset(buffer, ' ', sz);
1349 int read = (int)fread(buffer, 1, sz, file);
1350 SkAssertResult(read > 0);
1351 sz = read; // FIXME: ? why are sz and read different?
1352 fclose(file);
1353 int i = 0;
1354 int start = i;
1355 string word;
1356 do {
1357 if (' ' < buffer[i]) {
1358 ++i;
1359 continue;
1360 }
1361 word = string(&buffer[start], i - start);
1362 if (fGlobalNames.fRefMap.end() == fGlobalNames.fRefMap.find(word)) {
1363 fGlobalNames.fRefMap[word] = nullptr;
1364 } else {
1365 SkDebugf("%s ", word.c_str()); // debugging: word missing from spelling list
1366 }
1367 do {
1368 ++i;
1369 } while (i < sz && ' ' >= buffer[i]);
1370 start = i;
1371 } while (i < sz);
1372 delete[] buffer;
1373}
1374
1375void BmhParser::setUpSubstitutes(const Definition* parent, NameMap* names) {
1376 for (const auto& child : parent->fChildren) {
1377 MarkType markType = child->fMarkType;
1378 if (MarkType::kAlias == markType) {
1379 continue;
1380 }
1381 if (MarkType::kSubstitute == markType) {
1382 continue;
1383 }
1384 string name(child->fName);
1385 if (&fGlobalNames != names) {
1386 size_t lastDC = name.rfind("::");
1387 if (string::npos != lastDC) {
1388 name = name.substr(lastDC + 2);
1389 }
1390 if ("" == name) {
1391 continue;
1392 }
1393 }
1394 size_t lastUnder = name.rfind('_');
1395 if (string::npos != lastUnder && ++lastUnder < name.length()) {
1396 bool numbers = true;
1397 for (size_t index = lastUnder; index < name.length(); ++index) {
1398 numbers &= (bool) isdigit(name[index]);
1399 }
1400 if (numbers) {
1401 continue;
1402 }
1403 }
1404 string ref;
1405 if (&fGlobalNames == names) {
1406 ref = ParserCommon::HtmlFileName(child->fFileName);
1407 }
1408 ref += '#' + child->fFiddle;
1409 if (MarkType::kClass == markType || MarkType::kStruct == markType
1410 || MarkType::kMethod == markType || MarkType::kEnum == markType
1411 || MarkType::kEnumClass == markType || MarkType::kConst == markType
1412 || MarkType::kMember == markType || MarkType::kDefine == markType
1413 || MarkType::kTypedef == markType) {
1414 SkASSERT(names->fLinkMap.end() == names->fLinkMap.find(name));
1415 names->fLinkMap[name] = ref;
1416 SkASSERT(names->fRefMap.end() == names->fRefMap.find(name));
1417 names->fRefMap[name] = child;
1418 }
1419 if (MarkType::kClass == markType || MarkType::kStruct == markType
1420 || MarkType::kEnumClass == markType) {
1421 RootDefinition* rootDef = child->asRoot();
1422 NameMap* nameMap = &rootDef->fNames;
1423 this->setUpSubstitutes(child, nameMap);
1424 nameMap->copyToParent(names);
1425 }
1426 if (MarkType::kEnum == markType) {
1427 this->setUpSubstitutes(child, names);
1428 }
1429 if (MarkType::kSubtopic == markType) {
1430 if (&fGlobalNames != names && string::npos != child->fName.find('_')) {
1431 string lowered = this->loweredTopic(child->fName, child);
1432 SkDEBUGCODE(auto refIter = names->fRefMap.find(lowered));
1433 SkDEBUGCODE(auto iter = names->fLinkMap.find(lowered));
1434 SkASSERT(names->fLinkMap.end() == iter);
1435 names->fLinkMap[lowered] = '#' + child->fName;
1436 SkASSERT(names->fRefMap.end() == refIter);
1437 names->fRefMap[lowered] = child;
1438 }
1439 this->setUpSubstitutes(child, names);
1440 }
1441 }
1442}
1443
1444void BmhParser::setUpPartialSubstitute(string name) {
1445 auto iter = fGlobalNames.fRefMap.find(name);
1446 if (fGlobalNames.fRefMap.end() != iter) {
1447 SkASSERT(nullptr == iter->second);
1448 return;
1449 }
1450 fGlobalNames.fRefMap[name] = nullptr;
1451}
1452
1453void BmhParser::setUpSubstitute(string name, Definition* def) {
1454 SkASSERT(fGlobalNames.fRefMap.end() == fGlobalNames.fRefMap.find(name));
1455 fGlobalNames.fRefMap[name] = def;
1456 SkASSERT(fGlobalNames.fLinkMap.end() == fGlobalNames.fLinkMap.find(name));
1457 string str = ParserCommon::HtmlFileName(def->fFileName) + '#' + def->fName;
1458 fGlobalNames.fLinkMap[name] = str;
1459 size_t stop = name.length();
1460 do {
1461 size_t space = name.rfind(' ', stop);
1462 if (string::npos == space) {
1463 break;
1464 }
1465 string partial = name.substr(0, space + 1);
1466 stop = space - 1;
1467 this->setUpPartialSubstitute(partial);
1468 } while (true);
1469}
1470
Cary Clark1a8d7622018-03-05 13:26:16 -05001471void BmhParser::setWrapper(Definition* def) const {
1472 const char drawWrapper[] = "void draw(SkCanvas* canvas) {";
1473 const char drawNoCanvas[] = "void draw(SkCanvas* ) {";
1474 string text = this->extractText(def, TrimExtract::kNo);
1475 size_t nonSpace = 0;
1476 while (nonSpace < text.length() && ' ' >= text[nonSpace]) {
1477 ++nonSpace;
1478 }
1479 bool hasFunc = !text.compare(nonSpace, sizeof(drawWrapper) - 1, drawWrapper);
1480 bool noCanvas = !text.compare(nonSpace, sizeof(drawNoCanvas) - 1, drawNoCanvas);
1481 bool hasCanvas = string::npos != text.find("SkCanvas canvas");
1482 SkASSERT(!hasFunc || !noCanvas);
1483 bool preprocessor = text[0] == '#';
1484 bool wrapCode = !hasFunc && !noCanvas && !preprocessor;
1485 if (wrapCode) {
1486 def->fWrapper = hasCanvas ? string(drawNoCanvas) : string(drawWrapper);
1487 }
1488}
1489
Cary Clark0d225392018-06-07 09:59:07 -04001490RootDefinition* BmhParser::findBmhObject(MarkType markType, string typeName) {
1491 const auto& mapIter = std::find_if(fMaps.begin(), fMaps.end(),
1492 [markType](DefinitionMap& defMap){ return markType == defMap.fMarkType; } );
1493 if (mapIter == fMaps.end()) {
1494 return nullptr;
1495 }
1496 return &(*mapIter->fMap)[typeName];
1497}
1498
Ben Wagner63fd7602017-10-09 15:45:33 -04001499// FIXME: some examples may produce different output on different platforms
Cary Clark8032b982017-07-28 11:04:54 -04001500// if the text output can be different, think of how to author that
1501
1502bool BmhParser::findDefinitions() {
1503 bool lineStart = true;
Cary Clarkce101242017-09-01 15:51:02 -04001504 const char* lastChar = nullptr;
1505 const char* lastMC = nullptr;
Cary Clark8032b982017-07-28 11:04:54 -04001506 fParent = nullptr;
1507 while (!this->eof()) {
1508 if (this->peek() == fMC) {
Cary Clarkce101242017-09-01 15:51:02 -04001509 lastMC = fChar;
Cary Clark8032b982017-07-28 11:04:54 -04001510 this->next();
1511 if (this->peek() == fMC) {
1512 this->next();
1513 if (!lineStart && ' ' < this->peek()) {
Cary Clark2be81cf2018-09-13 12:04:30 -04001514 if (!fParent || MarkType::kFormula != fParent->fMarkType) {
1515 return this->reportError<bool>("expected definition");
1516 }
Cary Clark8032b982017-07-28 11:04:54 -04001517 }
1518 if (this->peek() != fMC) {
Cary Clarkce101242017-09-01 15:51:02 -04001519 if (MarkType::kColumn == fParent->fMarkType) {
1520 SkASSERT(TableState::kColumnEnd == fTableState);
1521 if (!this->endTableColumn(lastChar, lastMC)) {
1522 return false;
1523 }
1524 SkASSERT(fRow);
1525 if (!this->popParentStack(fParent)) {
1526 return false;
1527 }
1528 fRow->fContentEnd = fWorkingColumn->fContentEnd;
1529 fWorkingColumn = nullptr;
1530 fRow = nullptr;
1531 fTableState = TableState::kNone;
1532 } else {
1533 vector<string> parentName;
1534 parentName.push_back(fParent->fName);
Cary Clarkf895a422018-02-27 09:54:21 -05001535 if (!this->addDefinition(fChar - 1, true, fParent->fMarkType, parentName,
1536 HasTag::kNo)) {
Cary Clarkce101242017-09-01 15:51:02 -04001537 return false;
1538 }
Cary Clark8032b982017-07-28 11:04:54 -04001539 }
1540 } else {
1541 SkAssertResult(this->next() == fMC);
1542 fMC = this->next(); // change markup character
1543 if (' ' >= fMC) {
1544 return this->reportError<bool>("illegal markup character");
1545 }
Cary Clark1a8d7622018-03-05 13:26:16 -05001546 fMarkup.emplace_front(MarkType::kMarkChar, fChar - 4, fLineCount, fParent, fMC);
Cary Clark8032b982017-07-28 11:04:54 -04001547 Definition* markChar = &fMarkup.front();
1548 markChar->fContentStart = fChar - 1;
1549 this->skipToEndBracket('\n');
1550 markChar->fContentEnd = fChar;
1551 markChar->fTerminator = fChar;
1552 fParent->fChildren.push_back(markChar);
1553 }
1554 } else if (this->peek() >= 'A' && this->peek() <= 'Z') {
1555 const char* defStart = fChar - 1;
1556 MarkType markType = this->getMarkType(MarkLookup::kRequire);
1557 bool hasEnd = this->hasEndToken();
Cary Clark682c58d2018-05-16 07:07:07 -04001558 if (!hasEnd && fParent) {
1559 MarkType parentType = fParent->fMarkType;
Cary Clark2d4bf5f2018-04-16 08:37:38 -04001560 uint64_t parentMask = kMarkProps[(int) markType].fParentMask;
Cary Clark8032b982017-07-28 11:04:54 -04001561 if (parentMask && !(parentMask & (1LL << (int) parentType))) {
1562 return this->reportError<bool>("invalid parent");
1563 }
1564 }
Cary Clark2d4bf5f2018-04-16 08:37:38 -04001565 if (!this->skipName(kMarkProps[(int) markType].fName)) {
Cary Clark8032b982017-07-28 11:04:54 -04001566 return this->reportError<bool>("illegal markup character");
1567 }
1568 if (!this->skipSpace()) {
1569 return this->reportError<bool>("unexpected end");
1570 }
Cary Clark81abc432018-06-25 16:30:08 -04001571 lineStart = '\n' == this->peek();
Cary Clark8032b982017-07-28 11:04:54 -04001572 bool expectEnd = true;
1573 vector<string> typeNameBuilder = this->typeName(markType, &expectEnd);
1574 if (fCloned && MarkType::kMethod != markType && MarkType::kExample != markType
1575 && !fAnonymous) {
1576 return this->reportError<bool>("duplicate name");
1577 }
1578 if (hasEnd && expectEnd) {
Cary Clark137b8742018-05-30 09:21:49 -04001579 if (fMC == this->peek()) {
1580 return this->reportError<bool>("missing body");
1581 }
Cary Clark8032b982017-07-28 11:04:54 -04001582 }
Cary Clarkf895a422018-02-27 09:54:21 -05001583 if (!this->addDefinition(defStart, hasEnd, markType, typeNameBuilder,
1584 HasTag::kYes)) {
Cary Clark8032b982017-07-28 11:04:54 -04001585 return false;
1586 }
1587 continue;
1588 } else if (this->peek() == ' ') {
Cary Clark2be81cf2018-09-13 12:04:30 -04001589 if (!fParent || (MarkType::kFormula != fParent->fMarkType
Cary Clark8032b982017-07-28 11:04:54 -04001590 && MarkType::kLegend != fParent->fMarkType
Cary Clarkab2621d2018-01-30 10:08:57 -05001591 && MarkType::kList != fParent->fMarkType
Cary Clark2be81cf2018-09-13 12:04:30 -04001592 && MarkType::kLine != fParent->fMarkType
1593 && MarkType::kTable != fParent->fMarkType)) {
Cary Clark8032b982017-07-28 11:04:54 -04001594 int endHashes = this->endHashCount();
Cary Clarkce101242017-09-01 15:51:02 -04001595 if (endHashes <= 1) {
Cary Clark8032b982017-07-28 11:04:54 -04001596 if (fParent) {
Cary Clarkce101242017-09-01 15:51:02 -04001597 if (TableState::kColumnEnd == fTableState) {
1598 if (!this->endTableColumn(lastChar, lastMC)) {
1599 return false;
1600 }
1601 } else { // one line comment
1602 fMarkup.emplace_front(MarkType::kComment, fChar - 1, fLineCount,
Cary Clark1a8d7622018-03-05 13:26:16 -05001603 fParent, fMC);
Cary Clarkce101242017-09-01 15:51:02 -04001604 Definition* comment = &fMarkup.front();
1605 comment->fContentStart = fChar - 1;
1606 this->skipToEndBracket('\n');
1607 comment->fContentEnd = fChar;
1608 comment->fTerminator = fChar;
1609 fParent->fChildren.push_back(comment);
1610 }
Cary Clark8032b982017-07-28 11:04:54 -04001611 } else {
1612 fChar = fLine + this->lineLength() - 1;
1613 }
1614 } else { // table row
1615 if (2 != endHashes) {
1616 string errorStr = "expect ";
1617 errorStr += fMC;
1618 errorStr += fMC;
1619 return this->reportError<bool>(errorStr.c_str());
1620 }
1621 if (!fParent || MarkType::kTable != fParent->fMarkType) {
1622 return this->reportError<bool>("missing table");
1623 }
1624 }
Cary Clarkce101242017-09-01 15:51:02 -04001625 } else if (TableState::kNone == fTableState) {
Cary Clarkce101242017-09-01 15:51:02 -04001626 // fixme? no nested tables for now
1627 fColStart = fChar - 1;
Cary Clark1a8d7622018-03-05 13:26:16 -05001628 fMarkup.emplace_front(MarkType::kRow, fColStart, fLineCount, fParent, fMC);
Cary Clarkce101242017-09-01 15:51:02 -04001629 fRow = &fMarkup.front();
1630 fRow->fName = fParent->fName;
1631 this->skipWhiteSpace();
1632 fRow->fContentStart = fChar;
1633 this->setAsParent(fRow);
1634 fTableState = TableState::kColumnStart;
1635 }
1636 if (TableState::kColumnStart == fTableState) {
Cary Clark1a8d7622018-03-05 13:26:16 -05001637 fMarkup.emplace_front(MarkType::kColumn, fColStart, fLineCount, fParent, fMC);
Cary Clarkce101242017-09-01 15:51:02 -04001638 fWorkingColumn = &fMarkup.front();
1639 fWorkingColumn->fName = fParent->fName;
1640 fWorkingColumn->fContentStart = fChar;
1641 this->setAsParent(fWorkingColumn);
1642 fTableState = TableState::kColumnEnd;
1643 continue;
Cary Clark8032b982017-07-28 11:04:54 -04001644 }
Cary Clark1a8d7622018-03-05 13:26:16 -05001645 } else if (this->peek() >= 'a' && this->peek() <= 'z') {
1646 // expect zero or more letters and underscores (no spaces) then hash
1647 const char* phraseNameStart = fChar;
1648 this->skipPhraseName();
1649 string phraseKey = string(phraseNameStart, fChar - phraseNameStart);
Cary Clark682c58d2018-05-16 07:07:07 -04001650 char delimiter = this->next();
1651 vector<string> params;
1652 vector<const char*> paramsLoc;
1653 if (fMC != delimiter) {
1654 if ('(' != delimiter) {
1655 return this->reportError<bool>("expect # after phrase name");
1656 }
1657 // phrase may take comma delimited parameter list
1658 do {
1659 const char* subEnd = this->anyOf(",)\n");
1660 if (!subEnd || '\n' == *subEnd) {
1661 return this->reportError<bool>("unexpected phrase list end");
1662 }
1663 params.push_back(string(fChar, subEnd - fChar));
1664 paramsLoc.push_back(fChar);
1665 this->skipTo(subEnd);
1666
1667 } while (')' != this->next());
Cary Clark1a8d7622018-03-05 13:26:16 -05001668 }
1669 const char* start = phraseNameStart;
1670 SkASSERT('#' == start[-1]);
1671 --start;
1672 if (start > fStart && ' ' >= start[-1]) {
1673 --start; // preserve whether to add whitespace before substitution
1674 }
1675 fMarkup.emplace_front(MarkType::kPhraseRef, start, fLineCount, fParent, fMC);
1676 Definition* markChar = &fMarkup.front();
Cary Clark682c58d2018-05-16 07:07:07 -04001677 this->skipExact("#");
Cary Clark1a8d7622018-03-05 13:26:16 -05001678 markChar->fContentStart = fChar;
Cary Clark1a8d7622018-03-05 13:26:16 -05001679 markChar->fContentEnd = fChar;
1680 markChar->fTerminator = fChar;
1681 markChar->fName = phraseKey;
1682 fParent->fChildren.push_back(markChar);
Cary Clark682c58d2018-05-16 07:07:07 -04001683 int paramLocIndex = 0;
1684 for (auto param : params) {
1685 const char* paramLoc = paramsLoc[paramLocIndex++];
1686 fMarkup.emplace_front(MarkType::kPhraseParam, paramLoc, fLineCount, fParent,
1687 fMC);
1688 Definition* phraseParam = &fMarkup.front();
1689 phraseParam->fContentStart = paramLoc;
1690 phraseParam->fContentEnd = paramLoc + param.length();
1691 phraseParam->fTerminator = paramLoc + param.length();
1692 phraseParam->fName = param;
1693 markChar->fChildren.push_back(phraseParam);
1694 }
Cary Clark8032b982017-07-28 11:04:54 -04001695 }
1696 }
Cary Clarkce101242017-09-01 15:51:02 -04001697 char nextChar = this->next();
Cary Clarkce101242017-09-01 15:51:02 -04001698 if (' ' < nextChar) {
1699 lastChar = fChar;
Cary Clark81abc432018-06-25 16:30:08 -04001700 lineStart = false;
1701 } else if (nextChar == '\n') {
1702 lineStart = true;
Cary Clarkce101242017-09-01 15:51:02 -04001703 }
Cary Clark8032b982017-07-28 11:04:54 -04001704 }
1705 if (fParent) {
Cary Clarka560c472017-11-27 10:44:06 -05001706 return fParent->reportError<bool>("mismatched end");
Cary Clark8032b982017-07-28 11:04:54 -04001707 }
1708 return true;
1709}
1710
1711MarkType BmhParser::getMarkType(MarkLookup lookup) const {
1712 for (int index = 0; index <= Last_MarkType; ++index) {
Cary Clark2d4bf5f2018-04-16 08:37:38 -04001713 int typeLen = strlen(kMarkProps[index].fName);
Cary Clark8032b982017-07-28 11:04:54 -04001714 if (typeLen == 0) {
1715 continue;
1716 }
1717 if (fChar + typeLen >= fEnd || fChar[typeLen] > ' ') {
1718 continue;
1719 }
Cary Clark2d4bf5f2018-04-16 08:37:38 -04001720 int chCompare = strncmp(fChar, kMarkProps[index].fName, typeLen);
Cary Clark8032b982017-07-28 11:04:54 -04001721 if (chCompare < 0) {
1722 goto fail;
1723 }
1724 if (chCompare == 0) {
1725 return (MarkType) index;
1726 }
1727 }
1728fail:
1729 if (MarkLookup::kRequire == lookup) {
1730 return this->reportError<MarkType>("unknown mark type");
1731 }
1732 return MarkType::kNone;
1733}
1734
Cary Clark09d80c02018-10-31 12:14:03 -04001735 // replace #Method description, #Param, #Return with #Populate
1736 // if description, params, return are free of phrase refs
Cary Clark8032b982017-07-28 11:04:54 -04001737bool HackParser::hackFiles() {
1738 string filename(fFileName);
1739 size_t len = filename.length() - 1;
1740 while (len > 0 && (isalnum(filename[len]) || '_' == filename[len] || '.' == filename[len])) {
1741 --len;
1742 }
1743 filename = filename.substr(len + 1);
Cary Clarkab2621d2018-01-30 10:08:57 -05001744 if (filename.substr(0, 2) != "Sk") {
1745 return true;
1746 }
1747 size_t under = filename.find('_');
1748 SkASSERT(under);
1749 string className = filename.substr(0, under);
1750 fOut = fopen(filename.c_str(), "wb");
1751 if (!fOut) {
Cary Clark8032b982017-07-28 11:04:54 -04001752 SkDebugf("could not open output file %s\n", filename.c_str());
1753 return false;
1754 }
Cary Clarkab2621d2018-01-30 10:08:57 -05001755 auto mapEntry = fBmhParser.fClassMap.find(className);
Cary Clark09d80c02018-10-31 12:14:03 -04001756 if (fBmhParser.fClassMap.end() == mapEntry) {
1757 remove(filename.c_str());
1758 return true;
1759 }
Cary Clarkab2621d2018-01-30 10:08:57 -05001760 const Definition* classMarkup = &mapEntry->second;
1761 const Definition* root = classMarkup->fParent;
1762 SkASSERT(root);
1763 SkASSERT(root->fTerminator);
1764 SkASSERT('\n' == root->fTerminator[0]);
1765 SkASSERT(!root->fParent);
1766 fStart = root->fStart;
1767 fChar = fStart;
Cary Clark09d80c02018-10-31 12:14:03 -04001768 fEnd = root->fTerminator;
1769 this->replaceWithPop(root);
1770 FPRINTF("%.*s", (int) (fEnd - fChar), fChar);
1771 if ('\n' != fEnd[-1]) {
1772 FPRINTF("\n");
1773 }
Cary Clarkab2621d2018-01-30 10:08:57 -05001774 fclose(fOut);
Cary Clark5b1f9532018-08-28 14:53:37 -04001775 if (ParserCommon::WrittenFileDiffers(filename, root->fFileName)) {
Cary Clark08895c42018-02-01 09:37:32 -05001776 SkDebugf("wrote %s\n", filename.c_str());
1777 } else {
1778 remove(filename.c_str());
1779 }
Cary Clark8032b982017-07-28 11:04:54 -04001780 return true;
1781}
1782
Cary Clarkab2621d2018-01-30 10:08:57 -05001783// returns true if topic has method
Cary Clark09d80c02018-10-31 12:14:03 -04001784void HackParser::replaceWithPop(const Definition* root) {
1785 for (auto child : root->fChildren) {
1786 if (MarkType::kClass == child->fMarkType || MarkType::kStruct == child->fMarkType
1787 || MarkType::kSubtopic == child->fMarkType) {
1788 this->replaceWithPop(child);
Cary Clarkab2621d2018-01-30 10:08:57 -05001789 }
Cary Clark09d80c02018-10-31 12:14:03 -04001790 if (MarkType::kMethod != child->fMarkType) {
1791 continue;
Cary Clarkab2621d2018-01-30 10:08:57 -05001792 }
Cary Clark09d80c02018-10-31 12:14:03 -04001793 auto& grans = child->fChildren;
1794 if (grans.end() != std::find_if(grans.begin(), grans.end(),
1795 [](const Definition* def) {
1796 return MarkType::kPopulate == def->fMarkType
1797 || MarkType::kPhraseRef == def->fMarkType
1798 || MarkType::kFormula == def->fMarkType
1799 || MarkType::kAnchor == def->fMarkType
1800 || MarkType::kList == def->fMarkType
1801 || MarkType::kTable == def->fMarkType
1802 || MarkType::kDeprecated == def->fMarkType
1803 || MarkType::kExperimental == def->fMarkType
1804 || MarkType::kPrivate == def->fMarkType;
1805 } )) {
1806 continue;
1807 }
1808 // write #Populate in place of description, #Param(s), #Return (if present)
1809 const char* keep = child->fContentStart;
1810 const char* next = nullptr;
1811 for (auto gran : grans) {
1812 if (MarkType::kIn == gran->fMarkType || MarkType::kLine == gran->fMarkType) {
1813 keep = gran->fTerminator;
1814 continue;
1815 }
1816 if (MarkType::kExample == gran->fMarkType
1817 || MarkType::kNoExample == gran->fMarkType) {
1818 next = gran->fStart;
1819 break;
1820 }
1821 if (MarkType::kParam == gran->fMarkType
1822 || MarkType::kReturn == gran->fMarkType
1823 || MarkType::kToDo == gran->fMarkType
1824 || MarkType::kComment == gran->fMarkType) {
1825 continue;
1826 }
1827 SkDebugf(""); // convenient place to set a breakpoint
1828 }
1829 SkASSERT(next);
1830 FPRINTF("%.*s", (int) (keep - fChar), fChar);
1831 if ('\n' != keep[-1]) {
1832 FPRINTF("\n");
1833 }
1834 FPRINTF("#Populate\n\n");
1835 fChar = next;
Cary Clark08895c42018-02-01 09:37:32 -05001836 }
1837}
Cary Clarkab2621d2018-01-30 10:08:57 -05001838
Cary Clark8032b982017-07-28 11:04:54 -04001839bool BmhParser::hasEndToken() const {
Cary Clark2be81cf2018-09-13 12:04:30 -04001840 const char* ptr = fLine;
1841 char test;
1842 do {
1843 if (ptr >= fEnd) {
1844 return false;
1845 }
1846 test = *ptr++;
1847 if ('\n' == test) {
1848 return false;
1849 }
1850 } while (fMC != test || fMC != *ptr);
1851 return true;
Cary Clark8032b982017-07-28 11:04:54 -04001852}
1853
1854string BmhParser::memberName() {
1855 const char* wordStart;
1856 const char* prefixes[] = { "static", "const" };
1857 do {
1858 this->skipSpace();
1859 wordStart = fChar;
Cary Clark2d4bf5f2018-04-16 08:37:38 -04001860 this->skipToNonName();
Cary Clark8032b982017-07-28 11:04:54 -04001861 } while (this->anyOf(wordStart, prefixes, SK_ARRAY_COUNT(prefixes)));
1862 if ('*' == this->peek()) {
1863 this->next();
1864 }
1865 return this->className(MarkType::kMember);
1866}
1867
1868string BmhParser::methodName() {
1869 if (this->hasEndToken()) {
1870 if (!fParent || !fParent->fName.length()) {
1871 return this->reportError<string>("missing parent method name");
1872 }
1873 SkASSERT(fMC == this->peek());
1874 this->next();
1875 SkASSERT(fMC == this->peek());
1876 this->next();
1877 SkASSERT(fMC != this->peek());
1878 return fParent->fName;
1879 }
1880 string builder;
1881 const char* end = this->lineEnd();
1882 const char* paren = this->strnchr('(', end);
1883 if (!paren) {
1884 return this->reportError<string>("missing method name and reference");
1885 }
Cary Clark224c7002018-06-27 11:00:21 -04001886 {
1887 TextParserSave endCheck(this);
1888 while (end < fEnd && !this->strnchr(')', end)) {
1889 fChar = end + 1;
1890 end = this->lineEnd();
1891 }
1892 if (end >= fEnd) {
1893 return this->reportError<string>("missing method end paren");
1894 }
1895 endCheck.restore();
1896 }
Cary Clark8032b982017-07-28 11:04:54 -04001897 const char* nameStart = paren;
1898 char ch;
1899 bool expectOperator = false;
1900 bool isConstructor = false;
1901 const char* nameEnd = nullptr;
1902 while (nameStart > fChar && ' ' != (ch = *--nameStart)) {
1903 if (!isalnum(ch) && '_' != ch) {
1904 if (nameEnd) {
1905 break;
1906 }
1907 expectOperator = true;
1908 continue;
1909 }
1910 if (!nameEnd) {
1911 nameEnd = nameStart + 1;
1912 }
1913 }
1914 if (!nameEnd) {
1915 return this->reportError<string>("unexpected method name char");
1916 }
1917 if (' ' == nameStart[0]) {
1918 ++nameStart;
1919 }
1920 if (nameEnd <= nameStart) {
1921 return this->reportError<string>("missing method name");
1922 }
1923 if (nameStart >= paren) {
1924 return this->reportError<string>("missing method name length");
1925 }
1926 string name(nameStart, nameEnd - nameStart);
1927 bool allLower = true;
1928 for (int index = 0; index < (int) (nameEnd - nameStart); ++index) {
1929 if (!islower(nameStart[index])) {
1930 allLower = false;
1931 break;
1932 }
1933 }
1934 if (expectOperator && "operator" != name) {
1935 return this->reportError<string>("expected operator");
1936 }
1937 const Definition* parent = this->parentSpace();
1938 if (parent && parent->fName.length() > 0) {
Cary Clark224c7002018-06-27 11:00:21 -04001939 size_t parentNameIndex = parent->fName.rfind(':');
1940 parentNameIndex = string::npos == parentNameIndex ? 0 : parentNameIndex + 1;
1941 string parentName = parent->fName.substr(parentNameIndex);
1942 if (parentName == name) {
Cary Clark8032b982017-07-28 11:04:54 -04001943 isConstructor = true;
1944 } else if ('~' == name[0]) {
Cary Clark224c7002018-06-27 11:00:21 -04001945 if (parentName != name.substr(1)) {
Cary Clark8032b982017-07-28 11:04:54 -04001946 return this->reportError<string>("expected destructor");
1947 }
1948 isConstructor = true;
1949 }
1950 builder = parent->fName + "::";
Ben Wagner63fd7602017-10-09 15:45:33 -04001951 }
Cary Clarka560c472017-11-27 10:44:06 -05001952 bool addConst = false;
Cary Clark8032b982017-07-28 11:04:54 -04001953 if (isConstructor || expectOperator) {
1954 paren = this->strnchr(')', end) + 1;
Cary Clark186d08f2018-04-03 08:43:27 -04001955 TextParserSave saveState(this);
Cary Clarka560c472017-11-27 10:44:06 -05001956 this->skipTo(paren);
1957 if (this->skipExact("_const")) {
1958 addConst = true;
1959 }
1960 saveState.restore();
Cary Clark8032b982017-07-28 11:04:54 -04001961 }
1962 builder.append(nameStart, paren - nameStart);
Cary Clarka560c472017-11-27 10:44:06 -05001963 if (addConst) {
1964 builder.append("_const");
1965 }
Cary Clark8032b982017-07-28 11:04:54 -04001966 if (!expectOperator && allLower) {
1967 builder.append("()");
1968 }
1969 int parens = 0;
1970 while (fChar < end || parens > 0) {
1971 if ('(' == this->peek()) {
1972 ++parens;
1973 } else if (')' == this->peek()) {
1974 --parens;
1975 }
1976 this->next();
1977 }
Cary Clark186d08f2018-04-03 08:43:27 -04001978 TextParserSave saveState(this);
Cary Clark8032b982017-07-28 11:04:54 -04001979 this->skipWhiteSpace();
1980 if (this->startsWith("const")) {
1981 this->skipName("const");
1982 } else {
1983 saveState.restore();
1984 }
1985// this->next();
Cary Clark82f1f742018-06-28 08:50:35 -04001986 if (string::npos != builder.find('\n')) {
1987 builder.erase(std::remove(builder.begin(), builder.end(), '\n'), builder.end());
1988 }
Cary Clark8032b982017-07-28 11:04:54 -04001989 return uniqueRootName(builder, MarkType::kMethod);
1990}
1991
1992const Definition* BmhParser::parentSpace() const {
1993 Definition* parent = nullptr;
1994 Definition* test = fParent;
1995 while (test) {
1996 if (MarkType::kClass == test->fMarkType ||
1997 MarkType::kEnumClass == test->fMarkType ||
1998 MarkType::kStruct == test->fMarkType) {
1999 parent = test;
2000 break;
2001 }
2002 test = test->fParent;
2003 }
2004 return parent;
2005}
2006
Cary Clark137b8742018-05-30 09:21:49 -04002007// A full terminal statement is in the form:
2008// \n optional-white-space #MarkType white-space #[# white-space]
2009// \n optional-white-space #MarkType white-space Name white-space #[# white-space]
2010// MarkType must match definition->fMarkType
Cary Clark1a8d7622018-03-05 13:26:16 -05002011const char* BmhParser::checkForFullTerminal(const char* end, const Definition* definition) const {
2012 const char* start = end;
2013 while ('\n' != start[0] && start > fStart) {
2014 --start;
2015 }
2016 SkASSERT (start < end);
2017 // if end is preceeeded by \n#MarkType ## backup to there
2018 TextParser parser(fFileName, start, fChar, fLineCount);
2019 parser.skipWhiteSpace();
2020 if (parser.eof() || fMC != parser.next()) {
2021 return end;
2022 }
Cary Clark2d4bf5f2018-04-16 08:37:38 -04002023 const char* markName = kMarkProps[(int) definition->fMarkType].fName;
Cary Clark1a8d7622018-03-05 13:26:16 -05002024 if (!parser.skipExact(markName)) {
2025 return end;
2026 }
2027 parser.skipWhiteSpace();
Cary Clark137b8742018-05-30 09:21:49 -04002028 TextParser startName(fFileName, definition->fStart, definition->fContentStart,
2029 definition->fLineCount);
2030 if ('#' == startName.next()) {
2031 startName.skipToSpace();
2032 if (!startName.eof() && startName.skipSpace()) {
2033 const char* nameBegin = startName.fChar;
2034 startName.skipToWhiteSpace();
2035 string name(nameBegin, (int) (startName.fChar - nameBegin));
2036 if (fMC != parser.peek() && !parser.skipExact(name.c_str())) {
2037 return end;
2038 }
2039 parser.skipSpace();
Cary Clark1a8d7622018-03-05 13:26:16 -05002040 }
2041 }
Cary Clark137b8742018-05-30 09:21:49 -04002042 if (parser.eof() || fMC != parser.next()) {
Cary Clark1a8d7622018-03-05 13:26:16 -05002043 return end;
2044 }
2045 if (!parser.eof() && fMC != parser.next()) {
2046 return end;
2047 }
Cary Clark137b8742018-05-30 09:21:49 -04002048 SkASSERT(parser.eof());
Cary Clark1a8d7622018-03-05 13:26:16 -05002049 return start;
2050}
2051
Cary Clark2be81cf2018-09-13 12:04:30 -04002052void BmhParser::parseHashAnchor(Definition* definition) {
2053 this->skipToEndBracket(fMC);
2054 fMarkup.emplace_front(MarkType::kLink, fChar, fLineCount, definition, fMC);
2055 SkAssertResult(fMC == this->next());
2056 this->skipWhiteSpace();
2057 Definition* link = &fMarkup.front();
2058 link->fContentStart = fChar;
2059 link->fContentEnd = this->trimmedBracketEnd(fMC);
2060 this->skipToEndBracket(fMC);
2061 SkAssertResult(fMC == this->next());
2062 SkAssertResult(fMC == this->next());
2063 link->fTerminator = fChar;
2064 definition->fContentEnd = link->fContentEnd;
2065 definition->fTerminator = fChar;
2066 definition->fChildren.emplace_back(link);
2067}
2068
2069void BmhParser::parseHashFormula(Definition* definition) {
2070 const char* start = definition->fContentStart;
2071 definition->trimEnd();
2072 const char* end = definition->fContentEnd;
2073 fMarkup.emplace_front(MarkType::kText, start, fLineCount, definition, fMC);
2074 Definition* text = &fMarkup.front();
2075 text->fContentStart = start;
2076 text->fContentEnd = end;
2077 text->fTerminator = definition->fTerminator;
2078 definition->fChildren.emplace_back(text);
2079}
2080
2081void BmhParser::parseHashLine(Definition* definition) {
2082 const char* nextLF = this->strnchr('\n', this->fEnd);
2083 const char* start = fChar;
2084 const char* end = this->trimmedBracketEnd(fMC);
2085 this->skipToEndBracket(fMC, nextLF);
2086 if (fMC != this->next() || fMC != this->next()) {
2087 return this->reportError<void>("expected ## to delineate line");
2088 }
2089 fMarkup.emplace_front(MarkType::kText, start, fLineCount, definition, fMC);
2090 Definition* text = &fMarkup.front();
2091 if (!islower(start[0]) && (!isdigit(start[0])
2092 || MarkType::kConst != definition->fParent->fMarkType)) {
2093 return this->reportError<void>("expect lower case start");
2094 }
2095 string contents = string(start, end - start);
2096 if (string::npos != contents.find('.')) {
2097 return this->reportError<void>("expect phrase, not sentence");
2098 }
2099 size_t firstSpace = contents.find(' ');
2100 if (string::npos == firstSpace || 0 == firstSpace || 's' != start[firstSpace - 1]) {
2101 if (MarkType::kMethod == fParent->fMarkType && "experimental" != contents
2102 && "incomplete" != contents) {
2103 return this->reportError<void>( "expect phrase in third person present"
2104 " tense (1st word should end in 's'");
2105 }
2106 }
2107 text->fContentStart = start;
2108 text->fContentEnd = end;
2109 text->fTerminator = fChar;
2110 definition->fContentEnd = text->fContentEnd;
2111 definition->fTerminator = fChar;
2112 definition->fChildren.emplace_back(text);
2113}
2114
Cary Clark8032b982017-07-28 11:04:54 -04002115bool BmhParser::popParentStack(Definition* definition) {
2116 if (!fParent) {
2117 return this->reportError<bool>("missing parent");
2118 }
2119 if (definition != fParent) {
2120 return this->reportError<bool>("definition end is not parent");
2121 }
2122 if (!definition->fStart) {
2123 return this->reportError<bool>("definition missing start");
2124 }
2125 if (definition->fContentEnd) {
2126 return this->reportError<bool>("definition already ended");
2127 }
Cary Clark1a8d7622018-03-05 13:26:16 -05002128 // more to figure out to handle table columns, at minimum
2129 const char* end = fChar;
2130 if (fMC != end[0]) {
2131 while (end > definition->fContentStart && ' ' >= end[-1]) {
2132 --end;
2133 }
2134 SkASSERT(&end[-1] >= definition->fContentStart && fMC == end[-1]
2135 && (MarkType::kColumn == definition->fMarkType
2136 || (&end[-2] >= definition->fContentStart && fMC == end[-2])));
2137 end -= 2;
2138 }
2139 end = checkForFullTerminal(end, definition);
2140 definition->fContentEnd = end;
Cary Clark8032b982017-07-28 11:04:54 -04002141 definition->fTerminator = fChar;
2142 fParent = definition->fParent;
2143 if (!fParent || (MarkType::kTopic == fParent->fMarkType && !fParent->fParent)) {
2144 fRoot = nullptr;
2145 }
2146 return true;
2147}
2148
2149TextParser::TextParser(const Definition* definition) :
Ben Wagner63fd7602017-10-09 15:45:33 -04002150 TextParser(definition->fFileName, definition->fContentStart, definition->fContentEnd,
Cary Clark8032b982017-07-28 11:04:54 -04002151 definition->fLineCount) {
2152}
2153
Cary Clark4855f782018-02-06 09:41:53 -05002154string TextParser::ReportFilename(string file) {
2155 string fullName;
2156#ifdef SK_BUILD_FOR_WIN
2157 TCHAR pathChars[MAX_PATH];
2158 DWORD pathLen = GetCurrentDirectory(MAX_PATH, pathChars);
2159 for (DWORD index = 0; index < pathLen; ++index) {
2160 fullName += pathChars[index] == (char)pathChars[index] ? (char)pathChars[index] : '?';
2161 }
2162 fullName += '\\';
2163#endif
2164 fullName += file;
2165 return fullName;
2166}
2167
Cary Clark8032b982017-07-28 11:04:54 -04002168void TextParser::reportError(const char* errorStr) const {
2169 this->reportWarning(errorStr);
2170 SkDebugf(""); // convenient place to set a breakpoint
2171}
2172
2173void TextParser::reportWarning(const char* errorStr) const {
Cary Clark61313f32018-10-08 14:57:48 -04002174 const char* lineStart = fLine;
2175 if (lineStart >= fEnd) {
2176 lineStart = fChar;
2177 }
2178 SkASSERT(lineStart < fEnd);
2179 TextParser err(fFileName, lineStart, fEnd, fLineCount);
Cary Clark8032b982017-07-28 11:04:54 -04002180 size_t lineLen = this->lineLength();
Cary Clark61313f32018-10-08 14:57:48 -04002181 ptrdiff_t spaces = fChar - lineStart;
Cary Clark8032b982017-07-28 11:04:54 -04002182 while (spaces > 0 && (size_t) spaces > lineLen) {
2183 ++err.fLineCount;
2184 err.fLine += lineLen;
2185 spaces -= lineLen;
2186 lineLen = err.lineLength();
2187 }
Cary Clark4855f782018-02-06 09:41:53 -05002188 string fullName = this->ReportFilename(fFileName);
2189 SkDebugf("\n%s(%zd): error: %s\n", fullName.c_str(), err.fLineCount, errorStr);
Cary Clark8032b982017-07-28 11:04:54 -04002190 if (0 == lineLen) {
2191 SkDebugf("[blank line]\n");
2192 } else {
2193 while (lineLen > 0 && '\n' == err.fLine[lineLen - 1]) {
2194 --lineLen;
2195 }
2196 SkDebugf("%.*s\n", (int) lineLen, err.fLine);
2197 SkDebugf("%*s^\n", (int) spaces, "");
2198 }
2199}
2200
Cary Clark186d08f2018-04-03 08:43:27 -04002201void TextParser::setForErrorReporting(const Definition* definition, const char* str) {
2202 fFileName = definition->fFileName;
2203 fStart = definition->fContentStart;
2204 fLine = str;
2205 while (fLine > fStart && fLine[-1] != '\n') {
2206 --fLine;
2207 }
2208 fChar = str;
2209 fEnd = definition->fContentEnd;
2210 fLineCount = definition->fLineCount;
2211 const char* lineInc = fStart;
2212 while (lineInc < str) {
2213 fLineCount += '\n' == *lineInc++;
2214 }
2215}
2216
Cary Clark2f466242017-12-11 16:03:17 -05002217string TextParser::typedefName() {
2218 // look for typedef as one of three forms:
2219 // typedef return-type (*NAME)(params);
2220 // typedef alias NAME;
2221 // typedef std::function<alias> NAME;
2222 string builder;
2223 const char* end = this->doubleLF();
2224 if (!end) {
2225 end = fEnd;
2226 }
2227 const char* altEnd = this->strnstr("#Typedef ##", end);
2228 if (altEnd) {
2229 end = this->strnchr('\n', end);
2230 }
2231 if (!end) {
2232 return this->reportError<string>("missing typedef std::function end bracket >");
2233 }
Cary Clark61ca7c52018-01-02 11:34:14 -05002234 bool stdFunction = this->startsWith("std::function");
2235 if (stdFunction) {
Cary Clark2f466242017-12-11 16:03:17 -05002236 if (!this->skipToEndBracket('>')) {
2237 return this->reportError<string>("missing typedef std::function end bracket >");
2238 }
2239 this->next();
2240 this->skipWhiteSpace();
2241 builder += string(fChar, end - fChar);
2242 } else {
2243 const char* paren = this->strnchr('(', end);
2244 if (!paren) {
2245 const char* lastWord = nullptr;
2246 do {
2247 this->skipToWhiteSpace();
2248 if (fChar < end && isspace(fChar[0])) {
Cary Clark682c58d2018-05-16 07:07:07 -04002249 const char* whiteStart = fChar;
Cary Clark2f466242017-12-11 16:03:17 -05002250 this->skipWhiteSpace();
Cary Clark682c58d2018-05-16 07:07:07 -04002251 // FIXME: test should be for fMC
2252 if ('#' == fChar[0]) {
2253 end = whiteStart;
2254 break;
2255 }
Cary Clark2f466242017-12-11 16:03:17 -05002256 lastWord = fChar;
2257 } else {
2258 break;
2259 }
2260 } while (true);
2261 if (!lastWord) {
2262 return this->reportError<string>("missing typedef name");
2263 }
2264 builder += string(lastWord, end - lastWord);
2265 } else {
2266 this->skipTo(paren);
2267 this->next();
2268 if ('*' != this->next()) {
2269 return this->reportError<string>("missing typedef function asterisk");
2270 }
2271 const char* nameStart = fChar;
2272 if (!this->skipToEndBracket(')')) {
2273 return this->reportError<string>("missing typedef function )");
2274 }
2275 builder += string(nameStart, fChar - nameStart);
2276 if (!this->skipToEndBracket('(')) {
2277 return this->reportError<string>("missing typedef params (");
2278 }
2279 if (! this->skipToEndBracket(')')) {
2280 return this->reportError<string>("missing typedef params )");
2281 }
2282 this->skipTo(end);
2283 }
2284 }
2285 return builder;
2286}
2287
Cary Clark8032b982017-07-28 11:04:54 -04002288bool BmhParser::skipNoName() {
2289 if ('\n' == this->peek()) {
2290 this->next();
2291 return true;
2292 }
2293 this->skipWhiteSpace();
2294 if (fMC != this->peek()) {
Cary Clark2be81cf2018-09-13 12:04:30 -04002295 return this->reportError<bool>("expected end mark 1");
Cary Clark8032b982017-07-28 11:04:54 -04002296 }
2297 this->next();
2298 if (fMC != this->peek()) {
Cary Clark2be81cf2018-09-13 12:04:30 -04002299 return this->reportError<bool>("expected end mark 2");
Cary Clark8032b982017-07-28 11:04:54 -04002300 }
2301 this->next();
2302 return true;
2303}
2304
2305bool BmhParser::skipToDefinitionEnd(MarkType markType) {
2306 if (this->eof()) {
2307 return this->reportError<bool>("missing end");
2308 }
2309 const char* start = fLine;
2310 int startLineCount = fLineCount;
2311 int stack = 1;
2312 ptrdiff_t lineLen;
2313 bool foundEnd = false;
2314 do {
2315 lineLen = this->lineLength();
2316 if (fMC != *fChar++) {
2317 continue;
2318 }
2319 if (fMC == *fChar) {
2320 continue;
2321 }
2322 if (' ' == *fChar) {
2323 continue;
2324 }
2325 MarkType nextType = this->getMarkType(MarkLookup::kAllowUnknown);
2326 if (markType != nextType) {
2327 continue;
2328 }
2329 bool hasEnd = this->hasEndToken();
2330 if (hasEnd) {
2331 if (!--stack) {
2332 foundEnd = true;
2333 continue;
2334 }
2335 } else {
2336 ++stack;
2337 }
2338 } while ((void) ++fLineCount, (void) (fLine += lineLen), (void) (fChar = fLine),
2339 !this->eof() && !foundEnd);
2340 if (foundEnd) {
2341 return true;
2342 }
2343 fLineCount = startLineCount;
2344 fLine = start;
2345 fChar = start;
2346 return this->reportError<bool>("unbalanced stack");
2347}
2348
Cary Clarkab2621d2018-01-30 10:08:57 -05002349bool BmhParser::skipToString() {
2350 this->skipSpace();
2351 if (fMC != this->peek()) {
Cary Clark2be81cf2018-09-13 12:04:30 -04002352 return this->reportError<bool>("expected end mark 3");
Cary Clarkab2621d2018-01-30 10:08:57 -05002353 }
2354 this->next();
2355 this->skipSpace();
2356 // body is text from here to double fMC
2357 // no single fMC allowed, no linefeed allowed
2358 return true;
2359}
2360
Cary Clark8032b982017-07-28 11:04:54 -04002361vector<string> BmhParser::topicName() {
2362 vector<string> result;
2363 this->skipWhiteSpace();
2364 const char* lineEnd = fLine + this->lineLength();
2365 const char* nameStart = fChar;
2366 while (fChar < lineEnd) {
2367 char ch = this->next();
2368 SkASSERT(',' != ch);
2369 if ('\n' == ch) {
2370 break;
2371 }
2372 if (fMC == ch) {
2373 break;
2374 }
2375 }
2376 if (fChar - 1 > nameStart) {
2377 string builder(nameStart, fChar - nameStart - 1);
2378 trim_start_end(builder);
2379 result.push_back(builder);
2380 }
2381 if (fChar < lineEnd && fMC == this->peek()) {
2382 this->next();
2383 }
2384 return result;
2385}
2386
2387// typeName parsing rules depend on mark type
2388vector<string> BmhParser::typeName(MarkType markType, bool* checkEnd) {
2389 fAnonymous = false;
2390 fCloned = false;
2391 vector<string> result;
2392 string builder;
2393 if (fParent) {
2394 builder = fParent->fName;
2395 }
2396 switch (markType) {
Cary Clark2d4bf5f2018-04-16 08:37:38 -04002397 case MarkType::kDefine:
Cary Clark8032b982017-07-28 11:04:54 -04002398 case MarkType::kEnum:
2399 // enums may be nameless
2400 case MarkType::kConst:
2401 case MarkType::kEnumClass:
2402 case MarkType::kClass:
2403 case MarkType::kStruct:
Cary Clark8032b982017-07-28 11:04:54 -04002404 // expect name
2405 builder = this->className(markType);
2406 break;
2407 case MarkType::kExample:
2408 // check to see if one already exists -- if so, number this one
2409 builder = this->uniqueName(string(), markType);
2410 this->skipNoName();
2411 break;
2412 case MarkType::kCode:
Cary Clark8032b982017-07-28 11:04:54 -04002413 case MarkType::kDescription:
Cary Clark8032b982017-07-28 11:04:54 -04002414 case MarkType::kExternal:
Cary Clark8032b982017-07-28 11:04:54 -04002415 case MarkType::kFunction:
2416 case MarkType::kLegend:
2417 case MarkType::kList:
2418 case MarkType::kNoExample:
2419 case MarkType::kPrivate:
Cary Clark8032b982017-07-28 11:04:54 -04002420 this->skipNoName();
2421 break;
Cary Clark2be81cf2018-09-13 12:04:30 -04002422 case MarkType::kFormula:
Cary Clarkab2621d2018-01-30 10:08:57 -05002423 case MarkType::kLine:
2424 this->skipToString();
2425 break;
Cary Clark8032b982017-07-28 11:04:54 -04002426 case MarkType::kAlias:
Ben Wagner63fd7602017-10-09 15:45:33 -04002427 case MarkType::kAnchor:
Cary Clark8032b982017-07-28 11:04:54 -04002428 case MarkType::kBug: // fixme: expect number
Cary Clark4855f782018-02-06 09:41:53 -05002429 case MarkType::kDeprecated:
Cary Clark682c58d2018-05-16 07:07:07 -04002430 case MarkType::kDetails:
Cary Clarkac47b882018-01-11 10:35:44 -05002431 case MarkType::kDuration:
Cary Clark682c58d2018-05-16 07:07:07 -04002432 case MarkType::kExperimental:
Cary Clark0d225392018-06-07 09:59:07 -04002433 case MarkType::kFile:
Cary Clarka90ea222018-10-16 10:30:28 -04002434 case MarkType::kFilter:
Cary Clark8032b982017-07-28 11:04:54 -04002435 case MarkType::kHeight:
Cary Clarkf895a422018-02-27 09:54:21 -05002436 case MarkType::kIllustration:
Cary Clark8032b982017-07-28 11:04:54 -04002437 case MarkType::kImage:
Cary Clarkab2621d2018-01-30 10:08:57 -05002438 case MarkType::kIn:
Cary Clark154beea2017-10-26 07:58:48 -04002439 case MarkType::kLiteral:
Cary Clark682c58d2018-05-16 07:07:07 -04002440 case MarkType::kNoJustify:
Cary Clark154beea2017-10-26 07:58:48 -04002441 case MarkType::kOutdent:
Cary Clark8032b982017-07-28 11:04:54 -04002442 case MarkType::kPlatform:
Cary Clark08895c42018-02-01 09:37:32 -05002443 case MarkType::kPopulate:
Cary Clark8032b982017-07-28 11:04:54 -04002444 case MarkType::kReturn:
2445 case MarkType::kSeeAlso:
Cary Clark61dfc3a2018-01-03 08:37:53 -05002446 case MarkType::kSet:
Cary Clark8032b982017-07-28 11:04:54 -04002447 case MarkType::kSubstitute:
Cary Clark8032b982017-07-28 11:04:54 -04002448 case MarkType::kToDo:
2449 case MarkType::kVolatile:
2450 case MarkType::kWidth:
2451 *checkEnd = false; // no name, may have text body
2452 break;
2453 case MarkType::kStdOut:
2454 this->skipNoName();
2455 break; // unnamed
2456 case MarkType::kMember:
2457 builder = this->memberName();
2458 break;
2459 case MarkType::kMethod:
2460 builder = this->methodName();
2461 break;
Cary Clarka560c472017-11-27 10:44:06 -05002462 case MarkType::kTypedef:
2463 builder = this->typedefName();
2464 break;
Cary Clark8032b982017-07-28 11:04:54 -04002465 case MarkType::kParam:
Cary Clark1a8d7622018-03-05 13:26:16 -05002466 // fixme: expect camelCase for param
Cary Clark8032b982017-07-28 11:04:54 -04002467 builder = this->word("", "");
2468 this->skipSpace();
2469 *checkEnd = false;
2470 break;
Cary Clark682c58d2018-05-16 07:07:07 -04002471 case MarkType::kPhraseDef: {
2472 const char* nameEnd = this->anyOf("(\n");
2473 builder = string(fChar, nameEnd - fChar);
2474 this->skipLower();
2475 if (fChar != nameEnd) {
2476 this->reportError("expect lower case only");
2477 break;
2478 }
2479 this->skipTo(nameEnd);
2480 *checkEnd = false;
2481 } break;
Cary Clark8032b982017-07-28 11:04:54 -04002482 case MarkType::kTable:
2483 this->skipNoName();
2484 break; // unnamed
2485 case MarkType::kSubtopic:
2486 case MarkType::kTopic:
2487 // fixme: start with cap, allow space, hyphen, stop on comma
2488 // one topic can have multiple type names delineated by comma
2489 result = this->topicName();
2490 if (result.size() == 0 && this->hasEndToken()) {
2491 break;
2492 }
2493 return result;
2494 default:
2495 // fixme: don't allow silent failures
2496 SkASSERT(0);
2497 }
2498 result.push_back(builder);
2499 return result;
2500}
2501
Cary Clarka560c472017-11-27 10:44:06 -05002502string BmhParser::typedefName() {
2503 if (this->hasEndToken()) {
2504 if (!fParent || !fParent->fName.length()) {
2505 return this->reportError<string>("missing parent typedef name");
2506 }
2507 SkASSERT(fMC == this->peek());
2508 this->next();
2509 SkASSERT(fMC == this->peek());
2510 this->next();
2511 SkASSERT(fMC != this->peek());
2512 return fParent->fName;
2513 }
Cary Clarka560c472017-11-27 10:44:06 -05002514 string builder;
Cary Clark2f466242017-12-11 16:03:17 -05002515 const Definition* parent = this->parentSpace();
2516 if (parent && parent->fName.length() > 0) {
2517 builder = parent->fName + "::";
Cary Clarka560c472017-11-27 10:44:06 -05002518 }
Cary Clark2f466242017-12-11 16:03:17 -05002519 builder += TextParser::typedefName();
Cary Clarka560c472017-11-27 10:44:06 -05002520 return uniqueRootName(builder, MarkType::kTypedef);
2521}
2522
Cary Clark2d4bf5f2018-04-16 08:37:38 -04002523string BmhParser::uniqueName(string base, MarkType markType) {
Cary Clark8032b982017-07-28 11:04:54 -04002524 string builder(base);
2525 if (!builder.length()) {
2526 builder = fParent->fName;
2527 }
2528 if (!fParent) {
2529 return builder;
2530 }
2531 int number = 2;
2532 string numBuilder(builder);
2533 do {
Cary Clarkf895a422018-02-27 09:54:21 -05002534 for (auto& iter : fParent->fChildren) {
Cary Clark8032b982017-07-28 11:04:54 -04002535 if (markType == iter->fMarkType) {
2536 if (iter->fName == numBuilder) {
Cary Clarkf895a422018-02-27 09:54:21 -05002537 if (iter->fDeprecated) {
2538 iter->fClone = true;
2539 } else {
2540 fCloned = true;
2541 }
Cary Clark8032b982017-07-28 11:04:54 -04002542 numBuilder = builder + '_' + to_string(number);
2543 goto tryNext;
2544 }
2545 }
2546 }
2547 break;
2548tryNext: ;
2549 } while (++number);
2550 return numBuilder;
2551}
2552
Cary Clark2d4bf5f2018-04-16 08:37:38 -04002553string BmhParser::uniqueRootName(string base, MarkType markType) {
2554 auto checkName = [markType](const Definition& def, string numBuilder) -> bool {
Cary Clark8032b982017-07-28 11:04:54 -04002555 return markType == def.fMarkType && def.fName == numBuilder;
2556 };
2557
2558 string builder(base);
2559 if (!builder.length()) {
2560 builder = fParent->fName;
2561 }
2562 int number = 2;
2563 string numBuilder(builder);
2564 Definition* cloned = nullptr;
2565 do {
2566 if (fRoot) {
2567 for (auto& iter : fRoot->fBranches) {
2568 if (checkName(*iter.second, numBuilder)) {
2569 cloned = iter.second;
2570 goto tryNext;
2571 }
2572 }
2573 for (auto& iter : fRoot->fLeaves) {
2574 if (checkName(iter.second, numBuilder)) {
2575 cloned = &iter.second;
2576 goto tryNext;
2577 }
2578 }
2579 } else if (fParent) {
2580 for (auto& iter : fParent->fChildren) {
2581 if (checkName(*iter, numBuilder)) {
2582 cloned = &*iter;
2583 goto tryNext;
2584 }
2585 }
2586 }
2587 break;
2588tryNext: ;
2589 if ("()" == builder.substr(builder.length() - 2)) {
2590 builder = builder.substr(0, builder.length() - 2);
2591 }
2592 if (MarkType::kMethod == markType) {
2593 cloned->fCloned = true;
Cary Clarkf895a422018-02-27 09:54:21 -05002594 if (cloned->fDeprecated) {
2595 cloned->fClone = true;
2596 } else {
2597 fCloned = true;
2598 }
2599 } else {
2600 fCloned = true;
Cary Clark8032b982017-07-28 11:04:54 -04002601 }
Cary Clark8032b982017-07-28 11:04:54 -04002602 numBuilder = builder + '_' + to_string(number);
2603 } while (++number);
2604 return numBuilder;
2605}
2606
2607void BmhParser::validate() const {
2608 for (int index = 0; index <= (int) Last_MarkType; ++index) {
Cary Clark2d4bf5f2018-04-16 08:37:38 -04002609 SkASSERT(kMarkProps[index].fMarkType == (MarkType) index);
Cary Clark8032b982017-07-28 11:04:54 -04002610 }
2611 const char* last = "";
2612 for (int index = 0; index <= (int) Last_MarkType; ++index) {
Cary Clark2d4bf5f2018-04-16 08:37:38 -04002613 const char* next = kMarkProps[index].fName;
Cary Clark8032b982017-07-28 11:04:54 -04002614 if (!last[0]) {
2615 last = next;
2616 continue;
2617 }
2618 if (!next[0]) {
2619 continue;
2620 }
2621 SkASSERT(strcmp(last, next) < 0);
2622 last = next;
2623 }
2624}
2625
Cary Clark2d4bf5f2018-04-16 08:37:38 -04002626string BmhParser::word(string prefix, string delimiter) {
Cary Clark8032b982017-07-28 11:04:54 -04002627 string builder(prefix);
2628 this->skipWhiteSpace();
2629 const char* lineEnd = fLine + this->lineLength();
2630 const char* nameStart = fChar;
2631 while (fChar < lineEnd) {
2632 char ch = this->next();
2633 if (' ' >= ch) {
2634 break;
2635 }
2636 if (',' == ch) {
2637 return this->reportError<string>("no comma needed");
2638 break;
2639 }
2640 if (fMC == ch) {
2641 return builder;
2642 }
2643 if (!isalnum(ch) && '_' != ch && ':' != ch && '-' != ch) {
2644 return this->reportError<string>("unexpected char");
2645 }
2646 if (':' == ch) {
2647 // expect pair, and expect word to start with Sk
2648 if (nameStart[0] != 'S' || nameStart[1] != 'k') {
2649 return this->reportError<string>("expected Sk");
2650 }
2651 if (':' != this->peek()) {
2652 return this->reportError<string>("expected ::");
2653 }
2654 this->next();
2655 } else if ('-' == ch) {
2656 // expect word not to start with Sk or kX where X is A-Z
2657 if (nameStart[0] == 'k' && nameStart[1] >= 'A' && nameStart[1] <= 'Z') {
2658 return this->reportError<string>("didn't expected kX");
2659 }
2660 if (nameStart[0] == 'S' && nameStart[1] == 'k') {
2661 return this->reportError<string>("expected Sk");
2662 }
2663 }
2664 }
2665 if (prefix.size()) {
2666 builder += delimiter;
2667 }
2668 builder.append(nameStart, fChar - nameStart - 1);
2669 return builder;
2670}
2671
2672// pass one: parse text, collect definitions
2673// pass two: lookup references
2674
Cary Clark8032b982017-07-28 11:04:54 -04002675static int count_children(const Definition& def, MarkType markType) {
2676 int count = 0;
2677 if (markType == def.fMarkType) {
2678 ++count;
2679 }
2680 for (auto& child : def.fChildren ) {
2681 count += count_children(*child, markType);
2682 }
2683 return count;
2684}
2685
2686int main(int argc, char** const argv) {
Cary Clarka560c472017-11-27 10:44:06 -05002687 BmhParser bmhParser(FLAGS_skip);
Cary Clark8032b982017-07-28 11:04:54 -04002688 bmhParser.validate();
2689
2690 SkCommandLineFlags::SetUsage(
Cary Clarka560c472017-11-27 10:44:06 -05002691 "Common Usage: bookmaker -b path/to/bmh_files -i path/to/include.h -t\n"
Cary Clark8032b982017-07-28 11:04:54 -04002692 " bookmaker -b path/to/bmh_files -e fiddle.json\n"
2693 " ~/go/bin/fiddlecli --input fiddle.json --output fiddleout.json\n"
2694 " bookmaker -b path/to/bmh_files -f fiddleout.json -r path/to/md_files\n"
Cary Clark2f466242017-12-11 16:03:17 -05002695 " bookmaker -a path/to/status.json -x\n"
2696 " bookmaker -a path/to/status.json -p\n");
Cary Clark8032b982017-07-28 11:04:54 -04002697 bool help = false;
2698 for (int i = 1; i < argc; i++) {
2699 if (0 == strcmp("-h", argv[i]) || 0 == strcmp("--help", argv[i])) {
2700 help = true;
2701 for (int j = i + 1; j < argc; j++) {
2702 if (SkStrStartsWith(argv[j], '-')) {
2703 break;
2704 }
2705 help = false;
2706 }
2707 break;
2708 }
2709 }
2710 if (!help) {
2711 SkCommandLineFlags::Parse(argc, argv);
2712 } else {
2713 SkCommandLineFlags::PrintUsage();
Cary Clarkac47b882018-01-11 10:35:44 -05002714 const char* const commands[] = { "", "-h", "bmh", "-h", "examples", "-h", "include",
2715 "-h", "fiddle", "-h", "ref", "-h", "status", "-h", "tokens",
Cary Clark8032b982017-07-28 11:04:54 -04002716 "-h", "crosscheck", "-h", "populate", "-h", "spellcheck" };
Cary Clark7265ea32017-09-14 12:12:32 -04002717 SkCommandLineFlags::Parse(SK_ARRAY_COUNT(commands), commands);
Cary Clark8032b982017-07-28 11:04:54 -04002718 return 0;
2719 }
Cary Clark2f466242017-12-11 16:03:17 -05002720 if (FLAGS_bmh.isEmpty() && FLAGS_include.isEmpty() && FLAGS_status.isEmpty()) {
2721 SkDebugf("requires at least one of: -b -i -a\n");
Cary Clark8032b982017-07-28 11:04:54 -04002722 SkCommandLineFlags::PrintUsage();
2723 return 1;
2724 }
Cary Clark2f466242017-12-11 16:03:17 -05002725 if (!FLAGS_bmh.isEmpty() && !FLAGS_status.isEmpty()) {
2726 SkDebugf("requires -b or -a but not both\n");
Cary Clarkbef063a2017-10-31 15:44:45 -04002727 SkCommandLineFlags::PrintUsage();
2728 return 1;
2729 }
Cary Clark2f466242017-12-11 16:03:17 -05002730 if (!FLAGS_include.isEmpty() && !FLAGS_status.isEmpty()) {
2731 SkDebugf("requires -i or -a but not both\n");
2732 SkCommandLineFlags::PrintUsage();
2733 return 1;
2734 }
2735 if (FLAGS_bmh.isEmpty() && FLAGS_status.isEmpty() && FLAGS_catalog) {
2736 SkDebugf("-c requires -b or -a\n");
2737 SkCommandLineFlags::PrintUsage();
2738 return 1;
2739 }
2740 if ((FLAGS_fiddle.isEmpty() || FLAGS_ref.isEmpty()) && FLAGS_catalog) {
2741 SkDebugf("-c requires -f -r\n");
2742 SkCommandLineFlags::PrintUsage();
2743 return 1;
2744 }
2745 if (FLAGS_bmh.isEmpty() && FLAGS_status.isEmpty() && !FLAGS_examples.isEmpty()) {
2746 SkDebugf("-e requires -b or -a\n");
Cary Clark8032b982017-07-28 11:04:54 -04002747 SkCommandLineFlags::PrintUsage();
2748 return 1;
2749 }
Cary Clark2f466242017-12-11 16:03:17 -05002750 if ((FLAGS_include.isEmpty() || FLAGS_bmh.isEmpty()) && FLAGS_status.isEmpty() &&
2751 FLAGS_populate) {
2752 SkDebugf("-p requires -b -i or -a\n");
Cary Clark8032b982017-07-28 11:04:54 -04002753 SkCommandLineFlags::PrintUsage();
2754 return 1;
2755 }
Cary Clark2f466242017-12-11 16:03:17 -05002756 if (FLAGS_bmh.isEmpty() && FLAGS_status.isEmpty() && !FLAGS_ref.isEmpty()) {
2757 SkDebugf("-r requires -b or -a\n");
Cary Clark8032b982017-07-28 11:04:54 -04002758 SkCommandLineFlags::PrintUsage();
2759 return 1;
2760 }
Cary Clark2f466242017-12-11 16:03:17 -05002761 if (FLAGS_bmh.isEmpty() && FLAGS_status.isEmpty() && !FLAGS_spellcheck.isEmpty()) {
2762 SkDebugf("-s requires -b or -a\n");
Cary Clark8032b982017-07-28 11:04:54 -04002763 SkCommandLineFlags::PrintUsage();
2764 return 1;
2765 }
Cary Clarka560c472017-11-27 10:44:06 -05002766 if ((FLAGS_include.isEmpty() || FLAGS_bmh.isEmpty()) && FLAGS_tokens) {
2767 SkDebugf("-t requires -b -i\n");
Cary Clark8032b982017-07-28 11:04:54 -04002768 SkCommandLineFlags::PrintUsage();
2769 return 1;
2770 }
Cary Clark2f466242017-12-11 16:03:17 -05002771 if ((FLAGS_include.isEmpty() || FLAGS_bmh.isEmpty()) && FLAGS_status.isEmpty() &&
2772 FLAGS_crosscheck) {
2773 SkDebugf("-x requires -b -i or -a\n");
Cary Clark8032b982017-07-28 11:04:54 -04002774 SkCommandLineFlags::PrintUsage();
2775 return 1;
2776 }
Cary Clarkac47b882018-01-11 10:35:44 -05002777 bmhParser.reset();
Cary Clark8032b982017-07-28 11:04:54 -04002778 if (!FLAGS_bmh.isEmpty()) {
Cary Clark2dc84ad2018-01-26 12:56:22 -05002779 if (FLAGS_tokens) {
2780 IncludeParser::RemoveFile(FLAGS_bmh[0], FLAGS_include[0]);
2781 }
Cary Clark186d08f2018-04-03 08:43:27 -04002782 if (!bmhParser.parseFile(FLAGS_bmh[0], ".bmh", ParserCommon::OneFile::kNo)) {
Cary Clark8032b982017-07-28 11:04:54 -04002783 return -1;
2784 }
Cary Clark2f466242017-12-11 16:03:17 -05002785 } else if (!FLAGS_status.isEmpty()) {
Cary Clark2f466242017-12-11 16:03:17 -05002786 if (!bmhParser.parseStatus(FLAGS_status[0], ".bmh", StatusFilter::kInProgress)) {
2787 return -1;
2788 }
Cary Clark8032b982017-07-28 11:04:54 -04002789 }
Cary Clarkab2621d2018-01-30 10:08:57 -05002790 if (FLAGS_hack) {
Cary Clark09d80c02018-10-31 12:14:03 -04002791 if (FLAGS_bmh.isEmpty() && FLAGS_status.isEmpty()) {
2792 SkDebugf("-H or --hack requires -a or -b\n");
Cary Clarkab2621d2018-01-30 10:08:57 -05002793 SkCommandLineFlags::PrintUsage();
2794 return 1;
2795 }
2796 HackParser hacker(bmhParser);
Cary Clark09d80c02018-10-31 12:14:03 -04002797 hacker.fDebugOut = FLAGS_stdout;
2798 if (!FLAGS_status.isEmpty() && !hacker.parseStatus(FLAGS_status[0], ".bmh",
2799 StatusFilter::kInProgress)) {
2800 SkDebugf("hack failed\n");
2801 return -1;
2802 }
2803 if (!FLAGS_bmh.isEmpty() && !hacker.parseFile(FLAGS_bmh[0], ".bmh",
2804 ParserCommon::OneFile::kNo)) {
Cary Clarkab2621d2018-01-30 10:08:57 -05002805 SkDebugf("hack failed\n");
2806 return -1;
2807 }
2808 return 0;
2809 }
Cary Clarkac47b882018-01-11 10:35:44 -05002810 if (FLAGS_selfcheck && !SelfCheck(bmhParser)) {
2811 return -1;
2812 }
Cary Clark8032b982017-07-28 11:04:54 -04002813 bool done = false;
Cary Clark2f466242017-12-11 16:03:17 -05002814 if (!FLAGS_include.isEmpty() && FLAGS_tokens) {
2815 IncludeParser includeParser;
2816 includeParser.validate();
Cary Clark186d08f2018-04-03 08:43:27 -04002817 if (!includeParser.parseFile(FLAGS_include[0], ".h", ParserCommon::OneFile::kNo)) {
Cary Clark2f466242017-12-11 16:03:17 -05002818 return -1;
2819 }
2820 if (FLAGS_tokens) {
2821 includeParser.fDebugOut = FLAGS_stdout;
Cary Clark2d4bf5f2018-04-16 08:37:38 -04002822 if (includeParser.dumpTokens()) {
Cary Clark2f466242017-12-11 16:03:17 -05002823 bmhParser.fWroteOut = true;
2824 }
2825 done = true;
2826 }
2827 } else if (!FLAGS_include.isEmpty() || !FLAGS_status.isEmpty()) {
2828 if (FLAGS_crosscheck) {
Cary Clark8032b982017-07-28 11:04:54 -04002829 IncludeParser includeParser;
2830 includeParser.validate();
Cary Clark2f466242017-12-11 16:03:17 -05002831 if (!FLAGS_include.isEmpty() &&
Cary Clark186d08f2018-04-03 08:43:27 -04002832 !includeParser.parseFile(FLAGS_include[0], ".h", ParserCommon::OneFile::kNo)) {
Cary Clark8032b982017-07-28 11:04:54 -04002833 return -1;
2834 }
Cary Clark2f466242017-12-11 16:03:17 -05002835 if (!FLAGS_status.isEmpty() && !includeParser.parseStatus(FLAGS_status[0], ".h",
2836 StatusFilter::kCompleted)) {
2837 return -1;
Cary Clark8032b982017-07-28 11:04:54 -04002838 }
Cary Clark2f466242017-12-11 16:03:17 -05002839 if (!includeParser.crossCheck(bmhParser)) {
2840 return -1;
2841 }
2842 done = true;
Cary Clark8032b982017-07-28 11:04:54 -04002843 } else if (FLAGS_populate) {
2844 IncludeWriter includeWriter;
2845 includeWriter.validate();
Cary Clark2f466242017-12-11 16:03:17 -05002846 if (!FLAGS_include.isEmpty() &&
Cary Clark186d08f2018-04-03 08:43:27 -04002847 !includeWriter.parseFile(FLAGS_include[0], ".h", ParserCommon::OneFile::kNo)) {
Cary Clark2f466242017-12-11 16:03:17 -05002848 return -1;
2849 }
2850 if (!FLAGS_status.isEmpty() && !includeWriter.parseStatus(FLAGS_status[0], ".h",
2851 StatusFilter::kCompleted)) {
Cary Clark8032b982017-07-28 11:04:54 -04002852 return -1;
2853 }
Cary Clarkd0530ba2017-09-14 11:25:39 -04002854 includeWriter.fDebugOut = FLAGS_stdout;
Cary Clark8032b982017-07-28 11:04:54 -04002855 if (!includeWriter.populate(bmhParser)) {
2856 return -1;
2857 }
Cary Clarkd0530ba2017-09-14 11:25:39 -04002858 bmhParser.fWroteOut = true;
Cary Clark8032b982017-07-28 11:04:54 -04002859 done = true;
2860 }
2861 }
Cary Clark2f466242017-12-11 16:03:17 -05002862 if (!done && !FLAGS_fiddle.isEmpty() && FLAGS_examples.isEmpty()) {
Cary Clarkbef063a2017-10-31 15:44:45 -04002863 FiddleParser fparser(&bmhParser);
Cary Clarkd7895502018-07-18 15:10:08 -04002864 if (!fparser.parseFromFile(FLAGS_fiddle[0])) {
Cary Clark8032b982017-07-28 11:04:54 -04002865 return -1;
2866 }
2867 }
Cary Clarkbef063a2017-10-31 15:44:45 -04002868 if (!done && FLAGS_catalog && FLAGS_examples.isEmpty()) {
Cary Clark2f466242017-12-11 16:03:17 -05002869 Catalog cparser(&bmhParser);
2870 cparser.fDebugOut = FLAGS_stdout;
Cary Clark5b1f9532018-08-28 14:53:37 -04002871 if (!FLAGS_bmh.isEmpty() && !cparser.openCatalog(FLAGS_bmh[0])) {
Cary Clarkbef063a2017-10-31 15:44:45 -04002872 return -1;
2873 }
Cary Clark5b1f9532018-08-28 14:53:37 -04002874 if (!FLAGS_status.isEmpty() && !cparser.openStatus(FLAGS_status[0])) {
Cary Clarkbef063a2017-10-31 15:44:45 -04002875 return -1;
2876 }
Cary Clark186d08f2018-04-03 08:43:27 -04002877 if (!cparser.parseFile(FLAGS_fiddle[0], ".txt", ParserCommon::OneFile::kNo)) {
Cary Clark2f466242017-12-11 16:03:17 -05002878 return -1;
2879 }
Cary Clark5b1f9532018-08-28 14:53:37 -04002880 if (!cparser.closeCatalog(FLAGS_ref[0])) {
Cary Clarkbef063a2017-10-31 15:44:45 -04002881 return -1;
2882 }
2883 bmhParser.fWroteOut = true;
2884 done = true;
2885 }
Cary Clark8032b982017-07-28 11:04:54 -04002886 if (!done && !FLAGS_ref.isEmpty() && FLAGS_examples.isEmpty()) {
Cary Clark186d08f2018-04-03 08:43:27 -04002887 IncludeParser includeParser;
2888 includeParser.validate();
Cary Clark61313f32018-10-08 14:57:48 -04002889 if (!FLAGS_status.isEmpty() && !includeParser.parseStatus(FLAGS_status[0], ".h",
2890 StatusFilter::kCompleted)) {
2891 return -1;
2892 }
Cary Clark186d08f2018-04-03 08:43:27 -04002893 if (!FLAGS_include.isEmpty() && !includeParser.parseFile(FLAGS_include[0], ".h",
2894 ParserCommon::OneFile::kYes)) {
2895 return -1;
2896 }
Cary Clarka90ea222018-10-16 10:30:28 -04002897 includeParser.writeCodeBlock();
Cary Clark61313f32018-10-08 14:57:48 -04002898 MdOut mdOut(bmhParser, includeParser);
Cary Clark9174bda2017-09-19 17:39:32 -04002899 mdOut.fDebugOut = FLAGS_stdout;
Cary Clark682c58d2018-05-16 07:07:07 -04002900 mdOut.fValidate = FLAGS_validate;
Cary Clark61313f32018-10-08 14:57:48 -04002901 if (!FLAGS_bmh.isEmpty() && mdOut.buildReferences(FLAGS_bmh[0], FLAGS_ref[0])) {
Cary Clark2f466242017-12-11 16:03:17 -05002902 bmhParser.fWroteOut = true;
2903 }
2904 if (!FLAGS_status.isEmpty() && mdOut.buildStatus(FLAGS_status[0], FLAGS_ref[0])) {
Cary Clarkd0530ba2017-09-14 11:25:39 -04002905 bmhParser.fWroteOut = true;
2906 }
Cary Clark682c58d2018-05-16 07:07:07 -04002907 if (FLAGS_validate) {
2908 mdOut.checkAnchors();
2909 }
Cary Clark8032b982017-07-28 11:04:54 -04002910 }
Cary Clarkce101242017-09-01 15:51:02 -04002911 if (!done && !FLAGS_spellcheck.isEmpty() && FLAGS_examples.isEmpty()) {
Cary Clark2f466242017-12-11 16:03:17 -05002912 if (!FLAGS_bmh.isEmpty()) {
2913 bmhParser.spellCheck(FLAGS_bmh[0], FLAGS_spellcheck);
2914 }
2915 if (!FLAGS_status.isEmpty()) {
2916 bmhParser.spellStatus(FLAGS_status[0], FLAGS_spellcheck);
2917 }
Cary Clark154beea2017-10-26 07:58:48 -04002918 bmhParser.fWroteOut = true;
Cary Clark8032b982017-07-28 11:04:54 -04002919 done = true;
2920 }
2921 int examples = 0;
2922 int methods = 0;
2923 int topics = 0;
Cary Clark8032b982017-07-28 11:04:54 -04002924 if (!done && !FLAGS_examples.isEmpty()) {
Cary Clark73fa9722017-08-29 17:36:51 -04002925 // check to see if examples have duplicate names
2926 if (!bmhParser.checkExamples()) {
Cary Clark8032b982017-07-28 11:04:54 -04002927 return -1;
2928 }
Cary Clark9174bda2017-09-19 17:39:32 -04002929 bmhParser.fDebugOut = FLAGS_stdout;
Cary Clark73fa9722017-08-29 17:36:51 -04002930 if (!bmhParser.dumpExamples(FLAGS_examples[0])) {
2931 return -1;
Cary Clark8032b982017-07-28 11:04:54 -04002932 }
Cary Clarkd0530ba2017-09-14 11:25:39 -04002933 return 0;
Cary Clark8032b982017-07-28 11:04:54 -04002934 }
Cary Clarkd0530ba2017-09-14 11:25:39 -04002935 if (!bmhParser.fWroteOut) {
2936 for (const auto& topic : bmhParser.fTopicMap) {
2937 if (topic.second->fParent) {
2938 continue;
2939 }
2940 examples += count_children(*topic.second, MarkType::kExample);
2941 methods += count_children(*topic.second, MarkType::kMethod);
2942 topics += count_children(*topic.second, MarkType::kSubtopic);
2943 topics += count_children(*topic.second, MarkType::kTopic);
Cary Clark8032b982017-07-28 11:04:54 -04002944 }
Ben Wagner63fd7602017-10-09 15:45:33 -04002945 SkDebugf("topics=%d classes=%d methods=%d examples=%d\n",
Cary Clarkd0530ba2017-09-14 11:25:39 -04002946 bmhParser.fTopicMap.size(), bmhParser.fClassMap.size(),
2947 methods, examples);
Cary Clark8032b982017-07-28 11:04:54 -04002948 }
Cary Clark8032b982017-07-28 11:04:54 -04002949 return 0;
2950}
Cary Clark09d80c02018-10-31 12:14:03 -04002951
2952void NameMap::copyToParent(NameMap* parent) const {
2953 size_t colons = fName.rfind("::");
2954 string topName = string::npos == colons ? fName : fName.substr(colons + 2);
2955 for (auto& entry : fRefMap) {
2956 string scoped = topName + "::" + entry.first;
2957 SkASSERT(parent->fRefMap.end() == parent->fRefMap.find(scoped));
2958 parent->fRefMap[scoped] = entry.second;
2959 auto scopedLinkIter = fLinkMap.find(entry.first);
2960 if (fLinkMap.end() != scopedLinkIter) {
2961 SkASSERT(parent->fLinkMap.end() == parent->fLinkMap.find(scoped));
2962 parent->fLinkMap[scoped] = scopedLinkIter->second;
2963 }
2964 }
2965}