blob: a4968efef18931ab96dd836b0d9b87275326ab30 [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 Clark2f466242017-12-11 16:03:17 -050015DEFINE_string2(status, a, "", "File containing status of documentation. (Use in place of -b -i)");
Cary Clarkbc5697d2017-10-04 14:31:33 -040016DEFINE_string2(bmh, b, "", "Path to a *.bmh file or a directory.");
Cary Clarkbef063a2017-10-31 15:44:45 -040017DEFINE_bool2(catalog, c, false, "Write example catalog.htm. (Requires -b -f -r)");
Cary Clarkbc5697d2017-10-04 14:31:33 -040018DEFINE_string2(examples, e, "", "File of fiddlecli input, usually fiddle.json (For now, disables -r -f -s)");
19DEFINE_string2(fiddle, f, "", "File of fiddlecli output, usually fiddleout.json.");
Cary Clark5081eed2018-01-22 07:55:48 -050020DEFINE_bool2(hack, H, false, "Do a find/replace hack to update all *.bmh files. (Requires -b)");
21// h is reserved for help
Cary Clarkbc5697d2017-10-04 14:31:33 -040022DEFINE_string2(include, i, "", "Path to a *.h file or a directory.");
Cary Clarkac47b882018-01-11 10:35:44 -050023DEFINE_bool2(selfcheck, k, false, "Check bmh against itself. (Requires -b)");
Cary Clarkbc5697d2017-10-04 14:31:33 -040024DEFINE_bool2(stdout, o, false, "Write file out to standard out.");
25DEFINE_bool2(populate, p, false, "Populate include from bmh. (Requires -b -i)");
Cary Clark5081eed2018-01-22 07:55:48 -050026// q is reserved for quiet
Cary Clark7cfcbca2018-01-04 16:11:51 -050027DEFINE_string2(ref, r, "", "Resolve refs and write *.md files to path. (Requires -b -f)");
Cary Clarkbc5697d2017-10-04 14:31:33 -040028DEFINE_string2(spellcheck, s, "", "Spell-check [once, all, mispelling]. (Requires -b)");
Cary Clarka560c472017-11-27 10:44:06 -050029DEFINE_bool2(tokens, t, false, "Write bmh from include. (Requires -b -i)");
Cary Clarkbc5697d2017-10-04 14:31:33 -040030DEFINE_bool2(crosscheck, x, false, "Check bmh against includes. (Requires -b -i)");
Cary Clark5081eed2018-01-22 07:55:48 -050031// v is reserved for verbose
Cary Clark682c58d2018-05-16 07:07:07 -040032DEFINE_bool2(validate, V, false, "Validate that all anchor references have definitions. (Requires -r)");
Cary Clark884dd7d2017-10-11 10:37:52 -040033DEFINE_bool2(skip, z, false, "Skip degenerate missed in legacy preprocessor.");
Cary Clark8032b982017-07-28 11:04:54 -040034
Cary Clark682c58d2018-05-16 07:07:07 -040035/* todos:
Cary Clark8032b982017-07-28 11:04:54 -040036
Cary Clarka90ea222018-10-16 10:30:28 -040037if #Subtopic contains #SeeAlso or #Example generate horizontal rule at end
38constexpr populated with filter inside subtopic does not have definition body
39
Cary Clark8032b982017-07-28 11:04:54 -040040#List needs '# content ##', formatting
Cary Clark186d08f2018-04-03 08:43:27 -040041rewrap text to fit in some number of columns
42#Literal is inflexible, making the entire #Code block link-less (see $Literal in SkImageInfo)
Cary Clark80247e52018-07-11 16:18:41 -040043 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 -040044add check to require #Const to contain #Code block if defining const or constexpr (enum consts have
Cary Clark80247e52018-07-11 16:18:41 -040045 #Code blocks inside the #Enum def)
Cary Clarkd2ca79c2018-08-10 13:09:13 -040046subclasses (e.g. Iter in SkPath) need to check for #Line and generate overview
47 subclass methods should also disallow #In
Cary Clark682c58d2018-05-16 07:07:07 -040048
Cary Clark682c58d2018-05-16 07:07:07 -040049It's awkward that phrase param is a child of the phrase def. Since phrase refs may also be children,
50there is special case code to skip phrase def when looking for additional substitutions in the
51phrase def. Could put it in the token list instead I guess, or make a definition subclass used
52by phrase def with an additional slot...
53
Cary Clark682c58d2018-05-16 07:07:07 -040054rearrange const out for md so that const / value / short description comes first in a table,
55followed by more elaborate descriptions, examples, seealso. In md.cpp, look to see if #Subtopic
56has #Const children. If so, generate a summary table first.
57Or, only allow #Line and moderate text description in #Const. Put more verbose text, example,
58seealso, in subsequent #SubTopic. Alpha_Type does this and it looks good.
59
Cary Clark61313f32018-10-08 14:57:48 -040060IPoint is awkward. SkPoint and SkIPoint are named things; Point is a topic, which
61refers to float points or integer points. There needn't be an IPoint topic.
62One way to resolve this would be to combine SkPoint_Reference and SkIPoint_Reference into
63Point_Reference that then contains both structs (or just move SKIPoint into SkPoint_Reference).
64Most Point references would be replaced with SkPoint / SkIPoint (if that's what they mean),
65or remain Point if the text indicates the concept rather one of the C structs.
66
Cary Clarkac47b882018-01-11 10:35:44 -050067see head of selfCheck.cpp for additional todos
Cary Clark80247e52018-07-11 16:18:41 -040068see head of spellCheck.cpp for additional todos
Cary Clark8032b982017-07-28 11:04:54 -040069 */
70
Ben Wagner63fd7602017-10-09 15:45:33 -040071/*
Cary Clark8032b982017-07-28 11:04:54 -040072 class contains named struct, enum, enum-member, method, topic, subtopic
73 everything contained by class is uniquely named
74 contained names may be reused by other classes
75 method contains named parameters
76 parameters may be reused in other methods
77 */
78
Cary Clark2d4bf5f2018-04-16 08:37:38 -040079#define M(mt) (1LL << (int) MarkType::k##mt)
80#define M_D M(Description)
81#define M_CS M(Class) | M(Struct)
Cary Clark682c58d2018-05-16 07:07:07 -040082#define M_MD M(Method) | M(Define)
83#define M_MDCM M_MD | M(Const) | M(Member)
Cary Clark2d4bf5f2018-04-16 08:37:38 -040084#define M_ST M(Subtopic) | M(Topic)
85#define M_CSST M_CS | M_ST
86#ifdef M_E
87#undef M_E
88#endif
89#define M_E M(Enum) | M(EnumClass)
90
91#define R_Y Resolvable::kYes
92#define R_N Resolvable::kNo
93#define R_O Resolvable::kOut
Cary Clark2be81cf2018-09-13 12:04:30 -040094#define R_K Resolvable::kCode
Cary Clark2d4bf5f2018-04-16 08:37:38 -040095#define R_F Resolvable::kFormula
96#define R_C Resolvable::kClone
97
98#define E_Y Exemplary::kYes
99#define E_N Exemplary::kNo
100#define E_O Exemplary::kOptional
101
Cary Clark682c58d2018-05-16 07:07:07 -0400102// ToDo: add column to denote which marks are one-liners
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400103BmhParser::MarkProps BmhParser::kMarkProps[] = {
104// names without formal definitions (e.g. Column) aren't included
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400105 { "", MarkType::kNone, R_Y, E_N, 0 }
106, { "A", MarkType::kAnchor, R_N, E_N, 0 }
Cary Clark682c58d2018-05-16 07:07:07 -0400107, { "Alias", MarkType::kAlias, R_N, E_N, M_ST | M(Const) }
108, { "Bug", MarkType::kBug, R_N, E_N, M_CSST | M_MDCM | M_E
109 | M(Example) | M(NoExample) }
110, { "Class", MarkType::kClass, R_Y, E_O, M_CSST }
Cary Clark2be81cf2018-09-13 12:04:30 -0400111, { "Code", MarkType::kCode, R_K, E_N, M_CSST | M_E | M_MD | M(Typedef) }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400112, { "", MarkType::kColumn, R_Y, E_N, M(Row) }
113, { "", MarkType::kComment, R_N, E_N, 0 }
Cary Clark224c7002018-06-27 11:00:21 -0400114, { "Const", MarkType::kConst, R_Y, E_O, M_E | M_CSST }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400115, { "Define", MarkType::kDefine, R_O, E_Y, M_ST }
Cary Clark682c58d2018-05-16 07:07:07 -0400116, { "Deprecated", MarkType::kDeprecated, R_Y, E_N, M_CS | M_MDCM | M_E }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400117, { "Description", MarkType::kDescription, R_Y, E_N, M(Example) | M(NoExample) }
Cary Clark682c58d2018-05-16 07:07:07 -0400118, { "Details", MarkType::kDetails, R_N, E_N, M(Const) }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400119, { "Duration", MarkType::kDuration, R_N, E_N, M(Example) | M(NoExample) }
Cary Clark682c58d2018-05-16 07:07:07 -0400120, { "Enum", MarkType::kEnum, R_Y, E_O, M_CSST }
121, { "EnumClass", MarkType::kEnumClass, R_Y, E_O, M_CSST }
Cary Clark224c7002018-06-27 11:00:21 -0400122, { "Example", MarkType::kExample, R_O, E_N, M_CSST | M_E | M_MD | M(Const) }
Cary Clark682c58d2018-05-16 07:07:07 -0400123, { "Experimental", MarkType::kExperimental, R_Y, E_N, M_CS | M_MDCM | M_E }
124, { "External", MarkType::kExternal, R_Y, E_N, 0 }
Cary Clark0d225392018-06-07 09:59:07 -0400125, { "File", MarkType::kFile, R_Y, E_N, M(Topic) }
Cary Clarka90ea222018-10-16 10:30:28 -0400126, { "Filter", MarkType::kFilter, R_N, E_N, M(Subtopic) | M(Code) }
Cary Clark682c58d2018-05-16 07:07:07 -0400127, { "Formula", MarkType::kFormula, R_F, E_N, M(Column) | M(Description)
128 | M_E | M_ST | M_MDCM }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400129, { "Function", MarkType::kFunction, R_O, E_N, M(Example) | M(NoExample) }
130, { "Height", MarkType::kHeight, R_N, E_N, M(Example) | M(NoExample) }
Cary Clark682c58d2018-05-16 07:07:07 -0400131, { "Illustration", MarkType::kIllustration, R_N, E_N, M_CSST | M_MD }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400132, { "Image", MarkType::kImage, R_N, E_N, M(Example) | M(NoExample) }
Cary Clarka90ea222018-10-16 10:30:28 -0400133, { "In", MarkType::kIn, R_N, E_N, M_CSST | M_E | M(Method) | M(Typedef) | M(Code) }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400134, { "Legend", MarkType::kLegend, R_Y, E_N, M(Table) }
135, { "Line", MarkType::kLine, R_N, E_N, M_CSST | M_E | M(Method) | M(Typedef) }
136, { "", MarkType::kLink, R_N, E_N, M(Anchor) }
137, { "List", MarkType::kList, R_Y, E_N, M(Method) | M_CSST | M_E | M_D }
138, { "Literal", MarkType::kLiteral, R_N, E_N, M(Code) }
139, { "", MarkType::kMarkChar, R_N, E_N, 0 }
Cary Clark61313f32018-10-08 14:57:48 -0400140, { "Member", MarkType::kMember, R_Y, E_O, M_CSST }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400141, { "Method", MarkType::kMethod, R_Y, E_Y, M_CSST }
Cary Clark682c58d2018-05-16 07:07:07 -0400142, { "NoExample", MarkType::kNoExample, R_N, E_N, M_CSST | M_E | M_MD }
143, { "NoJustify", MarkType::kNoJustify, R_N, E_N, M(Const) | M(Member) }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400144, { "Outdent", MarkType::kOutdent, R_N, E_N, M(Code) }
145, { "Param", MarkType::kParam, R_Y, E_N, M(Method) | M(Define) }
Cary Clark224c7002018-06-27 11:00:21 -0400146, { "PhraseDef", MarkType::kPhraseDef, R_Y, E_N, M_ST }
Cary Clark682c58d2018-05-16 07:07:07 -0400147, { "", MarkType::kPhraseParam, R_Y, E_N, 0 }
148, { "", MarkType::kPhraseRef, R_N, E_N, 0 }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400149, { "Platform", MarkType::kPlatform, R_N, E_N, M(Example) | M(NoExample) }
Cary Clarka90ea222018-10-16 10:30:28 -0400150, { "Populate", MarkType::kPopulate, R_N, E_N, M(Code) }
Cary Clark224c7002018-06-27 11:00:21 -0400151, { "Private", MarkType::kPrivate, R_N, E_N, M_CSST | M_MDCM | M_E }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400152, { "Return", MarkType::kReturn, R_Y, E_N, M(Method) }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400153, { "", MarkType::kRow, R_Y, E_N, M(Table) | M(List) }
Cary Clark682c58d2018-05-16 07:07:07 -0400154, { "SeeAlso", MarkType::kSeeAlso, R_C, E_N, M_CSST | M_E | M_MD | M(Typedef) }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400155, { "Set", MarkType::kSet, R_N, E_N, M(Example) | M(NoExample) }
156, { "StdOut", MarkType::kStdOut, R_N, E_N, M(Example) | M(NoExample) }
Cary Clark682c58d2018-05-16 07:07:07 -0400157, { "Struct", MarkType::kStruct, R_Y, E_O, M(Class) | M_ST }
Cary Clark137b8742018-05-30 09:21:49 -0400158, { "Substitute", MarkType::kSubstitute, R_N, E_N, M(Alias) | M_ST }
Cary Clark61313f32018-10-08 14:57:48 -0400159, { "Subtopic", MarkType::kSubtopic, R_Y, E_Y, M_CSST | M_E }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400160, { "Table", MarkType::kTable, R_Y, E_N, M(Method) | M_CSST | M_E }
Cary Clark682c58d2018-05-16 07:07:07 -0400161, { "Template", MarkType::kTemplate, R_Y, E_N, M_CSST }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400162, { "", MarkType::kText, R_N, E_N, 0 }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400163, { "ToDo", MarkType::kToDo, R_N, E_N, 0 }
Cary Clark682c58d2018-05-16 07:07:07 -0400164, { "Topic", MarkType::kTopic, R_Y, E_Y, 0 }
Cary Clark61313f32018-10-08 14:57:48 -0400165, { "Typedef", MarkType::kTypedef, R_Y, E_O, M_CSST | M_E }
Cary Clark682c58d2018-05-16 07:07:07 -0400166, { "Union", MarkType::kUnion, R_Y, E_N, M_CSST }
Cary Clark61313f32018-10-08 14:57:48 -0400167, { "Using", MarkType::kUsing, R_Y, E_O, M_CSST }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400168, { "Volatile", MarkType::kVolatile, R_N, E_N, M(StdOut) }
169, { "Width", MarkType::kWidth, R_N, E_N, M(Example) | M(NoExample) }
170};
171
172#undef R_O
173#undef R_N
174#undef R_Y
Cary Clark2be81cf2018-09-13 12:04:30 -0400175#undef R_K
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400176#undef R_F
177#undef R_C
178
179#undef M_E
180#undef M_CSST
181#undef M_ST
182#undef M_CS
Cary Clark682c58d2018-05-16 07:07:07 -0400183#undef M_MCD
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400184#undef M_D
185#undef M
186
187#undef E_Y
188#undef E_N
189#undef E_O
190
Cary Clark8032b982017-07-28 11:04:54 -0400191bool BmhParser::addDefinition(const char* defStart, bool hasEnd, MarkType markType,
Cary Clarkf895a422018-02-27 09:54:21 -0500192 const vector<string>& typeNameBuilder, HasTag hasTag) {
Cary Clark8032b982017-07-28 11:04:54 -0400193 Definition* definition = nullptr;
194 switch (markType) {
195 case MarkType::kComment:
196 if (!this->skipToDefinitionEnd(markType)) {
197 return false;
198 }
199 return true;
200 // these types may be referred to by name
201 case MarkType::kClass:
202 case MarkType::kStruct:
203 case MarkType::kConst:
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400204 case MarkType::kDefine:
Cary Clark8032b982017-07-28 11:04:54 -0400205 case MarkType::kEnum:
206 case MarkType::kEnumClass:
207 case MarkType::kMember:
208 case MarkType::kMethod:
209 case MarkType::kTypedef: {
210 if (!typeNameBuilder.size()) {
211 return this->reportError<bool>("unnamed markup");
212 }
213 if (typeNameBuilder.size() > 1) {
214 return this->reportError<bool>("expected one name only");
215 }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400216 string name = typeNameBuilder[0];
Cary Clark8032b982017-07-28 11:04:54 -0400217 if (nullptr == fRoot) {
218 fRoot = this->findBmhObject(markType, name);
219 fRoot->fFileName = fFileName;
220 definition = fRoot;
221 } else {
222 if (nullptr == fParent) {
223 return this->reportError<bool>("expected parent");
224 }
225 if (fParent == fRoot && hasEnd) {
226 RootDefinition* rootParent = fRoot->rootParent();
227 if (rootParent) {
228 fRoot = rootParent;
229 }
230 definition = fParent;
231 } else {
Cary Clarkce101242017-09-01 15:51:02 -0400232 if (!hasEnd && fRoot->find(name, RootDefinition::AllowParens::kNo)) {
Cary Clark8032b982017-07-28 11:04:54 -0400233 return this->reportError<bool>("duplicate symbol");
234 }
Cary Clark61313f32018-10-08 14:57:48 -0400235 if (MarkType::kStruct == markType || MarkType::kClass == markType
236 || MarkType::kEnumClass == markType) {
Cary Clark8032b982017-07-28 11:04:54 -0400237 // if class or struct, build fRoot hierarchy
238 // and change isDefined to search all parents of fRoot
239 SkASSERT(!hasEnd);
240 RootDefinition* childRoot = new RootDefinition;
241 (fRoot->fBranches)[name] = childRoot;
242 childRoot->setRootParent(fRoot);
243 childRoot->fFileName = fFileName;
244 fRoot = childRoot;
245 definition = fRoot;
246 } else {
247 definition = &fRoot->fLeaves[name];
248 }
249 }
250 }
251 if (hasEnd) {
252 Exemplary hasExample = Exemplary::kNo;
253 bool hasExcluder = false;
254 for (auto child : definition->fChildren) {
255 if (MarkType::kExample == child->fMarkType) {
256 hasExample = Exemplary::kYes;
257 }
258 hasExcluder |= MarkType::kPrivate == child->fMarkType
259 || MarkType::kDeprecated == child->fMarkType
260 || MarkType::kExperimental == child->fMarkType
261 || MarkType::kNoExample == child->fMarkType;
262 }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400263 if (kMarkProps[(int) markType].fExemplary != hasExample
264 && kMarkProps[(int) markType].fExemplary != Exemplary::kOptional) {
Cary Clark8032b982017-07-28 11:04:54 -0400265 if (string::npos == fFileName.find("undocumented")
266 && !hasExcluder) {
Ben Wagner63fd7602017-10-09 15:45:33 -0400267 hasExample == Exemplary::kNo ?
268 this->reportWarning("missing example") :
Cary Clark8032b982017-07-28 11:04:54 -0400269 this->reportWarning("unexpected example");
270 }
271
272 }
273 if (MarkType::kMethod == markType) {
274 if (fCheckMethods && !definition->checkMethod()) {
275 return false;
276 }
277 }
Cary Clarkf895a422018-02-27 09:54:21 -0500278 if (HasTag::kYes == hasTag) {
279 if (!this->checkEndMarker(markType, definition->fName)) {
280 return false;
281 }
282 }
Cary Clark8032b982017-07-28 11:04:54 -0400283 if (!this->popParentStack(definition)) {
284 return false;
285 }
Cary Clark06c20f32018-03-20 15:53:27 -0400286 if (fRoot == definition) {
287 fRoot = nullptr;
288 }
Cary Clark8032b982017-07-28 11:04:54 -0400289 } else {
290 definition->fStart = defStart;
291 this->skipSpace();
292 definition->fFileName = fFileName;
293 definition->fContentStart = fChar;
294 definition->fLineCount = fLineCount;
295 definition->fClone = fCloned;
296 if (MarkType::kConst == markType) {
297 // todo: require that fChar points to def on same line as markup
298 // additionally add definition to class children if it is not already there
299 if (definition->fParent != fRoot) {
300// fRoot->fChildren.push_back(definition);
301 }
302 }
Cary Clark82f1f742018-06-28 08:50:35 -0400303 SkASSERT(string::npos == name.find('\n'));
Cary Clark8032b982017-07-28 11:04:54 -0400304 definition->fName = name;
305 if (MarkType::kMethod == markType) {
306 if (string::npos != name.find(':', 0)) {
307 definition->setCanonicalFiddle();
308 } else {
309 definition->fFiddle = name;
310 }
311 } else {
Cary Clarka560c472017-11-27 10:44:06 -0500312 definition->fFiddle = Definition::NormalizedName(name);
Cary Clark8032b982017-07-28 11:04:54 -0400313 }
314 definition->fMarkType = markType;
Cary Clarkd0530ba2017-09-14 11:25:39 -0400315 definition->fAnonymous = fAnonymous;
Cary Clark8032b982017-07-28 11:04:54 -0400316 this->setAsParent(definition);
317 }
318 } break;
319 case MarkType::kTopic:
320 case MarkType::kSubtopic:
321 SkASSERT(1 == typeNameBuilder.size());
322 if (!hasEnd) {
323 if (!typeNameBuilder.size()) {
324 return this->reportError<bool>("unnamed topic");
325 }
Cary Clark1a8d7622018-03-05 13:26:16 -0500326 fTopics.emplace_front(markType, defStart, fLineCount, fParent, fMC);
Cary Clark8032b982017-07-28 11:04:54 -0400327 RootDefinition* rootDefinition = &fTopics.front();
328 definition = rootDefinition;
329 definition->fFileName = fFileName;
330 definition->fContentStart = fChar;
Cary Clark2a8c48b2018-02-15 17:31:24 -0500331 if (MarkType::kTopic == markType) {
332 if (fParent) {
333 return this->reportError<bool>("#Topic must be root");
334 }
335 // topic name is unappended
336 definition->fName = typeNameBuilder[0];
337 } else {
338 if (!fParent) {
339 return this->reportError<bool>("#Subtopic may not be root");
340 }
341 Definition* parent = fParent;
342 while (MarkType::kTopic != parent->fMarkType && MarkType::kSubtopic != parent->fMarkType) {
343 parent = parent->fParent;
344 if (!parent) {
345 // subtopic must have subtopic or topic in parent chain
346 return this->reportError<bool>("#Subtopic missing parent");
347 }
348 }
349 if (MarkType::kSubtopic == parent->fMarkType) {
350 // subtopic prepends parent subtopic name, but not parent topic name
351 definition->fName = parent->fName + '_';
352 }
353 definition->fName += typeNameBuilder[0];
354 definition->fFiddle = parent->fFiddle + '_';
Cary Clark8032b982017-07-28 11:04:54 -0400355 }
Cary Clarka560c472017-11-27 10:44:06 -0500356 definition->fFiddle += Definition::NormalizedName(typeNameBuilder[0]);
Cary Clark8032b982017-07-28 11:04:54 -0400357 this->setAsParent(definition);
358 }
359 {
Cary Clark08895c42018-02-01 09:37:32 -0500360 SkASSERT(hasEnd ? fParent : definition);
361 string fullTopic = hasEnd ? fParent->fFiddle : definition->fFiddle;
Cary Clark8032b982017-07-28 11:04:54 -0400362 Definition* defPtr = fTopicMap[fullTopic];
363 if (hasEnd) {
Cary Clarkf895a422018-02-27 09:54:21 -0500364 if (HasTag::kYes == hasTag && !this->checkEndMarker(markType, fullTopic)) {
365 return false;
366 }
Cary Clark8032b982017-07-28 11:04:54 -0400367 if (!definition) {
368 definition = defPtr;
369 } else if (definition != defPtr) {
370 return this->reportError<bool>("mismatched topic");
371 }
372 } else {
373 if (nullptr != defPtr) {
374 return this->reportError<bool>("already declared topic");
375 }
376 fTopicMap[fullTopic] = definition;
377 }
378 }
379 if (hasEnd) {
380 if (!this->popParentStack(definition)) {
381 return false;
382 }
383 }
384 break;
Cary Clark2be81cf2018-09-13 12:04:30 -0400385 case MarkType::kFormula:
386 // hasEnd : single line / multiple line
387 if (!fParent || MarkType::kFormula != fParent->fMarkType) {
388 SkASSERT(!definition || MarkType::kFormula == definition->fMarkType);
389 fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC);
390 definition = &fMarkup.front();
391 definition->fContentStart = fChar;
392 definition->fName = typeNameBuilder[0];
393 definition->fFiddle = fParent->fFiddle;
394 fParent = definition;
395 } else {
396 SkASSERT(fParent && MarkType::kFormula == fParent->fMarkType);
397 SkASSERT(fMC == defStart[0]);
398 SkASSERT(fMC == defStart[-1]);
399 definition = fParent;
400 definition->fTerminator = fChar;
401 if (!this->popParentStack(definition)) {
402 return false;
403 }
404 this->parseHashFormula(definition);
405 fParent->fChildren.push_back(definition);
406 }
407 break;
Cary Clark8032b982017-07-28 11:04:54 -0400408 // these types are children of parents, but are not in named maps
Cary Clark8032b982017-07-28 11:04:54 -0400409 case MarkType::kDescription:
410 case MarkType::kStdOut:
411 // may be one-liner
Cary Clark137b8742018-05-30 09:21:49 -0400412 case MarkType::kAlias:
Cary Clark8032b982017-07-28 11:04:54 -0400413 case MarkType::kNoExample:
414 case MarkType::kParam:
Cary Clark1a8d7622018-03-05 13:26:16 -0500415 case MarkType::kPhraseDef:
Cary Clark8032b982017-07-28 11:04:54 -0400416 case MarkType::kReturn:
417 case MarkType::kToDo:
418 if (hasEnd) {
419 if (markType == fParent->fMarkType) {
420 definition = fParent;
421 if (MarkType::kBug == markType || MarkType::kReturn == markType
422 || MarkType::kToDo == markType) {
423 this->skipNoName();
424 }
425 if (!this->popParentStack(fParent)) { // if not one liner, pop
426 return false;
427 }
Cary Clark1a8d7622018-03-05 13:26:16 -0500428 if (MarkType::kParam == markType || MarkType::kReturn == markType
429 || MarkType::kPhraseDef == markType) {
Cary Clarka523d2d2017-08-30 08:58:10 -0400430 if (!this->checkParamReturn(definition)) {
431 return false;
Cary Clark579985c2017-07-31 11:48:27 -0400432 }
433 }
Cary Clark1a8d7622018-03-05 13:26:16 -0500434 if (MarkType::kPhraseDef == markType) {
435 string key = definition->fName;
436 if (fPhraseMap.end() != fPhraseMap.find(key)) {
437 this->reportError<bool>("duplicate phrase key");
438 }
439 fPhraseMap[key] = definition;
440 }
Cary Clark8032b982017-07-28 11:04:54 -0400441 } else {
Cary Clark1a8d7622018-03-05 13:26:16 -0500442 fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC);
Cary Clark8032b982017-07-28 11:04:54 -0400443 definition = &fMarkup.front();
444 definition->fName = typeNameBuilder[0];
Cary Clark73fa9722017-08-29 17:36:51 -0400445 definition->fFiddle = fParent->fFiddle;
Cary Clark8032b982017-07-28 11:04:54 -0400446 definition->fContentStart = fChar;
Cary Clark1a8d7622018-03-05 13:26:16 -0500447 string endBracket;
448 endBracket += fMC;
449 endBracket += fMC;
450 definition->fContentEnd = this->trimmedBracketEnd(endBracket);
451 this->skipToEndBracket(endBracket.c_str());
Cary Clark8032b982017-07-28 11:04:54 -0400452 SkAssertResult(fMC == this->next());
453 SkAssertResult(fMC == this->next());
454 definition->fTerminator = fChar;
Cary Clark1a8d7622018-03-05 13:26:16 -0500455 TextParser checkForChildren(definition);
456 if (checkForChildren.strnchr(fMC, definition->fContentEnd)) {
457 this->reportError<bool>("put ## on separate line");
458 }
Cary Clark8032b982017-07-28 11:04:54 -0400459 fParent->fChildren.push_back(definition);
460 }
Cary Clark137b8742018-05-30 09:21:49 -0400461 if (MarkType::kAlias == markType) {
462 const char* end = definition->fChildren.size() > 0 ?
463 definition->fChildren[0]->fStart : definition->fContentEnd;
464 TextParser parser(definition->fFileName, definition->fContentStart, end,
465 definition->fLineCount);
466 parser.trimEnd();
467 string key = string(parser.fStart, parser.lineLength());
468 if (fAliasMap.end() != fAliasMap.find(key)) {
469 return this->reportError<bool>("duplicate alias");
470 }
471 fAliasMap[key] = definition;
472 definition->fFiddle = definition->fParent->fFiddle;
473 }
Cary Clark8032b982017-07-28 11:04:54 -0400474 break;
Cary Clark682c58d2018-05-16 07:07:07 -0400475 } else if (MarkType::kPhraseDef == markType) {
476 bool hasParams = '(' == this->next();
477 fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC);
478 definition = &fMarkup.front();
479 definition->fName = typeNameBuilder[0];
480 definition->fFiddle = fParent->fFiddle;
481 definition->fContentStart = fChar;
482 if (hasParams) {
483 char lastChar;
484 do {
485 const char* subEnd = this->anyOf(",)\n");
486 if (!subEnd || '\n' == *subEnd) {
487 return this->reportError<bool>("unexpected phrase list end");
488 }
489 fMarkup.emplace_front(MarkType::kPhraseParam, fChar, fLineCount, fParent,
490 fMC);
491 Definition* phraseParam = &fMarkup.front();
492 phraseParam->fContentStart = fChar;
493 phraseParam->fContentEnd = subEnd;
494 phraseParam->fName = string(fChar, subEnd - fChar);
495 definition->fChildren.push_back(phraseParam);
496 this->skipTo(subEnd);
497 lastChar = this->next();
498 phraseParam->fTerminator = fChar;
499 } while (')' != lastChar);
500 this->skipWhiteSpace();
501 definition->fContentStart = fChar;
502 }
503 this->setAsParent(definition);
504 break;
Cary Clark8032b982017-07-28 11:04:54 -0400505 }
506 // not one-liners
507 case MarkType::kCode:
Cary Clark8032b982017-07-28 11:04:54 -0400508 case MarkType::kExample:
Cary Clark0d225392018-06-07 09:59:07 -0400509 case MarkType::kFile:
Cary Clark8032b982017-07-28 11:04:54 -0400510 case MarkType::kFunction:
511 case MarkType::kLegend:
512 case MarkType::kList:
513 case MarkType::kPrivate:
514 case MarkType::kTable:
Cary Clark8032b982017-07-28 11:04:54 -0400515 if (hasEnd) {
516 definition = fParent;
517 if (markType != fParent->fMarkType) {
518 return this->reportError<bool>("end element mismatch");
519 } else if (!this->popParentStack(fParent)) {
520 return false;
521 }
522 if (MarkType::kExample == markType) {
523 if (definition->fChildren.size() == 0) {
524 TextParser emptyCheck(definition);
525 if (emptyCheck.eof() || !emptyCheck.skipWhiteSpace()) {
Cary Clark884dd7d2017-10-11 10:37:52 -0400526 return this->reportError<bool>("missing example body");
Cary Clark8032b982017-07-28 11:04:54 -0400527 }
528 }
Cary Clark1a8d7622018-03-05 13:26:16 -0500529// can't do this here; phrase refs may not have been defined yet
530// this->setWrapper(definition);
Cary Clark8032b982017-07-28 11:04:54 -0400531 }
532 } else {
Cary Clark1a8d7622018-03-05 13:26:16 -0500533 fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC);
Cary Clark8032b982017-07-28 11:04:54 -0400534 definition = &fMarkup.front();
535 definition->fContentStart = fChar;
536 definition->fName = typeNameBuilder[0];
537 definition->fFiddle = fParent->fFiddle;
538 char suffix = '\0';
539 bool tryAgain;
540 do {
541 tryAgain = false;
542 for (const auto& child : fParent->fChildren) {
543 if (child->fFiddle == definition->fFiddle) {
544 if (MarkType::kExample != child->fMarkType) {
545 continue;
546 }
547 if ('\0' == suffix) {
548 suffix = 'a';
549 } else if (++suffix > 'z') {
550 return reportError<bool>("too many examples");
551 }
552 definition->fFiddle = fParent->fFiddle + '_';
553 definition->fFiddle += suffix;
554 tryAgain = true;
555 break;
556 }
557 }
558 } while (tryAgain);
559 this->setAsParent(definition);
560 }
561 break;
562 // always treated as one-liners (can't detect misuse easily)
Ben Wagner63fd7602017-10-09 15:45:33 -0400563 case MarkType::kAnchor:
Cary Clark4855f782018-02-06 09:41:53 -0500564 case MarkType::kBug:
Cary Clark4855f782018-02-06 09:41:53 -0500565 case MarkType::kDeprecated:
Cary Clark682c58d2018-05-16 07:07:07 -0400566 case MarkType::kDetails:
Cary Clarkac47b882018-01-11 10:35:44 -0500567 case MarkType::kDuration:
Cary Clark682c58d2018-05-16 07:07:07 -0400568 case MarkType::kExperimental:
Cary Clarka90ea222018-10-16 10:30:28 -0400569 case MarkType::kFilter:
Cary Clark8032b982017-07-28 11:04:54 -0400570 case MarkType::kHeight:
Cary Clarkf895a422018-02-27 09:54:21 -0500571 case MarkType::kIllustration:
Cary Clark8032b982017-07-28 11:04:54 -0400572 case MarkType::kImage:
Cary Clarkab2621d2018-01-30 10:08:57 -0500573 case MarkType::kIn:
574 case MarkType::kLine:
575 case MarkType::kLiteral:
Cary Clark682c58d2018-05-16 07:07:07 -0400576 case MarkType::kNoJustify:
Cary Clark154beea2017-10-26 07:58:48 -0400577 case MarkType::kOutdent:
Cary Clark8032b982017-07-28 11:04:54 -0400578 case MarkType::kPlatform:
Cary Clark08895c42018-02-01 09:37:32 -0500579 case MarkType::kPopulate:
Cary Clark8032b982017-07-28 11:04:54 -0400580 case MarkType::kSeeAlso:
Cary Clark61dfc3a2018-01-03 08:37:53 -0500581 case MarkType::kSet:
Cary Clark8032b982017-07-28 11:04:54 -0400582 case MarkType::kSubstitute:
Cary Clark8032b982017-07-28 11:04:54 -0400583 case MarkType::kVolatile:
584 case MarkType::kWidth:
Cary Clark4855f782018-02-06 09:41:53 -0500585 // todo : add check disallowing children?
Cary Clarkab2621d2018-01-30 10:08:57 -0500586 if (hasEnd && MarkType::kAnchor != markType && MarkType::kLine != markType) {
Cary Clark8032b982017-07-28 11:04:54 -0400587 return this->reportError<bool>("one liners omit end element");
Cary Clark6fc50412017-09-21 12:31:06 -0400588 } else if (!hasEnd && MarkType::kAnchor == markType) {
589 return this->reportError<bool>("anchor line must have end element last");
Cary Clark8032b982017-07-28 11:04:54 -0400590 }
Cary Clark1a8d7622018-03-05 13:26:16 -0500591 fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC);
Cary Clark8032b982017-07-28 11:04:54 -0400592 definition = &fMarkup.front();
593 definition->fName = typeNameBuilder[0];
Cary Clarka560c472017-11-27 10:44:06 -0500594 definition->fFiddle = Definition::NormalizedName(typeNameBuilder[0]);
Cary Clark8032b982017-07-28 11:04:54 -0400595 definition->fContentStart = fChar;
Cary Clarkce101242017-09-01 15:51:02 -0400596 definition->fContentEnd = this->trimmedBracketEnd('\n');
Cary Clark8032b982017-07-28 11:04:54 -0400597 definition->fTerminator = this->lineEnd() - 1;
598 fParent->fChildren.push_back(definition);
599 if (MarkType::kAnchor == markType) {
Cary Clark2be81cf2018-09-13 12:04:30 -0400600 this->parseHashAnchor(definition);
Cary Clark137b8742018-05-30 09:21:49 -0400601 } else if (MarkType::kLine == markType) {
Cary Clark2be81cf2018-09-13 12:04:30 -0400602 this->parseHashLine(definition);
Cary Clark682c58d2018-05-16 07:07:07 -0400603 } else if (IncompleteAllowed(markType)) {
Cary Clark4855f782018-02-06 09:41:53 -0500604 this->skipSpace();
605 fParent->fDeprecated = true;
Cary Clark682c58d2018-05-16 07:07:07 -0400606 fParent->fDetails =
607 this->skipExact("soon") ? Definition::Details::kSoonToBe_Deprecated :
608 this->skipExact("testing") ? Definition::Details::kTestingOnly_Experiment :
Cary Clark137b8742018-05-30 09:21:49 -0400609 this->skipExact("do not use") ? Definition::Details::kDoNotUse_Experiment :
Cary Clark682c58d2018-05-16 07:07:07 -0400610 this->skipExact("not ready") ? Definition::Details::kNotReady_Experiment :
611 Definition::Details::kNone;
Cary Clark4855f782018-02-06 09:41:53 -0500612 this->skipSpace();
613 if ('\n' != this->peek()) {
614 return this->reportError<bool>("unexpected text after #Deprecated");
615 }
616 }
Cary Clark8032b982017-07-28 11:04:54 -0400617 break;
618 case MarkType::kExternal:
619 (void) this->collectExternals(); // FIXME: detect errors in external defs?
620 break;
621 default:
622 SkASSERT(0); // fixme : don't let any types be invisible
623 return true;
624 }
625 if (fParent) {
626 SkASSERT(definition);
627 SkASSERT(definition->fName.length() > 0);
628 }
629 return true;
630}
631
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400632void BmhParser::reportDuplicates(const Definition& def, string dup) const {
Cary Clark73fa9722017-08-29 17:36:51 -0400633 if (MarkType::kExample == def.fMarkType && dup == def.fFiddle) {
634 TextParser reporter(&def);
635 reporter.reportError("duplicate example name");
636 }
637 for (auto& child : def.fChildren ) {
638 reportDuplicates(*child, dup);
639 }
640}
641
Cary Clarkf895a422018-02-27 09:54:21 -0500642
643static Definition* find_fiddle(Definition* def, string name) {
644 if (MarkType::kExample == def->fMarkType && name == def->fFiddle) {
645 return def;
646 }
647 for (auto& child : def->fChildren) {
648 Definition* result = find_fiddle(child, name);
649 if (result) {
650 return result;
651 }
652 }
653 return nullptr;
654}
655
656Definition* BmhParser::findExample(string name) const {
657 for (const auto& topic : fTopicMap) {
658 if (topic.second->fParent) {
659 continue;
660 }
661 Definition* def = find_fiddle(topic.second, name);
662 if (def) {
663 return def;
664 }
665 }
666 return nullptr;
667}
668
Cary Clarkd7895502018-07-18 15:10:08 -0400669static bool check_example_hashes(Definition* def) {
670 if (MarkType::kExample == def->fMarkType) {
671 if (def->fHash.length()) {
672 return true;
673 }
674 for (auto child : def->fChildren) {
675 if (MarkType::kPlatform == child->fMarkType) {
676 if (string::npos != string(child->fContentStart, child->length()).find("!fiddle")) {
677 return true;
678 }
679 }
680 }
681 return def->reportError<bool>("missing hash");
682 }
683 for (auto& child : def->fChildren) {
684 if (!check_example_hashes(child)) {
685 return false;
686 }
687 }
688 return true;
689}
690
691bool BmhParser::checkExampleHashes() const {
692 for (const auto& topic : fTopicMap) {
693 if (!topic.second->fParent && !check_example_hashes(topic.second)) {
694 return false;
695 }
696 }
697 return true;
698}
699
700static void reset_example_hashes(Definition* def) {
701 if (MarkType::kExample == def->fMarkType) {
702 def->fHash.clear();
703 return;
704 }
705 for (auto& child : def->fChildren) {
706 reset_example_hashes(child);
707 }
708}
709
710void BmhParser::resetExampleHashes() {
711 for (const auto& topic : fTopicMap) {
712 if (!topic.second->fParent) {
713 reset_example_hashes(topic.second);
714 }
715 }
716}
717
Cary Clark73fa9722017-08-29 17:36:51 -0400718static void find_examples(const Definition& def, vector<string>* exampleNames) {
719 if (MarkType::kExample == def.fMarkType) {
720 exampleNames->push_back(def.fFiddle);
721 }
722 for (auto& child : def.fChildren ) {
723 find_examples(*child, exampleNames);
724 }
725}
726
Cary Clarkf895a422018-02-27 09:54:21 -0500727bool BmhParser::checkEndMarker(MarkType markType, string match) const {
728 TextParser tp(fFileName, fLine, fChar, fLineCount);
729 tp.skipSpace();
730 if (fMC != tp.next()) {
731 return this->reportError<bool>("mismatched end marker expect #");
732 }
733 const char* nameStart = tp.fChar;
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400734 tp.skipToNonName();
Cary Clarkf895a422018-02-27 09:54:21 -0500735 string markName(nameStart, tp.fChar - nameStart);
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400736 if (kMarkProps[(int) markType].fName != markName) {
Cary Clarkf895a422018-02-27 09:54:21 -0500737 return this->reportError<bool>("expected #XXX ## to match");
738 }
739 tp.skipSpace();
740 nameStart = tp.fChar;
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400741 tp.skipToNonName();
Cary Clarkf895a422018-02-27 09:54:21 -0500742 markName = string(nameStart, tp.fChar - nameStart);
743 if ("" == markName) {
744 if (fMC != tp.next() || fMC != tp.next()) {
745 return this->reportError<bool>("expected ##");
746 }
747 return true;
748 }
749 std::replace(markName.begin(), markName.end(), '-', '_');
750 auto defPos = match.rfind(markName);
751 if (string::npos == defPos) {
752 return this->reportError<bool>("mismatched end marker v1");
753 }
754 if (markName.size() != match.size() - defPos) {
755 return this->reportError<bool>("mismatched end marker v2");
756 }
757 return true;
758}
759
Cary Clark73fa9722017-08-29 17:36:51 -0400760bool BmhParser::checkExamples() const {
761 vector<string> exampleNames;
762 for (const auto& topic : fTopicMap) {
763 if (topic.second->fParent) {
764 continue;
765 }
766 find_examples(*topic.second, &exampleNames);
767 }
768 std::sort(exampleNames.begin(), exampleNames.end());
769 string* last = nullptr;
770 string reported;
771 bool checkOK = true;
772 for (auto& nameIter : exampleNames) {
773 if (last && *last == nameIter && reported != *last) {
774 reported = *last;
775 SkDebugf("%s\n", reported.c_str());
776 for (const auto& topic : fTopicMap) {
777 if (topic.second->fParent) {
778 continue;
779 }
780 this->reportDuplicates(*topic.second, reported);
781 }
782 checkOK = false;
783 }
784 last = &nameIter;
785 }
786 return checkOK;
787}
788
Cary Clarka523d2d2017-08-30 08:58:10 -0400789bool BmhParser::checkParamReturn(const Definition* definition) const {
790 const char* parmEndCheck = definition->fContentEnd;
791 while (parmEndCheck < definition->fTerminator) {
792 if (fMC == parmEndCheck[0]) {
793 break;
794 }
795 if (' ' < parmEndCheck[0]) {
796 this->reportError<bool>(
797 "use full end marker on multiline #Param and #Return");
798 }
799 ++parmEndCheck;
800 }
801 return true;
802}
803
Cary Clark8032b982017-07-28 11:04:54 -0400804bool BmhParser::childOf(MarkType markType) const {
805 auto childError = [this](MarkType markType) -> bool {
806 string errStr = "expected ";
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400807 errStr += kMarkProps[(int) markType].fName;
Cary Clark8032b982017-07-28 11:04:54 -0400808 errStr += " parent";
809 return this->reportError<bool>(errStr.c_str());
810 };
811
812 if (markType == fParent->fMarkType) {
813 return true;
814 }
815 if (this->hasEndToken()) {
816 if (!fParent->fParent) {
817 return this->reportError<bool>("expected grandparent");
818 }
819 if (markType == fParent->fParent->fMarkType) {
820 return true;
821 }
822 }
823 return childError(markType);
824}
825
826string BmhParser::className(MarkType markType) {
Cary Clark8032b982017-07-28 11:04:54 -0400827 const char* end = this->lineEnd();
828 const char* mc = this->strnchr(fMC, end);
Cary Clark73fa9722017-08-29 17:36:51 -0400829 string classID;
Cary Clark186d08f2018-04-03 08:43:27 -0400830 TextParserSave savePlace(this);
Cary Clark73fa9722017-08-29 17:36:51 -0400831 this->skipSpace();
832 const char* wordStart = fChar;
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400833 this->skipToNonName();
Cary Clark73fa9722017-08-29 17:36:51 -0400834 const char* wordEnd = fChar;
835 classID = string(wordStart, wordEnd - wordStart);
836 if (!mc) {
837 savePlace.restore();
838 }
839 string builder;
840 const Definition* parent = this->parentSpace();
841 if (parent && parent->fName != classID) {
842 builder += parent->fName;
843 }
Cary Clark8032b982017-07-28 11:04:54 -0400844 if (mc) {
Cary Clark8032b982017-07-28 11:04:54 -0400845 if (mc + 1 < fEnd && fMC == mc[1]) { // if ##
846 if (markType != fParent->fMarkType) {
847 return this->reportError<string>("unbalanced method");
848 }
Cary Clark73fa9722017-08-29 17:36:51 -0400849 if (builder.length() > 0 && classID.size() > 0) {
Cary Clark8032b982017-07-28 11:04:54 -0400850 if (builder != fParent->fName) {
851 builder += "::";
Cary Clark73fa9722017-08-29 17:36:51 -0400852 builder += classID;
Cary Clark8032b982017-07-28 11:04:54 -0400853 if (builder != fParent->fName) {
854 return this->reportError<string>("name mismatch");
855 }
856 }
857 }
858 this->skipLine();
859 return fParent->fName;
860 }
861 fChar = mc;
862 this->next();
863 }
864 this->skipWhiteSpace();
865 if (MarkType::kEnum == markType && fChar >= end) {
866 fAnonymous = true;
867 builder += "::_anonymous";
868 return uniqueRootName(builder, markType);
869 }
870 builder = this->word(builder, "::");
871 return builder;
872}
873
874bool BmhParser::collectExternals() {
875 do {
876 this->skipWhiteSpace();
877 if (this->eof()) {
878 break;
879 }
880 if (fMC == this->peek()) {
881 this->next();
882 if (this->eof()) {
883 break;
884 }
885 if (fMC == this->peek()) {
886 this->skipLine();
887 break;
888 }
889 if (' ' >= this->peek()) {
890 this->skipLine();
891 continue;
892 }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400893 if (this->startsWith(kMarkProps[(int) MarkType::kExternal].fName)) {
894 this->skipToNonName();
Cary Clark8032b982017-07-28 11:04:54 -0400895 continue;
896 }
897 }
898 this->skipToAlpha();
899 const char* wordStart = fChar;
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400900 this->skipToNonName();
Cary Clark8032b982017-07-28 11:04:54 -0400901 if (fChar - wordStart > 0) {
Cary Clark1a8d7622018-03-05 13:26:16 -0500902 fExternals.emplace_front(MarkType::kExternal, wordStart, fChar, fLineCount, fParent,
903 fMC);
Cary Clark8032b982017-07-28 11:04:54 -0400904 RootDefinition* definition = &fExternals.front();
905 definition->fFileName = fFileName;
906 definition->fName = string(wordStart ,fChar - wordStart);
Cary Clarka560c472017-11-27 10:44:06 -0500907 definition->fFiddle = Definition::NormalizedName(definition->fName);
Cary Clark8032b982017-07-28 11:04:54 -0400908 }
909 } while (!this->eof());
910 return true;
911}
912
Cary Clark1a8d7622018-03-05 13:26:16 -0500913bool BmhParser::dumpExamples(FILE* fiddleOut, Definition& def, bool* continuation) const {
Cary Clark73fa9722017-08-29 17:36:51 -0400914 if (MarkType::kExample == def.fMarkType) {
915 string result;
Cary Clark1a8d7622018-03-05 13:26:16 -0500916 if (!this->exampleToScript(&def, BmhParser::ExampleOptions::kAll, &result)) {
Cary Clark73fa9722017-08-29 17:36:51 -0400917 return false;
918 }
919 if (result.length() > 0) {
Cary Clarkbef063a2017-10-31 15:44:45 -0400920 result += "\n";
921 result += "}";
Cary Clark73fa9722017-08-29 17:36:51 -0400922 if (*continuation) {
923 fprintf(fiddleOut, ",\n");
924 } else {
925 *continuation = true;
926 }
927 fprintf(fiddleOut, "%s", result.c_str());
928 }
929 return true;
930 }
931 for (auto& child : def.fChildren ) {
Cary Clark1a8d7622018-03-05 13:26:16 -0500932 if (!this->dumpExamples(fiddleOut, *child, continuation)) {
Cary Clark73fa9722017-08-29 17:36:51 -0400933 return false;
934 }
935 }
936 return true;
937}
938
939bool BmhParser::dumpExamples(const char* fiddleJsonFileName) const {
Cary Clark5b1f9532018-08-28 14:53:37 -0400940 string oldFiddle(fiddleJsonFileName);
941 string newFiddle(fiddleJsonFileName);
942 newFiddle += "_new";
943 FILE* fiddleOut = fopen(newFiddle.c_str(), "wb");
Cary Clark73fa9722017-08-29 17:36:51 -0400944 if (!fiddleOut) {
Cary Clark5b1f9532018-08-28 14:53:37 -0400945 SkDebugf("could not open output file %s\n", newFiddle.c_str());
Cary Clark73fa9722017-08-29 17:36:51 -0400946 return false;
947 }
948 fprintf(fiddleOut, "{\n");
949 bool continuation = false;
950 for (const auto& topic : fTopicMap) {
951 if (topic.second->fParent) {
952 continue;
953 }
Cary Clark1a8d7622018-03-05 13:26:16 -0500954 this->dumpExamples(fiddleOut, *topic.second, &continuation);
Cary Clark73fa9722017-08-29 17:36:51 -0400955 }
956 fprintf(fiddleOut, "\n}\n");
957 fclose(fiddleOut);
Cary Clark5b1f9532018-08-28 14:53:37 -0400958 if (ParserCommon::WrittenFileDiffers(oldFiddle, newFiddle)) {
959 ParserCommon::CopyToFile(oldFiddle, newFiddle);
960 SkDebugf("wrote %s\n", fiddleJsonFileName);
Cary Clark6a1185a2018-09-04 16:15:11 -0400961 } else {
962 remove(newFiddle.c_str());
Cary Clark5b1f9532018-08-28 14:53:37 -0400963 }
Cary Clark73fa9722017-08-29 17:36:51 -0400964 return true;
965}
966
Cary Clark8032b982017-07-28 11:04:54 -0400967int BmhParser::endHashCount() const {
968 const char* end = fLine + this->lineLength();
969 int count = 0;
970 while (fLine < end && fMC == *--end) {
971 count++;
972 }
973 return count;
974}
975
Cary Clarkce101242017-09-01 15:51:02 -0400976bool BmhParser::endTableColumn(const char* end, const char* terminator) {
977 if (!this->popParentStack(fParent)) {
978 return false;
979 }
980 fWorkingColumn->fContentEnd = end;
981 fWorkingColumn->fTerminator = terminator;
982 fColStart = fChar - 1;
983 this->skipSpace();
984 fTableState = TableState::kColumnStart;
985 return true;
986}
987
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400988static size_t count_indent(string text, size_t test, size_t end) {
Cary Clark1a8d7622018-03-05 13:26:16 -0500989 size_t result = test;
990 while (test < end) {
991 if (' ' != text[test]) {
992 break;
993 }
994 ++test;
995 }
996 return test - result;
997}
998
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400999static void add_code(string text, int pos, int end,
Cary Clark1a8d7622018-03-05 13:26:16 -05001000 size_t outIndent, size_t textIndent, string& example) {
1001 do {
1002 // fix this to move whole paragraph in, out, but preserve doc indent
1003 int nextIndent = count_indent(text, pos, end);
1004 size_t len = text.find('\n', pos);
1005 if (string::npos == len) {
1006 len = end;
1007 }
1008 if ((size_t) (pos + nextIndent) < len) {
1009 size_t indent = outIndent + nextIndent;
1010 SkASSERT(indent >= textIndent);
1011 indent -= textIndent;
1012 for (size_t index = 0; index < indent; ++index) {
1013 example += ' ';
1014 }
1015 pos += nextIndent;
1016 while ((size_t) pos < len) {
1017 example += '"' == text[pos] ? "\\\"" :
1018 '\\' == text[pos] ? "\\\\" :
1019 text.substr(pos, 1);
1020 ++pos;
1021 }
1022 example += "\\n";
1023 } else {
1024 pos += nextIndent;
1025 }
1026 if ('\n' == text[pos]) {
1027 ++pos;
1028 }
1029 } while (pos < end);
1030}
1031
Cary Clark61313f32018-10-08 14:57:48 -04001032bool BmhParser::IsExemplary(const Definition* def) {
1033 return kMarkProps[(int) def->fMarkType].fExemplary != Exemplary::kNo;
1034}
1035
Cary Clark1a8d7622018-03-05 13:26:16 -05001036bool BmhParser::exampleToScript(Definition* def, ExampleOptions exampleOptions,
1037 string* result) const {
1038 bool hasFiddle = true;
1039 const Definition* platform = def->hasChild(MarkType::kPlatform);
1040 if (platform) {
1041 TextParser platParse(platform);
1042 hasFiddle = !platParse.strnstr("!fiddle", platParse.fEnd);
1043 }
1044 if (!hasFiddle) {
1045 *result = "";
1046 return true;
1047 }
1048 string text = this->extractText(def, TrimExtract::kNo);
1049 bool textOut = string::npos != text.find("SkDebugf(")
1050 || string::npos != text.find("dump(")
1051 || string::npos != text.find("dumpHex(");
1052 string heightStr = "256";
1053 string widthStr = "256";
1054 string normalizedName(def->fFiddle);
1055 string code;
1056 string imageStr = "0";
1057 string srgbStr = "false";
1058 string durationStr = "0";
1059 for (auto iter : def->fChildren) {
1060 switch (iter->fMarkType) {
1061 case MarkType::kDuration:
1062 durationStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
1063 break;
1064 case MarkType::kHeight:
1065 heightStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
1066 break;
1067 case MarkType::kWidth:
1068 widthStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
1069 break;
1070 case MarkType::kDescription:
1071 // ignore for now
1072 break;
1073 case MarkType::kFunction: {
1074 // emit this, but don't wrap this in draw()
1075 string funcText = this->extractText(&*iter, TrimExtract::kNo);
1076 size_t pos = 0;
1077 while (pos < funcText.length() && ' ' > funcText[pos]) {
1078 ++pos;
1079 }
1080 size_t indent = count_indent(funcText, pos, funcText.length());
1081 add_code(funcText, pos, funcText.length(), 0, indent, code);
1082 code += "\\n";
1083 } break;
1084 case MarkType::kComment:
1085 break;
1086 case MarkType::kImage:
1087 imageStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
1088 break;
1089 case MarkType::kToDo:
1090 break;
1091 case MarkType::kBug:
1092 case MarkType::kMarkChar:
1093 case MarkType::kPlatform:
1094 case MarkType::kPhraseRef:
1095 // ignore for now
1096 break;
1097 case MarkType::kSet:
1098 if ("sRGB" == string(iter->fContentStart,
1099 iter->fContentEnd - iter->fContentStart)) {
1100 srgbStr = "true";
1101 } else {
1102 SkASSERT(0); // more work to do
1103 return false;
1104 }
1105 break;
1106 case MarkType::kStdOut:
1107 textOut = true;
1108 break;
1109 default:
1110 SkASSERT(0); // more coding to do
1111 }
1112 }
1113 string animatedStr = "0" != durationStr ? "true" : "false";
1114 string textOutStr = textOut ? "true" : "false";
1115 size_t pos = 0;
1116 while (pos < text.length() && ' ' > text[pos]) {
1117 ++pos;
1118 }
1119 size_t end = text.length();
1120 size_t outIndent = 0;
1121 size_t textIndent = count_indent(text, pos, end);
1122 if ("" == def->fWrapper) {
1123 this->setWrapper(def);
1124 }
1125 if (def->fWrapper.length() > 0) {
1126 code += def->fWrapper;
1127 code += "\\n";
1128 outIndent = 4;
1129 }
1130 add_code(text, pos, end, outIndent, textIndent, code);
1131 if (def->fWrapper.length() > 0) {
1132 code += "}";
1133 }
1134 string example = "\"" + normalizedName + "\": {\n";
Cary Clark2d4bf5f2018-04-16 08:37:38 -04001135 string filename = def->fileName();
1136 string baseFile = filename.substr(0, filename.length() - 4);
Cary Clark1a8d7622018-03-05 13:26:16 -05001137 if (ExampleOptions::kText == exampleOptions) {
1138 example += " \"code\": \"" + code + "\",\n";
1139 example += " \"hash\": \"" + def->fHash + "\",\n";
1140 example += " \"file\": \"" + baseFile + "\",\n";
1141 example += " \"name\": \"" + def->fName + "\",";
1142 } else {
1143 example += " \"code\": \"" + code + "\",\n";
1144 if (ExampleOptions::kPng == exampleOptions) {
1145 example += " \"width\": " + widthStr + ",\n";
1146 example += " \"height\": " + heightStr + ",\n";
1147 example += " \"hash\": \"" + def->fHash + "\",\n";
1148 example += " \"file\": \"" + baseFile + "\",\n";
1149 example += " \"name\": \"" + def->fName + "\"\n";
1150 example += "}";
1151 } else {
1152 example += " \"options\": {\n";
1153 example += " \"width\": " + widthStr + ",\n";
1154 example += " \"height\": " + heightStr + ",\n";
1155 example += " \"source\": " + imageStr + ",\n";
1156 example += " \"srgb\": " + srgbStr + ",\n";
1157 example += " \"f16\": false,\n";
1158 example += " \"textOnly\": " + textOutStr + ",\n";
1159 example += " \"animated\": " + animatedStr + ",\n";
1160 example += " \"duration\": " + durationStr + "\n";
1161 example += " },\n";
1162 example += " \"fast\": true";
1163 }
1164 }
1165 *result = example;
1166 return true;
1167}
1168
1169string BmhParser::extractText(const Definition* def, TrimExtract trimExtract) const {
1170 string result;
1171 TextParser parser(def);
1172 auto childIter = def->fChildren.begin();
1173 while (!parser.eof()) {
1174 const char* end = def->fChildren.end() == childIter ? parser.fEnd : (*childIter)->fStart;
1175 string fragment(parser.fChar, end - parser.fChar);
1176 trim_end(fragment);
1177 if (TrimExtract::kYes == trimExtract) {
1178 trim_start(fragment);
1179 if (result.length()) {
1180 result += '\n';
1181 result += '\n';
1182 }
1183 }
1184 if (TrimExtract::kYes == trimExtract || has_nonwhitespace(fragment)) {
1185 result += fragment;
1186 }
1187 parser.skipTo(end);
1188 if (def->fChildren.end() != childIter) {
1189 Definition* child = *childIter;
1190 if (MarkType::kPhraseRef == child->fMarkType) {
1191 auto phraseIter = fPhraseMap.find(child->fName);
1192 if (fPhraseMap.end() == phraseIter) {
1193 return def->reportError<string>("missing phrase definition");
1194 }
1195 Definition* phrase = phraseIter->second;
1196 // count indent of last line in result
1197 size_t lastLF = result.rfind('\n');
1198 size_t startPos = string::npos == lastLF ? 0 : lastLF;
1199 size_t lastLen = result.length() - startPos;
1200 size_t indent = count_indent(result, startPos, result.length()) + 4;
1201 string phraseStr = this->extractText(phrase, TrimExtract::kNo);
1202 startPos = 0;
1203 bool firstTime = true;
1204 size_t endPos;
1205 do {
1206 endPos = phraseStr.find('\n', startPos);
1207 size_t len = (string::npos != endPos ? endPos : phraseStr.length()) - startPos;
1208 if (firstTime && lastLen + len + 1 < 100) { // FIXME: make 100 global const or something
1209 result += ' ';
1210 } else {
1211 result += '\n';
1212 result += string(indent, ' ');
1213 }
1214 firstTime = false;
1215 string tmp = phraseStr.substr(startPos, len);
1216 result += tmp;
1217 startPos = endPos + 1;
1218 } while (string::npos != endPos);
1219 result += '\n';
1220 }
1221 parser.skipTo(child->fTerminator);
1222 std::advance(childIter, 1);
1223 }
1224 }
1225 return result;
1226}
1227
1228void BmhParser::setWrapper(Definition* def) const {
1229 const char drawWrapper[] = "void draw(SkCanvas* canvas) {";
1230 const char drawNoCanvas[] = "void draw(SkCanvas* ) {";
1231 string text = this->extractText(def, TrimExtract::kNo);
1232 size_t nonSpace = 0;
1233 while (nonSpace < text.length() && ' ' >= text[nonSpace]) {
1234 ++nonSpace;
1235 }
1236 bool hasFunc = !text.compare(nonSpace, sizeof(drawWrapper) - 1, drawWrapper);
1237 bool noCanvas = !text.compare(nonSpace, sizeof(drawNoCanvas) - 1, drawNoCanvas);
1238 bool hasCanvas = string::npos != text.find("SkCanvas canvas");
1239 SkASSERT(!hasFunc || !noCanvas);
1240 bool preprocessor = text[0] == '#';
1241 bool wrapCode = !hasFunc && !noCanvas && !preprocessor;
1242 if (wrapCode) {
1243 def->fWrapper = hasCanvas ? string(drawNoCanvas) : string(drawWrapper);
1244 }
1245}
1246
Cary Clark0d225392018-06-07 09:59:07 -04001247RootDefinition* BmhParser::findBmhObject(MarkType markType, string typeName) {
1248 const auto& mapIter = std::find_if(fMaps.begin(), fMaps.end(),
1249 [markType](DefinitionMap& defMap){ return markType == defMap.fMarkType; } );
1250 if (mapIter == fMaps.end()) {
1251 return nullptr;
1252 }
1253 return &(*mapIter->fMap)[typeName];
1254}
1255
Ben Wagner63fd7602017-10-09 15:45:33 -04001256// FIXME: some examples may produce different output on different platforms
Cary Clark8032b982017-07-28 11:04:54 -04001257// if the text output can be different, think of how to author that
1258
1259bool BmhParser::findDefinitions() {
1260 bool lineStart = true;
Cary Clarkce101242017-09-01 15:51:02 -04001261 const char* lastChar = nullptr;
1262 const char* lastMC = nullptr;
Cary Clark8032b982017-07-28 11:04:54 -04001263 fParent = nullptr;
1264 while (!this->eof()) {
1265 if (this->peek() == fMC) {
Cary Clarkce101242017-09-01 15:51:02 -04001266 lastMC = fChar;
Cary Clark8032b982017-07-28 11:04:54 -04001267 this->next();
1268 if (this->peek() == fMC) {
1269 this->next();
1270 if (!lineStart && ' ' < this->peek()) {
Cary Clark2be81cf2018-09-13 12:04:30 -04001271 if (!fParent || MarkType::kFormula != fParent->fMarkType) {
1272 return this->reportError<bool>("expected definition");
1273 }
Cary Clark8032b982017-07-28 11:04:54 -04001274 }
1275 if (this->peek() != fMC) {
Cary Clarkce101242017-09-01 15:51:02 -04001276 if (MarkType::kColumn == fParent->fMarkType) {
1277 SkASSERT(TableState::kColumnEnd == fTableState);
1278 if (!this->endTableColumn(lastChar, lastMC)) {
1279 return false;
1280 }
1281 SkASSERT(fRow);
1282 if (!this->popParentStack(fParent)) {
1283 return false;
1284 }
1285 fRow->fContentEnd = fWorkingColumn->fContentEnd;
1286 fWorkingColumn = nullptr;
1287 fRow = nullptr;
1288 fTableState = TableState::kNone;
1289 } else {
1290 vector<string> parentName;
1291 parentName.push_back(fParent->fName);
Cary Clarkf895a422018-02-27 09:54:21 -05001292 if (!this->addDefinition(fChar - 1, true, fParent->fMarkType, parentName,
1293 HasTag::kNo)) {
Cary Clarkce101242017-09-01 15:51:02 -04001294 return false;
1295 }
Cary Clark8032b982017-07-28 11:04:54 -04001296 }
1297 } else {
1298 SkAssertResult(this->next() == fMC);
1299 fMC = this->next(); // change markup character
1300 if (' ' >= fMC) {
1301 return this->reportError<bool>("illegal markup character");
1302 }
Cary Clark1a8d7622018-03-05 13:26:16 -05001303 fMarkup.emplace_front(MarkType::kMarkChar, fChar - 4, fLineCount, fParent, fMC);
Cary Clark8032b982017-07-28 11:04:54 -04001304 Definition* markChar = &fMarkup.front();
1305 markChar->fContentStart = fChar - 1;
1306 this->skipToEndBracket('\n');
1307 markChar->fContentEnd = fChar;
1308 markChar->fTerminator = fChar;
1309 fParent->fChildren.push_back(markChar);
1310 }
1311 } else if (this->peek() >= 'A' && this->peek() <= 'Z') {
1312 const char* defStart = fChar - 1;
1313 MarkType markType = this->getMarkType(MarkLookup::kRequire);
1314 bool hasEnd = this->hasEndToken();
Cary Clark682c58d2018-05-16 07:07:07 -04001315 if (!hasEnd && fParent) {
1316 MarkType parentType = fParent->fMarkType;
Cary Clark2d4bf5f2018-04-16 08:37:38 -04001317 uint64_t parentMask = kMarkProps[(int) markType].fParentMask;
Cary Clark8032b982017-07-28 11:04:54 -04001318 if (parentMask && !(parentMask & (1LL << (int) parentType))) {
1319 return this->reportError<bool>("invalid parent");
1320 }
1321 }
Cary Clark2d4bf5f2018-04-16 08:37:38 -04001322 if (!this->skipName(kMarkProps[(int) markType].fName)) {
Cary Clark8032b982017-07-28 11:04:54 -04001323 return this->reportError<bool>("illegal markup character");
1324 }
1325 if (!this->skipSpace()) {
1326 return this->reportError<bool>("unexpected end");
1327 }
Cary Clark81abc432018-06-25 16:30:08 -04001328 lineStart = '\n' == this->peek();
Cary Clark8032b982017-07-28 11:04:54 -04001329 bool expectEnd = true;
1330 vector<string> typeNameBuilder = this->typeName(markType, &expectEnd);
1331 if (fCloned && MarkType::kMethod != markType && MarkType::kExample != markType
1332 && !fAnonymous) {
1333 return this->reportError<bool>("duplicate name");
1334 }
1335 if (hasEnd && expectEnd) {
Cary Clark137b8742018-05-30 09:21:49 -04001336 if (fMC == this->peek()) {
1337 return this->reportError<bool>("missing body");
1338 }
Cary Clark8032b982017-07-28 11:04:54 -04001339 }
Cary Clarkf895a422018-02-27 09:54:21 -05001340 if (!this->addDefinition(defStart, hasEnd, markType, typeNameBuilder,
1341 HasTag::kYes)) {
Cary Clark8032b982017-07-28 11:04:54 -04001342 return false;
1343 }
1344 continue;
1345 } else if (this->peek() == ' ') {
Cary Clark2be81cf2018-09-13 12:04:30 -04001346 if (!fParent || (MarkType::kFormula != fParent->fMarkType
Cary Clark8032b982017-07-28 11:04:54 -04001347 && MarkType::kLegend != fParent->fMarkType
Cary Clarkab2621d2018-01-30 10:08:57 -05001348 && MarkType::kList != fParent->fMarkType
Cary Clark2be81cf2018-09-13 12:04:30 -04001349 && MarkType::kLine != fParent->fMarkType
1350 && MarkType::kTable != fParent->fMarkType)) {
Cary Clark8032b982017-07-28 11:04:54 -04001351 int endHashes = this->endHashCount();
Cary Clarkce101242017-09-01 15:51:02 -04001352 if (endHashes <= 1) {
Cary Clark8032b982017-07-28 11:04:54 -04001353 if (fParent) {
Cary Clarkce101242017-09-01 15:51:02 -04001354 if (TableState::kColumnEnd == fTableState) {
1355 if (!this->endTableColumn(lastChar, lastMC)) {
1356 return false;
1357 }
1358 } else { // one line comment
1359 fMarkup.emplace_front(MarkType::kComment, fChar - 1, fLineCount,
Cary Clark1a8d7622018-03-05 13:26:16 -05001360 fParent, fMC);
Cary Clarkce101242017-09-01 15:51:02 -04001361 Definition* comment = &fMarkup.front();
1362 comment->fContentStart = fChar - 1;
1363 this->skipToEndBracket('\n');
1364 comment->fContentEnd = fChar;
1365 comment->fTerminator = fChar;
1366 fParent->fChildren.push_back(comment);
1367 }
Cary Clark8032b982017-07-28 11:04:54 -04001368 } else {
1369 fChar = fLine + this->lineLength() - 1;
1370 }
1371 } else { // table row
1372 if (2 != endHashes) {
1373 string errorStr = "expect ";
1374 errorStr += fMC;
1375 errorStr += fMC;
1376 return this->reportError<bool>(errorStr.c_str());
1377 }
1378 if (!fParent || MarkType::kTable != fParent->fMarkType) {
1379 return this->reportError<bool>("missing table");
1380 }
1381 }
Cary Clarkce101242017-09-01 15:51:02 -04001382 } else if (TableState::kNone == fTableState) {
Cary Clarkce101242017-09-01 15:51:02 -04001383 // fixme? no nested tables for now
1384 fColStart = fChar - 1;
Cary Clark1a8d7622018-03-05 13:26:16 -05001385 fMarkup.emplace_front(MarkType::kRow, fColStart, fLineCount, fParent, fMC);
Cary Clarkce101242017-09-01 15:51:02 -04001386 fRow = &fMarkup.front();
1387 fRow->fName = fParent->fName;
1388 this->skipWhiteSpace();
1389 fRow->fContentStart = fChar;
1390 this->setAsParent(fRow);
1391 fTableState = TableState::kColumnStart;
1392 }
1393 if (TableState::kColumnStart == fTableState) {
Cary Clark1a8d7622018-03-05 13:26:16 -05001394 fMarkup.emplace_front(MarkType::kColumn, fColStart, fLineCount, fParent, fMC);
Cary Clarkce101242017-09-01 15:51:02 -04001395 fWorkingColumn = &fMarkup.front();
1396 fWorkingColumn->fName = fParent->fName;
1397 fWorkingColumn->fContentStart = fChar;
1398 this->setAsParent(fWorkingColumn);
1399 fTableState = TableState::kColumnEnd;
1400 continue;
Cary Clark8032b982017-07-28 11:04:54 -04001401 }
Cary Clark1a8d7622018-03-05 13:26:16 -05001402 } else if (this->peek() >= 'a' && this->peek() <= 'z') {
1403 // expect zero or more letters and underscores (no spaces) then hash
1404 const char* phraseNameStart = fChar;
1405 this->skipPhraseName();
1406 string phraseKey = string(phraseNameStart, fChar - phraseNameStart);
Cary Clark682c58d2018-05-16 07:07:07 -04001407 char delimiter = this->next();
1408 vector<string> params;
1409 vector<const char*> paramsLoc;
1410 if (fMC != delimiter) {
1411 if ('(' != delimiter) {
1412 return this->reportError<bool>("expect # after phrase name");
1413 }
1414 // phrase may take comma delimited parameter list
1415 do {
1416 const char* subEnd = this->anyOf(",)\n");
1417 if (!subEnd || '\n' == *subEnd) {
1418 return this->reportError<bool>("unexpected phrase list end");
1419 }
1420 params.push_back(string(fChar, subEnd - fChar));
1421 paramsLoc.push_back(fChar);
1422 this->skipTo(subEnd);
1423
1424 } while (')' != this->next());
Cary Clark1a8d7622018-03-05 13:26:16 -05001425 }
1426 const char* start = phraseNameStart;
1427 SkASSERT('#' == start[-1]);
1428 --start;
1429 if (start > fStart && ' ' >= start[-1]) {
1430 --start; // preserve whether to add whitespace before substitution
1431 }
1432 fMarkup.emplace_front(MarkType::kPhraseRef, start, fLineCount, fParent, fMC);
1433 Definition* markChar = &fMarkup.front();
Cary Clark682c58d2018-05-16 07:07:07 -04001434 this->skipExact("#");
Cary Clark1a8d7622018-03-05 13:26:16 -05001435 markChar->fContentStart = fChar;
Cary Clark1a8d7622018-03-05 13:26:16 -05001436 markChar->fContentEnd = fChar;
1437 markChar->fTerminator = fChar;
1438 markChar->fName = phraseKey;
1439 fParent->fChildren.push_back(markChar);
Cary Clark682c58d2018-05-16 07:07:07 -04001440 int paramLocIndex = 0;
1441 for (auto param : params) {
1442 const char* paramLoc = paramsLoc[paramLocIndex++];
1443 fMarkup.emplace_front(MarkType::kPhraseParam, paramLoc, fLineCount, fParent,
1444 fMC);
1445 Definition* phraseParam = &fMarkup.front();
1446 phraseParam->fContentStart = paramLoc;
1447 phraseParam->fContentEnd = paramLoc + param.length();
1448 phraseParam->fTerminator = paramLoc + param.length();
1449 phraseParam->fName = param;
1450 markChar->fChildren.push_back(phraseParam);
1451 }
Cary Clark8032b982017-07-28 11:04:54 -04001452 }
1453 }
Cary Clarkce101242017-09-01 15:51:02 -04001454 char nextChar = this->next();
Cary Clarkce101242017-09-01 15:51:02 -04001455 if (' ' < nextChar) {
1456 lastChar = fChar;
Cary Clark81abc432018-06-25 16:30:08 -04001457 lineStart = false;
1458 } else if (nextChar == '\n') {
1459 lineStart = true;
Cary Clarkce101242017-09-01 15:51:02 -04001460 }
Cary Clark8032b982017-07-28 11:04:54 -04001461 }
1462 if (fParent) {
Cary Clarka560c472017-11-27 10:44:06 -05001463 return fParent->reportError<bool>("mismatched end");
Cary Clark8032b982017-07-28 11:04:54 -04001464 }
1465 return true;
1466}
1467
1468MarkType BmhParser::getMarkType(MarkLookup lookup) const {
1469 for (int index = 0; index <= Last_MarkType; ++index) {
Cary Clark2d4bf5f2018-04-16 08:37:38 -04001470 int typeLen = strlen(kMarkProps[index].fName);
Cary Clark8032b982017-07-28 11:04:54 -04001471 if (typeLen == 0) {
1472 continue;
1473 }
1474 if (fChar + typeLen >= fEnd || fChar[typeLen] > ' ') {
1475 continue;
1476 }
Cary Clark2d4bf5f2018-04-16 08:37:38 -04001477 int chCompare = strncmp(fChar, kMarkProps[index].fName, typeLen);
Cary Clark8032b982017-07-28 11:04:54 -04001478 if (chCompare < 0) {
1479 goto fail;
1480 }
1481 if (chCompare == 0) {
1482 return (MarkType) index;
1483 }
1484 }
1485fail:
1486 if (MarkLookup::kRequire == lookup) {
1487 return this->reportError<MarkType>("unknown mark type");
1488 }
1489 return MarkType::kNone;
1490}
1491
Cary Clarkab2621d2018-01-30 10:08:57 -05001492 // write #In to show containing #Topic
1493 // write #Line with one liner from Member_Functions, Constructors, Operators if method,
1494 // from Constants if enum, otherwise from #Subtopic containing match
Cary Clark8032b982017-07-28 11:04:54 -04001495bool HackParser::hackFiles() {
1496 string filename(fFileName);
1497 size_t len = filename.length() - 1;
1498 while (len > 0 && (isalnum(filename[len]) || '_' == filename[len] || '.' == filename[len])) {
1499 --len;
1500 }
1501 filename = filename.substr(len + 1);
Cary Clarkab2621d2018-01-30 10:08:57 -05001502 if (filename.substr(0, 2) != "Sk") {
1503 return true;
1504 }
1505 size_t under = filename.find('_');
1506 SkASSERT(under);
1507 string className = filename.substr(0, under);
1508 fOut = fopen(filename.c_str(), "wb");
1509 if (!fOut) {
Cary Clark8032b982017-07-28 11:04:54 -04001510 SkDebugf("could not open output file %s\n", filename.c_str());
1511 return false;
1512 }
Cary Clarkab2621d2018-01-30 10:08:57 -05001513 auto mapEntry = fBmhParser.fClassMap.find(className);
1514 SkASSERT(fBmhParser.fClassMap.end() != mapEntry);
1515 const Definition* classMarkup = &mapEntry->second;
1516 const Definition* root = classMarkup->fParent;
1517 SkASSERT(root);
1518 SkASSERT(root->fTerminator);
1519 SkASSERT('\n' == root->fTerminator[0]);
1520 SkASSERT(!root->fParent);
1521 fStart = root->fStart;
1522 fChar = fStart;
Cary Clark682c58d2018-05-16 07:07:07 -04001523 fClasses = nullptr;
Cary Clark08895c42018-02-01 09:37:32 -05001524 fConstants = nullptr;
Cary Clarkab2621d2018-01-30 10:08:57 -05001525 fConstructors = nullptr;
Cary Clarkab2621d2018-01-30 10:08:57 -05001526 fMemberFunctions = nullptr;
Cary Clark08895c42018-02-01 09:37:32 -05001527 fMembers = nullptr;
1528 fOperators = nullptr;
1529 fRelatedFunctions = nullptr;
Cary Clark682c58d2018-05-16 07:07:07 -04001530 fStructs = nullptr;
Cary Clarkab2621d2018-01-30 10:08:57 -05001531 this->topicIter(root);
Cary Clark08895c42018-02-01 09:37:32 -05001532 fprintf(fOut, "%.*s", (int) (fEnd - fChar), fChar);
Cary Clarkab2621d2018-01-30 10:08:57 -05001533 fclose(fOut);
Cary Clark5b1f9532018-08-28 14:53:37 -04001534 if (ParserCommon::WrittenFileDiffers(filename, root->fFileName)) {
Cary Clark08895c42018-02-01 09:37:32 -05001535 SkDebugf("wrote %s\n", filename.c_str());
1536 } else {
1537 remove(filename.c_str());
1538 }
Cary Clark8032b982017-07-28 11:04:54 -04001539 return true;
1540}
1541
Cary Clarkab2621d2018-01-30 10:08:57 -05001542string HackParser::searchTable(const Definition* tableHolder, const Definition* match) {
1543 if (!tableHolder) {
1544 return "";
1545 }
1546 string bestMatch;
1547 string result;
1548 for (auto table : tableHolder->fChildren) {
1549 if (MarkType::kTable == table->fMarkType) {
1550 for (auto row : table->fChildren) {
1551 if (MarkType::kRow == row->fMarkType) {
1552 const Definition* col0 = row->fChildren[0];
1553 size_t len = col0->fContentEnd - col0->fContentStart;
1554 string method = string(col0->fContentStart, len);
Cary Clark08895c42018-02-01 09:37:32 -05001555 if (len - 2 == method.find("()") && islower(method[0])
1556 && Definition::MethodType::kOperator != match->fMethodType) {
Cary Clarkab2621d2018-01-30 10:08:57 -05001557 method = method.substr(0, len - 2);
1558 }
1559 if (string::npos == match->fName.find(method)) {
1560 continue;
1561 }
1562 if (bestMatch.length() < method.length()) {
1563 bestMatch = method;
1564 const Definition * col1 = row->fChildren[1];
1565 if (col1->fContentEnd <= col1->fContentStart) {
1566 SkASSERT(string::npos != col1->fFileName.find("SkImageInfo"));
1567 result = "incomplete";
1568 } else {
1569 result = string(col1->fContentStart, col1->fContentEnd -
1570 col1->fContentStart);
1571 }
1572 }
1573 }
1574 }
1575 }
1576 }
1577 return result;
1578}
1579
1580// returns true if topic has method
1581void HackParser::topicIter(const Definition* topic) {
Cary Clark682c58d2018-05-16 07:07:07 -04001582 if (string::npos != topic->fName.find(SubtopicKeys::kClasses)) {
1583 SkASSERT(!fClasses);
1584 fClasses = topic;
Cary Clarkab2621d2018-01-30 10:08:57 -05001585 }
Cary Clark682c58d2018-05-16 07:07:07 -04001586 if (string::npos != topic->fName.find(SubtopicKeys::kStructs)) {
1587 SkASSERT(!fStructs);
1588 fStructs = topic;
1589 }
1590 if (string::npos != topic->fName.find(SubtopicKeys::kConstants)) {
Cary Clark08895c42018-02-01 09:37:32 -05001591 SkASSERT(!fConstants);
1592 fConstants = topic;
1593 }
Cary Clark682c58d2018-05-16 07:07:07 -04001594 if (string::npos != topic->fName.find(SubtopicKeys::kConstructors)) {
Cary Clarkab2621d2018-01-30 10:08:57 -05001595 SkASSERT(!fConstructors);
1596 fConstructors = topic;
1597 }
Cary Clark682c58d2018-05-16 07:07:07 -04001598 if (string::npos != topic->fName.find(SubtopicKeys::kMemberFunctions)) {
Cary Clark08895c42018-02-01 09:37:32 -05001599 SkASSERT(!fMemberFunctions);
1600 fMemberFunctions = topic;
1601 }
Cary Clark682c58d2018-05-16 07:07:07 -04001602 if (string::npos != topic->fName.find(SubtopicKeys::kMembers)) {
Cary Clark08895c42018-02-01 09:37:32 -05001603 SkASSERT(!fMembers);
1604 fMembers = topic;
1605 }
Cary Clark682c58d2018-05-16 07:07:07 -04001606 if (string::npos != topic->fName.find(SubtopicKeys::kOperators)) {
Cary Clarkab2621d2018-01-30 10:08:57 -05001607 SkASSERT(!fOperators);
1608 fOperators = topic;
1609 }
Cary Clark682c58d2018-05-16 07:07:07 -04001610 if (string::npos != topic->fName.find(SubtopicKeys::kRelatedFunctions)) {
Cary Clark08895c42018-02-01 09:37:32 -05001611 SkASSERT(!fRelatedFunctions);
1612 fRelatedFunctions = topic;
1613 }
Cary Clarkab2621d2018-01-30 10:08:57 -05001614 for (auto child : topic->fChildren) {
Cary Clark08895c42018-02-01 09:37:32 -05001615 string oneLiner;
1616 bool hasIn = false;
1617 bool hasLine = false;
1618 for (auto part : child->fChildren) {
1619 hasIn |= MarkType::kIn == part->fMarkType;
1620 hasLine |= MarkType::kLine == part->fMarkType;
Cary Clarkab2621d2018-01-30 10:08:57 -05001621 }
Cary Clark08895c42018-02-01 09:37:32 -05001622 switch (child->fMarkType) {
1623 case MarkType::kMethod: {
Cary Clark08895c42018-02-01 09:37:32 -05001624 hasIn |= MarkType::kTopic != topic->fMarkType &&
1625 MarkType::kSubtopic != topic->fMarkType; // don't write #In if parent is class
1626 hasLine |= child->fClone;
1627 if (!hasLine) {
1628 // find member_functions, add entry 2nd column text to #Line
1629 for (auto tableHolder : { fMemberFunctions, fConstructors, fOperators }) {
1630 if (!tableHolder) {
1631 continue;
1632 }
1633 if (Definition::MethodType::kConstructor == child->fMethodType
1634 && fConstructors != tableHolder) {
1635 continue;
1636 }
1637 if (Definition::MethodType::kOperator == child->fMethodType
1638 && fOperators != tableHolder) {
1639 continue;
1640 }
1641 string temp = this->searchTable(tableHolder, child);
1642 if ("" != temp) {
1643 SkASSERT("" == oneLiner || temp == oneLiner);
1644 oneLiner = temp;
1645 }
1646 }
1647 if ("" == oneLiner) {
Cary Clark08895c42018-02-01 09:37:32 -05001648 #ifdef SK_DEBUG
1649 const Definition* rootParent = topic;
1650 while (rootParent->fParent && MarkType::kClass != rootParent->fMarkType
1651 && MarkType::kStruct != rootParent->fMarkType) {
1652 rootParent = rootParent->fParent;
1653 }
1654 #endif
1655 SkASSERT(rootParent);
1656 SkASSERT(MarkType::kClass == rootParent->fMarkType
1657 || MarkType::kStruct == rootParent->fMarkType);
1658 hasLine = true;
1659 }
1660 }
1661
1662 if (hasIn && hasLine) {
1663 continue;
1664 }
1665 const char* start = fChar;
1666 const char* end = child->fContentStart;
1667 fprintf(fOut, "%.*s", (int) (end - start), start);
1668 fChar = end;
1669 // write to method markup header end
1670 if (!hasIn) {
1671 fprintf(fOut, "\n#In %s", topic->fName.c_str());
1672 }
1673 if (!hasLine) {
1674 fprintf(fOut, "\n#Line # %s ##", oneLiner.c_str());
1675 }
1676 } break;
1677 case MarkType::kTopic:
1678 case MarkType::kSubtopic:
1679 this->addOneLiner(fRelatedFunctions, child, hasLine, true);
1680 this->topicIter(child);
1681 break;
1682 case MarkType::kStruct:
Cary Clark682c58d2018-05-16 07:07:07 -04001683 this->addOneLiner(fStructs, child, hasLine, false);
1684 this->topicIter(child);
1685 break;
Cary Clark08895c42018-02-01 09:37:32 -05001686 case MarkType::kClass:
Cary Clark682c58d2018-05-16 07:07:07 -04001687 this->addOneLiner(fClasses, child, hasLine, false);
Cary Clark08895c42018-02-01 09:37:32 -05001688 this->topicIter(child);
1689 break;
1690 case MarkType::kEnum:
1691 case MarkType::kEnumClass:
1692 this->addOneLiner(fConstants, child, hasLine, true);
1693 break;
1694 case MarkType::kMember:
1695 this->addOneLiner(fMembers, child, hasLine, false);
1696 break;
1697 default:
1698 ;
Cary Clarkab2621d2018-01-30 10:08:57 -05001699 }
1700 }
1701}
1702
Cary Clark08895c42018-02-01 09:37:32 -05001703void HackParser::addOneLiner(const Definition* defTable, const Definition* child, bool hasLine,
1704 bool lfAfter) {
1705 if (hasLine) {
1706 return;
1707 }
1708 string oneLiner = this->searchTable(defTable, child);
1709 if ("" == oneLiner) {
1710 return;
1711 }
1712 const char* start = fChar;
1713 const char* end = child->fContentStart;
1714 fprintf(fOut, "%.*s", (int) (end - start), start);
1715 fChar = end;
1716 if (!lfAfter) {
1717 fprintf(fOut, "\n");
1718 }
1719 fprintf(fOut, "#Line # %s ##", oneLiner.c_str());
1720 if (lfAfter) {
1721 fprintf(fOut, "\n");
1722 }
1723}
Cary Clarkab2621d2018-01-30 10:08:57 -05001724
Cary Clark8032b982017-07-28 11:04:54 -04001725bool BmhParser::hasEndToken() const {
Cary Clark2be81cf2018-09-13 12:04:30 -04001726 const char* ptr = fLine;
1727 char test;
1728 do {
1729 if (ptr >= fEnd) {
1730 return false;
1731 }
1732 test = *ptr++;
1733 if ('\n' == test) {
1734 return false;
1735 }
1736 } while (fMC != test || fMC != *ptr);
1737 return true;
Cary Clark8032b982017-07-28 11:04:54 -04001738}
1739
1740string BmhParser::memberName() {
1741 const char* wordStart;
1742 const char* prefixes[] = { "static", "const" };
1743 do {
1744 this->skipSpace();
1745 wordStart = fChar;
Cary Clark2d4bf5f2018-04-16 08:37:38 -04001746 this->skipToNonName();
Cary Clark8032b982017-07-28 11:04:54 -04001747 } while (this->anyOf(wordStart, prefixes, SK_ARRAY_COUNT(prefixes)));
1748 if ('*' == this->peek()) {
1749 this->next();
1750 }
1751 return this->className(MarkType::kMember);
1752}
1753
1754string BmhParser::methodName() {
1755 if (this->hasEndToken()) {
1756 if (!fParent || !fParent->fName.length()) {
1757 return this->reportError<string>("missing parent method name");
1758 }
1759 SkASSERT(fMC == this->peek());
1760 this->next();
1761 SkASSERT(fMC == this->peek());
1762 this->next();
1763 SkASSERT(fMC != this->peek());
1764 return fParent->fName;
1765 }
1766 string builder;
1767 const char* end = this->lineEnd();
1768 const char* paren = this->strnchr('(', end);
1769 if (!paren) {
1770 return this->reportError<string>("missing method name and reference");
1771 }
Cary Clark224c7002018-06-27 11:00:21 -04001772 {
1773 TextParserSave endCheck(this);
1774 while (end < fEnd && !this->strnchr(')', end)) {
1775 fChar = end + 1;
1776 end = this->lineEnd();
1777 }
1778 if (end >= fEnd) {
1779 return this->reportError<string>("missing method end paren");
1780 }
1781 endCheck.restore();
1782 }
Cary Clark8032b982017-07-28 11:04:54 -04001783 const char* nameStart = paren;
1784 char ch;
1785 bool expectOperator = false;
1786 bool isConstructor = false;
1787 const char* nameEnd = nullptr;
1788 while (nameStart > fChar && ' ' != (ch = *--nameStart)) {
1789 if (!isalnum(ch) && '_' != ch) {
1790 if (nameEnd) {
1791 break;
1792 }
1793 expectOperator = true;
1794 continue;
1795 }
1796 if (!nameEnd) {
1797 nameEnd = nameStart + 1;
1798 }
1799 }
1800 if (!nameEnd) {
1801 return this->reportError<string>("unexpected method name char");
1802 }
1803 if (' ' == nameStart[0]) {
1804 ++nameStart;
1805 }
1806 if (nameEnd <= nameStart) {
1807 return this->reportError<string>("missing method name");
1808 }
1809 if (nameStart >= paren) {
1810 return this->reportError<string>("missing method name length");
1811 }
1812 string name(nameStart, nameEnd - nameStart);
1813 bool allLower = true;
1814 for (int index = 0; index < (int) (nameEnd - nameStart); ++index) {
1815 if (!islower(nameStart[index])) {
1816 allLower = false;
1817 break;
1818 }
1819 }
1820 if (expectOperator && "operator" != name) {
1821 return this->reportError<string>("expected operator");
1822 }
1823 const Definition* parent = this->parentSpace();
1824 if (parent && parent->fName.length() > 0) {
Cary Clark224c7002018-06-27 11:00:21 -04001825 size_t parentNameIndex = parent->fName.rfind(':');
1826 parentNameIndex = string::npos == parentNameIndex ? 0 : parentNameIndex + 1;
1827 string parentName = parent->fName.substr(parentNameIndex);
1828 if (parentName == name) {
Cary Clark8032b982017-07-28 11:04:54 -04001829 isConstructor = true;
1830 } else if ('~' == name[0]) {
Cary Clark224c7002018-06-27 11:00:21 -04001831 if (parentName != name.substr(1)) {
Cary Clark8032b982017-07-28 11:04:54 -04001832 return this->reportError<string>("expected destructor");
1833 }
1834 isConstructor = true;
1835 }
1836 builder = parent->fName + "::";
Ben Wagner63fd7602017-10-09 15:45:33 -04001837 }
Cary Clarka560c472017-11-27 10:44:06 -05001838 bool addConst = false;
Cary Clark8032b982017-07-28 11:04:54 -04001839 if (isConstructor || expectOperator) {
1840 paren = this->strnchr(')', end) + 1;
Cary Clark186d08f2018-04-03 08:43:27 -04001841 TextParserSave saveState(this);
Cary Clarka560c472017-11-27 10:44:06 -05001842 this->skipTo(paren);
1843 if (this->skipExact("_const")) {
1844 addConst = true;
1845 }
1846 saveState.restore();
Cary Clark8032b982017-07-28 11:04:54 -04001847 }
1848 builder.append(nameStart, paren - nameStart);
Cary Clarka560c472017-11-27 10:44:06 -05001849 if (addConst) {
1850 builder.append("_const");
1851 }
Cary Clark8032b982017-07-28 11:04:54 -04001852 if (!expectOperator && allLower) {
1853 builder.append("()");
1854 }
1855 int parens = 0;
1856 while (fChar < end || parens > 0) {
1857 if ('(' == this->peek()) {
1858 ++parens;
1859 } else if (')' == this->peek()) {
1860 --parens;
1861 }
1862 this->next();
1863 }
Cary Clark186d08f2018-04-03 08:43:27 -04001864 TextParserSave saveState(this);
Cary Clark8032b982017-07-28 11:04:54 -04001865 this->skipWhiteSpace();
1866 if (this->startsWith("const")) {
1867 this->skipName("const");
1868 } else {
1869 saveState.restore();
1870 }
1871// this->next();
Cary Clark82f1f742018-06-28 08:50:35 -04001872 if (string::npos != builder.find('\n')) {
1873 builder.erase(std::remove(builder.begin(), builder.end(), '\n'), builder.end());
1874 }
Cary Clark8032b982017-07-28 11:04:54 -04001875 return uniqueRootName(builder, MarkType::kMethod);
1876}
1877
1878const Definition* BmhParser::parentSpace() const {
1879 Definition* parent = nullptr;
1880 Definition* test = fParent;
1881 while (test) {
1882 if (MarkType::kClass == test->fMarkType ||
1883 MarkType::kEnumClass == test->fMarkType ||
1884 MarkType::kStruct == test->fMarkType) {
1885 parent = test;
1886 break;
1887 }
1888 test = test->fParent;
1889 }
1890 return parent;
1891}
1892
Cary Clark137b8742018-05-30 09:21:49 -04001893// A full terminal statement is in the form:
1894// \n optional-white-space #MarkType white-space #[# white-space]
1895// \n optional-white-space #MarkType white-space Name white-space #[# white-space]
1896// MarkType must match definition->fMarkType
Cary Clark1a8d7622018-03-05 13:26:16 -05001897const char* BmhParser::checkForFullTerminal(const char* end, const Definition* definition) const {
1898 const char* start = end;
1899 while ('\n' != start[0] && start > fStart) {
1900 --start;
1901 }
1902 SkASSERT (start < end);
1903 // if end is preceeeded by \n#MarkType ## backup to there
1904 TextParser parser(fFileName, start, fChar, fLineCount);
1905 parser.skipWhiteSpace();
1906 if (parser.eof() || fMC != parser.next()) {
1907 return end;
1908 }
Cary Clark2d4bf5f2018-04-16 08:37:38 -04001909 const char* markName = kMarkProps[(int) definition->fMarkType].fName;
Cary Clark1a8d7622018-03-05 13:26:16 -05001910 if (!parser.skipExact(markName)) {
1911 return end;
1912 }
1913 parser.skipWhiteSpace();
Cary Clark137b8742018-05-30 09:21:49 -04001914 TextParser startName(fFileName, definition->fStart, definition->fContentStart,
1915 definition->fLineCount);
1916 if ('#' == startName.next()) {
1917 startName.skipToSpace();
1918 if (!startName.eof() && startName.skipSpace()) {
1919 const char* nameBegin = startName.fChar;
1920 startName.skipToWhiteSpace();
1921 string name(nameBegin, (int) (startName.fChar - nameBegin));
1922 if (fMC != parser.peek() && !parser.skipExact(name.c_str())) {
1923 return end;
1924 }
1925 parser.skipSpace();
Cary Clark1a8d7622018-03-05 13:26:16 -05001926 }
1927 }
Cary Clark137b8742018-05-30 09:21:49 -04001928 if (parser.eof() || fMC != parser.next()) {
Cary Clark1a8d7622018-03-05 13:26:16 -05001929 return end;
1930 }
1931 if (!parser.eof() && fMC != parser.next()) {
1932 return end;
1933 }
Cary Clark137b8742018-05-30 09:21:49 -04001934 SkASSERT(parser.eof());
Cary Clark1a8d7622018-03-05 13:26:16 -05001935 return start;
1936}
1937
Cary Clark2be81cf2018-09-13 12:04:30 -04001938void BmhParser::parseHashAnchor(Definition* definition) {
1939 this->skipToEndBracket(fMC);
1940 fMarkup.emplace_front(MarkType::kLink, fChar, fLineCount, definition, fMC);
1941 SkAssertResult(fMC == this->next());
1942 this->skipWhiteSpace();
1943 Definition* link = &fMarkup.front();
1944 link->fContentStart = fChar;
1945 link->fContentEnd = this->trimmedBracketEnd(fMC);
1946 this->skipToEndBracket(fMC);
1947 SkAssertResult(fMC == this->next());
1948 SkAssertResult(fMC == this->next());
1949 link->fTerminator = fChar;
1950 definition->fContentEnd = link->fContentEnd;
1951 definition->fTerminator = fChar;
1952 definition->fChildren.emplace_back(link);
1953}
1954
1955void BmhParser::parseHashFormula(Definition* definition) {
1956 const char* start = definition->fContentStart;
1957 definition->trimEnd();
1958 const char* end = definition->fContentEnd;
1959 fMarkup.emplace_front(MarkType::kText, start, fLineCount, definition, fMC);
1960 Definition* text = &fMarkup.front();
1961 text->fContentStart = start;
1962 text->fContentEnd = end;
1963 text->fTerminator = definition->fTerminator;
1964 definition->fChildren.emplace_back(text);
1965}
1966
1967void BmhParser::parseHashLine(Definition* definition) {
1968 const char* nextLF = this->strnchr('\n', this->fEnd);
1969 const char* start = fChar;
1970 const char* end = this->trimmedBracketEnd(fMC);
1971 this->skipToEndBracket(fMC, nextLF);
1972 if (fMC != this->next() || fMC != this->next()) {
1973 return this->reportError<void>("expected ## to delineate line");
1974 }
1975 fMarkup.emplace_front(MarkType::kText, start, fLineCount, definition, fMC);
1976 Definition* text = &fMarkup.front();
1977 if (!islower(start[0]) && (!isdigit(start[0])
1978 || MarkType::kConst != definition->fParent->fMarkType)) {
1979 return this->reportError<void>("expect lower case start");
1980 }
1981 string contents = string(start, end - start);
1982 if (string::npos != contents.find('.')) {
1983 return this->reportError<void>("expect phrase, not sentence");
1984 }
1985 size_t firstSpace = contents.find(' ');
1986 if (string::npos == firstSpace || 0 == firstSpace || 's' != start[firstSpace - 1]) {
1987 if (MarkType::kMethod == fParent->fMarkType && "experimental" != contents
1988 && "incomplete" != contents) {
1989 return this->reportError<void>( "expect phrase in third person present"
1990 " tense (1st word should end in 's'");
1991 }
1992 }
1993 text->fContentStart = start;
1994 text->fContentEnd = end;
1995 text->fTerminator = fChar;
1996 definition->fContentEnd = text->fContentEnd;
1997 definition->fTerminator = fChar;
1998 definition->fChildren.emplace_back(text);
1999}
2000
Cary Clark8032b982017-07-28 11:04:54 -04002001bool BmhParser::popParentStack(Definition* definition) {
2002 if (!fParent) {
2003 return this->reportError<bool>("missing parent");
2004 }
2005 if (definition != fParent) {
2006 return this->reportError<bool>("definition end is not parent");
2007 }
2008 if (!definition->fStart) {
2009 return this->reportError<bool>("definition missing start");
2010 }
2011 if (definition->fContentEnd) {
2012 return this->reportError<bool>("definition already ended");
2013 }
Cary Clark1a8d7622018-03-05 13:26:16 -05002014 // more to figure out to handle table columns, at minimum
2015 const char* end = fChar;
2016 if (fMC != end[0]) {
2017 while (end > definition->fContentStart && ' ' >= end[-1]) {
2018 --end;
2019 }
2020 SkASSERT(&end[-1] >= definition->fContentStart && fMC == end[-1]
2021 && (MarkType::kColumn == definition->fMarkType
2022 || (&end[-2] >= definition->fContentStart && fMC == end[-2])));
2023 end -= 2;
2024 }
2025 end = checkForFullTerminal(end, definition);
2026 definition->fContentEnd = end;
Cary Clark8032b982017-07-28 11:04:54 -04002027 definition->fTerminator = fChar;
2028 fParent = definition->fParent;
2029 if (!fParent || (MarkType::kTopic == fParent->fMarkType && !fParent->fParent)) {
2030 fRoot = nullptr;
2031 }
2032 return true;
2033}
2034
2035TextParser::TextParser(const Definition* definition) :
Ben Wagner63fd7602017-10-09 15:45:33 -04002036 TextParser(definition->fFileName, definition->fContentStart, definition->fContentEnd,
Cary Clark8032b982017-07-28 11:04:54 -04002037 definition->fLineCount) {
2038}
2039
Cary Clark4855f782018-02-06 09:41:53 -05002040string TextParser::ReportFilename(string file) {
2041 string fullName;
2042#ifdef SK_BUILD_FOR_WIN
2043 TCHAR pathChars[MAX_PATH];
2044 DWORD pathLen = GetCurrentDirectory(MAX_PATH, pathChars);
2045 for (DWORD index = 0; index < pathLen; ++index) {
2046 fullName += pathChars[index] == (char)pathChars[index] ? (char)pathChars[index] : '?';
2047 }
2048 fullName += '\\';
2049#endif
2050 fullName += file;
2051 return fullName;
2052}
2053
Cary Clark8032b982017-07-28 11:04:54 -04002054void TextParser::reportError(const char* errorStr) const {
2055 this->reportWarning(errorStr);
2056 SkDebugf(""); // convenient place to set a breakpoint
2057}
2058
2059void TextParser::reportWarning(const char* errorStr) const {
Cary Clark61313f32018-10-08 14:57:48 -04002060 const char* lineStart = fLine;
2061 if (lineStart >= fEnd) {
2062 lineStart = fChar;
2063 }
2064 SkASSERT(lineStart < fEnd);
2065 TextParser err(fFileName, lineStart, fEnd, fLineCount);
Cary Clark8032b982017-07-28 11:04:54 -04002066 size_t lineLen = this->lineLength();
Cary Clark61313f32018-10-08 14:57:48 -04002067 ptrdiff_t spaces = fChar - lineStart;
Cary Clark8032b982017-07-28 11:04:54 -04002068 while (spaces > 0 && (size_t) spaces > lineLen) {
2069 ++err.fLineCount;
2070 err.fLine += lineLen;
2071 spaces -= lineLen;
2072 lineLen = err.lineLength();
2073 }
Cary Clark4855f782018-02-06 09:41:53 -05002074 string fullName = this->ReportFilename(fFileName);
2075 SkDebugf("\n%s(%zd): error: %s\n", fullName.c_str(), err.fLineCount, errorStr);
Cary Clark8032b982017-07-28 11:04:54 -04002076 if (0 == lineLen) {
2077 SkDebugf("[blank line]\n");
2078 } else {
2079 while (lineLen > 0 && '\n' == err.fLine[lineLen - 1]) {
2080 --lineLen;
2081 }
2082 SkDebugf("%.*s\n", (int) lineLen, err.fLine);
2083 SkDebugf("%*s^\n", (int) spaces, "");
2084 }
2085}
2086
Cary Clark186d08f2018-04-03 08:43:27 -04002087void TextParser::setForErrorReporting(const Definition* definition, const char* str) {
2088 fFileName = definition->fFileName;
2089 fStart = definition->fContentStart;
2090 fLine = str;
2091 while (fLine > fStart && fLine[-1] != '\n') {
2092 --fLine;
2093 }
2094 fChar = str;
2095 fEnd = definition->fContentEnd;
2096 fLineCount = definition->fLineCount;
2097 const char* lineInc = fStart;
2098 while (lineInc < str) {
2099 fLineCount += '\n' == *lineInc++;
2100 }
2101}
2102
Cary Clark2f466242017-12-11 16:03:17 -05002103string TextParser::typedefName() {
2104 // look for typedef as one of three forms:
2105 // typedef return-type (*NAME)(params);
2106 // typedef alias NAME;
2107 // typedef std::function<alias> NAME;
2108 string builder;
2109 const char* end = this->doubleLF();
2110 if (!end) {
2111 end = fEnd;
2112 }
2113 const char* altEnd = this->strnstr("#Typedef ##", end);
2114 if (altEnd) {
2115 end = this->strnchr('\n', end);
2116 }
2117 if (!end) {
2118 return this->reportError<string>("missing typedef std::function end bracket >");
2119 }
Cary Clark61ca7c52018-01-02 11:34:14 -05002120 bool stdFunction = this->startsWith("std::function");
2121 if (stdFunction) {
Cary Clark2f466242017-12-11 16:03:17 -05002122 if (!this->skipToEndBracket('>')) {
2123 return this->reportError<string>("missing typedef std::function end bracket >");
2124 }
2125 this->next();
2126 this->skipWhiteSpace();
2127 builder += string(fChar, end - fChar);
2128 } else {
2129 const char* paren = this->strnchr('(', end);
2130 if (!paren) {
2131 const char* lastWord = nullptr;
2132 do {
2133 this->skipToWhiteSpace();
2134 if (fChar < end && isspace(fChar[0])) {
Cary Clark682c58d2018-05-16 07:07:07 -04002135 const char* whiteStart = fChar;
Cary Clark2f466242017-12-11 16:03:17 -05002136 this->skipWhiteSpace();
Cary Clark682c58d2018-05-16 07:07:07 -04002137 // FIXME: test should be for fMC
2138 if ('#' == fChar[0]) {
2139 end = whiteStart;
2140 break;
2141 }
Cary Clark2f466242017-12-11 16:03:17 -05002142 lastWord = fChar;
2143 } else {
2144 break;
2145 }
2146 } while (true);
2147 if (!lastWord) {
2148 return this->reportError<string>("missing typedef name");
2149 }
2150 builder += string(lastWord, end - lastWord);
2151 } else {
2152 this->skipTo(paren);
2153 this->next();
2154 if ('*' != this->next()) {
2155 return this->reportError<string>("missing typedef function asterisk");
2156 }
2157 const char* nameStart = fChar;
2158 if (!this->skipToEndBracket(')')) {
2159 return this->reportError<string>("missing typedef function )");
2160 }
2161 builder += string(nameStart, fChar - nameStart);
2162 if (!this->skipToEndBracket('(')) {
2163 return this->reportError<string>("missing typedef params (");
2164 }
2165 if (! this->skipToEndBracket(')')) {
2166 return this->reportError<string>("missing typedef params )");
2167 }
2168 this->skipTo(end);
2169 }
2170 }
2171 return builder;
2172}
2173
Cary Clark8032b982017-07-28 11:04:54 -04002174bool BmhParser::skipNoName() {
2175 if ('\n' == this->peek()) {
2176 this->next();
2177 return true;
2178 }
2179 this->skipWhiteSpace();
2180 if (fMC != this->peek()) {
Cary Clark2be81cf2018-09-13 12:04:30 -04002181 return this->reportError<bool>("expected end mark 1");
Cary Clark8032b982017-07-28 11:04:54 -04002182 }
2183 this->next();
2184 if (fMC != this->peek()) {
Cary Clark2be81cf2018-09-13 12:04:30 -04002185 return this->reportError<bool>("expected end mark 2");
Cary Clark8032b982017-07-28 11:04:54 -04002186 }
2187 this->next();
2188 return true;
2189}
2190
2191bool BmhParser::skipToDefinitionEnd(MarkType markType) {
2192 if (this->eof()) {
2193 return this->reportError<bool>("missing end");
2194 }
2195 const char* start = fLine;
2196 int startLineCount = fLineCount;
2197 int stack = 1;
2198 ptrdiff_t lineLen;
2199 bool foundEnd = false;
2200 do {
2201 lineLen = this->lineLength();
2202 if (fMC != *fChar++) {
2203 continue;
2204 }
2205 if (fMC == *fChar) {
2206 continue;
2207 }
2208 if (' ' == *fChar) {
2209 continue;
2210 }
2211 MarkType nextType = this->getMarkType(MarkLookup::kAllowUnknown);
2212 if (markType != nextType) {
2213 continue;
2214 }
2215 bool hasEnd = this->hasEndToken();
2216 if (hasEnd) {
2217 if (!--stack) {
2218 foundEnd = true;
2219 continue;
2220 }
2221 } else {
2222 ++stack;
2223 }
2224 } while ((void) ++fLineCount, (void) (fLine += lineLen), (void) (fChar = fLine),
2225 !this->eof() && !foundEnd);
2226 if (foundEnd) {
2227 return true;
2228 }
2229 fLineCount = startLineCount;
2230 fLine = start;
2231 fChar = start;
2232 return this->reportError<bool>("unbalanced stack");
2233}
2234
Cary Clarkab2621d2018-01-30 10:08:57 -05002235bool BmhParser::skipToString() {
2236 this->skipSpace();
2237 if (fMC != this->peek()) {
Cary Clark2be81cf2018-09-13 12:04:30 -04002238 return this->reportError<bool>("expected end mark 3");
Cary Clarkab2621d2018-01-30 10:08:57 -05002239 }
2240 this->next();
2241 this->skipSpace();
2242 // body is text from here to double fMC
2243 // no single fMC allowed, no linefeed allowed
2244 return true;
2245}
2246
Cary Clark8032b982017-07-28 11:04:54 -04002247vector<string> BmhParser::topicName() {
2248 vector<string> result;
2249 this->skipWhiteSpace();
2250 const char* lineEnd = fLine + this->lineLength();
2251 const char* nameStart = fChar;
2252 while (fChar < lineEnd) {
2253 char ch = this->next();
2254 SkASSERT(',' != ch);
2255 if ('\n' == ch) {
2256 break;
2257 }
2258 if (fMC == ch) {
2259 break;
2260 }
2261 }
2262 if (fChar - 1 > nameStart) {
2263 string builder(nameStart, fChar - nameStart - 1);
2264 trim_start_end(builder);
2265 result.push_back(builder);
2266 }
2267 if (fChar < lineEnd && fMC == this->peek()) {
2268 this->next();
2269 }
2270 return result;
2271}
2272
2273// typeName parsing rules depend on mark type
2274vector<string> BmhParser::typeName(MarkType markType, bool* checkEnd) {
2275 fAnonymous = false;
2276 fCloned = false;
2277 vector<string> result;
2278 string builder;
2279 if (fParent) {
2280 builder = fParent->fName;
2281 }
2282 switch (markType) {
Cary Clark2d4bf5f2018-04-16 08:37:38 -04002283 case MarkType::kDefine:
Cary Clark8032b982017-07-28 11:04:54 -04002284 case MarkType::kEnum:
2285 // enums may be nameless
2286 case MarkType::kConst:
2287 case MarkType::kEnumClass:
2288 case MarkType::kClass:
2289 case MarkType::kStruct:
Cary Clark8032b982017-07-28 11:04:54 -04002290 // expect name
2291 builder = this->className(markType);
2292 break;
2293 case MarkType::kExample:
2294 // check to see if one already exists -- if so, number this one
2295 builder = this->uniqueName(string(), markType);
2296 this->skipNoName();
2297 break;
2298 case MarkType::kCode:
Cary Clark8032b982017-07-28 11:04:54 -04002299 case MarkType::kDescription:
Cary Clark8032b982017-07-28 11:04:54 -04002300 case MarkType::kExternal:
Cary Clark8032b982017-07-28 11:04:54 -04002301 case MarkType::kFunction:
2302 case MarkType::kLegend:
2303 case MarkType::kList:
2304 case MarkType::kNoExample:
2305 case MarkType::kPrivate:
Cary Clark8032b982017-07-28 11:04:54 -04002306 this->skipNoName();
2307 break;
Cary Clark2be81cf2018-09-13 12:04:30 -04002308 case MarkType::kFormula:
Cary Clarkab2621d2018-01-30 10:08:57 -05002309 case MarkType::kLine:
2310 this->skipToString();
2311 break;
Cary Clark8032b982017-07-28 11:04:54 -04002312 case MarkType::kAlias:
Ben Wagner63fd7602017-10-09 15:45:33 -04002313 case MarkType::kAnchor:
Cary Clark8032b982017-07-28 11:04:54 -04002314 case MarkType::kBug: // fixme: expect number
Cary Clark4855f782018-02-06 09:41:53 -05002315 case MarkType::kDeprecated:
Cary Clark682c58d2018-05-16 07:07:07 -04002316 case MarkType::kDetails:
Cary Clarkac47b882018-01-11 10:35:44 -05002317 case MarkType::kDuration:
Cary Clark682c58d2018-05-16 07:07:07 -04002318 case MarkType::kExperimental:
Cary Clark0d225392018-06-07 09:59:07 -04002319 case MarkType::kFile:
Cary Clarka90ea222018-10-16 10:30:28 -04002320 case MarkType::kFilter:
Cary Clark8032b982017-07-28 11:04:54 -04002321 case MarkType::kHeight:
Cary Clarkf895a422018-02-27 09:54:21 -05002322 case MarkType::kIllustration:
Cary Clark8032b982017-07-28 11:04:54 -04002323 case MarkType::kImage:
Cary Clarkab2621d2018-01-30 10:08:57 -05002324 case MarkType::kIn:
Cary Clark154beea2017-10-26 07:58:48 -04002325 case MarkType::kLiteral:
Cary Clark682c58d2018-05-16 07:07:07 -04002326 case MarkType::kNoJustify:
Cary Clark154beea2017-10-26 07:58:48 -04002327 case MarkType::kOutdent:
Cary Clark8032b982017-07-28 11:04:54 -04002328 case MarkType::kPlatform:
Cary Clark08895c42018-02-01 09:37:32 -05002329 case MarkType::kPopulate:
Cary Clark8032b982017-07-28 11:04:54 -04002330 case MarkType::kReturn:
2331 case MarkType::kSeeAlso:
Cary Clark61dfc3a2018-01-03 08:37:53 -05002332 case MarkType::kSet:
Cary Clark8032b982017-07-28 11:04:54 -04002333 case MarkType::kSubstitute:
Cary Clark8032b982017-07-28 11:04:54 -04002334 case MarkType::kToDo:
2335 case MarkType::kVolatile:
2336 case MarkType::kWidth:
2337 *checkEnd = false; // no name, may have text body
2338 break;
2339 case MarkType::kStdOut:
2340 this->skipNoName();
2341 break; // unnamed
2342 case MarkType::kMember:
2343 builder = this->memberName();
2344 break;
2345 case MarkType::kMethod:
2346 builder = this->methodName();
2347 break;
Cary Clarka560c472017-11-27 10:44:06 -05002348 case MarkType::kTypedef:
2349 builder = this->typedefName();
2350 break;
Cary Clark8032b982017-07-28 11:04:54 -04002351 case MarkType::kParam:
Cary Clark1a8d7622018-03-05 13:26:16 -05002352 // fixme: expect camelCase for param
Cary Clark8032b982017-07-28 11:04:54 -04002353 builder = this->word("", "");
2354 this->skipSpace();
2355 *checkEnd = false;
2356 break;
Cary Clark682c58d2018-05-16 07:07:07 -04002357 case MarkType::kPhraseDef: {
2358 const char* nameEnd = this->anyOf("(\n");
2359 builder = string(fChar, nameEnd - fChar);
2360 this->skipLower();
2361 if (fChar != nameEnd) {
2362 this->reportError("expect lower case only");
2363 break;
2364 }
2365 this->skipTo(nameEnd);
2366 *checkEnd = false;
2367 } break;
Cary Clark8032b982017-07-28 11:04:54 -04002368 case MarkType::kTable:
2369 this->skipNoName();
2370 break; // unnamed
2371 case MarkType::kSubtopic:
2372 case MarkType::kTopic:
2373 // fixme: start with cap, allow space, hyphen, stop on comma
2374 // one topic can have multiple type names delineated by comma
2375 result = this->topicName();
2376 if (result.size() == 0 && this->hasEndToken()) {
2377 break;
2378 }
2379 return result;
2380 default:
2381 // fixme: don't allow silent failures
2382 SkASSERT(0);
2383 }
2384 result.push_back(builder);
2385 return result;
2386}
2387
Cary Clarka560c472017-11-27 10:44:06 -05002388string BmhParser::typedefName() {
2389 if (this->hasEndToken()) {
2390 if (!fParent || !fParent->fName.length()) {
2391 return this->reportError<string>("missing parent typedef name");
2392 }
2393 SkASSERT(fMC == this->peek());
2394 this->next();
2395 SkASSERT(fMC == this->peek());
2396 this->next();
2397 SkASSERT(fMC != this->peek());
2398 return fParent->fName;
2399 }
Cary Clarka560c472017-11-27 10:44:06 -05002400 string builder;
Cary Clark2f466242017-12-11 16:03:17 -05002401 const Definition* parent = this->parentSpace();
2402 if (parent && parent->fName.length() > 0) {
2403 builder = parent->fName + "::";
Cary Clarka560c472017-11-27 10:44:06 -05002404 }
Cary Clark2f466242017-12-11 16:03:17 -05002405 builder += TextParser::typedefName();
Cary Clarka560c472017-11-27 10:44:06 -05002406 return uniqueRootName(builder, MarkType::kTypedef);
2407}
2408
Cary Clark2d4bf5f2018-04-16 08:37:38 -04002409string BmhParser::uniqueName(string base, MarkType markType) {
Cary Clark8032b982017-07-28 11:04:54 -04002410 string builder(base);
2411 if (!builder.length()) {
2412 builder = fParent->fName;
2413 }
2414 if (!fParent) {
2415 return builder;
2416 }
2417 int number = 2;
2418 string numBuilder(builder);
2419 do {
Cary Clarkf895a422018-02-27 09:54:21 -05002420 for (auto& iter : fParent->fChildren) {
Cary Clark8032b982017-07-28 11:04:54 -04002421 if (markType == iter->fMarkType) {
2422 if (iter->fName == numBuilder) {
Cary Clarkf895a422018-02-27 09:54:21 -05002423 if (iter->fDeprecated) {
2424 iter->fClone = true;
2425 } else {
2426 fCloned = true;
2427 }
Cary Clark8032b982017-07-28 11:04:54 -04002428 numBuilder = builder + '_' + to_string(number);
2429 goto tryNext;
2430 }
2431 }
2432 }
2433 break;
2434tryNext: ;
2435 } while (++number);
2436 return numBuilder;
2437}
2438
Cary Clark2d4bf5f2018-04-16 08:37:38 -04002439string BmhParser::uniqueRootName(string base, MarkType markType) {
2440 auto checkName = [markType](const Definition& def, string numBuilder) -> bool {
Cary Clark8032b982017-07-28 11:04:54 -04002441 return markType == def.fMarkType && def.fName == numBuilder;
2442 };
2443
2444 string builder(base);
2445 if (!builder.length()) {
2446 builder = fParent->fName;
2447 }
2448 int number = 2;
2449 string numBuilder(builder);
2450 Definition* cloned = nullptr;
2451 do {
2452 if (fRoot) {
2453 for (auto& iter : fRoot->fBranches) {
2454 if (checkName(*iter.second, numBuilder)) {
2455 cloned = iter.second;
2456 goto tryNext;
2457 }
2458 }
2459 for (auto& iter : fRoot->fLeaves) {
2460 if (checkName(iter.second, numBuilder)) {
2461 cloned = &iter.second;
2462 goto tryNext;
2463 }
2464 }
2465 } else if (fParent) {
2466 for (auto& iter : fParent->fChildren) {
2467 if (checkName(*iter, numBuilder)) {
2468 cloned = &*iter;
2469 goto tryNext;
2470 }
2471 }
2472 }
2473 break;
2474tryNext: ;
2475 if ("()" == builder.substr(builder.length() - 2)) {
2476 builder = builder.substr(0, builder.length() - 2);
2477 }
2478 if (MarkType::kMethod == markType) {
2479 cloned->fCloned = true;
Cary Clarkf895a422018-02-27 09:54:21 -05002480 if (cloned->fDeprecated) {
2481 cloned->fClone = true;
2482 } else {
2483 fCloned = true;
2484 }
2485 } else {
2486 fCloned = true;
Cary Clark8032b982017-07-28 11:04:54 -04002487 }
Cary Clark8032b982017-07-28 11:04:54 -04002488 numBuilder = builder + '_' + to_string(number);
2489 } while (++number);
2490 return numBuilder;
2491}
2492
2493void BmhParser::validate() const {
2494 for (int index = 0; index <= (int) Last_MarkType; ++index) {
Cary Clark2d4bf5f2018-04-16 08:37:38 -04002495 SkASSERT(kMarkProps[index].fMarkType == (MarkType) index);
Cary Clark8032b982017-07-28 11:04:54 -04002496 }
2497 const char* last = "";
2498 for (int index = 0; index <= (int) Last_MarkType; ++index) {
Cary Clark2d4bf5f2018-04-16 08:37:38 -04002499 const char* next = kMarkProps[index].fName;
Cary Clark8032b982017-07-28 11:04:54 -04002500 if (!last[0]) {
2501 last = next;
2502 continue;
2503 }
2504 if (!next[0]) {
2505 continue;
2506 }
2507 SkASSERT(strcmp(last, next) < 0);
2508 last = next;
2509 }
2510}
2511
Cary Clark2d4bf5f2018-04-16 08:37:38 -04002512string BmhParser::word(string prefix, string delimiter) {
Cary Clark8032b982017-07-28 11:04:54 -04002513 string builder(prefix);
2514 this->skipWhiteSpace();
2515 const char* lineEnd = fLine + this->lineLength();
2516 const char* nameStart = fChar;
2517 while (fChar < lineEnd) {
2518 char ch = this->next();
2519 if (' ' >= ch) {
2520 break;
2521 }
2522 if (',' == ch) {
2523 return this->reportError<string>("no comma needed");
2524 break;
2525 }
2526 if (fMC == ch) {
2527 return builder;
2528 }
2529 if (!isalnum(ch) && '_' != ch && ':' != ch && '-' != ch) {
2530 return this->reportError<string>("unexpected char");
2531 }
2532 if (':' == ch) {
2533 // expect pair, and expect word to start with Sk
2534 if (nameStart[0] != 'S' || nameStart[1] != 'k') {
2535 return this->reportError<string>("expected Sk");
2536 }
2537 if (':' != this->peek()) {
2538 return this->reportError<string>("expected ::");
2539 }
2540 this->next();
2541 } else if ('-' == ch) {
2542 // expect word not to start with Sk or kX where X is A-Z
2543 if (nameStart[0] == 'k' && nameStart[1] >= 'A' && nameStart[1] <= 'Z') {
2544 return this->reportError<string>("didn't expected kX");
2545 }
2546 if (nameStart[0] == 'S' && nameStart[1] == 'k') {
2547 return this->reportError<string>("expected Sk");
2548 }
2549 }
2550 }
2551 if (prefix.size()) {
2552 builder += delimiter;
2553 }
2554 builder.append(nameStart, fChar - nameStart - 1);
2555 return builder;
2556}
2557
2558// pass one: parse text, collect definitions
2559// pass two: lookup references
2560
Cary Clark8032b982017-07-28 11:04:54 -04002561static int count_children(const Definition& def, MarkType markType) {
2562 int count = 0;
2563 if (markType == def.fMarkType) {
2564 ++count;
2565 }
2566 for (auto& child : def.fChildren ) {
2567 count += count_children(*child, markType);
2568 }
2569 return count;
2570}
2571
2572int main(int argc, char** const argv) {
Cary Clarka560c472017-11-27 10:44:06 -05002573 BmhParser bmhParser(FLAGS_skip);
Cary Clark8032b982017-07-28 11:04:54 -04002574 bmhParser.validate();
2575
2576 SkCommandLineFlags::SetUsage(
Cary Clarka560c472017-11-27 10:44:06 -05002577 "Common Usage: bookmaker -b path/to/bmh_files -i path/to/include.h -t\n"
Cary Clark8032b982017-07-28 11:04:54 -04002578 " bookmaker -b path/to/bmh_files -e fiddle.json\n"
2579 " ~/go/bin/fiddlecli --input fiddle.json --output fiddleout.json\n"
2580 " bookmaker -b path/to/bmh_files -f fiddleout.json -r path/to/md_files\n"
Cary Clark2f466242017-12-11 16:03:17 -05002581 " bookmaker -a path/to/status.json -x\n"
2582 " bookmaker -a path/to/status.json -p\n");
Cary Clark8032b982017-07-28 11:04:54 -04002583 bool help = false;
2584 for (int i = 1; i < argc; i++) {
2585 if (0 == strcmp("-h", argv[i]) || 0 == strcmp("--help", argv[i])) {
2586 help = true;
2587 for (int j = i + 1; j < argc; j++) {
2588 if (SkStrStartsWith(argv[j], '-')) {
2589 break;
2590 }
2591 help = false;
2592 }
2593 break;
2594 }
2595 }
2596 if (!help) {
2597 SkCommandLineFlags::Parse(argc, argv);
2598 } else {
2599 SkCommandLineFlags::PrintUsage();
Cary Clarkac47b882018-01-11 10:35:44 -05002600 const char* const commands[] = { "", "-h", "bmh", "-h", "examples", "-h", "include",
2601 "-h", "fiddle", "-h", "ref", "-h", "status", "-h", "tokens",
Cary Clark8032b982017-07-28 11:04:54 -04002602 "-h", "crosscheck", "-h", "populate", "-h", "spellcheck" };
Cary Clark7265ea32017-09-14 12:12:32 -04002603 SkCommandLineFlags::Parse(SK_ARRAY_COUNT(commands), commands);
Cary Clark8032b982017-07-28 11:04:54 -04002604 return 0;
2605 }
Cary Clark2f466242017-12-11 16:03:17 -05002606 if (FLAGS_bmh.isEmpty() && FLAGS_include.isEmpty() && FLAGS_status.isEmpty()) {
2607 SkDebugf("requires at least one of: -b -i -a\n");
Cary Clark8032b982017-07-28 11:04:54 -04002608 SkCommandLineFlags::PrintUsage();
2609 return 1;
2610 }
Cary Clark2f466242017-12-11 16:03:17 -05002611 if (!FLAGS_bmh.isEmpty() && !FLAGS_status.isEmpty()) {
2612 SkDebugf("requires -b or -a but not both\n");
Cary Clarkbef063a2017-10-31 15:44:45 -04002613 SkCommandLineFlags::PrintUsage();
2614 return 1;
2615 }
Cary Clark2f466242017-12-11 16:03:17 -05002616 if (!FLAGS_include.isEmpty() && !FLAGS_status.isEmpty()) {
2617 SkDebugf("requires -i or -a but not both\n");
2618 SkCommandLineFlags::PrintUsage();
2619 return 1;
2620 }
2621 if (FLAGS_bmh.isEmpty() && FLAGS_status.isEmpty() && FLAGS_catalog) {
2622 SkDebugf("-c requires -b or -a\n");
2623 SkCommandLineFlags::PrintUsage();
2624 return 1;
2625 }
2626 if ((FLAGS_fiddle.isEmpty() || FLAGS_ref.isEmpty()) && FLAGS_catalog) {
2627 SkDebugf("-c requires -f -r\n");
2628 SkCommandLineFlags::PrintUsage();
2629 return 1;
2630 }
2631 if (FLAGS_bmh.isEmpty() && FLAGS_status.isEmpty() && !FLAGS_examples.isEmpty()) {
2632 SkDebugf("-e requires -b or -a\n");
Cary Clark8032b982017-07-28 11:04:54 -04002633 SkCommandLineFlags::PrintUsage();
2634 return 1;
2635 }
Cary Clark2f466242017-12-11 16:03:17 -05002636 if ((FLAGS_include.isEmpty() || FLAGS_bmh.isEmpty()) && FLAGS_status.isEmpty() &&
2637 FLAGS_populate) {
2638 SkDebugf("-p requires -b -i or -a\n");
Cary Clark8032b982017-07-28 11:04:54 -04002639 SkCommandLineFlags::PrintUsage();
2640 return 1;
2641 }
Cary Clark2f466242017-12-11 16:03:17 -05002642 if (FLAGS_bmh.isEmpty() && FLAGS_status.isEmpty() && !FLAGS_ref.isEmpty()) {
2643 SkDebugf("-r requires -b or -a\n");
Cary Clark8032b982017-07-28 11:04:54 -04002644 SkCommandLineFlags::PrintUsage();
2645 return 1;
2646 }
Cary Clark2f466242017-12-11 16:03:17 -05002647 if (FLAGS_bmh.isEmpty() && FLAGS_status.isEmpty() && !FLAGS_spellcheck.isEmpty()) {
2648 SkDebugf("-s requires -b or -a\n");
Cary Clark8032b982017-07-28 11:04:54 -04002649 SkCommandLineFlags::PrintUsage();
2650 return 1;
2651 }
Cary Clarka560c472017-11-27 10:44:06 -05002652 if ((FLAGS_include.isEmpty() || FLAGS_bmh.isEmpty()) && FLAGS_tokens) {
2653 SkDebugf("-t requires -b -i\n");
Cary Clark8032b982017-07-28 11:04:54 -04002654 SkCommandLineFlags::PrintUsage();
2655 return 1;
2656 }
Cary Clark2f466242017-12-11 16:03:17 -05002657 if ((FLAGS_include.isEmpty() || FLAGS_bmh.isEmpty()) && FLAGS_status.isEmpty() &&
2658 FLAGS_crosscheck) {
2659 SkDebugf("-x requires -b -i or -a\n");
Cary Clark8032b982017-07-28 11:04:54 -04002660 SkCommandLineFlags::PrintUsage();
2661 return 1;
2662 }
Cary Clarkac47b882018-01-11 10:35:44 -05002663 bmhParser.reset();
Cary Clark8032b982017-07-28 11:04:54 -04002664 if (!FLAGS_bmh.isEmpty()) {
Cary Clark2dc84ad2018-01-26 12:56:22 -05002665 if (FLAGS_tokens) {
2666 IncludeParser::RemoveFile(FLAGS_bmh[0], FLAGS_include[0]);
2667 }
Cary Clark186d08f2018-04-03 08:43:27 -04002668 if (!bmhParser.parseFile(FLAGS_bmh[0], ".bmh", ParserCommon::OneFile::kNo)) {
Cary Clark8032b982017-07-28 11:04:54 -04002669 return -1;
2670 }
Cary Clark2f466242017-12-11 16:03:17 -05002671 } else if (!FLAGS_status.isEmpty()) {
Cary Clark2f466242017-12-11 16:03:17 -05002672 if (!bmhParser.parseStatus(FLAGS_status[0], ".bmh", StatusFilter::kInProgress)) {
2673 return -1;
2674 }
Cary Clark8032b982017-07-28 11:04:54 -04002675 }
Cary Clarkab2621d2018-01-30 10:08:57 -05002676 if (FLAGS_hack) {
2677 if (FLAGS_bmh.isEmpty()) {
2678 SkDebugf("-k or --hack requires -b\n");
2679 SkCommandLineFlags::PrintUsage();
2680 return 1;
2681 }
2682 HackParser hacker(bmhParser);
Cary Clark186d08f2018-04-03 08:43:27 -04002683 if (!hacker.parseFile(FLAGS_bmh[0], ".bmh", ParserCommon::OneFile::kNo)) {
Cary Clarkab2621d2018-01-30 10:08:57 -05002684 SkDebugf("hack failed\n");
2685 return -1;
2686 }
2687 return 0;
2688 }
Cary Clarkac47b882018-01-11 10:35:44 -05002689 if (FLAGS_selfcheck && !SelfCheck(bmhParser)) {
2690 return -1;
2691 }
Cary Clark8032b982017-07-28 11:04:54 -04002692 bool done = false;
Cary Clark2f466242017-12-11 16:03:17 -05002693 if (!FLAGS_include.isEmpty() && FLAGS_tokens) {
2694 IncludeParser includeParser;
2695 includeParser.validate();
Cary Clark186d08f2018-04-03 08:43:27 -04002696 if (!includeParser.parseFile(FLAGS_include[0], ".h", ParserCommon::OneFile::kNo)) {
Cary Clark2f466242017-12-11 16:03:17 -05002697 return -1;
2698 }
2699 if (FLAGS_tokens) {
2700 includeParser.fDebugOut = FLAGS_stdout;
Cary Clark2d4bf5f2018-04-16 08:37:38 -04002701 if (includeParser.dumpTokens()) {
Cary Clark2f466242017-12-11 16:03:17 -05002702 bmhParser.fWroteOut = true;
2703 }
2704 done = true;
2705 }
2706 } else if (!FLAGS_include.isEmpty() || !FLAGS_status.isEmpty()) {
2707 if (FLAGS_crosscheck) {
Cary Clark8032b982017-07-28 11:04:54 -04002708 IncludeParser includeParser;
2709 includeParser.validate();
Cary Clark2f466242017-12-11 16:03:17 -05002710 if (!FLAGS_include.isEmpty() &&
Cary Clark186d08f2018-04-03 08:43:27 -04002711 !includeParser.parseFile(FLAGS_include[0], ".h", ParserCommon::OneFile::kNo)) {
Cary Clark8032b982017-07-28 11:04:54 -04002712 return -1;
2713 }
Cary Clark2f466242017-12-11 16:03:17 -05002714 if (!FLAGS_status.isEmpty() && !includeParser.parseStatus(FLAGS_status[0], ".h",
2715 StatusFilter::kCompleted)) {
2716 return -1;
Cary Clark8032b982017-07-28 11:04:54 -04002717 }
Cary Clark2f466242017-12-11 16:03:17 -05002718 if (!includeParser.crossCheck(bmhParser)) {
2719 return -1;
2720 }
2721 done = true;
Cary Clark8032b982017-07-28 11:04:54 -04002722 } else if (FLAGS_populate) {
2723 IncludeWriter includeWriter;
2724 includeWriter.validate();
Cary Clark2f466242017-12-11 16:03:17 -05002725 if (!FLAGS_include.isEmpty() &&
Cary Clark186d08f2018-04-03 08:43:27 -04002726 !includeWriter.parseFile(FLAGS_include[0], ".h", ParserCommon::OneFile::kNo)) {
Cary Clark2f466242017-12-11 16:03:17 -05002727 return -1;
2728 }
2729 if (!FLAGS_status.isEmpty() && !includeWriter.parseStatus(FLAGS_status[0], ".h",
2730 StatusFilter::kCompleted)) {
Cary Clark8032b982017-07-28 11:04:54 -04002731 return -1;
2732 }
Cary Clarkd0530ba2017-09-14 11:25:39 -04002733 includeWriter.fDebugOut = FLAGS_stdout;
Cary Clark8032b982017-07-28 11:04:54 -04002734 if (!includeWriter.populate(bmhParser)) {
2735 return -1;
2736 }
Cary Clarkd0530ba2017-09-14 11:25:39 -04002737 bmhParser.fWroteOut = true;
Cary Clark8032b982017-07-28 11:04:54 -04002738 done = true;
2739 }
2740 }
Cary Clark2f466242017-12-11 16:03:17 -05002741 if (!done && !FLAGS_fiddle.isEmpty() && FLAGS_examples.isEmpty()) {
Cary Clarkbef063a2017-10-31 15:44:45 -04002742 FiddleParser fparser(&bmhParser);
Cary Clarkd7895502018-07-18 15:10:08 -04002743 if (!fparser.parseFromFile(FLAGS_fiddle[0])) {
Cary Clark8032b982017-07-28 11:04:54 -04002744 return -1;
2745 }
2746 }
Cary Clarkbef063a2017-10-31 15:44:45 -04002747 if (!done && FLAGS_catalog && FLAGS_examples.isEmpty()) {
Cary Clark2f466242017-12-11 16:03:17 -05002748 Catalog cparser(&bmhParser);
2749 cparser.fDebugOut = FLAGS_stdout;
Cary Clark5b1f9532018-08-28 14:53:37 -04002750 if (!FLAGS_bmh.isEmpty() && !cparser.openCatalog(FLAGS_bmh[0])) {
Cary Clarkbef063a2017-10-31 15:44:45 -04002751 return -1;
2752 }
Cary Clark5b1f9532018-08-28 14:53:37 -04002753 if (!FLAGS_status.isEmpty() && !cparser.openStatus(FLAGS_status[0])) {
Cary Clarkbef063a2017-10-31 15:44:45 -04002754 return -1;
2755 }
Cary Clark186d08f2018-04-03 08:43:27 -04002756 if (!cparser.parseFile(FLAGS_fiddle[0], ".txt", ParserCommon::OneFile::kNo)) {
Cary Clark2f466242017-12-11 16:03:17 -05002757 return -1;
2758 }
Cary Clark5b1f9532018-08-28 14:53:37 -04002759 if (!cparser.closeCatalog(FLAGS_ref[0])) {
Cary Clarkbef063a2017-10-31 15:44:45 -04002760 return -1;
2761 }
2762 bmhParser.fWroteOut = true;
2763 done = true;
2764 }
Cary Clark8032b982017-07-28 11:04:54 -04002765 if (!done && !FLAGS_ref.isEmpty() && FLAGS_examples.isEmpty()) {
Cary Clark186d08f2018-04-03 08:43:27 -04002766 IncludeParser includeParser;
2767 includeParser.validate();
Cary Clark61313f32018-10-08 14:57:48 -04002768 if (!FLAGS_status.isEmpty() && !includeParser.parseStatus(FLAGS_status[0], ".h",
2769 StatusFilter::kCompleted)) {
2770 return -1;
2771 }
Cary Clark186d08f2018-04-03 08:43:27 -04002772 if (!FLAGS_include.isEmpty() && !includeParser.parseFile(FLAGS_include[0], ".h",
2773 ParserCommon::OneFile::kYes)) {
2774 return -1;
2775 }
Cary Clarka90ea222018-10-16 10:30:28 -04002776 includeParser.writeCodeBlock();
Cary Clark61313f32018-10-08 14:57:48 -04002777 MdOut mdOut(bmhParser, includeParser);
Cary Clark9174bda2017-09-19 17:39:32 -04002778 mdOut.fDebugOut = FLAGS_stdout;
Cary Clark682c58d2018-05-16 07:07:07 -04002779 mdOut.fValidate = FLAGS_validate;
Cary Clark61313f32018-10-08 14:57:48 -04002780 if (!FLAGS_bmh.isEmpty() && mdOut.buildReferences(FLAGS_bmh[0], FLAGS_ref[0])) {
Cary Clark2f466242017-12-11 16:03:17 -05002781 bmhParser.fWroteOut = true;
2782 }
2783 if (!FLAGS_status.isEmpty() && mdOut.buildStatus(FLAGS_status[0], FLAGS_ref[0])) {
Cary Clarkd0530ba2017-09-14 11:25:39 -04002784 bmhParser.fWroteOut = true;
2785 }
Cary Clark682c58d2018-05-16 07:07:07 -04002786 if (FLAGS_validate) {
2787 mdOut.checkAnchors();
2788 }
Cary Clark8032b982017-07-28 11:04:54 -04002789 }
Cary Clarkce101242017-09-01 15:51:02 -04002790 if (!done && !FLAGS_spellcheck.isEmpty() && FLAGS_examples.isEmpty()) {
Cary Clark2f466242017-12-11 16:03:17 -05002791 if (!FLAGS_bmh.isEmpty()) {
2792 bmhParser.spellCheck(FLAGS_bmh[0], FLAGS_spellcheck);
2793 }
2794 if (!FLAGS_status.isEmpty()) {
2795 bmhParser.spellStatus(FLAGS_status[0], FLAGS_spellcheck);
2796 }
Cary Clark154beea2017-10-26 07:58:48 -04002797 bmhParser.fWroteOut = true;
Cary Clark8032b982017-07-28 11:04:54 -04002798 done = true;
2799 }
2800 int examples = 0;
2801 int methods = 0;
2802 int topics = 0;
Cary Clark8032b982017-07-28 11:04:54 -04002803 if (!done && !FLAGS_examples.isEmpty()) {
Cary Clark73fa9722017-08-29 17:36:51 -04002804 // check to see if examples have duplicate names
2805 if (!bmhParser.checkExamples()) {
Cary Clark8032b982017-07-28 11:04:54 -04002806 return -1;
2807 }
Cary Clark9174bda2017-09-19 17:39:32 -04002808 bmhParser.fDebugOut = FLAGS_stdout;
Cary Clark73fa9722017-08-29 17:36:51 -04002809 if (!bmhParser.dumpExamples(FLAGS_examples[0])) {
2810 return -1;
Cary Clark8032b982017-07-28 11:04:54 -04002811 }
Cary Clarkd0530ba2017-09-14 11:25:39 -04002812 return 0;
Cary Clark8032b982017-07-28 11:04:54 -04002813 }
Cary Clarkd0530ba2017-09-14 11:25:39 -04002814 if (!bmhParser.fWroteOut) {
2815 for (const auto& topic : bmhParser.fTopicMap) {
2816 if (topic.second->fParent) {
2817 continue;
2818 }
2819 examples += count_children(*topic.second, MarkType::kExample);
2820 methods += count_children(*topic.second, MarkType::kMethod);
2821 topics += count_children(*topic.second, MarkType::kSubtopic);
2822 topics += count_children(*topic.second, MarkType::kTopic);
Cary Clark8032b982017-07-28 11:04:54 -04002823 }
Ben Wagner63fd7602017-10-09 15:45:33 -04002824 SkDebugf("topics=%d classes=%d methods=%d examples=%d\n",
Cary Clarkd0530ba2017-09-14 11:25:39 -04002825 bmhParser.fTopicMap.size(), bmhParser.fClassMap.size(),
2826 methods, examples);
Cary Clark8032b982017-07-28 11:04:54 -04002827 }
Cary Clark8032b982017-07-28 11:04:54 -04002828 return 0;
2829}