blob: 5b282146add2d0b300dc6d82afe80cb66393bcfe [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)");
Cary Clark3bdaa462018-10-31 16:16:54 -040021DEFINE_bool2(extract, E, false, "Extract examples into fiddle.json");
Cary Clarkbc5697d2017-10-04 14:31:33 -040022DEFINE_string2(fiddle, f, "", "File of fiddlecli output, usually fiddleout.json.");
Cary Clark5081eed2018-01-22 07:55:48 -050023DEFINE_bool2(hack, H, false, "Do a find/replace hack to update all *.bmh files. (Requires -b)");
24// h is reserved for help
Cary Clarkbc5697d2017-10-04 14:31:33 -040025DEFINE_string2(include, i, "", "Path to a *.h file or a directory.");
Cary Clarkac47b882018-01-11 10:35:44 -050026DEFINE_bool2(selfcheck, k, false, "Check bmh against itself. (Requires -b)");
Cary Clarkbc5697d2017-10-04 14:31:33 -040027DEFINE_bool2(stdout, o, false, "Write file out to standard out.");
28DEFINE_bool2(populate, p, false, "Populate include from bmh. (Requires -b -i)");
Cary Clark5081eed2018-01-22 07:55:48 -050029// q is reserved for quiet
Cary Clark7cfcbca2018-01-04 16:11:51 -050030DEFINE_string2(ref, r, "", "Resolve refs and write *.md files to path. (Requires -b -f)");
Cary Clarkbc5697d2017-10-04 14:31:33 -040031DEFINE_string2(spellcheck, s, "", "Spell-check [once, all, mispelling]. (Requires -b)");
Cary Clarka560c472017-11-27 10:44:06 -050032DEFINE_bool2(tokens, t, false, "Write bmh from include. (Requires -b -i)");
Cary Clarkbc5697d2017-10-04 14:31:33 -040033DEFINE_bool2(crosscheck, x, false, "Check bmh against includes. (Requires -b -i)");
Cary Clark5081eed2018-01-22 07:55:48 -050034// v is reserved for verbose
Cary Clark682c58d2018-05-16 07:07:07 -040035DEFINE_bool2(validate, V, false, "Validate that all anchor references have definitions. (Requires -r)");
Cary Clark884dd7d2017-10-11 10:37:52 -040036DEFINE_bool2(skip, z, false, "Skip degenerate missed in legacy preprocessor.");
Cary Clark8032b982017-07-28 11:04:54 -040037
Cary Clark09d80c02018-10-31 12:14:03 -040038// -b docs -i include/core/SkRect.h -f fiddleout.json -r site/user/api
39// -b docs/SkIRect_Reference.bmh -H
Cary Clark682c58d2018-05-16 07:07:07 -040040/* todos:
Cary Clark8032b982017-07-28 11:04:54 -040041
Cary Clarka90ea222018-10-16 10:30:28 -040042if #Subtopic contains #SeeAlso or #Example generate horizontal rule at end
43constexpr populated with filter inside subtopic does not have definition body
44
Cary Clark8032b982017-07-28 11:04:54 -040045#List needs '# content ##', formatting
Cary Clark186d08f2018-04-03 08:43:27 -040046rewrap text to fit in some number of columns
47#Literal is inflexible, making the entire #Code block link-less (see $Literal in SkImageInfo)
Cary Clark80247e52018-07-11 16:18:41 -040048 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 -040049add check to require #Const to contain #Code block if defining const or constexpr (enum consts have
Cary Clark80247e52018-07-11 16:18:41 -040050 #Code blocks inside the #Enum def)
Cary Clarkd2ca79c2018-08-10 13:09:13 -040051subclasses (e.g. Iter in SkPath) need to check for #Line and generate overview
52 subclass methods should also disallow #In
Cary Clark682c58d2018-05-16 07:07:07 -040053
Cary Clark682c58d2018-05-16 07:07:07 -040054It's awkward that phrase param is a child of the phrase def. Since phrase refs may also be children,
55there is special case code to skip phrase def when looking for additional substitutions in the
56phrase def. Could put it in the token list instead I guess, or make a definition subclass used
57by phrase def with an additional slot...
58
Cary Clark682c58d2018-05-16 07:07:07 -040059rearrange const out for md so that const / value / short description comes first in a table,
60followed by more elaborate descriptions, examples, seealso. In md.cpp, look to see if #Subtopic
61has #Const children. If so, generate a summary table first.
62Or, only allow #Line and moderate text description in #Const. Put more verbose text, example,
63seealso, in subsequent #SubTopic. Alpha_Type does this and it looks good.
64
Cary Clark61313f32018-10-08 14:57:48 -040065IPoint is awkward. SkPoint and SkIPoint are named things; Point is a topic, which
66refers to float points or integer points. There needn't be an IPoint topic.
67One way to resolve this would be to combine SkPoint_Reference and SkIPoint_Reference into
68Point_Reference that then contains both structs (or just move SKIPoint into SkPoint_Reference).
69Most Point references would be replaced with SkPoint / SkIPoint (if that's what they mean),
70or remain Point if the text indicates the concept rather one of the C structs.
71
Cary Clarkac47b882018-01-11 10:35:44 -050072see head of selfCheck.cpp for additional todos
Cary Clark80247e52018-07-11 16:18:41 -040073see head of spellCheck.cpp for additional todos
Cary Clark8032b982017-07-28 11:04:54 -040074 */
75
Ben Wagner63fd7602017-10-09 15:45:33 -040076/*
Cary Clark8032b982017-07-28 11:04:54 -040077 class contains named struct, enum, enum-member, method, topic, subtopic
78 everything contained by class is uniquely named
79 contained names may be reused by other classes
80 method contains named parameters
81 parameters may be reused in other methods
82 */
83
Cary Clark2d4bf5f2018-04-16 08:37:38 -040084#define M(mt) (1LL << (int) MarkType::k##mt)
85#define M_D M(Description)
86#define M_CS M(Class) | M(Struct)
Cary Clark682c58d2018-05-16 07:07:07 -040087#define M_MD M(Method) | M(Define)
88#define M_MDCM M_MD | M(Const) | M(Member)
Cary Clark2d4bf5f2018-04-16 08:37:38 -040089#define M_ST M(Subtopic) | M(Topic)
90#define M_CSST M_CS | M_ST
91#ifdef M_E
92#undef M_E
93#endif
94#define M_E M(Enum) | M(EnumClass)
95
96#define R_Y Resolvable::kYes
97#define R_N Resolvable::kNo
98#define R_O Resolvable::kOut
Cary Clark2be81cf2018-09-13 12:04:30 -040099#define R_K Resolvable::kCode
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400100#define R_F Resolvable::kFormula
101#define R_C Resolvable::kClone
102
103#define E_Y Exemplary::kYes
104#define E_N Exemplary::kNo
105#define E_O Exemplary::kOptional
106
Cary Clark682c58d2018-05-16 07:07:07 -0400107// ToDo: add column to denote which marks are one-liners
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400108BmhParser::MarkProps BmhParser::kMarkProps[] = {
109// names without formal definitions (e.g. Column) aren't included
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400110 { "", MarkType::kNone, R_Y, E_N, 0 }
111, { "A", MarkType::kAnchor, R_N, E_N, 0 }
Cary Clark682c58d2018-05-16 07:07:07 -0400112, { "Alias", MarkType::kAlias, R_N, E_N, M_ST | M(Const) }
113, { "Bug", MarkType::kBug, R_N, E_N, M_CSST | M_MDCM | M_E
114 | M(Example) | M(NoExample) }
115, { "Class", MarkType::kClass, R_Y, E_O, M_CSST }
Cary Clark2be81cf2018-09-13 12:04:30 -0400116, { "Code", MarkType::kCode, R_K, E_N, M_CSST | M_E | M_MD | M(Typedef) }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400117, { "", MarkType::kColumn, R_Y, E_N, M(Row) }
118, { "", MarkType::kComment, R_N, E_N, 0 }
Cary Clark224c7002018-06-27 11:00:21 -0400119, { "Const", MarkType::kConst, R_Y, E_O, M_E | M_CSST }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400120, { "Define", MarkType::kDefine, R_O, E_Y, M_ST }
Cary Clark682c58d2018-05-16 07:07:07 -0400121, { "Deprecated", MarkType::kDeprecated, R_Y, E_N, M_CS | M_MDCM | M_E }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400122, { "Description", MarkType::kDescription, R_Y, E_N, M(Example) | M(NoExample) }
Cary Clark682c58d2018-05-16 07:07:07 -0400123, { "Details", MarkType::kDetails, R_N, E_N, M(Const) }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400124, { "Duration", MarkType::kDuration, R_N, E_N, M(Example) | M(NoExample) }
Cary Clark682c58d2018-05-16 07:07:07 -0400125, { "Enum", MarkType::kEnum, R_Y, E_O, M_CSST }
126, { "EnumClass", MarkType::kEnumClass, R_Y, E_O, M_CSST }
Cary Clark224c7002018-06-27 11:00:21 -0400127, { "Example", MarkType::kExample, R_O, E_N, M_CSST | M_E | M_MD | M(Const) }
Cary Clark682c58d2018-05-16 07:07:07 -0400128, { "Experimental", MarkType::kExperimental, R_Y, E_N, M_CS | M_MDCM | M_E }
129, { "External", MarkType::kExternal, R_Y, E_N, 0 }
Cary Clark0d225392018-06-07 09:59:07 -0400130, { "File", MarkType::kFile, R_Y, E_N, M(Topic) }
Cary Clarka90ea222018-10-16 10:30:28 -0400131, { "Filter", MarkType::kFilter, R_N, E_N, M(Subtopic) | M(Code) }
Cary Clark682c58d2018-05-16 07:07:07 -0400132, { "Formula", MarkType::kFormula, R_F, E_N, M(Column) | M(Description)
133 | M_E | M_ST | M_MDCM }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400134, { "Function", MarkType::kFunction, R_O, E_N, M(Example) | M(NoExample) }
135, { "Height", MarkType::kHeight, R_N, E_N, M(Example) | M(NoExample) }
Cary Clark682c58d2018-05-16 07:07:07 -0400136, { "Illustration", MarkType::kIllustration, R_N, E_N, M_CSST | M_MD }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400137, { "Image", MarkType::kImage, R_N, E_N, M(Example) | M(NoExample) }
Cary Clarka90ea222018-10-16 10:30:28 -0400138, { "In", MarkType::kIn, R_N, E_N, M_CSST | M_E | M(Method) | M(Typedef) | M(Code) }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400139, { "Legend", MarkType::kLegend, R_Y, E_N, M(Table) }
140, { "Line", MarkType::kLine, R_N, E_N, M_CSST | M_E | M(Method) | M(Typedef) }
141, { "", MarkType::kLink, R_N, E_N, M(Anchor) }
142, { "List", MarkType::kList, R_Y, E_N, M(Method) | M_CSST | M_E | M_D }
143, { "Literal", MarkType::kLiteral, R_N, E_N, M(Code) }
144, { "", MarkType::kMarkChar, R_N, E_N, 0 }
Cary Clark61313f32018-10-08 14:57:48 -0400145, { "Member", MarkType::kMember, R_Y, E_O, M_CSST }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400146, { "Method", MarkType::kMethod, R_Y, E_Y, M_CSST }
Cary Clark682c58d2018-05-16 07:07:07 -0400147, { "NoExample", MarkType::kNoExample, R_N, E_N, M_CSST | M_E | M_MD }
148, { "NoJustify", MarkType::kNoJustify, R_N, E_N, M(Const) | M(Member) }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400149, { "Outdent", MarkType::kOutdent, R_N, E_N, M(Code) }
150, { "Param", MarkType::kParam, R_Y, E_N, M(Method) | M(Define) }
Cary Clark224c7002018-06-27 11:00:21 -0400151, { "PhraseDef", MarkType::kPhraseDef, R_Y, E_N, M_ST }
Cary Clark682c58d2018-05-16 07:07:07 -0400152, { "", MarkType::kPhraseParam, R_Y, E_N, 0 }
153, { "", MarkType::kPhraseRef, R_N, E_N, 0 }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400154, { "Platform", MarkType::kPlatform, R_N, E_N, M(Example) | M(NoExample) }
Cary Clarka64e4ee2018-10-18 08:30:34 -0400155, { "Populate", MarkType::kPopulate, R_N, E_N, M(Code) | M(Method) }
Cary Clark224c7002018-06-27 11:00:21 -0400156, { "Private", MarkType::kPrivate, R_N, E_N, M_CSST | M_MDCM | M_E }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400157, { "Return", MarkType::kReturn, R_Y, E_N, M(Method) }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400158, { "", MarkType::kRow, R_Y, E_N, M(Table) | M(List) }
Cary Clark682c58d2018-05-16 07:07:07 -0400159, { "SeeAlso", MarkType::kSeeAlso, R_C, E_N, M_CSST | M_E | M_MD | M(Typedef) }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400160, { "Set", MarkType::kSet, R_N, E_N, M(Example) | M(NoExample) }
161, { "StdOut", MarkType::kStdOut, R_N, E_N, M(Example) | M(NoExample) }
Cary Clark682c58d2018-05-16 07:07:07 -0400162, { "Struct", MarkType::kStruct, R_Y, E_O, M(Class) | M_ST }
Cary Clark137b8742018-05-30 09:21:49 -0400163, { "Substitute", MarkType::kSubstitute, R_N, E_N, M(Alias) | M_ST }
Cary Clark61313f32018-10-08 14:57:48 -0400164, { "Subtopic", MarkType::kSubtopic, R_Y, E_Y, M_CSST | M_E }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400165, { "Table", MarkType::kTable, R_Y, E_N, M(Method) | M_CSST | M_E }
Cary Clark682c58d2018-05-16 07:07:07 -0400166, { "Template", MarkType::kTemplate, R_Y, E_N, M_CSST }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400167, { "", MarkType::kText, R_N, E_N, 0 }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400168, { "ToDo", MarkType::kToDo, R_N, E_N, 0 }
Cary Clark682c58d2018-05-16 07:07:07 -0400169, { "Topic", MarkType::kTopic, R_Y, E_Y, 0 }
Cary Clark61313f32018-10-08 14:57:48 -0400170, { "Typedef", MarkType::kTypedef, R_Y, E_O, M_CSST | M_E }
Cary Clark682c58d2018-05-16 07:07:07 -0400171, { "Union", MarkType::kUnion, R_Y, E_N, M_CSST }
Cary Clark61313f32018-10-08 14:57:48 -0400172, { "Using", MarkType::kUsing, R_Y, E_O, M_CSST }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400173, { "Volatile", MarkType::kVolatile, R_N, E_N, M(StdOut) }
174, { "Width", MarkType::kWidth, R_N, E_N, M(Example) | M(NoExample) }
175};
176
177#undef R_O
178#undef R_N
179#undef R_Y
Cary Clark2be81cf2018-09-13 12:04:30 -0400180#undef R_K
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400181#undef R_F
182#undef R_C
183
184#undef M_E
185#undef M_CSST
186#undef M_ST
187#undef M_CS
Cary Clark682c58d2018-05-16 07:07:07 -0400188#undef M_MCD
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400189#undef M_D
190#undef M
191
192#undef E_Y
193#undef E_N
194#undef E_O
195
Cary Clark8032b982017-07-28 11:04:54 -0400196bool BmhParser::addDefinition(const char* defStart, bool hasEnd, MarkType markType,
Cary Clarkf895a422018-02-27 09:54:21 -0500197 const vector<string>& typeNameBuilder, HasTag hasTag) {
Cary Clark8032b982017-07-28 11:04:54 -0400198 Definition* definition = nullptr;
199 switch (markType) {
200 case MarkType::kComment:
201 if (!this->skipToDefinitionEnd(markType)) {
202 return false;
203 }
204 return true;
205 // these types may be referred to by name
206 case MarkType::kClass:
207 case MarkType::kStruct:
208 case MarkType::kConst:
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400209 case MarkType::kDefine:
Cary Clark8032b982017-07-28 11:04:54 -0400210 case MarkType::kEnum:
211 case MarkType::kEnumClass:
212 case MarkType::kMember:
213 case MarkType::kMethod:
214 case MarkType::kTypedef: {
215 if (!typeNameBuilder.size()) {
216 return this->reportError<bool>("unnamed markup");
217 }
218 if (typeNameBuilder.size() > 1) {
219 return this->reportError<bool>("expected one name only");
220 }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400221 string name = typeNameBuilder[0];
Cary Clark8032b982017-07-28 11:04:54 -0400222 if (nullptr == fRoot) {
223 fRoot = this->findBmhObject(markType, name);
224 fRoot->fFileName = fFileName;
Cary Clark09d80c02018-10-31 12:14:03 -0400225 fRoot->fName = name;
226 fRoot->fNames.fName = name;
227 fRoot->fNames.fParent = &fGlobalNames;
Cary Clark8032b982017-07-28 11:04:54 -0400228 definition = fRoot;
229 } else {
230 if (nullptr == fParent) {
231 return this->reportError<bool>("expected parent");
232 }
233 if (fParent == fRoot && hasEnd) {
234 RootDefinition* rootParent = fRoot->rootParent();
235 if (rootParent) {
236 fRoot = rootParent;
237 }
238 definition = fParent;
239 } else {
Cary Clarkce101242017-09-01 15:51:02 -0400240 if (!hasEnd && fRoot->find(name, RootDefinition::AllowParens::kNo)) {
Cary Clark8032b982017-07-28 11:04:54 -0400241 return this->reportError<bool>("duplicate symbol");
242 }
Cary Clark61313f32018-10-08 14:57:48 -0400243 if (MarkType::kStruct == markType || MarkType::kClass == markType
244 || MarkType::kEnumClass == markType) {
Cary Clark8032b982017-07-28 11:04:54 -0400245 // if class or struct, build fRoot hierarchy
246 // and change isDefined to search all parents of fRoot
247 SkASSERT(!hasEnd);
248 RootDefinition* childRoot = new RootDefinition;
249 (fRoot->fBranches)[name] = childRoot;
250 childRoot->setRootParent(fRoot);
251 childRoot->fFileName = fFileName;
Cary Clark09d80c02018-10-31 12:14:03 -0400252 SkASSERT(MarkType::kSubtopic != fRoot->fMarkType
253 && MarkType::kTopic != fRoot->fMarkType);
254 childRoot->fNames.fName = name;
255 childRoot->fNames.fParent = &fRoot->fNames;
Cary Clark8032b982017-07-28 11:04:54 -0400256 fRoot = childRoot;
257 definition = fRoot;
258 } else {
259 definition = &fRoot->fLeaves[name];
260 }
261 }
262 }
263 if (hasEnd) {
264 Exemplary hasExample = Exemplary::kNo;
265 bool hasExcluder = false;
266 for (auto child : definition->fChildren) {
267 if (MarkType::kExample == child->fMarkType) {
268 hasExample = Exemplary::kYes;
269 }
270 hasExcluder |= MarkType::kPrivate == child->fMarkType
271 || MarkType::kDeprecated == child->fMarkType
272 || MarkType::kExperimental == child->fMarkType
273 || MarkType::kNoExample == child->fMarkType;
274 }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400275 if (kMarkProps[(int) markType].fExemplary != hasExample
276 && kMarkProps[(int) markType].fExemplary != Exemplary::kOptional) {
Cary Clark8032b982017-07-28 11:04:54 -0400277 if (string::npos == fFileName.find("undocumented")
278 && !hasExcluder) {
Ben Wagner63fd7602017-10-09 15:45:33 -0400279 hasExample == Exemplary::kNo ?
280 this->reportWarning("missing example") :
Cary Clark8032b982017-07-28 11:04:54 -0400281 this->reportWarning("unexpected example");
282 }
283
284 }
285 if (MarkType::kMethod == markType) {
286 if (fCheckMethods && !definition->checkMethod()) {
287 return false;
288 }
289 }
Cary Clarkf895a422018-02-27 09:54:21 -0500290 if (HasTag::kYes == hasTag) {
291 if (!this->checkEndMarker(markType, definition->fName)) {
292 return false;
293 }
294 }
Cary Clark8032b982017-07-28 11:04:54 -0400295 if (!this->popParentStack(definition)) {
296 return false;
297 }
Cary Clark06c20f32018-03-20 15:53:27 -0400298 if (fRoot == definition) {
299 fRoot = nullptr;
300 }
Cary Clark8032b982017-07-28 11:04:54 -0400301 } else {
302 definition->fStart = defStart;
303 this->skipSpace();
304 definition->fFileName = fFileName;
305 definition->fContentStart = fChar;
306 definition->fLineCount = fLineCount;
307 definition->fClone = fCloned;
308 if (MarkType::kConst == markType) {
309 // todo: require that fChar points to def on same line as markup
310 // additionally add definition to class children if it is not already there
311 if (definition->fParent != fRoot) {
312// fRoot->fChildren.push_back(definition);
313 }
314 }
Cary Clark82f1f742018-06-28 08:50:35 -0400315 SkASSERT(string::npos == name.find('\n'));
Cary Clark8032b982017-07-28 11:04:54 -0400316 definition->fName = name;
317 if (MarkType::kMethod == markType) {
318 if (string::npos != name.find(':', 0)) {
319 definition->setCanonicalFiddle();
320 } else {
321 definition->fFiddle = name;
322 }
323 } else {
Cary Clarka560c472017-11-27 10:44:06 -0500324 definition->fFiddle = Definition::NormalizedName(name);
Cary Clark8032b982017-07-28 11:04:54 -0400325 }
326 definition->fMarkType = markType;
Cary Clarkd0530ba2017-09-14 11:25:39 -0400327 definition->fAnonymous = fAnonymous;
Cary Clark8032b982017-07-28 11:04:54 -0400328 this->setAsParent(definition);
329 }
330 } break;
331 case MarkType::kTopic:
332 case MarkType::kSubtopic:
333 SkASSERT(1 == typeNameBuilder.size());
334 if (!hasEnd) {
335 if (!typeNameBuilder.size()) {
336 return this->reportError<bool>("unnamed topic");
337 }
Cary Clark1a8d7622018-03-05 13:26:16 -0500338 fTopics.emplace_front(markType, defStart, fLineCount, fParent, fMC);
Cary Clark8032b982017-07-28 11:04:54 -0400339 RootDefinition* rootDefinition = &fTopics.front();
340 definition = rootDefinition;
341 definition->fFileName = fFileName;
342 definition->fContentStart = fChar;
Cary Clark2a8c48b2018-02-15 17:31:24 -0500343 if (MarkType::kTopic == markType) {
344 if (fParent) {
345 return this->reportError<bool>("#Topic must be root");
346 }
347 // topic name is unappended
348 definition->fName = typeNameBuilder[0];
349 } else {
350 if (!fParent) {
351 return this->reportError<bool>("#Subtopic may not be root");
352 }
353 Definition* parent = fParent;
354 while (MarkType::kTopic != parent->fMarkType && MarkType::kSubtopic != parent->fMarkType) {
355 parent = parent->fParent;
356 if (!parent) {
357 // subtopic must have subtopic or topic in parent chain
358 return this->reportError<bool>("#Subtopic missing parent");
359 }
360 }
361 if (MarkType::kSubtopic == parent->fMarkType) {
362 // subtopic prepends parent subtopic name, but not parent topic name
363 definition->fName = parent->fName + '_';
364 }
365 definition->fName += typeNameBuilder[0];
366 definition->fFiddle = parent->fFiddle + '_';
Cary Clark8032b982017-07-28 11:04:54 -0400367 }
Cary Clark09d80c02018-10-31 12:14:03 -0400368 rootDefinition->fNames.fName = rootDefinition->fName;
Cary Clarka560c472017-11-27 10:44:06 -0500369 definition->fFiddle += Definition::NormalizedName(typeNameBuilder[0]);
Cary Clark8032b982017-07-28 11:04:54 -0400370 this->setAsParent(definition);
371 }
372 {
Cary Clark08895c42018-02-01 09:37:32 -0500373 SkASSERT(hasEnd ? fParent : definition);
374 string fullTopic = hasEnd ? fParent->fFiddle : definition->fFiddle;
Cary Clark8032b982017-07-28 11:04:54 -0400375 Definition* defPtr = fTopicMap[fullTopic];
376 if (hasEnd) {
Cary Clarkf895a422018-02-27 09:54:21 -0500377 if (HasTag::kYes == hasTag && !this->checkEndMarker(markType, fullTopic)) {
378 return false;
379 }
Cary Clark8032b982017-07-28 11:04:54 -0400380 if (!definition) {
381 definition = defPtr;
382 } else if (definition != defPtr) {
383 return this->reportError<bool>("mismatched topic");
384 }
385 } else {
386 if (nullptr != defPtr) {
387 return this->reportError<bool>("already declared topic");
388 }
389 fTopicMap[fullTopic] = definition;
390 }
391 }
392 if (hasEnd) {
393 if (!this->popParentStack(definition)) {
394 return false;
395 }
396 }
397 break;
Cary Clark2be81cf2018-09-13 12:04:30 -0400398 case MarkType::kFormula:
399 // hasEnd : single line / multiple line
400 if (!fParent || MarkType::kFormula != fParent->fMarkType) {
401 SkASSERT(!definition || MarkType::kFormula == definition->fMarkType);
402 fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC);
403 definition = &fMarkup.front();
404 definition->fContentStart = fChar;
405 definition->fName = typeNameBuilder[0];
406 definition->fFiddle = fParent->fFiddle;
407 fParent = definition;
408 } else {
409 SkASSERT(fParent && MarkType::kFormula == fParent->fMarkType);
410 SkASSERT(fMC == defStart[0]);
411 SkASSERT(fMC == defStart[-1]);
412 definition = fParent;
413 definition->fTerminator = fChar;
414 if (!this->popParentStack(definition)) {
415 return false;
416 }
417 this->parseHashFormula(definition);
418 fParent->fChildren.push_back(definition);
419 }
420 break;
Cary Clark8032b982017-07-28 11:04:54 -0400421 // these types are children of parents, but are not in named maps
Cary Clark8032b982017-07-28 11:04:54 -0400422 case MarkType::kDescription:
423 case MarkType::kStdOut:
424 // may be one-liner
Cary Clark137b8742018-05-30 09:21:49 -0400425 case MarkType::kAlias:
Cary Clark8032b982017-07-28 11:04:54 -0400426 case MarkType::kNoExample:
427 case MarkType::kParam:
Cary Clark1a8d7622018-03-05 13:26:16 -0500428 case MarkType::kPhraseDef:
Cary Clark8032b982017-07-28 11:04:54 -0400429 case MarkType::kReturn:
430 case MarkType::kToDo:
431 if (hasEnd) {
432 if (markType == fParent->fMarkType) {
433 definition = fParent;
434 if (MarkType::kBug == markType || MarkType::kReturn == markType
435 || MarkType::kToDo == markType) {
436 this->skipNoName();
437 }
438 if (!this->popParentStack(fParent)) { // if not one liner, pop
439 return false;
440 }
Cary Clark1a8d7622018-03-05 13:26:16 -0500441 if (MarkType::kParam == markType || MarkType::kReturn == markType
442 || MarkType::kPhraseDef == markType) {
Cary Clarka523d2d2017-08-30 08:58:10 -0400443 if (!this->checkParamReturn(definition)) {
444 return false;
Cary Clark579985c2017-07-31 11:48:27 -0400445 }
446 }
Cary Clark1a8d7622018-03-05 13:26:16 -0500447 if (MarkType::kPhraseDef == markType) {
448 string key = definition->fName;
449 if (fPhraseMap.end() != fPhraseMap.find(key)) {
450 this->reportError<bool>("duplicate phrase key");
451 }
452 fPhraseMap[key] = definition;
453 }
Cary Clark8032b982017-07-28 11:04:54 -0400454 } else {
Cary Clark1a8d7622018-03-05 13:26:16 -0500455 fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC);
Cary Clark8032b982017-07-28 11:04:54 -0400456 definition = &fMarkup.front();
457 definition->fName = typeNameBuilder[0];
Cary Clark73fa9722017-08-29 17:36:51 -0400458 definition->fFiddle = fParent->fFiddle;
Cary Clark8032b982017-07-28 11:04:54 -0400459 definition->fContentStart = fChar;
Cary Clark1a8d7622018-03-05 13:26:16 -0500460 string endBracket;
461 endBracket += fMC;
462 endBracket += fMC;
463 definition->fContentEnd = this->trimmedBracketEnd(endBracket);
464 this->skipToEndBracket(endBracket.c_str());
Cary Clark8032b982017-07-28 11:04:54 -0400465 SkAssertResult(fMC == this->next());
466 SkAssertResult(fMC == this->next());
467 definition->fTerminator = fChar;
Cary Clark1a8d7622018-03-05 13:26:16 -0500468 TextParser checkForChildren(definition);
469 if (checkForChildren.strnchr(fMC, definition->fContentEnd)) {
470 this->reportError<bool>("put ## on separate line");
471 }
Cary Clark8032b982017-07-28 11:04:54 -0400472 fParent->fChildren.push_back(definition);
473 }
Cary Clark137b8742018-05-30 09:21:49 -0400474 if (MarkType::kAlias == markType) {
475 const char* end = definition->fChildren.size() > 0 ?
476 definition->fChildren[0]->fStart : definition->fContentEnd;
477 TextParser parser(definition->fFileName, definition->fContentStart, end,
478 definition->fLineCount);
479 parser.trimEnd();
480 string key = string(parser.fStart, parser.lineLength());
481 if (fAliasMap.end() != fAliasMap.find(key)) {
482 return this->reportError<bool>("duplicate alias");
483 }
484 fAliasMap[key] = definition;
485 definition->fFiddle = definition->fParent->fFiddle;
486 }
Cary Clark8032b982017-07-28 11:04:54 -0400487 break;
Cary Clark682c58d2018-05-16 07:07:07 -0400488 } else if (MarkType::kPhraseDef == markType) {
489 bool hasParams = '(' == this->next();
490 fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC);
491 definition = &fMarkup.front();
492 definition->fName = typeNameBuilder[0];
493 definition->fFiddle = fParent->fFiddle;
494 definition->fContentStart = fChar;
495 if (hasParams) {
496 char lastChar;
497 do {
498 const char* subEnd = this->anyOf(",)\n");
499 if (!subEnd || '\n' == *subEnd) {
500 return this->reportError<bool>("unexpected phrase list end");
501 }
502 fMarkup.emplace_front(MarkType::kPhraseParam, fChar, fLineCount, fParent,
503 fMC);
504 Definition* phraseParam = &fMarkup.front();
505 phraseParam->fContentStart = fChar;
506 phraseParam->fContentEnd = subEnd;
507 phraseParam->fName = string(fChar, subEnd - fChar);
508 definition->fChildren.push_back(phraseParam);
509 this->skipTo(subEnd);
510 lastChar = this->next();
511 phraseParam->fTerminator = fChar;
512 } while (')' != lastChar);
513 this->skipWhiteSpace();
514 definition->fContentStart = fChar;
515 }
516 this->setAsParent(definition);
517 break;
Cary Clark8032b982017-07-28 11:04:54 -0400518 }
519 // not one-liners
520 case MarkType::kCode:
Cary Clark8032b982017-07-28 11:04:54 -0400521 case MarkType::kExample:
Cary Clark0d225392018-06-07 09:59:07 -0400522 case MarkType::kFile:
Cary Clark8032b982017-07-28 11:04:54 -0400523 case MarkType::kFunction:
524 case MarkType::kLegend:
525 case MarkType::kList:
526 case MarkType::kPrivate:
527 case MarkType::kTable:
Cary Clark8032b982017-07-28 11:04:54 -0400528 if (hasEnd) {
529 definition = fParent;
530 if (markType != fParent->fMarkType) {
531 return this->reportError<bool>("end element mismatch");
532 } else if (!this->popParentStack(fParent)) {
533 return false;
534 }
535 if (MarkType::kExample == markType) {
536 if (definition->fChildren.size() == 0) {
537 TextParser emptyCheck(definition);
538 if (emptyCheck.eof() || !emptyCheck.skipWhiteSpace()) {
Cary Clark884dd7d2017-10-11 10:37:52 -0400539 return this->reportError<bool>("missing example body");
Cary Clark8032b982017-07-28 11:04:54 -0400540 }
541 }
Cary Clark1a8d7622018-03-05 13:26:16 -0500542// can't do this here; phrase refs may not have been defined yet
543// this->setWrapper(definition);
Cary Clark8032b982017-07-28 11:04:54 -0400544 }
545 } else {
Cary Clark1a8d7622018-03-05 13:26:16 -0500546 fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC);
Cary Clark8032b982017-07-28 11:04:54 -0400547 definition = &fMarkup.front();
548 definition->fContentStart = fChar;
549 definition->fName = typeNameBuilder[0];
550 definition->fFiddle = fParent->fFiddle;
551 char suffix = '\0';
552 bool tryAgain;
553 do {
554 tryAgain = false;
555 for (const auto& child : fParent->fChildren) {
556 if (child->fFiddle == definition->fFiddle) {
557 if (MarkType::kExample != child->fMarkType) {
558 continue;
559 }
560 if ('\0' == suffix) {
561 suffix = 'a';
562 } else if (++suffix > 'z') {
563 return reportError<bool>("too many examples");
564 }
565 definition->fFiddle = fParent->fFiddle + '_';
566 definition->fFiddle += suffix;
567 tryAgain = true;
568 break;
569 }
570 }
571 } while (tryAgain);
572 this->setAsParent(definition);
573 }
574 break;
575 // always treated as one-liners (can't detect misuse easily)
Ben Wagner63fd7602017-10-09 15:45:33 -0400576 case MarkType::kAnchor:
Cary Clark4855f782018-02-06 09:41:53 -0500577 case MarkType::kBug:
Cary Clark4855f782018-02-06 09:41:53 -0500578 case MarkType::kDeprecated:
Cary Clark682c58d2018-05-16 07:07:07 -0400579 case MarkType::kDetails:
Cary Clarkac47b882018-01-11 10:35:44 -0500580 case MarkType::kDuration:
Cary Clark682c58d2018-05-16 07:07:07 -0400581 case MarkType::kExperimental:
Cary Clarka90ea222018-10-16 10:30:28 -0400582 case MarkType::kFilter:
Cary Clark8032b982017-07-28 11:04:54 -0400583 case MarkType::kHeight:
Cary Clarkf895a422018-02-27 09:54:21 -0500584 case MarkType::kIllustration:
Cary Clark8032b982017-07-28 11:04:54 -0400585 case MarkType::kImage:
Cary Clarkab2621d2018-01-30 10:08:57 -0500586 case MarkType::kIn:
587 case MarkType::kLine:
588 case MarkType::kLiteral:
Cary Clark682c58d2018-05-16 07:07:07 -0400589 case MarkType::kNoJustify:
Cary Clark154beea2017-10-26 07:58:48 -0400590 case MarkType::kOutdent:
Cary Clark8032b982017-07-28 11:04:54 -0400591 case MarkType::kPlatform:
Cary Clark08895c42018-02-01 09:37:32 -0500592 case MarkType::kPopulate:
Cary Clark8032b982017-07-28 11:04:54 -0400593 case MarkType::kSeeAlso:
Cary Clark61dfc3a2018-01-03 08:37:53 -0500594 case MarkType::kSet:
Cary Clark8032b982017-07-28 11:04:54 -0400595 case MarkType::kSubstitute:
Cary Clark8032b982017-07-28 11:04:54 -0400596 case MarkType::kVolatile:
597 case MarkType::kWidth:
Cary Clark4855f782018-02-06 09:41:53 -0500598 // todo : add check disallowing children?
Cary Clarkab2621d2018-01-30 10:08:57 -0500599 if (hasEnd && MarkType::kAnchor != markType && MarkType::kLine != markType) {
Cary Clark8032b982017-07-28 11:04:54 -0400600 return this->reportError<bool>("one liners omit end element");
Cary Clark6fc50412017-09-21 12:31:06 -0400601 } else if (!hasEnd && MarkType::kAnchor == markType) {
602 return this->reportError<bool>("anchor line must have end element last");
Cary Clark8032b982017-07-28 11:04:54 -0400603 }
Cary Clark1a8d7622018-03-05 13:26:16 -0500604 fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC);
Cary Clark8032b982017-07-28 11:04:54 -0400605 definition = &fMarkup.front();
606 definition->fName = typeNameBuilder[0];
Cary Clarka560c472017-11-27 10:44:06 -0500607 definition->fFiddle = Definition::NormalizedName(typeNameBuilder[0]);
Cary Clark8032b982017-07-28 11:04:54 -0400608 definition->fContentStart = fChar;
Cary Clarkce101242017-09-01 15:51:02 -0400609 definition->fContentEnd = this->trimmedBracketEnd('\n');
Cary Clark8032b982017-07-28 11:04:54 -0400610 definition->fTerminator = this->lineEnd() - 1;
611 fParent->fChildren.push_back(definition);
612 if (MarkType::kAnchor == markType) {
Cary Clark2be81cf2018-09-13 12:04:30 -0400613 this->parseHashAnchor(definition);
Cary Clark137b8742018-05-30 09:21:49 -0400614 } else if (MarkType::kLine == markType) {
Cary Clark2be81cf2018-09-13 12:04:30 -0400615 this->parseHashLine(definition);
Cary Clark682c58d2018-05-16 07:07:07 -0400616 } else if (IncompleteAllowed(markType)) {
Cary Clark4855f782018-02-06 09:41:53 -0500617 this->skipSpace();
618 fParent->fDeprecated = true;
Cary Clark682c58d2018-05-16 07:07:07 -0400619 fParent->fDetails =
620 this->skipExact("soon") ? Definition::Details::kSoonToBe_Deprecated :
621 this->skipExact("testing") ? Definition::Details::kTestingOnly_Experiment :
Cary Clark137b8742018-05-30 09:21:49 -0400622 this->skipExact("do not use") ? Definition::Details::kDoNotUse_Experiment :
Cary Clark682c58d2018-05-16 07:07:07 -0400623 this->skipExact("not ready") ? Definition::Details::kNotReady_Experiment :
624 Definition::Details::kNone;
Cary Clark4855f782018-02-06 09:41:53 -0500625 this->skipSpace();
626 if ('\n' != this->peek()) {
627 return this->reportError<bool>("unexpected text after #Deprecated");
628 }
629 }
Cary Clark8032b982017-07-28 11:04:54 -0400630 break;
631 case MarkType::kExternal:
632 (void) this->collectExternals(); // FIXME: detect errors in external defs?
633 break;
634 default:
635 SkASSERT(0); // fixme : don't let any types be invisible
636 return true;
637 }
638 if (fParent) {
639 SkASSERT(definition);
640 SkASSERT(definition->fName.length() > 0);
641 }
642 return true;
643}
644
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400645void BmhParser::reportDuplicates(const Definition& def, string dup) const {
Cary Clark73fa9722017-08-29 17:36:51 -0400646 if (MarkType::kExample == def.fMarkType && dup == def.fFiddle) {
647 TextParser reporter(&def);
648 reporter.reportError("duplicate example name");
649 }
650 for (auto& child : def.fChildren ) {
651 reportDuplicates(*child, dup);
652 }
653}
654
Cary Clarkf895a422018-02-27 09:54:21 -0500655
656static Definition* find_fiddle(Definition* def, string name) {
657 if (MarkType::kExample == def->fMarkType && name == def->fFiddle) {
658 return def;
659 }
660 for (auto& child : def->fChildren) {
661 Definition* result = find_fiddle(child, name);
662 if (result) {
663 return result;
664 }
665 }
666 return nullptr;
667}
668
669Definition* BmhParser::findExample(string name) const {
670 for (const auto& topic : fTopicMap) {
671 if (topic.second->fParent) {
672 continue;
673 }
674 Definition* def = find_fiddle(topic.second, name);
675 if (def) {
676 return def;
677 }
678 }
679 return nullptr;
680}
681
Cary Clarkd7895502018-07-18 15:10:08 -0400682static bool check_example_hashes(Definition* def) {
683 if (MarkType::kExample == def->fMarkType) {
684 if (def->fHash.length()) {
685 return true;
686 }
687 for (auto child : def->fChildren) {
688 if (MarkType::kPlatform == child->fMarkType) {
689 if (string::npos != string(child->fContentStart, child->length()).find("!fiddle")) {
690 return true;
691 }
692 }
693 }
694 return def->reportError<bool>("missing hash");
695 }
696 for (auto& child : def->fChildren) {
697 if (!check_example_hashes(child)) {
698 return false;
699 }
700 }
701 return true;
702}
703
704bool BmhParser::checkExampleHashes() const {
705 for (const auto& topic : fTopicMap) {
706 if (!topic.second->fParent && !check_example_hashes(topic.second)) {
707 return false;
708 }
709 }
710 return true;
711}
712
713static void reset_example_hashes(Definition* def) {
714 if (MarkType::kExample == def->fMarkType) {
715 def->fHash.clear();
716 return;
717 }
718 for (auto& child : def->fChildren) {
719 reset_example_hashes(child);
720 }
721}
722
723void BmhParser::resetExampleHashes() {
724 for (const auto& topic : fTopicMap) {
725 if (!topic.second->fParent) {
726 reset_example_hashes(topic.second);
727 }
728 }
729}
730
Cary Clark73fa9722017-08-29 17:36:51 -0400731static void find_examples(const Definition& def, vector<string>* exampleNames) {
732 if (MarkType::kExample == def.fMarkType) {
733 exampleNames->push_back(def.fFiddle);
734 }
735 for (auto& child : def.fChildren ) {
736 find_examples(*child, exampleNames);
737 }
738}
739
Cary Clarkf895a422018-02-27 09:54:21 -0500740bool BmhParser::checkEndMarker(MarkType markType, string match) const {
741 TextParser tp(fFileName, fLine, fChar, fLineCount);
742 tp.skipSpace();
743 if (fMC != tp.next()) {
744 return this->reportError<bool>("mismatched end marker expect #");
745 }
746 const char* nameStart = tp.fChar;
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400747 tp.skipToNonName();
Cary Clarkf895a422018-02-27 09:54:21 -0500748 string markName(nameStart, tp.fChar - nameStart);
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400749 if (kMarkProps[(int) markType].fName != markName) {
Cary Clarkf895a422018-02-27 09:54:21 -0500750 return this->reportError<bool>("expected #XXX ## to match");
751 }
752 tp.skipSpace();
753 nameStart = tp.fChar;
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400754 tp.skipToNonName();
Cary Clarkf895a422018-02-27 09:54:21 -0500755 markName = string(nameStart, tp.fChar - nameStart);
756 if ("" == markName) {
757 if (fMC != tp.next() || fMC != tp.next()) {
758 return this->reportError<bool>("expected ##");
759 }
760 return true;
761 }
762 std::replace(markName.begin(), markName.end(), '-', '_');
763 auto defPos = match.rfind(markName);
764 if (string::npos == defPos) {
765 return this->reportError<bool>("mismatched end marker v1");
766 }
767 if (markName.size() != match.size() - defPos) {
768 return this->reportError<bool>("mismatched end marker v2");
769 }
770 return true;
771}
772
Cary Clark73fa9722017-08-29 17:36:51 -0400773bool BmhParser::checkExamples() const {
774 vector<string> exampleNames;
775 for (const auto& topic : fTopicMap) {
776 if (topic.second->fParent) {
777 continue;
778 }
779 find_examples(*topic.second, &exampleNames);
780 }
781 std::sort(exampleNames.begin(), exampleNames.end());
782 string* last = nullptr;
783 string reported;
784 bool checkOK = true;
785 for (auto& nameIter : exampleNames) {
786 if (last && *last == nameIter && reported != *last) {
787 reported = *last;
788 SkDebugf("%s\n", reported.c_str());
789 for (const auto& topic : fTopicMap) {
790 if (topic.second->fParent) {
791 continue;
792 }
793 this->reportDuplicates(*topic.second, reported);
794 }
795 checkOK = false;
796 }
797 last = &nameIter;
798 }
799 return checkOK;
800}
801
Cary Clarka523d2d2017-08-30 08:58:10 -0400802bool BmhParser::checkParamReturn(const Definition* definition) const {
803 const char* parmEndCheck = definition->fContentEnd;
804 while (parmEndCheck < definition->fTerminator) {
805 if (fMC == parmEndCheck[0]) {
806 break;
807 }
808 if (' ' < parmEndCheck[0]) {
809 this->reportError<bool>(
810 "use full end marker on multiline #Param and #Return");
811 }
812 ++parmEndCheck;
813 }
814 return true;
815}
816
Cary Clark8032b982017-07-28 11:04:54 -0400817bool BmhParser::childOf(MarkType markType) const {
818 auto childError = [this](MarkType markType) -> bool {
819 string errStr = "expected ";
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400820 errStr += kMarkProps[(int) markType].fName;
Cary Clark8032b982017-07-28 11:04:54 -0400821 errStr += " parent";
822 return this->reportError<bool>(errStr.c_str());
823 };
824
825 if (markType == fParent->fMarkType) {
826 return true;
827 }
828 if (this->hasEndToken()) {
829 if (!fParent->fParent) {
830 return this->reportError<bool>("expected grandparent");
831 }
832 if (markType == fParent->fParent->fMarkType) {
833 return true;
834 }
835 }
836 return childError(markType);
837}
838
839string BmhParser::className(MarkType markType) {
Cary Clark8032b982017-07-28 11:04:54 -0400840 const char* end = this->lineEnd();
841 const char* mc = this->strnchr(fMC, end);
Cary Clark73fa9722017-08-29 17:36:51 -0400842 string classID;
Cary Clark186d08f2018-04-03 08:43:27 -0400843 TextParserSave savePlace(this);
Cary Clark73fa9722017-08-29 17:36:51 -0400844 this->skipSpace();
845 const char* wordStart = fChar;
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400846 this->skipToNonName();
Cary Clark73fa9722017-08-29 17:36:51 -0400847 const char* wordEnd = fChar;
848 classID = string(wordStart, wordEnd - wordStart);
849 if (!mc) {
850 savePlace.restore();
851 }
852 string builder;
853 const Definition* parent = this->parentSpace();
854 if (parent && parent->fName != classID) {
855 builder += parent->fName;
856 }
Cary Clark8032b982017-07-28 11:04:54 -0400857 if (mc) {
Cary Clark8032b982017-07-28 11:04:54 -0400858 if (mc + 1 < fEnd && fMC == mc[1]) { // if ##
859 if (markType != fParent->fMarkType) {
860 return this->reportError<string>("unbalanced method");
861 }
Cary Clark73fa9722017-08-29 17:36:51 -0400862 if (builder.length() > 0 && classID.size() > 0) {
Cary Clark8032b982017-07-28 11:04:54 -0400863 if (builder != fParent->fName) {
864 builder += "::";
Cary Clark73fa9722017-08-29 17:36:51 -0400865 builder += classID;
Cary Clark8032b982017-07-28 11:04:54 -0400866 if (builder != fParent->fName) {
867 return this->reportError<string>("name mismatch");
868 }
869 }
870 }
871 this->skipLine();
872 return fParent->fName;
873 }
874 fChar = mc;
875 this->next();
876 }
877 this->skipWhiteSpace();
878 if (MarkType::kEnum == markType && fChar >= end) {
879 fAnonymous = true;
880 builder += "::_anonymous";
881 return uniqueRootName(builder, markType);
882 }
883 builder = this->word(builder, "::");
884 return builder;
885}
886
887bool BmhParser::collectExternals() {
888 do {
889 this->skipWhiteSpace();
890 if (this->eof()) {
891 break;
892 }
893 if (fMC == this->peek()) {
894 this->next();
895 if (this->eof()) {
896 break;
897 }
898 if (fMC == this->peek()) {
899 this->skipLine();
900 break;
901 }
902 if (' ' >= this->peek()) {
903 this->skipLine();
904 continue;
905 }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400906 if (this->startsWith(kMarkProps[(int) MarkType::kExternal].fName)) {
907 this->skipToNonName();
Cary Clark8032b982017-07-28 11:04:54 -0400908 continue;
909 }
910 }
911 this->skipToAlpha();
912 const char* wordStart = fChar;
Cary Clark09d80c02018-10-31 12:14:03 -0400913 this->skipToWhiteSpace();
Cary Clark8032b982017-07-28 11:04:54 -0400914 if (fChar - wordStart > 0) {
Cary Clark1a8d7622018-03-05 13:26:16 -0500915 fExternals.emplace_front(MarkType::kExternal, wordStart, fChar, fLineCount, fParent,
916 fMC);
Cary Clark8032b982017-07-28 11:04:54 -0400917 RootDefinition* definition = &fExternals.front();
918 definition->fFileName = fFileName;
919 definition->fName = string(wordStart ,fChar - wordStart);
Cary Clarka560c472017-11-27 10:44:06 -0500920 definition->fFiddle = Definition::NormalizedName(definition->fName);
Cary Clark8032b982017-07-28 11:04:54 -0400921 }
922 } while (!this->eof());
923 return true;
924}
925
Cary Clark1a8d7622018-03-05 13:26:16 -0500926bool BmhParser::dumpExamples(FILE* fiddleOut, Definition& def, bool* continuation) const {
Cary Clark73fa9722017-08-29 17:36:51 -0400927 if (MarkType::kExample == def.fMarkType) {
928 string result;
Cary Clark1a8d7622018-03-05 13:26:16 -0500929 if (!this->exampleToScript(&def, BmhParser::ExampleOptions::kAll, &result)) {
Cary Clark73fa9722017-08-29 17:36:51 -0400930 return false;
931 }
932 if (result.length() > 0) {
Cary Clarkbef063a2017-10-31 15:44:45 -0400933 result += "\n";
934 result += "}";
Cary Clark73fa9722017-08-29 17:36:51 -0400935 if (*continuation) {
936 fprintf(fiddleOut, ",\n");
937 } else {
938 *continuation = true;
939 }
940 fprintf(fiddleOut, "%s", result.c_str());
941 }
942 return true;
943 }
944 for (auto& child : def.fChildren ) {
Cary Clark1a8d7622018-03-05 13:26:16 -0500945 if (!this->dumpExamples(fiddleOut, *child, continuation)) {
Cary Clark73fa9722017-08-29 17:36:51 -0400946 return false;
947 }
948 }
949 return true;
950}
951
952bool BmhParser::dumpExamples(const char* fiddleJsonFileName) const {
Cary Clark5b1f9532018-08-28 14:53:37 -0400953 string oldFiddle(fiddleJsonFileName);
954 string newFiddle(fiddleJsonFileName);
955 newFiddle += "_new";
956 FILE* fiddleOut = fopen(newFiddle.c_str(), "wb");
Cary Clark73fa9722017-08-29 17:36:51 -0400957 if (!fiddleOut) {
Cary Clark5b1f9532018-08-28 14:53:37 -0400958 SkDebugf("could not open output file %s\n", newFiddle.c_str());
Cary Clark73fa9722017-08-29 17:36:51 -0400959 return false;
960 }
961 fprintf(fiddleOut, "{\n");
962 bool continuation = false;
963 for (const auto& topic : fTopicMap) {
964 if (topic.second->fParent) {
965 continue;
966 }
Cary Clark1a8d7622018-03-05 13:26:16 -0500967 this->dumpExamples(fiddleOut, *topic.second, &continuation);
Cary Clark73fa9722017-08-29 17:36:51 -0400968 }
969 fprintf(fiddleOut, "\n}\n");
970 fclose(fiddleOut);
Cary Clark5b1f9532018-08-28 14:53:37 -0400971 if (ParserCommon::WrittenFileDiffers(oldFiddle, newFiddle)) {
972 ParserCommon::CopyToFile(oldFiddle, newFiddle);
973 SkDebugf("wrote %s\n", fiddleJsonFileName);
Cary Clark6a1185a2018-09-04 16:15:11 -0400974 } else {
975 remove(newFiddle.c_str());
Cary Clark5b1f9532018-08-28 14:53:37 -0400976 }
Cary Clark73fa9722017-08-29 17:36:51 -0400977 return true;
978}
979
Cary Clark8032b982017-07-28 11:04:54 -0400980int BmhParser::endHashCount() const {
981 const char* end = fLine + this->lineLength();
982 int count = 0;
983 while (fLine < end && fMC == *--end) {
984 count++;
985 }
986 return count;
987}
988
Cary Clarkce101242017-09-01 15:51:02 -0400989bool BmhParser::endTableColumn(const char* end, const char* terminator) {
990 if (!this->popParentStack(fParent)) {
991 return false;
992 }
993 fWorkingColumn->fContentEnd = end;
994 fWorkingColumn->fTerminator = terminator;
995 fColStart = fChar - 1;
996 this->skipSpace();
997 fTableState = TableState::kColumnStart;
998 return true;
999}
1000
Cary Clark2d4bf5f2018-04-16 08:37:38 -04001001static size_t count_indent(string text, size_t test, size_t end) {
Cary Clark1a8d7622018-03-05 13:26:16 -05001002 size_t result = test;
1003 while (test < end) {
1004 if (' ' != text[test]) {
1005 break;
1006 }
1007 ++test;
1008 }
1009 return test - result;
1010}
1011
Cary Clark2d4bf5f2018-04-16 08:37:38 -04001012static void add_code(string text, int pos, int end,
Cary Clark1a8d7622018-03-05 13:26:16 -05001013 size_t outIndent, size_t textIndent, string& example) {
1014 do {
1015 // fix this to move whole paragraph in, out, but preserve doc indent
1016 int nextIndent = count_indent(text, pos, end);
1017 size_t len = text.find('\n', pos);
1018 if (string::npos == len) {
1019 len = end;
1020 }
1021 if ((size_t) (pos + nextIndent) < len) {
1022 size_t indent = outIndent + nextIndent;
1023 SkASSERT(indent >= textIndent);
1024 indent -= textIndent;
1025 for (size_t index = 0; index < indent; ++index) {
1026 example += ' ';
1027 }
1028 pos += nextIndent;
1029 while ((size_t) pos < len) {
1030 example += '"' == text[pos] ? "\\\"" :
1031 '\\' == text[pos] ? "\\\\" :
1032 text.substr(pos, 1);
1033 ++pos;
1034 }
1035 example += "\\n";
1036 } else {
1037 pos += nextIndent;
1038 }
1039 if ('\n' == text[pos]) {
1040 ++pos;
1041 }
1042 } while (pos < end);
1043}
1044
Cary Clark61313f32018-10-08 14:57:48 -04001045bool BmhParser::IsExemplary(const Definition* def) {
1046 return kMarkProps[(int) def->fMarkType].fExemplary != Exemplary::kNo;
1047}
1048
Cary Clark1a8d7622018-03-05 13:26:16 -05001049bool BmhParser::exampleToScript(Definition* def, ExampleOptions exampleOptions,
1050 string* result) const {
1051 bool hasFiddle = true;
1052 const Definition* platform = def->hasChild(MarkType::kPlatform);
1053 if (platform) {
1054 TextParser platParse(platform);
1055 hasFiddle = !platParse.strnstr("!fiddle", platParse.fEnd);
1056 }
1057 if (!hasFiddle) {
1058 *result = "";
1059 return true;
1060 }
1061 string text = this->extractText(def, TrimExtract::kNo);
1062 bool textOut = string::npos != text.find("SkDebugf(")
1063 || string::npos != text.find("dump(")
1064 || string::npos != text.find("dumpHex(");
1065 string heightStr = "256";
1066 string widthStr = "256";
1067 string normalizedName(def->fFiddle);
1068 string code;
1069 string imageStr = "0";
1070 string srgbStr = "false";
1071 string durationStr = "0";
1072 for (auto iter : def->fChildren) {
1073 switch (iter->fMarkType) {
1074 case MarkType::kDuration:
1075 durationStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
1076 break;
1077 case MarkType::kHeight:
1078 heightStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
1079 break;
1080 case MarkType::kWidth:
1081 widthStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
1082 break;
1083 case MarkType::kDescription:
1084 // ignore for now
1085 break;
1086 case MarkType::kFunction: {
1087 // emit this, but don't wrap this in draw()
1088 string funcText = this->extractText(&*iter, TrimExtract::kNo);
1089 size_t pos = 0;
1090 while (pos < funcText.length() && ' ' > funcText[pos]) {
1091 ++pos;
1092 }
1093 size_t indent = count_indent(funcText, pos, funcText.length());
1094 add_code(funcText, pos, funcText.length(), 0, indent, code);
1095 code += "\\n";
1096 } break;
1097 case MarkType::kComment:
1098 break;
1099 case MarkType::kImage:
1100 imageStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
1101 break;
1102 case MarkType::kToDo:
1103 break;
1104 case MarkType::kBug:
1105 case MarkType::kMarkChar:
1106 case MarkType::kPlatform:
1107 case MarkType::kPhraseRef:
1108 // ignore for now
1109 break;
1110 case MarkType::kSet:
1111 if ("sRGB" == string(iter->fContentStart,
1112 iter->fContentEnd - iter->fContentStart)) {
1113 srgbStr = "true";
1114 } else {
1115 SkASSERT(0); // more work to do
1116 return false;
1117 }
1118 break;
1119 case MarkType::kStdOut:
1120 textOut = true;
1121 break;
1122 default:
1123 SkASSERT(0); // more coding to do
1124 }
1125 }
1126 string animatedStr = "0" != durationStr ? "true" : "false";
1127 string textOutStr = textOut ? "true" : "false";
1128 size_t pos = 0;
1129 while (pos < text.length() && ' ' > text[pos]) {
1130 ++pos;
1131 }
1132 size_t end = text.length();
1133 size_t outIndent = 0;
1134 size_t textIndent = count_indent(text, pos, end);
1135 if ("" == def->fWrapper) {
1136 this->setWrapper(def);
1137 }
1138 if (def->fWrapper.length() > 0) {
1139 code += def->fWrapper;
1140 code += "\\n";
1141 outIndent = 4;
1142 }
1143 add_code(text, pos, end, outIndent, textIndent, code);
1144 if (def->fWrapper.length() > 0) {
1145 code += "}";
1146 }
1147 string example = "\"" + normalizedName + "\": {\n";
Cary Clark2d4bf5f2018-04-16 08:37:38 -04001148 string filename = def->fileName();
1149 string baseFile = filename.substr(0, filename.length() - 4);
Cary Clark1a8d7622018-03-05 13:26:16 -05001150 if (ExampleOptions::kText == exampleOptions) {
1151 example += " \"code\": \"" + code + "\",\n";
1152 example += " \"hash\": \"" + def->fHash + "\",\n";
1153 example += " \"file\": \"" + baseFile + "\",\n";
1154 example += " \"name\": \"" + def->fName + "\",";
1155 } else {
1156 example += " \"code\": \"" + code + "\",\n";
1157 if (ExampleOptions::kPng == exampleOptions) {
1158 example += " \"width\": " + widthStr + ",\n";
1159 example += " \"height\": " + heightStr + ",\n";
1160 example += " \"hash\": \"" + def->fHash + "\",\n";
1161 example += " \"file\": \"" + baseFile + "\",\n";
1162 example += " \"name\": \"" + def->fName + "\"\n";
1163 example += "}";
1164 } else {
1165 example += " \"options\": {\n";
1166 example += " \"width\": " + widthStr + ",\n";
1167 example += " \"height\": " + heightStr + ",\n";
1168 example += " \"source\": " + imageStr + ",\n";
1169 example += " \"srgb\": " + srgbStr + ",\n";
1170 example += " \"f16\": false,\n";
1171 example += " \"textOnly\": " + textOutStr + ",\n";
1172 example += " \"animated\": " + animatedStr + ",\n";
1173 example += " \"duration\": " + durationStr + "\n";
1174 example += " },\n";
1175 example += " \"fast\": true";
1176 }
1177 }
1178 *result = example;
1179 return true;
1180}
1181
1182string BmhParser::extractText(const Definition* def, TrimExtract trimExtract) const {
1183 string result;
1184 TextParser parser(def);
1185 auto childIter = def->fChildren.begin();
1186 while (!parser.eof()) {
1187 const char* end = def->fChildren.end() == childIter ? parser.fEnd : (*childIter)->fStart;
1188 string fragment(parser.fChar, end - parser.fChar);
1189 trim_end(fragment);
1190 if (TrimExtract::kYes == trimExtract) {
1191 trim_start(fragment);
1192 if (result.length()) {
1193 result += '\n';
1194 result += '\n';
1195 }
1196 }
1197 if (TrimExtract::kYes == trimExtract || has_nonwhitespace(fragment)) {
1198 result += fragment;
1199 }
1200 parser.skipTo(end);
1201 if (def->fChildren.end() != childIter) {
1202 Definition* child = *childIter;
1203 if (MarkType::kPhraseRef == child->fMarkType) {
1204 auto phraseIter = fPhraseMap.find(child->fName);
1205 if (fPhraseMap.end() == phraseIter) {
1206 return def->reportError<string>("missing phrase definition");
1207 }
1208 Definition* phrase = phraseIter->second;
1209 // count indent of last line in result
1210 size_t lastLF = result.rfind('\n');
1211 size_t startPos = string::npos == lastLF ? 0 : lastLF;
1212 size_t lastLen = result.length() - startPos;
1213 size_t indent = count_indent(result, startPos, result.length()) + 4;
1214 string phraseStr = this->extractText(phrase, TrimExtract::kNo);
1215 startPos = 0;
1216 bool firstTime = true;
1217 size_t endPos;
1218 do {
1219 endPos = phraseStr.find('\n', startPos);
1220 size_t len = (string::npos != endPos ? endPos : phraseStr.length()) - startPos;
1221 if (firstTime && lastLen + len + 1 < 100) { // FIXME: make 100 global const or something
1222 result += ' ';
1223 } else {
1224 result += '\n';
1225 result += string(indent, ' ');
1226 }
1227 firstTime = false;
1228 string tmp = phraseStr.substr(startPos, len);
1229 result += tmp;
1230 startPos = endPos + 1;
1231 } while (string::npos != endPos);
1232 result += '\n';
1233 }
1234 parser.skipTo(child->fTerminator);
1235 std::advance(childIter, 1);
1236 }
1237 }
1238 return result;
1239}
1240
Cary Clark09d80c02018-10-31 12:14:03 -04001241string BmhParser::loweredTopic(string name, Definition* def) {
1242 string lowered;
1243 SkASSERT('_' != name[0]);
1244 char last = '_';
1245 for (char c : name) {
1246 SkASSERT(' ' != c);
1247 if (isupper(last)) {
1248 lowered += islower(c) ? tolower(last) : last;
1249 last = '\0';
1250 }
1251 if ('_' == c) {
1252 last = c;
1253 c = ' ';
1254 } else if ('_' == last && isupper(c)) {
1255 last = c;
1256 continue;
1257 }
1258 lowered += c;
1259 if (' ' == c) {
1260 this->setUpPartialSubstitute(lowered);
1261 }
1262 }
1263 if (isupper(last)) {
1264 lowered += tolower(last);
1265 }
1266 return lowered;
1267}
1268
1269void BmhParser::setUpGlobalSubstitutes() {
1270 for (auto& entry : fExternals) {
1271 string externalName = entry.fName;
1272 SkASSERT(fGlobalNames.fRefMap.end() == fGlobalNames.fRefMap.find(externalName));
1273 fGlobalNames.fRefMap[externalName] = nullptr;
1274 }
1275 for (auto bMap : { &fClassMap, &fConstMap, &fDefineMap, &fEnumMap, &fMethodMap,
1276 &fTypedefMap } ) {
1277 for (auto& entry : *bMap) {
1278 Definition* parent = (Definition*) &entry.second;
1279 string name = parent->fName;
1280 SkASSERT(fGlobalNames.fLinkMap.end() == fGlobalNames.fLinkMap.find(name));
1281 string ref = ParserCommon::HtmlFileName(parent->fFileName) + '#' + parent->fFiddle;
1282 fGlobalNames.fLinkMap[name] = ref;
1283 SkASSERT(fGlobalNames.fRefMap.end() == fGlobalNames.fRefMap.find(name));
1284 fGlobalNames.fRefMap[name] = const_cast<Definition*>(parent);
1285 NameMap* names = MarkType::kClass == parent->fMarkType
1286 || MarkType::kStruct == parent->fMarkType
1287 || MarkType::kEnumClass == parent->fMarkType ? &parent->asRoot()->fNames :
1288 &fGlobalNames;
1289 this->setUpSubstitutes(parent, names);
1290 if (names != &fGlobalNames) {
1291 names->copyToParent(&fGlobalNames);
1292 }
1293 }
1294 }
1295 for (auto& topic : fTopicMap) {
1296 bool hasSubstitute = false;
1297 for (auto& child : topic.second->fChildren) {
1298 bool isAlias = MarkType::kAlias == child->fMarkType;
1299 bool isSubstitute = MarkType::kSubstitute == child->fMarkType;
1300 if (!isAlias && !isSubstitute) {
1301 continue;
1302 }
1303 hasSubstitute |= isSubstitute;
1304 string name(child->fContentStart, child->length());
1305 if (isAlias) {
1306 name = ParserCommon::ConvertRef(name, false);
1307 for (auto aliasChild : child->fChildren) {
1308 if (MarkType::kSubstitute == aliasChild->fMarkType) {
1309 string sub(aliasChild->fContentStart, aliasChild->length());
1310 this->setUpSubstitute(sub, topic.second);
1311 }
1312 }
1313 }
1314 this->setUpSubstitute(name, topic.second);
1315 }
1316 if (hasSubstitute) {
1317 continue;
1318 }
1319 string lowered = this->loweredTopic(topic.first, topic.second);
1320 SkDEBUGCODE(auto globalIter = fGlobalNames.fLinkMap.find(lowered));
1321 SkASSERT(fGlobalNames.fLinkMap.end() == globalIter);
1322 fGlobalNames.fLinkMap[lowered] =
1323 ParserCommon::HtmlFileName(topic.second->fFileName) + '#' + topic.first;
1324 SkASSERT(fGlobalNames.fRefMap.end() == fGlobalNames.fRefMap.find(lowered));
1325 fGlobalNames.fRefMap[lowered] = topic.second;
1326 }
1327 size_t slash = fRawFilePathDir.rfind('/');
1328 size_t bslash = fRawFilePathDir.rfind('\\');
1329 string spellFile;
1330 if (string::npos == slash && string::npos == bslash) {
1331 spellFile = fRawFilePathDir;
1332 } else {
1333 if (string::npos != bslash && bslash > slash) {
1334 slash = bslash;
1335 }
1336 spellFile = fRawFilePathDir.substr(0, slash);
1337 }
1338 spellFile += '/';
1339 spellFile += kSpellingFileName;
1340 FILE* file = fopen(spellFile.c_str(), "r");
1341 if (!file) {
1342 SkDebugf("missing %s\n", spellFile.c_str());
1343 return;
1344 }
1345 fseek(file, 0L, SEEK_END);
1346 int sz = (int) ftell(file);
1347 rewind(file);
1348 char* buffer = new char[sz];
1349 memset(buffer, ' ', sz);
1350 int read = (int)fread(buffer, 1, sz, file);
1351 SkAssertResult(read > 0);
1352 sz = read; // FIXME: ? why are sz and read different?
1353 fclose(file);
1354 int i = 0;
1355 int start = i;
1356 string word;
1357 do {
1358 if (' ' < buffer[i]) {
1359 ++i;
1360 continue;
1361 }
1362 word = string(&buffer[start], i - start);
1363 if (fGlobalNames.fRefMap.end() == fGlobalNames.fRefMap.find(word)) {
1364 fGlobalNames.fRefMap[word] = nullptr;
1365 } else {
1366 SkDebugf("%s ", word.c_str()); // debugging: word missing from spelling list
1367 }
1368 do {
1369 ++i;
1370 } while (i < sz && ' ' >= buffer[i]);
1371 start = i;
1372 } while (i < sz);
1373 delete[] buffer;
1374}
1375
1376void BmhParser::setUpSubstitutes(const Definition* parent, NameMap* names) {
1377 for (const auto& child : parent->fChildren) {
1378 MarkType markType = child->fMarkType;
1379 if (MarkType::kAlias == markType) {
1380 continue;
1381 }
1382 if (MarkType::kSubstitute == markType) {
1383 continue;
1384 }
1385 string name(child->fName);
1386 if (&fGlobalNames != names) {
1387 size_t lastDC = name.rfind("::");
1388 if (string::npos != lastDC) {
1389 name = name.substr(lastDC + 2);
1390 }
1391 if ("" == name) {
1392 continue;
1393 }
1394 }
1395 size_t lastUnder = name.rfind('_');
1396 if (string::npos != lastUnder && ++lastUnder < name.length()) {
1397 bool numbers = true;
1398 for (size_t index = lastUnder; index < name.length(); ++index) {
1399 numbers &= (bool) isdigit(name[index]);
1400 }
1401 if (numbers) {
1402 continue;
1403 }
1404 }
1405 string ref;
1406 if (&fGlobalNames == names) {
1407 ref = ParserCommon::HtmlFileName(child->fFileName);
1408 }
1409 ref += '#' + child->fFiddle;
1410 if (MarkType::kClass == markType || MarkType::kStruct == markType
1411 || MarkType::kMethod == markType || MarkType::kEnum == markType
1412 || MarkType::kEnumClass == markType || MarkType::kConst == markType
1413 || MarkType::kMember == markType || MarkType::kDefine == markType
1414 || MarkType::kTypedef == markType) {
1415 SkASSERT(names->fLinkMap.end() == names->fLinkMap.find(name));
1416 names->fLinkMap[name] = ref;
1417 SkASSERT(names->fRefMap.end() == names->fRefMap.find(name));
1418 names->fRefMap[name] = child;
1419 }
1420 if (MarkType::kClass == markType || MarkType::kStruct == markType
1421 || MarkType::kEnumClass == markType) {
1422 RootDefinition* rootDef = child->asRoot();
1423 NameMap* nameMap = &rootDef->fNames;
1424 this->setUpSubstitutes(child, nameMap);
1425 nameMap->copyToParent(names);
1426 }
1427 if (MarkType::kEnum == markType) {
1428 this->setUpSubstitutes(child, names);
1429 }
1430 if (MarkType::kSubtopic == markType) {
1431 if (&fGlobalNames != names && string::npos != child->fName.find('_')) {
1432 string lowered = this->loweredTopic(child->fName, child);
1433 SkDEBUGCODE(auto refIter = names->fRefMap.find(lowered));
1434 SkDEBUGCODE(auto iter = names->fLinkMap.find(lowered));
1435 SkASSERT(names->fLinkMap.end() == iter);
1436 names->fLinkMap[lowered] = '#' + child->fName;
1437 SkASSERT(names->fRefMap.end() == refIter);
1438 names->fRefMap[lowered] = child;
1439 }
1440 this->setUpSubstitutes(child, names);
1441 }
1442 }
1443}
1444
1445void BmhParser::setUpPartialSubstitute(string name) {
1446 auto iter = fGlobalNames.fRefMap.find(name);
1447 if (fGlobalNames.fRefMap.end() != iter) {
1448 SkASSERT(nullptr == iter->second);
1449 return;
1450 }
1451 fGlobalNames.fRefMap[name] = nullptr;
1452}
1453
1454void BmhParser::setUpSubstitute(string name, Definition* def) {
1455 SkASSERT(fGlobalNames.fRefMap.end() == fGlobalNames.fRefMap.find(name));
1456 fGlobalNames.fRefMap[name] = def;
1457 SkASSERT(fGlobalNames.fLinkMap.end() == fGlobalNames.fLinkMap.find(name));
1458 string str = ParserCommon::HtmlFileName(def->fFileName) + '#' + def->fName;
1459 fGlobalNames.fLinkMap[name] = str;
1460 size_t stop = name.length();
1461 do {
1462 size_t space = name.rfind(' ', stop);
1463 if (string::npos == space) {
1464 break;
1465 }
1466 string partial = name.substr(0, space + 1);
1467 stop = space - 1;
1468 this->setUpPartialSubstitute(partial);
1469 } while (true);
1470}
1471
Cary Clark1a8d7622018-03-05 13:26:16 -05001472void BmhParser::setWrapper(Definition* def) const {
1473 const char drawWrapper[] = "void draw(SkCanvas* canvas) {";
1474 const char drawNoCanvas[] = "void draw(SkCanvas* ) {";
1475 string text = this->extractText(def, TrimExtract::kNo);
1476 size_t nonSpace = 0;
1477 while (nonSpace < text.length() && ' ' >= text[nonSpace]) {
1478 ++nonSpace;
1479 }
1480 bool hasFunc = !text.compare(nonSpace, sizeof(drawWrapper) - 1, drawWrapper);
1481 bool noCanvas = !text.compare(nonSpace, sizeof(drawNoCanvas) - 1, drawNoCanvas);
1482 bool hasCanvas = string::npos != text.find("SkCanvas canvas");
1483 SkASSERT(!hasFunc || !noCanvas);
1484 bool preprocessor = text[0] == '#';
1485 bool wrapCode = !hasFunc && !noCanvas && !preprocessor;
1486 if (wrapCode) {
1487 def->fWrapper = hasCanvas ? string(drawNoCanvas) : string(drawWrapper);
1488 }
1489}
1490
Cary Clark0d225392018-06-07 09:59:07 -04001491RootDefinition* BmhParser::findBmhObject(MarkType markType, string typeName) {
1492 const auto& mapIter = std::find_if(fMaps.begin(), fMaps.end(),
1493 [markType](DefinitionMap& defMap){ return markType == defMap.fMarkType; } );
1494 if (mapIter == fMaps.end()) {
1495 return nullptr;
1496 }
1497 return &(*mapIter->fMap)[typeName];
1498}
1499
Ben Wagner63fd7602017-10-09 15:45:33 -04001500// FIXME: some examples may produce different output on different platforms
Cary Clark8032b982017-07-28 11:04:54 -04001501// if the text output can be different, think of how to author that
1502
1503bool BmhParser::findDefinitions() {
1504 bool lineStart = true;
Cary Clarkce101242017-09-01 15:51:02 -04001505 const char* lastChar = nullptr;
1506 const char* lastMC = nullptr;
Cary Clark8032b982017-07-28 11:04:54 -04001507 fParent = nullptr;
1508 while (!this->eof()) {
1509 if (this->peek() == fMC) {
Cary Clarkce101242017-09-01 15:51:02 -04001510 lastMC = fChar;
Cary Clark8032b982017-07-28 11:04:54 -04001511 this->next();
1512 if (this->peek() == fMC) {
1513 this->next();
1514 if (!lineStart && ' ' < this->peek()) {
Cary Clark2be81cf2018-09-13 12:04:30 -04001515 if (!fParent || MarkType::kFormula != fParent->fMarkType) {
1516 return this->reportError<bool>("expected definition");
1517 }
Cary Clark8032b982017-07-28 11:04:54 -04001518 }
1519 if (this->peek() != fMC) {
Cary Clarkce101242017-09-01 15:51:02 -04001520 if (MarkType::kColumn == fParent->fMarkType) {
1521 SkASSERT(TableState::kColumnEnd == fTableState);
1522 if (!this->endTableColumn(lastChar, lastMC)) {
1523 return false;
1524 }
1525 SkASSERT(fRow);
1526 if (!this->popParentStack(fParent)) {
1527 return false;
1528 }
1529 fRow->fContentEnd = fWorkingColumn->fContentEnd;
1530 fWorkingColumn = nullptr;
1531 fRow = nullptr;
1532 fTableState = TableState::kNone;
1533 } else {
1534 vector<string> parentName;
1535 parentName.push_back(fParent->fName);
Cary Clarkf895a422018-02-27 09:54:21 -05001536 if (!this->addDefinition(fChar - 1, true, fParent->fMarkType, parentName,
1537 HasTag::kNo)) {
Cary Clarkce101242017-09-01 15:51:02 -04001538 return false;
1539 }
Cary Clark8032b982017-07-28 11:04:54 -04001540 }
1541 } else {
1542 SkAssertResult(this->next() == fMC);
1543 fMC = this->next(); // change markup character
1544 if (' ' >= fMC) {
1545 return this->reportError<bool>("illegal markup character");
1546 }
Cary Clark1a8d7622018-03-05 13:26:16 -05001547 fMarkup.emplace_front(MarkType::kMarkChar, fChar - 4, fLineCount, fParent, fMC);
Cary Clark8032b982017-07-28 11:04:54 -04001548 Definition* markChar = &fMarkup.front();
1549 markChar->fContentStart = fChar - 1;
1550 this->skipToEndBracket('\n');
1551 markChar->fContentEnd = fChar;
1552 markChar->fTerminator = fChar;
1553 fParent->fChildren.push_back(markChar);
1554 }
1555 } else if (this->peek() >= 'A' && this->peek() <= 'Z') {
1556 const char* defStart = fChar - 1;
1557 MarkType markType = this->getMarkType(MarkLookup::kRequire);
1558 bool hasEnd = this->hasEndToken();
Cary Clark682c58d2018-05-16 07:07:07 -04001559 if (!hasEnd && fParent) {
1560 MarkType parentType = fParent->fMarkType;
Cary Clark2d4bf5f2018-04-16 08:37:38 -04001561 uint64_t parentMask = kMarkProps[(int) markType].fParentMask;
Cary Clark8032b982017-07-28 11:04:54 -04001562 if (parentMask && !(parentMask & (1LL << (int) parentType))) {
1563 return this->reportError<bool>("invalid parent");
1564 }
1565 }
Cary Clark2d4bf5f2018-04-16 08:37:38 -04001566 if (!this->skipName(kMarkProps[(int) markType].fName)) {
Cary Clark8032b982017-07-28 11:04:54 -04001567 return this->reportError<bool>("illegal markup character");
1568 }
1569 if (!this->skipSpace()) {
1570 return this->reportError<bool>("unexpected end");
1571 }
Cary Clark81abc432018-06-25 16:30:08 -04001572 lineStart = '\n' == this->peek();
Cary Clark8032b982017-07-28 11:04:54 -04001573 bool expectEnd = true;
1574 vector<string> typeNameBuilder = this->typeName(markType, &expectEnd);
1575 if (fCloned && MarkType::kMethod != markType && MarkType::kExample != markType
1576 && !fAnonymous) {
1577 return this->reportError<bool>("duplicate name");
1578 }
1579 if (hasEnd && expectEnd) {
Cary Clark137b8742018-05-30 09:21:49 -04001580 if (fMC == this->peek()) {
1581 return this->reportError<bool>("missing body");
1582 }
Cary Clark8032b982017-07-28 11:04:54 -04001583 }
Cary Clarkf895a422018-02-27 09:54:21 -05001584 if (!this->addDefinition(defStart, hasEnd, markType, typeNameBuilder,
1585 HasTag::kYes)) {
Cary Clark8032b982017-07-28 11:04:54 -04001586 return false;
1587 }
1588 continue;
1589 } else if (this->peek() == ' ') {
Cary Clark2be81cf2018-09-13 12:04:30 -04001590 if (!fParent || (MarkType::kFormula != fParent->fMarkType
Cary Clark8032b982017-07-28 11:04:54 -04001591 && MarkType::kLegend != fParent->fMarkType
Cary Clarkab2621d2018-01-30 10:08:57 -05001592 && MarkType::kList != fParent->fMarkType
Cary Clark2be81cf2018-09-13 12:04:30 -04001593 && MarkType::kLine != fParent->fMarkType
1594 && MarkType::kTable != fParent->fMarkType)) {
Cary Clark8032b982017-07-28 11:04:54 -04001595 int endHashes = this->endHashCount();
Cary Clarkce101242017-09-01 15:51:02 -04001596 if (endHashes <= 1) {
Cary Clark8032b982017-07-28 11:04:54 -04001597 if (fParent) {
Cary Clarkce101242017-09-01 15:51:02 -04001598 if (TableState::kColumnEnd == fTableState) {
1599 if (!this->endTableColumn(lastChar, lastMC)) {
1600 return false;
1601 }
1602 } else { // one line comment
1603 fMarkup.emplace_front(MarkType::kComment, fChar - 1, fLineCount,
Cary Clark1a8d7622018-03-05 13:26:16 -05001604 fParent, fMC);
Cary Clarkce101242017-09-01 15:51:02 -04001605 Definition* comment = &fMarkup.front();
1606 comment->fContentStart = fChar - 1;
1607 this->skipToEndBracket('\n');
1608 comment->fContentEnd = fChar;
1609 comment->fTerminator = fChar;
1610 fParent->fChildren.push_back(comment);
1611 }
Cary Clark8032b982017-07-28 11:04:54 -04001612 } else {
1613 fChar = fLine + this->lineLength() - 1;
1614 }
1615 } else { // table row
1616 if (2 != endHashes) {
1617 string errorStr = "expect ";
1618 errorStr += fMC;
1619 errorStr += fMC;
1620 return this->reportError<bool>(errorStr.c_str());
1621 }
1622 if (!fParent || MarkType::kTable != fParent->fMarkType) {
1623 return this->reportError<bool>("missing table");
1624 }
1625 }
Cary Clarkce101242017-09-01 15:51:02 -04001626 } else if (TableState::kNone == fTableState) {
Cary Clarkce101242017-09-01 15:51:02 -04001627 // fixme? no nested tables for now
1628 fColStart = fChar - 1;
Cary Clark1a8d7622018-03-05 13:26:16 -05001629 fMarkup.emplace_front(MarkType::kRow, fColStart, fLineCount, fParent, fMC);
Cary Clarkce101242017-09-01 15:51:02 -04001630 fRow = &fMarkup.front();
1631 fRow->fName = fParent->fName;
1632 this->skipWhiteSpace();
1633 fRow->fContentStart = fChar;
1634 this->setAsParent(fRow);
1635 fTableState = TableState::kColumnStart;
1636 }
1637 if (TableState::kColumnStart == fTableState) {
Cary Clark1a8d7622018-03-05 13:26:16 -05001638 fMarkup.emplace_front(MarkType::kColumn, fColStart, fLineCount, fParent, fMC);
Cary Clarkce101242017-09-01 15:51:02 -04001639 fWorkingColumn = &fMarkup.front();
1640 fWorkingColumn->fName = fParent->fName;
1641 fWorkingColumn->fContentStart = fChar;
1642 this->setAsParent(fWorkingColumn);
1643 fTableState = TableState::kColumnEnd;
1644 continue;
Cary Clark8032b982017-07-28 11:04:54 -04001645 }
Cary Clark1a8d7622018-03-05 13:26:16 -05001646 } else if (this->peek() >= 'a' && this->peek() <= 'z') {
1647 // expect zero or more letters and underscores (no spaces) then hash
1648 const char* phraseNameStart = fChar;
1649 this->skipPhraseName();
1650 string phraseKey = string(phraseNameStart, fChar - phraseNameStart);
Cary Clark682c58d2018-05-16 07:07:07 -04001651 char delimiter = this->next();
1652 vector<string> params;
1653 vector<const char*> paramsLoc;
1654 if (fMC != delimiter) {
1655 if ('(' != delimiter) {
1656 return this->reportError<bool>("expect # after phrase name");
1657 }
1658 // phrase may take comma delimited parameter list
1659 do {
1660 const char* subEnd = this->anyOf(",)\n");
1661 if (!subEnd || '\n' == *subEnd) {
1662 return this->reportError<bool>("unexpected phrase list end");
1663 }
1664 params.push_back(string(fChar, subEnd - fChar));
1665 paramsLoc.push_back(fChar);
1666 this->skipTo(subEnd);
1667
1668 } while (')' != this->next());
Cary Clark1a8d7622018-03-05 13:26:16 -05001669 }
1670 const char* start = phraseNameStart;
1671 SkASSERT('#' == start[-1]);
1672 --start;
1673 if (start > fStart && ' ' >= start[-1]) {
1674 --start; // preserve whether to add whitespace before substitution
1675 }
1676 fMarkup.emplace_front(MarkType::kPhraseRef, start, fLineCount, fParent, fMC);
1677 Definition* markChar = &fMarkup.front();
Cary Clark682c58d2018-05-16 07:07:07 -04001678 this->skipExact("#");
Cary Clark1a8d7622018-03-05 13:26:16 -05001679 markChar->fContentStart = fChar;
Cary Clark1a8d7622018-03-05 13:26:16 -05001680 markChar->fContentEnd = fChar;
1681 markChar->fTerminator = fChar;
1682 markChar->fName = phraseKey;
1683 fParent->fChildren.push_back(markChar);
Cary Clark682c58d2018-05-16 07:07:07 -04001684 int paramLocIndex = 0;
1685 for (auto param : params) {
1686 const char* paramLoc = paramsLoc[paramLocIndex++];
1687 fMarkup.emplace_front(MarkType::kPhraseParam, paramLoc, fLineCount, fParent,
1688 fMC);
1689 Definition* phraseParam = &fMarkup.front();
1690 phraseParam->fContentStart = paramLoc;
1691 phraseParam->fContentEnd = paramLoc + param.length();
1692 phraseParam->fTerminator = paramLoc + param.length();
1693 phraseParam->fName = param;
1694 markChar->fChildren.push_back(phraseParam);
1695 }
Cary Clark8032b982017-07-28 11:04:54 -04001696 }
1697 }
Cary Clarkce101242017-09-01 15:51:02 -04001698 char nextChar = this->next();
Cary Clarkce101242017-09-01 15:51:02 -04001699 if (' ' < nextChar) {
1700 lastChar = fChar;
Cary Clark81abc432018-06-25 16:30:08 -04001701 lineStart = false;
1702 } else if (nextChar == '\n') {
1703 lineStart = true;
Cary Clarkce101242017-09-01 15:51:02 -04001704 }
Cary Clark8032b982017-07-28 11:04:54 -04001705 }
1706 if (fParent) {
Cary Clarka560c472017-11-27 10:44:06 -05001707 return fParent->reportError<bool>("mismatched end");
Cary Clark8032b982017-07-28 11:04:54 -04001708 }
1709 return true;
1710}
1711
1712MarkType BmhParser::getMarkType(MarkLookup lookup) const {
1713 for (int index = 0; index <= Last_MarkType; ++index) {
Cary Clark2d4bf5f2018-04-16 08:37:38 -04001714 int typeLen = strlen(kMarkProps[index].fName);
Cary Clark8032b982017-07-28 11:04:54 -04001715 if (typeLen == 0) {
1716 continue;
1717 }
1718 if (fChar + typeLen >= fEnd || fChar[typeLen] > ' ') {
1719 continue;
1720 }
Cary Clark2d4bf5f2018-04-16 08:37:38 -04001721 int chCompare = strncmp(fChar, kMarkProps[index].fName, typeLen);
Cary Clark8032b982017-07-28 11:04:54 -04001722 if (chCompare < 0) {
1723 goto fail;
1724 }
1725 if (chCompare == 0) {
1726 return (MarkType) index;
1727 }
1728 }
1729fail:
1730 if (MarkLookup::kRequire == lookup) {
1731 return this->reportError<MarkType>("unknown mark type");
1732 }
1733 return MarkType::kNone;
1734}
1735
Cary Clark09d80c02018-10-31 12:14:03 -04001736 // replace #Method description, #Param, #Return with #Populate
1737 // if description, params, return are free of phrase refs
Cary Clark8032b982017-07-28 11:04:54 -04001738bool HackParser::hackFiles() {
1739 string filename(fFileName);
1740 size_t len = filename.length() - 1;
1741 while (len > 0 && (isalnum(filename[len]) || '_' == filename[len] || '.' == filename[len])) {
1742 --len;
1743 }
1744 filename = filename.substr(len + 1);
Cary Clarkab2621d2018-01-30 10:08:57 -05001745 if (filename.substr(0, 2) != "Sk") {
1746 return true;
1747 }
1748 size_t under = filename.find('_');
1749 SkASSERT(under);
1750 string className = filename.substr(0, under);
1751 fOut = fopen(filename.c_str(), "wb");
1752 if (!fOut) {
Cary Clark8032b982017-07-28 11:04:54 -04001753 SkDebugf("could not open output file %s\n", filename.c_str());
1754 return false;
1755 }
Cary Clarkab2621d2018-01-30 10:08:57 -05001756 auto mapEntry = fBmhParser.fClassMap.find(className);
Cary Clark09d80c02018-10-31 12:14:03 -04001757 if (fBmhParser.fClassMap.end() == mapEntry) {
1758 remove(filename.c_str());
1759 return true;
1760 }
Cary Clarkab2621d2018-01-30 10:08:57 -05001761 const Definition* classMarkup = &mapEntry->second;
1762 const Definition* root = classMarkup->fParent;
1763 SkASSERT(root);
1764 SkASSERT(root->fTerminator);
1765 SkASSERT('\n' == root->fTerminator[0]);
1766 SkASSERT(!root->fParent);
1767 fStart = root->fStart;
1768 fChar = fStart;
Cary Clark09d80c02018-10-31 12:14:03 -04001769 fEnd = root->fTerminator;
1770 this->replaceWithPop(root);
1771 FPRINTF("%.*s", (int) (fEnd - fChar), fChar);
1772 if ('\n' != fEnd[-1]) {
1773 FPRINTF("\n");
1774 }
Cary Clarkab2621d2018-01-30 10:08:57 -05001775 fclose(fOut);
Cary Clark5b1f9532018-08-28 14:53:37 -04001776 if (ParserCommon::WrittenFileDiffers(filename, root->fFileName)) {
Cary Clark08895c42018-02-01 09:37:32 -05001777 SkDebugf("wrote %s\n", filename.c_str());
1778 } else {
1779 remove(filename.c_str());
1780 }
Cary Clark8032b982017-07-28 11:04:54 -04001781 return true;
1782}
1783
Cary Clarkab2621d2018-01-30 10:08:57 -05001784// returns true if topic has method
Cary Clark09d80c02018-10-31 12:14:03 -04001785void HackParser::replaceWithPop(const Definition* root) {
1786 for (auto child : root->fChildren) {
1787 if (MarkType::kClass == child->fMarkType || MarkType::kStruct == child->fMarkType
1788 || MarkType::kSubtopic == child->fMarkType) {
1789 this->replaceWithPop(child);
Cary Clarkab2621d2018-01-30 10:08:57 -05001790 }
Cary Clark09d80c02018-10-31 12:14:03 -04001791 if (MarkType::kMethod != child->fMarkType) {
1792 continue;
Cary Clarkab2621d2018-01-30 10:08:57 -05001793 }
Cary Clark09d80c02018-10-31 12:14:03 -04001794 auto& grans = child->fChildren;
1795 if (grans.end() != std::find_if(grans.begin(), grans.end(),
1796 [](const Definition* def) {
1797 return MarkType::kPopulate == def->fMarkType
1798 || MarkType::kPhraseRef == def->fMarkType
1799 || MarkType::kFormula == def->fMarkType
1800 || MarkType::kAnchor == def->fMarkType
1801 || MarkType::kList == def->fMarkType
1802 || MarkType::kTable == def->fMarkType
1803 || MarkType::kDeprecated == def->fMarkType
1804 || MarkType::kExperimental == def->fMarkType
1805 || MarkType::kPrivate == def->fMarkType;
1806 } )) {
1807 continue;
1808 }
1809 // write #Populate in place of description, #Param(s), #Return (if present)
1810 const char* keep = child->fContentStart;
1811 const char* next = nullptr;
1812 for (auto gran : grans) {
1813 if (MarkType::kIn == gran->fMarkType || MarkType::kLine == gran->fMarkType) {
1814 keep = gran->fTerminator;
1815 continue;
1816 }
1817 if (MarkType::kExample == gran->fMarkType
1818 || MarkType::kNoExample == gran->fMarkType) {
1819 next = gran->fStart;
1820 break;
1821 }
1822 if (MarkType::kParam == gran->fMarkType
1823 || MarkType::kReturn == gran->fMarkType
1824 || MarkType::kToDo == gran->fMarkType
1825 || MarkType::kComment == gran->fMarkType) {
1826 continue;
1827 }
1828 SkDebugf(""); // convenient place to set a breakpoint
1829 }
1830 SkASSERT(next);
1831 FPRINTF("%.*s", (int) (keep - fChar), fChar);
1832 if ('\n' != keep[-1]) {
1833 FPRINTF("\n");
1834 }
1835 FPRINTF("#Populate\n\n");
1836 fChar = next;
Cary Clark08895c42018-02-01 09:37:32 -05001837 }
1838}
Cary Clarkab2621d2018-01-30 10:08:57 -05001839
Cary Clark8032b982017-07-28 11:04:54 -04001840bool BmhParser::hasEndToken() const {
Cary Clark2be81cf2018-09-13 12:04:30 -04001841 const char* ptr = fLine;
1842 char test;
1843 do {
1844 if (ptr >= fEnd) {
1845 return false;
1846 }
1847 test = *ptr++;
1848 if ('\n' == test) {
1849 return false;
1850 }
1851 } while (fMC != test || fMC != *ptr);
1852 return true;
Cary Clark8032b982017-07-28 11:04:54 -04001853}
1854
1855string BmhParser::memberName() {
1856 const char* wordStart;
1857 const char* prefixes[] = { "static", "const" };
1858 do {
1859 this->skipSpace();
1860 wordStart = fChar;
Cary Clark2d4bf5f2018-04-16 08:37:38 -04001861 this->skipToNonName();
Cary Clark8032b982017-07-28 11:04:54 -04001862 } while (this->anyOf(wordStart, prefixes, SK_ARRAY_COUNT(prefixes)));
1863 if ('*' == this->peek()) {
1864 this->next();
1865 }
1866 return this->className(MarkType::kMember);
1867}
1868
1869string BmhParser::methodName() {
1870 if (this->hasEndToken()) {
1871 if (!fParent || !fParent->fName.length()) {
1872 return this->reportError<string>("missing parent method name");
1873 }
1874 SkASSERT(fMC == this->peek());
1875 this->next();
1876 SkASSERT(fMC == this->peek());
1877 this->next();
1878 SkASSERT(fMC != this->peek());
1879 return fParent->fName;
1880 }
1881 string builder;
1882 const char* end = this->lineEnd();
1883 const char* paren = this->strnchr('(', end);
1884 if (!paren) {
1885 return this->reportError<string>("missing method name and reference");
1886 }
Cary Clark224c7002018-06-27 11:00:21 -04001887 {
1888 TextParserSave endCheck(this);
1889 while (end < fEnd && !this->strnchr(')', end)) {
1890 fChar = end + 1;
1891 end = this->lineEnd();
1892 }
1893 if (end >= fEnd) {
1894 return this->reportError<string>("missing method end paren");
1895 }
1896 endCheck.restore();
1897 }
Cary Clark8032b982017-07-28 11:04:54 -04001898 const char* nameStart = paren;
1899 char ch;
1900 bool expectOperator = false;
1901 bool isConstructor = false;
1902 const char* nameEnd = nullptr;
1903 while (nameStart > fChar && ' ' != (ch = *--nameStart)) {
1904 if (!isalnum(ch) && '_' != ch) {
1905 if (nameEnd) {
1906 break;
1907 }
1908 expectOperator = true;
1909 continue;
1910 }
1911 if (!nameEnd) {
1912 nameEnd = nameStart + 1;
1913 }
1914 }
1915 if (!nameEnd) {
1916 return this->reportError<string>("unexpected method name char");
1917 }
1918 if (' ' == nameStart[0]) {
1919 ++nameStart;
1920 }
1921 if (nameEnd <= nameStart) {
1922 return this->reportError<string>("missing method name");
1923 }
1924 if (nameStart >= paren) {
1925 return this->reportError<string>("missing method name length");
1926 }
1927 string name(nameStart, nameEnd - nameStart);
1928 bool allLower = true;
1929 for (int index = 0; index < (int) (nameEnd - nameStart); ++index) {
1930 if (!islower(nameStart[index])) {
1931 allLower = false;
1932 break;
1933 }
1934 }
1935 if (expectOperator && "operator" != name) {
1936 return this->reportError<string>("expected operator");
1937 }
1938 const Definition* parent = this->parentSpace();
1939 if (parent && parent->fName.length() > 0) {
Cary Clark224c7002018-06-27 11:00:21 -04001940 size_t parentNameIndex = parent->fName.rfind(':');
1941 parentNameIndex = string::npos == parentNameIndex ? 0 : parentNameIndex + 1;
1942 string parentName = parent->fName.substr(parentNameIndex);
1943 if (parentName == name) {
Cary Clark8032b982017-07-28 11:04:54 -04001944 isConstructor = true;
1945 } else if ('~' == name[0]) {
Cary Clark224c7002018-06-27 11:00:21 -04001946 if (parentName != name.substr(1)) {
Cary Clark8032b982017-07-28 11:04:54 -04001947 return this->reportError<string>("expected destructor");
1948 }
1949 isConstructor = true;
1950 }
1951 builder = parent->fName + "::";
Ben Wagner63fd7602017-10-09 15:45:33 -04001952 }
Cary Clarka560c472017-11-27 10:44:06 -05001953 bool addConst = false;
Cary Clark8032b982017-07-28 11:04:54 -04001954 if (isConstructor || expectOperator) {
1955 paren = this->strnchr(')', end) + 1;
Cary Clark186d08f2018-04-03 08:43:27 -04001956 TextParserSave saveState(this);
Cary Clarka560c472017-11-27 10:44:06 -05001957 this->skipTo(paren);
1958 if (this->skipExact("_const")) {
1959 addConst = true;
1960 }
1961 saveState.restore();
Cary Clark8032b982017-07-28 11:04:54 -04001962 }
1963 builder.append(nameStart, paren - nameStart);
Cary Clarka560c472017-11-27 10:44:06 -05001964 if (addConst) {
1965 builder.append("_const");
1966 }
Cary Clark8032b982017-07-28 11:04:54 -04001967 if (!expectOperator && allLower) {
1968 builder.append("()");
1969 }
1970 int parens = 0;
1971 while (fChar < end || parens > 0) {
1972 if ('(' == this->peek()) {
1973 ++parens;
1974 } else if (')' == this->peek()) {
1975 --parens;
1976 }
1977 this->next();
1978 }
Cary Clark186d08f2018-04-03 08:43:27 -04001979 TextParserSave saveState(this);
Cary Clark8032b982017-07-28 11:04:54 -04001980 this->skipWhiteSpace();
1981 if (this->startsWith("const")) {
1982 this->skipName("const");
1983 } else {
1984 saveState.restore();
1985 }
1986// this->next();
Cary Clark82f1f742018-06-28 08:50:35 -04001987 if (string::npos != builder.find('\n')) {
1988 builder.erase(std::remove(builder.begin(), builder.end(), '\n'), builder.end());
1989 }
Cary Clark8032b982017-07-28 11:04:54 -04001990 return uniqueRootName(builder, MarkType::kMethod);
1991}
1992
1993const Definition* BmhParser::parentSpace() const {
1994 Definition* parent = nullptr;
1995 Definition* test = fParent;
1996 while (test) {
1997 if (MarkType::kClass == test->fMarkType ||
1998 MarkType::kEnumClass == test->fMarkType ||
1999 MarkType::kStruct == test->fMarkType) {
2000 parent = test;
2001 break;
2002 }
2003 test = test->fParent;
2004 }
2005 return parent;
2006}
2007
Cary Clark137b8742018-05-30 09:21:49 -04002008// A full terminal statement is in the form:
2009// \n optional-white-space #MarkType white-space #[# white-space]
2010// \n optional-white-space #MarkType white-space Name white-space #[# white-space]
2011// MarkType must match definition->fMarkType
Cary Clark1a8d7622018-03-05 13:26:16 -05002012const char* BmhParser::checkForFullTerminal(const char* end, const Definition* definition) const {
2013 const char* start = end;
2014 while ('\n' != start[0] && start > fStart) {
2015 --start;
2016 }
2017 SkASSERT (start < end);
2018 // if end is preceeeded by \n#MarkType ## backup to there
2019 TextParser parser(fFileName, start, fChar, fLineCount);
2020 parser.skipWhiteSpace();
2021 if (parser.eof() || fMC != parser.next()) {
2022 return end;
2023 }
Cary Clark2d4bf5f2018-04-16 08:37:38 -04002024 const char* markName = kMarkProps[(int) definition->fMarkType].fName;
Cary Clark1a8d7622018-03-05 13:26:16 -05002025 if (!parser.skipExact(markName)) {
2026 return end;
2027 }
2028 parser.skipWhiteSpace();
Cary Clark137b8742018-05-30 09:21:49 -04002029 TextParser startName(fFileName, definition->fStart, definition->fContentStart,
2030 definition->fLineCount);
2031 if ('#' == startName.next()) {
2032 startName.skipToSpace();
2033 if (!startName.eof() && startName.skipSpace()) {
2034 const char* nameBegin = startName.fChar;
2035 startName.skipToWhiteSpace();
2036 string name(nameBegin, (int) (startName.fChar - nameBegin));
2037 if (fMC != parser.peek() && !parser.skipExact(name.c_str())) {
2038 return end;
2039 }
2040 parser.skipSpace();
Cary Clark1a8d7622018-03-05 13:26:16 -05002041 }
2042 }
Cary Clark137b8742018-05-30 09:21:49 -04002043 if (parser.eof() || fMC != parser.next()) {
Cary Clark1a8d7622018-03-05 13:26:16 -05002044 return end;
2045 }
2046 if (!parser.eof() && fMC != parser.next()) {
2047 return end;
2048 }
Cary Clark137b8742018-05-30 09:21:49 -04002049 SkASSERT(parser.eof());
Cary Clark1a8d7622018-03-05 13:26:16 -05002050 return start;
2051}
2052
Cary Clark2be81cf2018-09-13 12:04:30 -04002053void BmhParser::parseHashAnchor(Definition* definition) {
2054 this->skipToEndBracket(fMC);
2055 fMarkup.emplace_front(MarkType::kLink, fChar, fLineCount, definition, fMC);
2056 SkAssertResult(fMC == this->next());
2057 this->skipWhiteSpace();
2058 Definition* link = &fMarkup.front();
2059 link->fContentStart = fChar;
2060 link->fContentEnd = this->trimmedBracketEnd(fMC);
2061 this->skipToEndBracket(fMC);
2062 SkAssertResult(fMC == this->next());
2063 SkAssertResult(fMC == this->next());
2064 link->fTerminator = fChar;
2065 definition->fContentEnd = link->fContentEnd;
2066 definition->fTerminator = fChar;
2067 definition->fChildren.emplace_back(link);
2068}
2069
2070void BmhParser::parseHashFormula(Definition* definition) {
2071 const char* start = definition->fContentStart;
2072 definition->trimEnd();
2073 const char* end = definition->fContentEnd;
2074 fMarkup.emplace_front(MarkType::kText, start, fLineCount, definition, fMC);
2075 Definition* text = &fMarkup.front();
2076 text->fContentStart = start;
2077 text->fContentEnd = end;
2078 text->fTerminator = definition->fTerminator;
2079 definition->fChildren.emplace_back(text);
2080}
2081
2082void BmhParser::parseHashLine(Definition* definition) {
2083 const char* nextLF = this->strnchr('\n', this->fEnd);
2084 const char* start = fChar;
2085 const char* end = this->trimmedBracketEnd(fMC);
2086 this->skipToEndBracket(fMC, nextLF);
2087 if (fMC != this->next() || fMC != this->next()) {
2088 return this->reportError<void>("expected ## to delineate line");
2089 }
2090 fMarkup.emplace_front(MarkType::kText, start, fLineCount, definition, fMC);
2091 Definition* text = &fMarkup.front();
2092 if (!islower(start[0]) && (!isdigit(start[0])
2093 || MarkType::kConst != definition->fParent->fMarkType)) {
2094 return this->reportError<void>("expect lower case start");
2095 }
2096 string contents = string(start, end - start);
2097 if (string::npos != contents.find('.')) {
2098 return this->reportError<void>("expect phrase, not sentence");
2099 }
2100 size_t firstSpace = contents.find(' ');
2101 if (string::npos == firstSpace || 0 == firstSpace || 's' != start[firstSpace - 1]) {
2102 if (MarkType::kMethod == fParent->fMarkType && "experimental" != contents
2103 && "incomplete" != contents) {
2104 return this->reportError<void>( "expect phrase in third person present"
2105 " tense (1st word should end in 's'");
2106 }
2107 }
2108 text->fContentStart = start;
2109 text->fContentEnd = end;
2110 text->fTerminator = fChar;
2111 definition->fContentEnd = text->fContentEnd;
2112 definition->fTerminator = fChar;
2113 definition->fChildren.emplace_back(text);
2114}
2115
Cary Clark8032b982017-07-28 11:04:54 -04002116bool BmhParser::popParentStack(Definition* definition) {
2117 if (!fParent) {
2118 return this->reportError<bool>("missing parent");
2119 }
2120 if (definition != fParent) {
2121 return this->reportError<bool>("definition end is not parent");
2122 }
2123 if (!definition->fStart) {
2124 return this->reportError<bool>("definition missing start");
2125 }
2126 if (definition->fContentEnd) {
2127 return this->reportError<bool>("definition already ended");
2128 }
Cary Clark1a8d7622018-03-05 13:26:16 -05002129 // more to figure out to handle table columns, at minimum
2130 const char* end = fChar;
2131 if (fMC != end[0]) {
2132 while (end > definition->fContentStart && ' ' >= end[-1]) {
2133 --end;
2134 }
2135 SkASSERT(&end[-1] >= definition->fContentStart && fMC == end[-1]
2136 && (MarkType::kColumn == definition->fMarkType
2137 || (&end[-2] >= definition->fContentStart && fMC == end[-2])));
2138 end -= 2;
2139 }
2140 end = checkForFullTerminal(end, definition);
2141 definition->fContentEnd = end;
Cary Clark8032b982017-07-28 11:04:54 -04002142 definition->fTerminator = fChar;
2143 fParent = definition->fParent;
2144 if (!fParent || (MarkType::kTopic == fParent->fMarkType && !fParent->fParent)) {
2145 fRoot = nullptr;
2146 }
2147 return true;
2148}
2149
2150TextParser::TextParser(const Definition* definition) :
Ben Wagner63fd7602017-10-09 15:45:33 -04002151 TextParser(definition->fFileName, definition->fContentStart, definition->fContentEnd,
Cary Clark8032b982017-07-28 11:04:54 -04002152 definition->fLineCount) {
2153}
2154
Cary Clark4855f782018-02-06 09:41:53 -05002155string TextParser::ReportFilename(string file) {
2156 string fullName;
2157#ifdef SK_BUILD_FOR_WIN
2158 TCHAR pathChars[MAX_PATH];
2159 DWORD pathLen = GetCurrentDirectory(MAX_PATH, pathChars);
2160 for (DWORD index = 0; index < pathLen; ++index) {
2161 fullName += pathChars[index] == (char)pathChars[index] ? (char)pathChars[index] : '?';
2162 }
2163 fullName += '\\';
2164#endif
2165 fullName += file;
2166 return fullName;
2167}
2168
Cary Clark8032b982017-07-28 11:04:54 -04002169void TextParser::reportError(const char* errorStr) const {
2170 this->reportWarning(errorStr);
2171 SkDebugf(""); // convenient place to set a breakpoint
2172}
2173
2174void TextParser::reportWarning(const char* errorStr) const {
Cary Clark61313f32018-10-08 14:57:48 -04002175 const char* lineStart = fLine;
2176 if (lineStart >= fEnd) {
2177 lineStart = fChar;
2178 }
2179 SkASSERT(lineStart < fEnd);
2180 TextParser err(fFileName, lineStart, fEnd, fLineCount);
Cary Clark8032b982017-07-28 11:04:54 -04002181 size_t lineLen = this->lineLength();
Cary Clark61313f32018-10-08 14:57:48 -04002182 ptrdiff_t spaces = fChar - lineStart;
Cary Clark8032b982017-07-28 11:04:54 -04002183 while (spaces > 0 && (size_t) spaces > lineLen) {
2184 ++err.fLineCount;
2185 err.fLine += lineLen;
2186 spaces -= lineLen;
2187 lineLen = err.lineLength();
2188 }
Cary Clark4855f782018-02-06 09:41:53 -05002189 string fullName = this->ReportFilename(fFileName);
2190 SkDebugf("\n%s(%zd): error: %s\n", fullName.c_str(), err.fLineCount, errorStr);
Cary Clark8032b982017-07-28 11:04:54 -04002191 if (0 == lineLen) {
2192 SkDebugf("[blank line]\n");
2193 } else {
2194 while (lineLen > 0 && '\n' == err.fLine[lineLen - 1]) {
2195 --lineLen;
2196 }
2197 SkDebugf("%.*s\n", (int) lineLen, err.fLine);
2198 SkDebugf("%*s^\n", (int) spaces, "");
2199 }
2200}
2201
Cary Clark186d08f2018-04-03 08:43:27 -04002202void TextParser::setForErrorReporting(const Definition* definition, const char* str) {
2203 fFileName = definition->fFileName;
2204 fStart = definition->fContentStart;
2205 fLine = str;
2206 while (fLine > fStart && fLine[-1] != '\n') {
2207 --fLine;
2208 }
2209 fChar = str;
2210 fEnd = definition->fContentEnd;
2211 fLineCount = definition->fLineCount;
2212 const char* lineInc = fStart;
2213 while (lineInc < str) {
2214 fLineCount += '\n' == *lineInc++;
2215 }
2216}
2217
Cary Clark2f466242017-12-11 16:03:17 -05002218string TextParser::typedefName() {
2219 // look for typedef as one of three forms:
2220 // typedef return-type (*NAME)(params);
2221 // typedef alias NAME;
2222 // typedef std::function<alias> NAME;
2223 string builder;
2224 const char* end = this->doubleLF();
2225 if (!end) {
2226 end = fEnd;
2227 }
2228 const char* altEnd = this->strnstr("#Typedef ##", end);
2229 if (altEnd) {
2230 end = this->strnchr('\n', end);
2231 }
2232 if (!end) {
2233 return this->reportError<string>("missing typedef std::function end bracket >");
2234 }
Cary Clark61ca7c52018-01-02 11:34:14 -05002235 bool stdFunction = this->startsWith("std::function");
2236 if (stdFunction) {
Cary Clark2f466242017-12-11 16:03:17 -05002237 if (!this->skipToEndBracket('>')) {
2238 return this->reportError<string>("missing typedef std::function end bracket >");
2239 }
2240 this->next();
2241 this->skipWhiteSpace();
2242 builder += string(fChar, end - fChar);
2243 } else {
2244 const char* paren = this->strnchr('(', end);
2245 if (!paren) {
2246 const char* lastWord = nullptr;
2247 do {
2248 this->skipToWhiteSpace();
2249 if (fChar < end && isspace(fChar[0])) {
Cary Clark682c58d2018-05-16 07:07:07 -04002250 const char* whiteStart = fChar;
Cary Clark2f466242017-12-11 16:03:17 -05002251 this->skipWhiteSpace();
Cary Clark682c58d2018-05-16 07:07:07 -04002252 // FIXME: test should be for fMC
2253 if ('#' == fChar[0]) {
2254 end = whiteStart;
2255 break;
2256 }
Cary Clark2f466242017-12-11 16:03:17 -05002257 lastWord = fChar;
2258 } else {
2259 break;
2260 }
2261 } while (true);
2262 if (!lastWord) {
2263 return this->reportError<string>("missing typedef name");
2264 }
2265 builder += string(lastWord, end - lastWord);
2266 } else {
2267 this->skipTo(paren);
2268 this->next();
2269 if ('*' != this->next()) {
2270 return this->reportError<string>("missing typedef function asterisk");
2271 }
2272 const char* nameStart = fChar;
2273 if (!this->skipToEndBracket(')')) {
2274 return this->reportError<string>("missing typedef function )");
2275 }
2276 builder += string(nameStart, fChar - nameStart);
2277 if (!this->skipToEndBracket('(')) {
2278 return this->reportError<string>("missing typedef params (");
2279 }
2280 if (! this->skipToEndBracket(')')) {
2281 return this->reportError<string>("missing typedef params )");
2282 }
2283 this->skipTo(end);
2284 }
2285 }
2286 return builder;
2287}
2288
Cary Clark8032b982017-07-28 11:04:54 -04002289bool BmhParser::skipNoName() {
2290 if ('\n' == this->peek()) {
2291 this->next();
2292 return true;
2293 }
2294 this->skipWhiteSpace();
2295 if (fMC != this->peek()) {
Cary Clark2be81cf2018-09-13 12:04:30 -04002296 return this->reportError<bool>("expected end mark 1");
Cary Clark8032b982017-07-28 11:04:54 -04002297 }
2298 this->next();
2299 if (fMC != this->peek()) {
Cary Clark2be81cf2018-09-13 12:04:30 -04002300 return this->reportError<bool>("expected end mark 2");
Cary Clark8032b982017-07-28 11:04:54 -04002301 }
2302 this->next();
2303 return true;
2304}
2305
2306bool BmhParser::skipToDefinitionEnd(MarkType markType) {
2307 if (this->eof()) {
2308 return this->reportError<bool>("missing end");
2309 }
2310 const char* start = fLine;
2311 int startLineCount = fLineCount;
2312 int stack = 1;
2313 ptrdiff_t lineLen;
2314 bool foundEnd = false;
2315 do {
2316 lineLen = this->lineLength();
2317 if (fMC != *fChar++) {
2318 continue;
2319 }
2320 if (fMC == *fChar) {
2321 continue;
2322 }
2323 if (' ' == *fChar) {
2324 continue;
2325 }
2326 MarkType nextType = this->getMarkType(MarkLookup::kAllowUnknown);
2327 if (markType != nextType) {
2328 continue;
2329 }
2330 bool hasEnd = this->hasEndToken();
2331 if (hasEnd) {
2332 if (!--stack) {
2333 foundEnd = true;
2334 continue;
2335 }
2336 } else {
2337 ++stack;
2338 }
2339 } while ((void) ++fLineCount, (void) (fLine += lineLen), (void) (fChar = fLine),
2340 !this->eof() && !foundEnd);
2341 if (foundEnd) {
2342 return true;
2343 }
2344 fLineCount = startLineCount;
2345 fLine = start;
2346 fChar = start;
2347 return this->reportError<bool>("unbalanced stack");
2348}
2349
Cary Clarkab2621d2018-01-30 10:08:57 -05002350bool BmhParser::skipToString() {
2351 this->skipSpace();
2352 if (fMC != this->peek()) {
Cary Clark2be81cf2018-09-13 12:04:30 -04002353 return this->reportError<bool>("expected end mark 3");
Cary Clarkab2621d2018-01-30 10:08:57 -05002354 }
2355 this->next();
2356 this->skipSpace();
2357 // body is text from here to double fMC
2358 // no single fMC allowed, no linefeed allowed
2359 return true;
2360}
2361
Cary Clark8032b982017-07-28 11:04:54 -04002362vector<string> BmhParser::topicName() {
2363 vector<string> result;
2364 this->skipWhiteSpace();
2365 const char* lineEnd = fLine + this->lineLength();
2366 const char* nameStart = fChar;
2367 while (fChar < lineEnd) {
2368 char ch = this->next();
2369 SkASSERT(',' != ch);
2370 if ('\n' == ch) {
2371 break;
2372 }
2373 if (fMC == ch) {
2374 break;
2375 }
2376 }
2377 if (fChar - 1 > nameStart) {
2378 string builder(nameStart, fChar - nameStart - 1);
2379 trim_start_end(builder);
2380 result.push_back(builder);
2381 }
2382 if (fChar < lineEnd && fMC == this->peek()) {
2383 this->next();
2384 }
2385 return result;
2386}
2387
2388// typeName parsing rules depend on mark type
2389vector<string> BmhParser::typeName(MarkType markType, bool* checkEnd) {
2390 fAnonymous = false;
2391 fCloned = false;
2392 vector<string> result;
2393 string builder;
2394 if (fParent) {
2395 builder = fParent->fName;
2396 }
2397 switch (markType) {
Cary Clark2d4bf5f2018-04-16 08:37:38 -04002398 case MarkType::kDefine:
Cary Clark8032b982017-07-28 11:04:54 -04002399 case MarkType::kEnum:
2400 // enums may be nameless
2401 case MarkType::kConst:
2402 case MarkType::kEnumClass:
2403 case MarkType::kClass:
2404 case MarkType::kStruct:
Cary Clark8032b982017-07-28 11:04:54 -04002405 // expect name
2406 builder = this->className(markType);
2407 break;
2408 case MarkType::kExample:
2409 // check to see if one already exists -- if so, number this one
2410 builder = this->uniqueName(string(), markType);
2411 this->skipNoName();
2412 break;
2413 case MarkType::kCode:
Cary Clark8032b982017-07-28 11:04:54 -04002414 case MarkType::kDescription:
Cary Clark8032b982017-07-28 11:04:54 -04002415 case MarkType::kExternal:
Cary Clark8032b982017-07-28 11:04:54 -04002416 case MarkType::kFunction:
2417 case MarkType::kLegend:
2418 case MarkType::kList:
2419 case MarkType::kNoExample:
2420 case MarkType::kPrivate:
Cary Clark8032b982017-07-28 11:04:54 -04002421 this->skipNoName();
2422 break;
Cary Clark2be81cf2018-09-13 12:04:30 -04002423 case MarkType::kFormula:
Cary Clarkab2621d2018-01-30 10:08:57 -05002424 case MarkType::kLine:
2425 this->skipToString();
2426 break;
Cary Clark8032b982017-07-28 11:04:54 -04002427 case MarkType::kAlias:
Ben Wagner63fd7602017-10-09 15:45:33 -04002428 case MarkType::kAnchor:
Cary Clark8032b982017-07-28 11:04:54 -04002429 case MarkType::kBug: // fixme: expect number
Cary Clark4855f782018-02-06 09:41:53 -05002430 case MarkType::kDeprecated:
Cary Clark682c58d2018-05-16 07:07:07 -04002431 case MarkType::kDetails:
Cary Clarkac47b882018-01-11 10:35:44 -05002432 case MarkType::kDuration:
Cary Clark682c58d2018-05-16 07:07:07 -04002433 case MarkType::kExperimental:
Cary Clark0d225392018-06-07 09:59:07 -04002434 case MarkType::kFile:
Cary Clarka90ea222018-10-16 10:30:28 -04002435 case MarkType::kFilter:
Cary Clark8032b982017-07-28 11:04:54 -04002436 case MarkType::kHeight:
Cary Clarkf895a422018-02-27 09:54:21 -05002437 case MarkType::kIllustration:
Cary Clark8032b982017-07-28 11:04:54 -04002438 case MarkType::kImage:
Cary Clarkab2621d2018-01-30 10:08:57 -05002439 case MarkType::kIn:
Cary Clark154beea2017-10-26 07:58:48 -04002440 case MarkType::kLiteral:
Cary Clark682c58d2018-05-16 07:07:07 -04002441 case MarkType::kNoJustify:
Cary Clark154beea2017-10-26 07:58:48 -04002442 case MarkType::kOutdent:
Cary Clark8032b982017-07-28 11:04:54 -04002443 case MarkType::kPlatform:
Cary Clark08895c42018-02-01 09:37:32 -05002444 case MarkType::kPopulate:
Cary Clark8032b982017-07-28 11:04:54 -04002445 case MarkType::kReturn:
2446 case MarkType::kSeeAlso:
Cary Clark61dfc3a2018-01-03 08:37:53 -05002447 case MarkType::kSet:
Cary Clark8032b982017-07-28 11:04:54 -04002448 case MarkType::kSubstitute:
Cary Clark8032b982017-07-28 11:04:54 -04002449 case MarkType::kToDo:
2450 case MarkType::kVolatile:
2451 case MarkType::kWidth:
2452 *checkEnd = false; // no name, may have text body
2453 break;
2454 case MarkType::kStdOut:
2455 this->skipNoName();
2456 break; // unnamed
2457 case MarkType::kMember:
2458 builder = this->memberName();
2459 break;
2460 case MarkType::kMethod:
2461 builder = this->methodName();
2462 break;
Cary Clarka560c472017-11-27 10:44:06 -05002463 case MarkType::kTypedef:
2464 builder = this->typedefName();
2465 break;
Cary Clark8032b982017-07-28 11:04:54 -04002466 case MarkType::kParam:
Cary Clark1a8d7622018-03-05 13:26:16 -05002467 // fixme: expect camelCase for param
Cary Clark8032b982017-07-28 11:04:54 -04002468 builder = this->word("", "");
2469 this->skipSpace();
2470 *checkEnd = false;
2471 break;
Cary Clark682c58d2018-05-16 07:07:07 -04002472 case MarkType::kPhraseDef: {
2473 const char* nameEnd = this->anyOf("(\n");
2474 builder = string(fChar, nameEnd - fChar);
2475 this->skipLower();
2476 if (fChar != nameEnd) {
2477 this->reportError("expect lower case only");
2478 break;
2479 }
2480 this->skipTo(nameEnd);
2481 *checkEnd = false;
2482 } break;
Cary Clark8032b982017-07-28 11:04:54 -04002483 case MarkType::kTable:
2484 this->skipNoName();
2485 break; // unnamed
2486 case MarkType::kSubtopic:
2487 case MarkType::kTopic:
2488 // fixme: start with cap, allow space, hyphen, stop on comma
2489 // one topic can have multiple type names delineated by comma
2490 result = this->topicName();
2491 if (result.size() == 0 && this->hasEndToken()) {
2492 break;
2493 }
2494 return result;
2495 default:
2496 // fixme: don't allow silent failures
2497 SkASSERT(0);
2498 }
2499 result.push_back(builder);
2500 return result;
2501}
2502
Cary Clarka560c472017-11-27 10:44:06 -05002503string BmhParser::typedefName() {
2504 if (this->hasEndToken()) {
2505 if (!fParent || !fParent->fName.length()) {
2506 return this->reportError<string>("missing parent typedef name");
2507 }
2508 SkASSERT(fMC == this->peek());
2509 this->next();
2510 SkASSERT(fMC == this->peek());
2511 this->next();
2512 SkASSERT(fMC != this->peek());
2513 return fParent->fName;
2514 }
Cary Clarka560c472017-11-27 10:44:06 -05002515 string builder;
Cary Clark2f466242017-12-11 16:03:17 -05002516 const Definition* parent = this->parentSpace();
2517 if (parent && parent->fName.length() > 0) {
2518 builder = parent->fName + "::";
Cary Clarka560c472017-11-27 10:44:06 -05002519 }
Cary Clark2f466242017-12-11 16:03:17 -05002520 builder += TextParser::typedefName();
Cary Clarka560c472017-11-27 10:44:06 -05002521 return uniqueRootName(builder, MarkType::kTypedef);
2522}
2523
Cary Clark2d4bf5f2018-04-16 08:37:38 -04002524string BmhParser::uniqueName(string base, MarkType markType) {
Cary Clark8032b982017-07-28 11:04:54 -04002525 string builder(base);
2526 if (!builder.length()) {
2527 builder = fParent->fName;
2528 }
2529 if (!fParent) {
2530 return builder;
2531 }
2532 int number = 2;
2533 string numBuilder(builder);
2534 do {
Cary Clarkf895a422018-02-27 09:54:21 -05002535 for (auto& iter : fParent->fChildren) {
Cary Clark8032b982017-07-28 11:04:54 -04002536 if (markType == iter->fMarkType) {
2537 if (iter->fName == numBuilder) {
Cary Clarkf895a422018-02-27 09:54:21 -05002538 if (iter->fDeprecated) {
2539 iter->fClone = true;
2540 } else {
2541 fCloned = true;
2542 }
Cary Clark8032b982017-07-28 11:04:54 -04002543 numBuilder = builder + '_' + to_string(number);
2544 goto tryNext;
2545 }
2546 }
2547 }
2548 break;
2549tryNext: ;
2550 } while (++number);
2551 return numBuilder;
2552}
2553
Cary Clark2d4bf5f2018-04-16 08:37:38 -04002554string BmhParser::uniqueRootName(string base, MarkType markType) {
2555 auto checkName = [markType](const Definition& def, string numBuilder) -> bool {
Cary Clark8032b982017-07-28 11:04:54 -04002556 return markType == def.fMarkType && def.fName == numBuilder;
2557 };
2558
2559 string builder(base);
2560 if (!builder.length()) {
2561 builder = fParent->fName;
2562 }
2563 int number = 2;
2564 string numBuilder(builder);
2565 Definition* cloned = nullptr;
2566 do {
2567 if (fRoot) {
2568 for (auto& iter : fRoot->fBranches) {
2569 if (checkName(*iter.second, numBuilder)) {
2570 cloned = iter.second;
2571 goto tryNext;
2572 }
2573 }
2574 for (auto& iter : fRoot->fLeaves) {
2575 if (checkName(iter.second, numBuilder)) {
2576 cloned = &iter.second;
2577 goto tryNext;
2578 }
2579 }
2580 } else if (fParent) {
2581 for (auto& iter : fParent->fChildren) {
2582 if (checkName(*iter, numBuilder)) {
2583 cloned = &*iter;
2584 goto tryNext;
2585 }
2586 }
2587 }
2588 break;
2589tryNext: ;
2590 if ("()" == builder.substr(builder.length() - 2)) {
2591 builder = builder.substr(0, builder.length() - 2);
2592 }
2593 if (MarkType::kMethod == markType) {
2594 cloned->fCloned = true;
Cary Clarkf895a422018-02-27 09:54:21 -05002595 if (cloned->fDeprecated) {
2596 cloned->fClone = true;
2597 } else {
2598 fCloned = true;
2599 }
2600 } else {
2601 fCloned = true;
Cary Clark8032b982017-07-28 11:04:54 -04002602 }
Cary Clark8032b982017-07-28 11:04:54 -04002603 numBuilder = builder + '_' + to_string(number);
2604 } while (++number);
2605 return numBuilder;
2606}
2607
2608void BmhParser::validate() const {
2609 for (int index = 0; index <= (int) Last_MarkType; ++index) {
Cary Clark2d4bf5f2018-04-16 08:37:38 -04002610 SkASSERT(kMarkProps[index].fMarkType == (MarkType) index);
Cary Clark8032b982017-07-28 11:04:54 -04002611 }
2612 const char* last = "";
2613 for (int index = 0; index <= (int) Last_MarkType; ++index) {
Cary Clark2d4bf5f2018-04-16 08:37:38 -04002614 const char* next = kMarkProps[index].fName;
Cary Clark8032b982017-07-28 11:04:54 -04002615 if (!last[0]) {
2616 last = next;
2617 continue;
2618 }
2619 if (!next[0]) {
2620 continue;
2621 }
2622 SkASSERT(strcmp(last, next) < 0);
2623 last = next;
2624 }
2625}
2626
Cary Clark2d4bf5f2018-04-16 08:37:38 -04002627string BmhParser::word(string prefix, string delimiter) {
Cary Clark8032b982017-07-28 11:04:54 -04002628 string builder(prefix);
2629 this->skipWhiteSpace();
2630 const char* lineEnd = fLine + this->lineLength();
2631 const char* nameStart = fChar;
2632 while (fChar < lineEnd) {
2633 char ch = this->next();
2634 if (' ' >= ch) {
2635 break;
2636 }
2637 if (',' == ch) {
2638 return this->reportError<string>("no comma needed");
2639 break;
2640 }
2641 if (fMC == ch) {
2642 return builder;
2643 }
2644 if (!isalnum(ch) && '_' != ch && ':' != ch && '-' != ch) {
2645 return this->reportError<string>("unexpected char");
2646 }
2647 if (':' == ch) {
2648 // expect pair, and expect word to start with Sk
2649 if (nameStart[0] != 'S' || nameStart[1] != 'k') {
2650 return this->reportError<string>("expected Sk");
2651 }
2652 if (':' != this->peek()) {
2653 return this->reportError<string>("expected ::");
2654 }
2655 this->next();
2656 } else if ('-' == ch) {
2657 // expect word not to start with Sk or kX where X is A-Z
2658 if (nameStart[0] == 'k' && nameStart[1] >= 'A' && nameStart[1] <= 'Z') {
2659 return this->reportError<string>("didn't expected kX");
2660 }
2661 if (nameStart[0] == 'S' && nameStart[1] == 'k') {
2662 return this->reportError<string>("expected Sk");
2663 }
2664 }
2665 }
2666 if (prefix.size()) {
2667 builder += delimiter;
2668 }
2669 builder.append(nameStart, fChar - nameStart - 1);
2670 return builder;
2671}
2672
2673// pass one: parse text, collect definitions
2674// pass two: lookup references
2675
Cary Clark8032b982017-07-28 11:04:54 -04002676static int count_children(const Definition& def, MarkType markType) {
2677 int count = 0;
2678 if (markType == def.fMarkType) {
2679 ++count;
2680 }
2681 for (auto& child : def.fChildren ) {
2682 count += count_children(*child, markType);
2683 }
2684 return count;
2685}
2686
2687int main(int argc, char** const argv) {
Cary Clarka560c472017-11-27 10:44:06 -05002688 BmhParser bmhParser(FLAGS_skip);
Cary Clark8032b982017-07-28 11:04:54 -04002689 bmhParser.validate();
2690
2691 SkCommandLineFlags::SetUsage(
Cary Clarka560c472017-11-27 10:44:06 -05002692 "Common Usage: bookmaker -b path/to/bmh_files -i path/to/include.h -t\n"
Cary Clark8032b982017-07-28 11:04:54 -04002693 " bookmaker -b path/to/bmh_files -e fiddle.json\n"
2694 " ~/go/bin/fiddlecli --input fiddle.json --output fiddleout.json\n"
2695 " bookmaker -b path/to/bmh_files -f fiddleout.json -r path/to/md_files\n"
Cary Clark2f466242017-12-11 16:03:17 -05002696 " bookmaker -a path/to/status.json -x\n"
2697 " bookmaker -a path/to/status.json -p\n");
Cary Clark8032b982017-07-28 11:04:54 -04002698 bool help = false;
2699 for (int i = 1; i < argc; i++) {
2700 if (0 == strcmp("-h", argv[i]) || 0 == strcmp("--help", argv[i])) {
2701 help = true;
2702 for (int j = i + 1; j < argc; j++) {
2703 if (SkStrStartsWith(argv[j], '-')) {
2704 break;
2705 }
2706 help = false;
2707 }
2708 break;
2709 }
2710 }
2711 if (!help) {
2712 SkCommandLineFlags::Parse(argc, argv);
2713 } else {
2714 SkCommandLineFlags::PrintUsage();
Cary Clarkac47b882018-01-11 10:35:44 -05002715 const char* const commands[] = { "", "-h", "bmh", "-h", "examples", "-h", "include",
2716 "-h", "fiddle", "-h", "ref", "-h", "status", "-h", "tokens",
Cary Clark8032b982017-07-28 11:04:54 -04002717 "-h", "crosscheck", "-h", "populate", "-h", "spellcheck" };
Cary Clark7265ea32017-09-14 12:12:32 -04002718 SkCommandLineFlags::Parse(SK_ARRAY_COUNT(commands), commands);
Cary Clark8032b982017-07-28 11:04:54 -04002719 return 0;
2720 }
Cary Clark3bdaa462018-10-31 16:16:54 -04002721 bool runAll = false;
Cary Clark2f466242017-12-11 16:03:17 -05002722 if (FLAGS_bmh.isEmpty() && FLAGS_include.isEmpty() && FLAGS_status.isEmpty()) {
Cary Clark3bdaa462018-10-31 16:16:54 -04002723 FLAGS_status.set(0, "docs/status.json");
2724 if (FLAGS_extract) {
2725 FLAGS_examples.set(0, "fiddle.json");
2726 } else {
2727 FLAGS_fiddle.set(0, "fiddleout.json");
2728 FLAGS_ref.set(0, "site/user/api");
2729 runAll = true;
2730 }
Cary Clark8032b982017-07-28 11:04:54 -04002731 }
Cary Clark2f466242017-12-11 16:03:17 -05002732 if (!FLAGS_bmh.isEmpty() && !FLAGS_status.isEmpty()) {
2733 SkDebugf("requires -b or -a but not both\n");
Cary Clarkbef063a2017-10-31 15:44:45 -04002734 SkCommandLineFlags::PrintUsage();
2735 return 1;
2736 }
Cary Clark2f466242017-12-11 16:03:17 -05002737 if (!FLAGS_include.isEmpty() && !FLAGS_status.isEmpty()) {
2738 SkDebugf("requires -i or -a but not both\n");
2739 SkCommandLineFlags::PrintUsage();
2740 return 1;
2741 }
2742 if (FLAGS_bmh.isEmpty() && FLAGS_status.isEmpty() && FLAGS_catalog) {
2743 SkDebugf("-c requires -b or -a\n");
2744 SkCommandLineFlags::PrintUsage();
2745 return 1;
2746 }
2747 if ((FLAGS_fiddle.isEmpty() || FLAGS_ref.isEmpty()) && FLAGS_catalog) {
2748 SkDebugf("-c requires -f -r\n");
2749 SkCommandLineFlags::PrintUsage();
2750 return 1;
2751 }
2752 if (FLAGS_bmh.isEmpty() && FLAGS_status.isEmpty() && !FLAGS_examples.isEmpty()) {
2753 SkDebugf("-e requires -b or -a\n");
Cary Clark8032b982017-07-28 11:04:54 -04002754 SkCommandLineFlags::PrintUsage();
2755 return 1;
2756 }
Cary Clark2f466242017-12-11 16:03:17 -05002757 if ((FLAGS_include.isEmpty() || FLAGS_bmh.isEmpty()) && FLAGS_status.isEmpty() &&
2758 FLAGS_populate) {
2759 SkDebugf("-p requires -b -i or -a\n");
Cary Clark8032b982017-07-28 11:04:54 -04002760 SkCommandLineFlags::PrintUsage();
2761 return 1;
2762 }
Cary Clark2f466242017-12-11 16:03:17 -05002763 if (FLAGS_bmh.isEmpty() && FLAGS_status.isEmpty() && !FLAGS_ref.isEmpty()) {
2764 SkDebugf("-r requires -b or -a\n");
Cary Clark8032b982017-07-28 11:04:54 -04002765 SkCommandLineFlags::PrintUsage();
2766 return 1;
2767 }
Cary Clark2f466242017-12-11 16:03:17 -05002768 if (FLAGS_bmh.isEmpty() && FLAGS_status.isEmpty() && !FLAGS_spellcheck.isEmpty()) {
2769 SkDebugf("-s requires -b or -a\n");
Cary Clark8032b982017-07-28 11:04:54 -04002770 SkCommandLineFlags::PrintUsage();
2771 return 1;
2772 }
Cary Clarka560c472017-11-27 10:44:06 -05002773 if ((FLAGS_include.isEmpty() || FLAGS_bmh.isEmpty()) && FLAGS_tokens) {
2774 SkDebugf("-t requires -b -i\n");
Cary Clark8032b982017-07-28 11:04:54 -04002775 SkCommandLineFlags::PrintUsage();
2776 return 1;
2777 }
Cary Clark2f466242017-12-11 16:03:17 -05002778 if ((FLAGS_include.isEmpty() || FLAGS_bmh.isEmpty()) && FLAGS_status.isEmpty() &&
2779 FLAGS_crosscheck) {
2780 SkDebugf("-x requires -b -i or -a\n");
Cary Clark8032b982017-07-28 11:04:54 -04002781 SkCommandLineFlags::PrintUsage();
2782 return 1;
2783 }
Cary Clarkac47b882018-01-11 10:35:44 -05002784 bmhParser.reset();
Cary Clark8032b982017-07-28 11:04:54 -04002785 if (!FLAGS_bmh.isEmpty()) {
Cary Clark2dc84ad2018-01-26 12:56:22 -05002786 if (FLAGS_tokens) {
2787 IncludeParser::RemoveFile(FLAGS_bmh[0], FLAGS_include[0]);
2788 }
Cary Clark186d08f2018-04-03 08:43:27 -04002789 if (!bmhParser.parseFile(FLAGS_bmh[0], ".bmh", ParserCommon::OneFile::kNo)) {
Cary Clark8032b982017-07-28 11:04:54 -04002790 return -1;
2791 }
Cary Clark2f466242017-12-11 16:03:17 -05002792 } else if (!FLAGS_status.isEmpty()) {
Cary Clark2f466242017-12-11 16:03:17 -05002793 if (!bmhParser.parseStatus(FLAGS_status[0], ".bmh", StatusFilter::kInProgress)) {
2794 return -1;
2795 }
Cary Clark8032b982017-07-28 11:04:54 -04002796 }
Cary Clarkab2621d2018-01-30 10:08:57 -05002797 if (FLAGS_hack) {
Cary Clark09d80c02018-10-31 12:14:03 -04002798 if (FLAGS_bmh.isEmpty() && FLAGS_status.isEmpty()) {
2799 SkDebugf("-H or --hack requires -a or -b\n");
Cary Clarkab2621d2018-01-30 10:08:57 -05002800 SkCommandLineFlags::PrintUsage();
2801 return 1;
2802 }
2803 HackParser hacker(bmhParser);
Cary Clark09d80c02018-10-31 12:14:03 -04002804 hacker.fDebugOut = FLAGS_stdout;
2805 if (!FLAGS_status.isEmpty() && !hacker.parseStatus(FLAGS_status[0], ".bmh",
2806 StatusFilter::kInProgress)) {
2807 SkDebugf("hack failed\n");
2808 return -1;
2809 }
2810 if (!FLAGS_bmh.isEmpty() && !hacker.parseFile(FLAGS_bmh[0], ".bmh",
2811 ParserCommon::OneFile::kNo)) {
Cary Clarkab2621d2018-01-30 10:08:57 -05002812 SkDebugf("hack failed\n");
2813 return -1;
2814 }
2815 return 0;
2816 }
Cary Clarkac47b882018-01-11 10:35:44 -05002817 if (FLAGS_selfcheck && !SelfCheck(bmhParser)) {
2818 return -1;
2819 }
Cary Clark3bdaa462018-10-31 16:16:54 -04002820 if (!FLAGS_fiddle.isEmpty() && FLAGS_examples.isEmpty()) {
Cary Clarkbef063a2017-10-31 15:44:45 -04002821 FiddleParser fparser(&bmhParser);
Cary Clarkd7895502018-07-18 15:10:08 -04002822 if (!fparser.parseFromFile(FLAGS_fiddle[0])) {
Cary Clark8032b982017-07-28 11:04:54 -04002823 return -1;
2824 }
2825 }
Cary Clark3bdaa462018-10-31 16:16:54 -04002826 if (runAll || (!FLAGS_catalog && !FLAGS_ref.isEmpty())) {
Cary Clark186d08f2018-04-03 08:43:27 -04002827 IncludeParser includeParser;
2828 includeParser.validate();
Cary Clark61313f32018-10-08 14:57:48 -04002829 if (!FLAGS_status.isEmpty() && !includeParser.parseStatus(FLAGS_status[0], ".h",
2830 StatusFilter::kCompleted)) {
2831 return -1;
2832 }
Cary Clark186d08f2018-04-03 08:43:27 -04002833 if (!FLAGS_include.isEmpty() && !includeParser.parseFile(FLAGS_include[0], ".h",
2834 ParserCommon::OneFile::kYes)) {
2835 return -1;
2836 }
Cary Clarka90ea222018-10-16 10:30:28 -04002837 includeParser.writeCodeBlock();
Cary Clark61313f32018-10-08 14:57:48 -04002838 MdOut mdOut(bmhParser, includeParser);
Cary Clark9174bda2017-09-19 17:39:32 -04002839 mdOut.fDebugOut = FLAGS_stdout;
Cary Clark682c58d2018-05-16 07:07:07 -04002840 mdOut.fValidate = FLAGS_validate;
Cary Clark61313f32018-10-08 14:57:48 -04002841 if (!FLAGS_bmh.isEmpty() && mdOut.buildReferences(FLAGS_bmh[0], FLAGS_ref[0])) {
Cary Clark2f466242017-12-11 16:03:17 -05002842 bmhParser.fWroteOut = true;
2843 }
2844 if (!FLAGS_status.isEmpty() && mdOut.buildStatus(FLAGS_status[0], FLAGS_ref[0])) {
Cary Clarkd0530ba2017-09-14 11:25:39 -04002845 bmhParser.fWroteOut = true;
2846 }
Cary Clark682c58d2018-05-16 07:07:07 -04002847 if (FLAGS_validate) {
2848 mdOut.checkAnchors();
2849 }
Cary Clark8032b982017-07-28 11:04:54 -04002850 }
Cary Clark3bdaa462018-10-31 16:16:54 -04002851 if (runAll || (FLAGS_catalog && FLAGS_ref.isEmpty())) {
2852 Catalog cparser(&bmhParser);
2853 cparser.fDebugOut = FLAGS_stdout;
2854 if (!FLAGS_bmh.isEmpty() && !cparser.openCatalog(FLAGS_bmh[0])) {
2855 return -1;
2856 }
2857 if (!FLAGS_status.isEmpty() && !cparser.openStatus(FLAGS_status[0])) {
2858 return -1;
2859 }
2860 if (!cparser.parseFile(FLAGS_fiddle[0], ".txt", ParserCommon::OneFile::kNo)) {
2861 return -1;
2862 }
2863 if (!cparser.closeCatalog(FLAGS_ref[0])) {
2864 return -1;
2865 }
2866 bmhParser.fWroteOut = true;
2867 }
2868 if (FLAGS_tokens) {
2869 IncludeParser includeParser;
2870 includeParser.validate();
2871 if (!includeParser.parseFile(FLAGS_include[0], ".h", ParserCommon::OneFile::kNo)) {
2872 return -1;
2873 }
2874 includeParser.fDebugOut = FLAGS_stdout;
2875 if (includeParser.dumpTokens()) {
2876 bmhParser.fWroteOut = true;
2877 }
2878 }
2879 if (runAll || FLAGS_crosscheck) {
2880 IncludeParser includeParser;
2881 includeParser.validate();
2882 if (!FLAGS_include.isEmpty() &&
2883 !includeParser.parseFile(FLAGS_include[0], ".h", ParserCommon::OneFile::kNo)) {
2884 return -1;
2885 }
2886 if (!FLAGS_status.isEmpty() && !includeParser.parseStatus(FLAGS_status[0], ".h",
2887 StatusFilter::kCompleted)) {
2888 return -1;
2889 }
2890 if (!includeParser.crossCheck(bmhParser)) {
2891 return -1;
2892 }
2893 }
2894 if (runAll || FLAGS_populate) {
2895 IncludeWriter includeWriter;
2896 includeWriter.validate();
2897 if (!FLAGS_include.isEmpty() &&
2898 !includeWriter.parseFile(FLAGS_include[0], ".h", ParserCommon::OneFile::kNo)) {
2899 return -1;
2900 }
2901 if (!FLAGS_status.isEmpty() && !includeWriter.parseStatus(FLAGS_status[0], ".h",
2902 StatusFilter::kCompleted)) {
2903 return -1;
2904 }
2905 includeWriter.fDebugOut = FLAGS_stdout;
2906 if (!includeWriter.populate(bmhParser)) {
2907 return -1;
2908 }
2909 bmhParser.fWroteOut = true;
2910 }
2911 if (!FLAGS_spellcheck.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 }
Cary Clark3bdaa462018-10-31 16:16:54 -04002920 if (!FLAGS_examples.isEmpty()) {
Cary Clark73fa9722017-08-29 17:36:51 -04002921 // check to see if examples have duplicate names
2922 if (!bmhParser.checkExamples()) {
Cary Clark8032b982017-07-28 11:04:54 -04002923 return -1;
2924 }
Cary Clark9174bda2017-09-19 17:39:32 -04002925 bmhParser.fDebugOut = FLAGS_stdout;
Cary Clark73fa9722017-08-29 17:36:51 -04002926 if (!bmhParser.dumpExamples(FLAGS_examples[0])) {
2927 return -1;
Cary Clark8032b982017-07-28 11:04:54 -04002928 }
Cary Clarkd0530ba2017-09-14 11:25:39 -04002929 return 0;
Cary Clark8032b982017-07-28 11:04:54 -04002930 }
Cary Clarkd0530ba2017-09-14 11:25:39 -04002931 if (!bmhParser.fWroteOut) {
Cary Clark3bdaa462018-10-31 16:16:54 -04002932 int examples = 0;
2933 int methods = 0;
2934 int topics = 0;
Cary Clarkd0530ba2017-09-14 11:25:39 -04002935 for (const auto& topic : bmhParser.fTopicMap) {
2936 if (topic.second->fParent) {
2937 continue;
2938 }
2939 examples += count_children(*topic.second, MarkType::kExample);
2940 methods += count_children(*topic.second, MarkType::kMethod);
2941 topics += count_children(*topic.second, MarkType::kSubtopic);
2942 topics += count_children(*topic.second, MarkType::kTopic);
Cary Clark8032b982017-07-28 11:04:54 -04002943 }
Ben Wagner63fd7602017-10-09 15:45:33 -04002944 SkDebugf("topics=%d classes=%d methods=%d examples=%d\n",
Cary Clarkd0530ba2017-09-14 11:25:39 -04002945 bmhParser.fTopicMap.size(), bmhParser.fClassMap.size(),
2946 methods, examples);
Cary Clark8032b982017-07-28 11:04:54 -04002947 }
Cary Clark8032b982017-07-28 11:04:54 -04002948 return 0;
2949}
Cary Clark09d80c02018-10-31 12:14:03 -04002950
2951void NameMap::copyToParent(NameMap* parent) const {
2952 size_t colons = fName.rfind("::");
2953 string topName = string::npos == colons ? fName : fName.substr(colons + 2);
2954 for (auto& entry : fRefMap) {
2955 string scoped = topName + "::" + entry.first;
2956 SkASSERT(parent->fRefMap.end() == parent->fRefMap.find(scoped));
2957 parent->fRefMap[scoped] = entry.second;
2958 auto scopedLinkIter = fLinkMap.find(entry.first);
2959 if (fLinkMap.end() != scopedLinkIter) {
2960 SkASSERT(parent->fLinkMap.end() == parent->fLinkMap.find(scoped));
2961 parent->fLinkMap[scoped] = scopedLinkIter->second;
2962 }
2963 }
2964}