blob: 08577b91bd9043a42793cce5da9c7d80824a5a0d [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 Clark8032b982017-07-28 11:04:54 -040037#List needs '# content ##', formatting
Cary Clark186d08f2018-04-03 08:43:27 -040038rewrap text to fit in some number of columns
39#Literal is inflexible, making the entire #Code block link-less (see $Literal in SkImageInfo)
Cary Clark80247e52018-07-11 16:18:41 -040040 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 -040041add check to require #Const to contain #Code block if defining const or constexpr (enum consts have
Cary Clark80247e52018-07-11 16:18:41 -040042 #Code blocks inside the #Enum def)
Cary Clarkd2ca79c2018-08-10 13:09:13 -040043subclasses (e.g. Iter in SkPath) need to check for #Line and generate overview
44 subclass methods should also disallow #In
Cary Clark682c58d2018-05-16 07:07:07 -040045
Cary Clark682c58d2018-05-16 07:07:07 -040046It's awkward that phrase param is a child of the phrase def. Since phrase refs may also be children,
47there is special case code to skip phrase def when looking for additional substitutions in the
48phrase def. Could put it in the token list instead I guess, or make a definition subclass used
49by phrase def with an additional slot...
50
Cary Clark682c58d2018-05-16 07:07:07 -040051rearrange const out for md so that const / value / short description comes first in a table,
52followed by more elaborate descriptions, examples, seealso. In md.cpp, look to see if #Subtopic
53has #Const children. If so, generate a summary table first.
54Or, only allow #Line and moderate text description in #Const. Put more verbose text, example,
55seealso, in subsequent #SubTopic. Alpha_Type does this and it looks good.
56
Cary Clark61313f32018-10-08 14:57:48 -040057IPoint is awkward. SkPoint and SkIPoint are named things; Point is a topic, which
58refers to float points or integer points. There needn't be an IPoint topic.
59One way to resolve this would be to combine SkPoint_Reference and SkIPoint_Reference into
60Point_Reference that then contains both structs (or just move SKIPoint into SkPoint_Reference).
61Most Point references would be replaced with SkPoint / SkIPoint (if that's what they mean),
62or remain Point if the text indicates the concept rather one of the C structs.
63
Cary Clarkac47b882018-01-11 10:35:44 -050064see head of selfCheck.cpp for additional todos
Cary Clark80247e52018-07-11 16:18:41 -040065see head of spellCheck.cpp for additional todos
Cary Clark8032b982017-07-28 11:04:54 -040066 */
67
Ben Wagner63fd7602017-10-09 15:45:33 -040068/*
Cary Clark8032b982017-07-28 11:04:54 -040069 class contains named struct, enum, enum-member, method, topic, subtopic
70 everything contained by class is uniquely named
71 contained names may be reused by other classes
72 method contains named parameters
73 parameters may be reused in other methods
74 */
75
Cary Clark2d4bf5f2018-04-16 08:37:38 -040076#define M(mt) (1LL << (int) MarkType::k##mt)
77#define M_D M(Description)
78#define M_CS M(Class) | M(Struct)
Cary Clark682c58d2018-05-16 07:07:07 -040079#define M_MD M(Method) | M(Define)
80#define M_MDCM M_MD | M(Const) | M(Member)
Cary Clark2d4bf5f2018-04-16 08:37:38 -040081#define M_ST M(Subtopic) | M(Topic)
82#define M_CSST M_CS | M_ST
83#ifdef M_E
84#undef M_E
85#endif
86#define M_E M(Enum) | M(EnumClass)
87
88#define R_Y Resolvable::kYes
89#define R_N Resolvable::kNo
90#define R_O Resolvable::kOut
Cary Clark2be81cf2018-09-13 12:04:30 -040091#define R_K Resolvable::kCode
Cary Clark2d4bf5f2018-04-16 08:37:38 -040092#define R_F Resolvable::kFormula
93#define R_C Resolvable::kClone
94
95#define E_Y Exemplary::kYes
96#define E_N Exemplary::kNo
97#define E_O Exemplary::kOptional
98
Cary Clark682c58d2018-05-16 07:07:07 -040099// ToDo: add column to denote which marks are one-liners
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400100BmhParser::MarkProps BmhParser::kMarkProps[] = {
101// names without formal definitions (e.g. Column) aren't included
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400102 { "", MarkType::kNone, R_Y, E_N, 0 }
103, { "A", MarkType::kAnchor, R_N, E_N, 0 }
Cary Clark682c58d2018-05-16 07:07:07 -0400104, { "Alias", MarkType::kAlias, R_N, E_N, M_ST | M(Const) }
105, { "Bug", MarkType::kBug, R_N, E_N, M_CSST | M_MDCM | M_E
106 | M(Example) | M(NoExample) }
107, { "Class", MarkType::kClass, R_Y, E_O, M_CSST }
Cary Clark2be81cf2018-09-13 12:04:30 -0400108, { "Code", MarkType::kCode, R_K, E_N, M_CSST | M_E | M_MD | M(Typedef) }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400109, { "", MarkType::kColumn, R_Y, E_N, M(Row) }
110, { "", MarkType::kComment, R_N, E_N, 0 }
Cary Clark224c7002018-06-27 11:00:21 -0400111, { "Const", MarkType::kConst, R_Y, E_O, M_E | M_CSST }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400112, { "Define", MarkType::kDefine, R_O, E_Y, M_ST }
Cary Clark682c58d2018-05-16 07:07:07 -0400113, { "Deprecated", MarkType::kDeprecated, R_Y, E_N, M_CS | M_MDCM | M_E }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400114, { "Description", MarkType::kDescription, R_Y, E_N, M(Example) | M(NoExample) }
Cary Clark682c58d2018-05-16 07:07:07 -0400115, { "Details", MarkType::kDetails, R_N, E_N, M(Const) }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400116, { "Duration", MarkType::kDuration, R_N, E_N, M(Example) | M(NoExample) }
Cary Clark682c58d2018-05-16 07:07:07 -0400117, { "Enum", MarkType::kEnum, R_Y, E_O, M_CSST }
118, { "EnumClass", MarkType::kEnumClass, R_Y, E_O, M_CSST }
Cary Clark224c7002018-06-27 11:00:21 -0400119, { "Example", MarkType::kExample, R_O, E_N, M_CSST | M_E | M_MD | M(Const) }
Cary Clark682c58d2018-05-16 07:07:07 -0400120, { "Experimental", MarkType::kExperimental, R_Y, E_N, M_CS | M_MDCM | M_E }
121, { "External", MarkType::kExternal, R_Y, E_N, 0 }
Cary Clark0d225392018-06-07 09:59:07 -0400122, { "File", MarkType::kFile, R_Y, E_N, M(Topic) }
Cary Clark682c58d2018-05-16 07:07:07 -0400123, { "Formula", MarkType::kFormula, R_F, E_N, M(Column) | M(Description)
124 | M_E | M_ST | M_MDCM }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400125, { "Function", MarkType::kFunction, R_O, E_N, M(Example) | M(NoExample) }
126, { "Height", MarkType::kHeight, R_N, E_N, M(Example) | M(NoExample) }
Cary Clark682c58d2018-05-16 07:07:07 -0400127, { "Illustration", MarkType::kIllustration, R_N, E_N, M_CSST | M_MD }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400128, { "Image", MarkType::kImage, R_N, E_N, M(Example) | M(NoExample) }
129, { "In", MarkType::kIn, R_N, E_N, M_CSST | M_E | M(Method) | M(Typedef) }
130, { "Legend", MarkType::kLegend, R_Y, E_N, M(Table) }
131, { "Line", MarkType::kLine, R_N, E_N, M_CSST | M_E | M(Method) | M(Typedef) }
132, { "", MarkType::kLink, R_N, E_N, M(Anchor) }
133, { "List", MarkType::kList, R_Y, E_N, M(Method) | M_CSST | M_E | M_D }
134, { "Literal", MarkType::kLiteral, R_N, E_N, M(Code) }
135, { "", MarkType::kMarkChar, R_N, E_N, 0 }
Cary Clark61313f32018-10-08 14:57:48 -0400136, { "Member", MarkType::kMember, R_Y, E_O, M_CSST }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400137, { "Method", MarkType::kMethod, R_Y, E_Y, M_CSST }
Cary Clark682c58d2018-05-16 07:07:07 -0400138, { "NoExample", MarkType::kNoExample, R_N, E_N, M_CSST | M_E | M_MD }
139, { "NoJustify", MarkType::kNoJustify, R_N, E_N, M(Const) | M(Member) }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400140, { "Outdent", MarkType::kOutdent, R_N, E_N, M(Code) }
141, { "Param", MarkType::kParam, R_Y, E_N, M(Method) | M(Define) }
Cary Clark224c7002018-06-27 11:00:21 -0400142, { "PhraseDef", MarkType::kPhraseDef, R_Y, E_N, M_ST }
Cary Clark682c58d2018-05-16 07:07:07 -0400143, { "", MarkType::kPhraseParam, R_Y, E_N, 0 }
144, { "", MarkType::kPhraseRef, R_N, E_N, 0 }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400145, { "Platform", MarkType::kPlatform, R_N, E_N, M(Example) | M(NoExample) }
Cary Clark61313f32018-10-08 14:57:48 -0400146, { "Populate", MarkType::kPopulate, R_N, E_N, M_CS | M(Code) }
Cary Clark224c7002018-06-27 11:00:21 -0400147, { "Private", MarkType::kPrivate, R_N, E_N, M_CSST | M_MDCM | M_E }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400148, { "Return", MarkType::kReturn, R_Y, E_N, M(Method) }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400149, { "", MarkType::kRow, R_Y, E_N, M(Table) | M(List) }
Cary Clark682c58d2018-05-16 07:07:07 -0400150, { "SeeAlso", MarkType::kSeeAlso, R_C, E_N, M_CSST | M_E | M_MD | M(Typedef) }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400151, { "Set", MarkType::kSet, R_N, E_N, M(Example) | M(NoExample) }
152, { "StdOut", MarkType::kStdOut, R_N, E_N, M(Example) | M(NoExample) }
Cary Clark682c58d2018-05-16 07:07:07 -0400153, { "Struct", MarkType::kStruct, R_Y, E_O, M(Class) | M_ST }
Cary Clark137b8742018-05-30 09:21:49 -0400154, { "Substitute", MarkType::kSubstitute, R_N, E_N, M(Alias) | M_ST }
Cary Clark61313f32018-10-08 14:57:48 -0400155, { "Subtopic", MarkType::kSubtopic, R_Y, E_Y, M_CSST | M_E }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400156, { "Table", MarkType::kTable, R_Y, E_N, M(Method) | M_CSST | M_E }
Cary Clark682c58d2018-05-16 07:07:07 -0400157, { "Template", MarkType::kTemplate, R_Y, E_N, M_CSST }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400158, { "", MarkType::kText, R_N, E_N, 0 }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400159, { "ToDo", MarkType::kToDo, R_N, E_N, 0 }
Cary Clark682c58d2018-05-16 07:07:07 -0400160, { "Topic", MarkType::kTopic, R_Y, E_Y, 0 }
Cary Clark61313f32018-10-08 14:57:48 -0400161, { "Typedef", MarkType::kTypedef, R_Y, E_O, M_CSST | M_E }
Cary Clark682c58d2018-05-16 07:07:07 -0400162, { "Union", MarkType::kUnion, R_Y, E_N, M_CSST }
Cary Clark61313f32018-10-08 14:57:48 -0400163, { "Using", MarkType::kUsing, R_Y, E_O, M_CSST }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400164, { "Volatile", MarkType::kVolatile, R_N, E_N, M(StdOut) }
165, { "Width", MarkType::kWidth, R_N, E_N, M(Example) | M(NoExample) }
166};
167
168#undef R_O
169#undef R_N
170#undef R_Y
Cary Clark2be81cf2018-09-13 12:04:30 -0400171#undef R_K
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400172#undef R_F
173#undef R_C
174
175#undef M_E
176#undef M_CSST
177#undef M_ST
178#undef M_CS
Cary Clark682c58d2018-05-16 07:07:07 -0400179#undef M_MCD
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400180#undef M_D
181#undef M
182
183#undef E_Y
184#undef E_N
185#undef E_O
186
Cary Clark8032b982017-07-28 11:04:54 -0400187bool BmhParser::addDefinition(const char* defStart, bool hasEnd, MarkType markType,
Cary Clarkf895a422018-02-27 09:54:21 -0500188 const vector<string>& typeNameBuilder, HasTag hasTag) {
Cary Clark8032b982017-07-28 11:04:54 -0400189 Definition* definition = nullptr;
190 switch (markType) {
191 case MarkType::kComment:
192 if (!this->skipToDefinitionEnd(markType)) {
193 return false;
194 }
195 return true;
196 // these types may be referred to by name
197 case MarkType::kClass:
198 case MarkType::kStruct:
199 case MarkType::kConst:
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400200 case MarkType::kDefine:
Cary Clark8032b982017-07-28 11:04:54 -0400201 case MarkType::kEnum:
202 case MarkType::kEnumClass:
203 case MarkType::kMember:
204 case MarkType::kMethod:
205 case MarkType::kTypedef: {
206 if (!typeNameBuilder.size()) {
207 return this->reportError<bool>("unnamed markup");
208 }
209 if (typeNameBuilder.size() > 1) {
210 return this->reportError<bool>("expected one name only");
211 }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400212 string name = typeNameBuilder[0];
Cary Clark8032b982017-07-28 11:04:54 -0400213 if (nullptr == fRoot) {
214 fRoot = this->findBmhObject(markType, name);
215 fRoot->fFileName = fFileName;
216 definition = fRoot;
217 } else {
218 if (nullptr == fParent) {
219 return this->reportError<bool>("expected parent");
220 }
221 if (fParent == fRoot && hasEnd) {
222 RootDefinition* rootParent = fRoot->rootParent();
223 if (rootParent) {
224 fRoot = rootParent;
225 }
226 definition = fParent;
227 } else {
Cary Clarkce101242017-09-01 15:51:02 -0400228 if (!hasEnd && fRoot->find(name, RootDefinition::AllowParens::kNo)) {
Cary Clark8032b982017-07-28 11:04:54 -0400229 return this->reportError<bool>("duplicate symbol");
230 }
Cary Clark61313f32018-10-08 14:57:48 -0400231 if (MarkType::kStruct == markType || MarkType::kClass == markType
232 || MarkType::kEnumClass == markType) {
Cary Clark8032b982017-07-28 11:04:54 -0400233 // if class or struct, build fRoot hierarchy
234 // and change isDefined to search all parents of fRoot
235 SkASSERT(!hasEnd);
236 RootDefinition* childRoot = new RootDefinition;
237 (fRoot->fBranches)[name] = childRoot;
238 childRoot->setRootParent(fRoot);
239 childRoot->fFileName = fFileName;
240 fRoot = childRoot;
241 definition = fRoot;
242 } else {
243 definition = &fRoot->fLeaves[name];
244 }
245 }
246 }
247 if (hasEnd) {
248 Exemplary hasExample = Exemplary::kNo;
249 bool hasExcluder = false;
250 for (auto child : definition->fChildren) {
251 if (MarkType::kExample == child->fMarkType) {
252 hasExample = Exemplary::kYes;
253 }
254 hasExcluder |= MarkType::kPrivate == child->fMarkType
255 || MarkType::kDeprecated == child->fMarkType
256 || MarkType::kExperimental == child->fMarkType
257 || MarkType::kNoExample == child->fMarkType;
258 }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400259 if (kMarkProps[(int) markType].fExemplary != hasExample
260 && kMarkProps[(int) markType].fExemplary != Exemplary::kOptional) {
Cary Clark8032b982017-07-28 11:04:54 -0400261 if (string::npos == fFileName.find("undocumented")
262 && !hasExcluder) {
Ben Wagner63fd7602017-10-09 15:45:33 -0400263 hasExample == Exemplary::kNo ?
264 this->reportWarning("missing example") :
Cary Clark8032b982017-07-28 11:04:54 -0400265 this->reportWarning("unexpected example");
266 }
267
268 }
269 if (MarkType::kMethod == markType) {
270 if (fCheckMethods && !definition->checkMethod()) {
271 return false;
272 }
273 }
Cary Clarkf895a422018-02-27 09:54:21 -0500274 if (HasTag::kYes == hasTag) {
275 if (!this->checkEndMarker(markType, definition->fName)) {
276 return false;
277 }
278 }
Cary Clark8032b982017-07-28 11:04:54 -0400279 if (!this->popParentStack(definition)) {
280 return false;
281 }
Cary Clark06c20f32018-03-20 15:53:27 -0400282 if (fRoot == definition) {
283 fRoot = nullptr;
284 }
Cary Clark8032b982017-07-28 11:04:54 -0400285 } else {
286 definition->fStart = defStart;
287 this->skipSpace();
288 definition->fFileName = fFileName;
289 definition->fContentStart = fChar;
290 definition->fLineCount = fLineCount;
291 definition->fClone = fCloned;
292 if (MarkType::kConst == markType) {
293 // todo: require that fChar points to def on same line as markup
294 // additionally add definition to class children if it is not already there
295 if (definition->fParent != fRoot) {
296// fRoot->fChildren.push_back(definition);
297 }
298 }
Cary Clark82f1f742018-06-28 08:50:35 -0400299 SkASSERT(string::npos == name.find('\n'));
Cary Clark8032b982017-07-28 11:04:54 -0400300 definition->fName = name;
301 if (MarkType::kMethod == markType) {
302 if (string::npos != name.find(':', 0)) {
303 definition->setCanonicalFiddle();
304 } else {
305 definition->fFiddle = name;
306 }
307 } else {
Cary Clarka560c472017-11-27 10:44:06 -0500308 definition->fFiddle = Definition::NormalizedName(name);
Cary Clark8032b982017-07-28 11:04:54 -0400309 }
310 definition->fMarkType = markType;
Cary Clarkd0530ba2017-09-14 11:25:39 -0400311 definition->fAnonymous = fAnonymous;
Cary Clark8032b982017-07-28 11:04:54 -0400312 this->setAsParent(definition);
313 }
314 } break;
315 case MarkType::kTopic:
316 case MarkType::kSubtopic:
317 SkASSERT(1 == typeNameBuilder.size());
318 if (!hasEnd) {
319 if (!typeNameBuilder.size()) {
320 return this->reportError<bool>("unnamed topic");
321 }
Cary Clark1a8d7622018-03-05 13:26:16 -0500322 fTopics.emplace_front(markType, defStart, fLineCount, fParent, fMC);
Cary Clark8032b982017-07-28 11:04:54 -0400323 RootDefinition* rootDefinition = &fTopics.front();
324 definition = rootDefinition;
325 definition->fFileName = fFileName;
326 definition->fContentStart = fChar;
Cary Clark2a8c48b2018-02-15 17:31:24 -0500327 if (MarkType::kTopic == markType) {
328 if (fParent) {
329 return this->reportError<bool>("#Topic must be root");
330 }
331 // topic name is unappended
332 definition->fName = typeNameBuilder[0];
333 } else {
334 if (!fParent) {
335 return this->reportError<bool>("#Subtopic may not be root");
336 }
337 Definition* parent = fParent;
338 while (MarkType::kTopic != parent->fMarkType && MarkType::kSubtopic != parent->fMarkType) {
339 parent = parent->fParent;
340 if (!parent) {
341 // subtopic must have subtopic or topic in parent chain
342 return this->reportError<bool>("#Subtopic missing parent");
343 }
344 }
345 if (MarkType::kSubtopic == parent->fMarkType) {
346 // subtopic prepends parent subtopic name, but not parent topic name
347 definition->fName = parent->fName + '_';
348 }
349 definition->fName += typeNameBuilder[0];
350 definition->fFiddle = parent->fFiddle + '_';
Cary Clark8032b982017-07-28 11:04:54 -0400351 }
Cary Clarka560c472017-11-27 10:44:06 -0500352 definition->fFiddle += Definition::NormalizedName(typeNameBuilder[0]);
Cary Clark8032b982017-07-28 11:04:54 -0400353 this->setAsParent(definition);
354 }
355 {
Cary Clark08895c42018-02-01 09:37:32 -0500356 SkASSERT(hasEnd ? fParent : definition);
357 string fullTopic = hasEnd ? fParent->fFiddle : definition->fFiddle;
Cary Clark8032b982017-07-28 11:04:54 -0400358 Definition* defPtr = fTopicMap[fullTopic];
359 if (hasEnd) {
Cary Clarkf895a422018-02-27 09:54:21 -0500360 if (HasTag::kYes == hasTag && !this->checkEndMarker(markType, fullTopic)) {
361 return false;
362 }
Cary Clark8032b982017-07-28 11:04:54 -0400363 if (!definition) {
364 definition = defPtr;
365 } else if (definition != defPtr) {
366 return this->reportError<bool>("mismatched topic");
367 }
368 } else {
369 if (nullptr != defPtr) {
370 return this->reportError<bool>("already declared topic");
371 }
372 fTopicMap[fullTopic] = definition;
373 }
374 }
375 if (hasEnd) {
376 if (!this->popParentStack(definition)) {
377 return false;
378 }
379 }
380 break;
Cary Clark2be81cf2018-09-13 12:04:30 -0400381 case MarkType::kFormula:
382 // hasEnd : single line / multiple line
383 if (!fParent || MarkType::kFormula != fParent->fMarkType) {
384 SkASSERT(!definition || MarkType::kFormula == definition->fMarkType);
385 fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC);
386 definition = &fMarkup.front();
387 definition->fContentStart = fChar;
388 definition->fName = typeNameBuilder[0];
389 definition->fFiddle = fParent->fFiddle;
390 fParent = definition;
391 } else {
392 SkASSERT(fParent && MarkType::kFormula == fParent->fMarkType);
393 SkASSERT(fMC == defStart[0]);
394 SkASSERT(fMC == defStart[-1]);
395 definition = fParent;
396 definition->fTerminator = fChar;
397 if (!this->popParentStack(definition)) {
398 return false;
399 }
400 this->parseHashFormula(definition);
401 fParent->fChildren.push_back(definition);
402 }
403 break;
Cary Clark8032b982017-07-28 11:04:54 -0400404 // these types are children of parents, but are not in named maps
Cary Clark8032b982017-07-28 11:04:54 -0400405 case MarkType::kDescription:
406 case MarkType::kStdOut:
407 // may be one-liner
Cary Clark137b8742018-05-30 09:21:49 -0400408 case MarkType::kAlias:
Cary Clark8032b982017-07-28 11:04:54 -0400409 case MarkType::kNoExample:
410 case MarkType::kParam:
Cary Clark1a8d7622018-03-05 13:26:16 -0500411 case MarkType::kPhraseDef:
Cary Clark8032b982017-07-28 11:04:54 -0400412 case MarkType::kReturn:
413 case MarkType::kToDo:
414 if (hasEnd) {
415 if (markType == fParent->fMarkType) {
416 definition = fParent;
417 if (MarkType::kBug == markType || MarkType::kReturn == markType
418 || MarkType::kToDo == markType) {
419 this->skipNoName();
420 }
421 if (!this->popParentStack(fParent)) { // if not one liner, pop
422 return false;
423 }
Cary Clark1a8d7622018-03-05 13:26:16 -0500424 if (MarkType::kParam == markType || MarkType::kReturn == markType
425 || MarkType::kPhraseDef == markType) {
Cary Clarka523d2d2017-08-30 08:58:10 -0400426 if (!this->checkParamReturn(definition)) {
427 return false;
Cary Clark579985c2017-07-31 11:48:27 -0400428 }
429 }
Cary Clark1a8d7622018-03-05 13:26:16 -0500430 if (MarkType::kPhraseDef == markType) {
431 string key = definition->fName;
432 if (fPhraseMap.end() != fPhraseMap.find(key)) {
433 this->reportError<bool>("duplicate phrase key");
434 }
435 fPhraseMap[key] = definition;
436 }
Cary Clark8032b982017-07-28 11:04:54 -0400437 } else {
Cary Clark1a8d7622018-03-05 13:26:16 -0500438 fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC);
Cary Clark8032b982017-07-28 11:04:54 -0400439 definition = &fMarkup.front();
440 definition->fName = typeNameBuilder[0];
Cary Clark73fa9722017-08-29 17:36:51 -0400441 definition->fFiddle = fParent->fFiddle;
Cary Clark8032b982017-07-28 11:04:54 -0400442 definition->fContentStart = fChar;
Cary Clark1a8d7622018-03-05 13:26:16 -0500443 string endBracket;
444 endBracket += fMC;
445 endBracket += fMC;
446 definition->fContentEnd = this->trimmedBracketEnd(endBracket);
447 this->skipToEndBracket(endBracket.c_str());
Cary Clark8032b982017-07-28 11:04:54 -0400448 SkAssertResult(fMC == this->next());
449 SkAssertResult(fMC == this->next());
450 definition->fTerminator = fChar;
Cary Clark1a8d7622018-03-05 13:26:16 -0500451 TextParser checkForChildren(definition);
452 if (checkForChildren.strnchr(fMC, definition->fContentEnd)) {
453 this->reportError<bool>("put ## on separate line");
454 }
Cary Clark8032b982017-07-28 11:04:54 -0400455 fParent->fChildren.push_back(definition);
456 }
Cary Clark137b8742018-05-30 09:21:49 -0400457 if (MarkType::kAlias == markType) {
458 const char* end = definition->fChildren.size() > 0 ?
459 definition->fChildren[0]->fStart : definition->fContentEnd;
460 TextParser parser(definition->fFileName, definition->fContentStart, end,
461 definition->fLineCount);
462 parser.trimEnd();
463 string key = string(parser.fStart, parser.lineLength());
464 if (fAliasMap.end() != fAliasMap.find(key)) {
465 return this->reportError<bool>("duplicate alias");
466 }
467 fAliasMap[key] = definition;
468 definition->fFiddle = definition->fParent->fFiddle;
469 }
Cary Clark8032b982017-07-28 11:04:54 -0400470 break;
Cary Clark682c58d2018-05-16 07:07:07 -0400471 } else if (MarkType::kPhraseDef == markType) {
472 bool hasParams = '(' == this->next();
473 fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC);
474 definition = &fMarkup.front();
475 definition->fName = typeNameBuilder[0];
476 definition->fFiddle = fParent->fFiddle;
477 definition->fContentStart = fChar;
478 if (hasParams) {
479 char lastChar;
480 do {
481 const char* subEnd = this->anyOf(",)\n");
482 if (!subEnd || '\n' == *subEnd) {
483 return this->reportError<bool>("unexpected phrase list end");
484 }
485 fMarkup.emplace_front(MarkType::kPhraseParam, fChar, fLineCount, fParent,
486 fMC);
487 Definition* phraseParam = &fMarkup.front();
488 phraseParam->fContentStart = fChar;
489 phraseParam->fContentEnd = subEnd;
490 phraseParam->fName = string(fChar, subEnd - fChar);
491 definition->fChildren.push_back(phraseParam);
492 this->skipTo(subEnd);
493 lastChar = this->next();
494 phraseParam->fTerminator = fChar;
495 } while (')' != lastChar);
496 this->skipWhiteSpace();
497 definition->fContentStart = fChar;
498 }
499 this->setAsParent(definition);
500 break;
Cary Clark8032b982017-07-28 11:04:54 -0400501 }
502 // not one-liners
503 case MarkType::kCode:
Cary Clark8032b982017-07-28 11:04:54 -0400504 case MarkType::kExample:
Cary Clark0d225392018-06-07 09:59:07 -0400505 case MarkType::kFile:
Cary Clark8032b982017-07-28 11:04:54 -0400506 case MarkType::kFunction:
507 case MarkType::kLegend:
508 case MarkType::kList:
509 case MarkType::kPrivate:
510 case MarkType::kTable:
Cary Clark8032b982017-07-28 11:04:54 -0400511 if (hasEnd) {
512 definition = fParent;
513 if (markType != fParent->fMarkType) {
514 return this->reportError<bool>("end element mismatch");
515 } else if (!this->popParentStack(fParent)) {
516 return false;
517 }
518 if (MarkType::kExample == markType) {
519 if (definition->fChildren.size() == 0) {
520 TextParser emptyCheck(definition);
521 if (emptyCheck.eof() || !emptyCheck.skipWhiteSpace()) {
Cary Clark884dd7d2017-10-11 10:37:52 -0400522 return this->reportError<bool>("missing example body");
Cary Clark8032b982017-07-28 11:04:54 -0400523 }
524 }
Cary Clark1a8d7622018-03-05 13:26:16 -0500525// can't do this here; phrase refs may not have been defined yet
526// this->setWrapper(definition);
Cary Clark8032b982017-07-28 11:04:54 -0400527 }
528 } else {
Cary Clark1a8d7622018-03-05 13:26:16 -0500529 fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC);
Cary Clark8032b982017-07-28 11:04:54 -0400530 definition = &fMarkup.front();
531 definition->fContentStart = fChar;
532 definition->fName = typeNameBuilder[0];
533 definition->fFiddle = fParent->fFiddle;
534 char suffix = '\0';
535 bool tryAgain;
536 do {
537 tryAgain = false;
538 for (const auto& child : fParent->fChildren) {
539 if (child->fFiddle == definition->fFiddle) {
540 if (MarkType::kExample != child->fMarkType) {
541 continue;
542 }
543 if ('\0' == suffix) {
544 suffix = 'a';
545 } else if (++suffix > 'z') {
546 return reportError<bool>("too many examples");
547 }
548 definition->fFiddle = fParent->fFiddle + '_';
549 definition->fFiddle += suffix;
550 tryAgain = true;
551 break;
552 }
553 }
554 } while (tryAgain);
555 this->setAsParent(definition);
556 }
557 break;
558 // always treated as one-liners (can't detect misuse easily)
Ben Wagner63fd7602017-10-09 15:45:33 -0400559 case MarkType::kAnchor:
Cary Clark4855f782018-02-06 09:41:53 -0500560 case MarkType::kBug:
Cary Clark4855f782018-02-06 09:41:53 -0500561 case MarkType::kDeprecated:
Cary Clark682c58d2018-05-16 07:07:07 -0400562 case MarkType::kDetails:
Cary Clarkac47b882018-01-11 10:35:44 -0500563 case MarkType::kDuration:
Cary Clark682c58d2018-05-16 07:07:07 -0400564 case MarkType::kExperimental:
Cary Clark8032b982017-07-28 11:04:54 -0400565 case MarkType::kHeight:
Cary Clarkf895a422018-02-27 09:54:21 -0500566 case MarkType::kIllustration:
Cary Clark8032b982017-07-28 11:04:54 -0400567 case MarkType::kImage:
Cary Clarkab2621d2018-01-30 10:08:57 -0500568 case MarkType::kIn:
569 case MarkType::kLine:
570 case MarkType::kLiteral:
Cary Clark682c58d2018-05-16 07:07:07 -0400571 case MarkType::kNoJustify:
Cary Clark154beea2017-10-26 07:58:48 -0400572 case MarkType::kOutdent:
Cary Clark8032b982017-07-28 11:04:54 -0400573 case MarkType::kPlatform:
Cary Clark08895c42018-02-01 09:37:32 -0500574 case MarkType::kPopulate:
Cary Clark8032b982017-07-28 11:04:54 -0400575 case MarkType::kSeeAlso:
Cary Clark61dfc3a2018-01-03 08:37:53 -0500576 case MarkType::kSet:
Cary Clark8032b982017-07-28 11:04:54 -0400577 case MarkType::kSubstitute:
Cary Clark8032b982017-07-28 11:04:54 -0400578 case MarkType::kVolatile:
579 case MarkType::kWidth:
Cary Clark4855f782018-02-06 09:41:53 -0500580 // todo : add check disallowing children?
Cary Clarkab2621d2018-01-30 10:08:57 -0500581 if (hasEnd && MarkType::kAnchor != markType && MarkType::kLine != markType) {
Cary Clark8032b982017-07-28 11:04:54 -0400582 return this->reportError<bool>("one liners omit end element");
Cary Clark6fc50412017-09-21 12:31:06 -0400583 } else if (!hasEnd && MarkType::kAnchor == markType) {
584 return this->reportError<bool>("anchor line must have end element last");
Cary Clark8032b982017-07-28 11:04:54 -0400585 }
Cary Clark1a8d7622018-03-05 13:26:16 -0500586 fMarkup.emplace_front(markType, defStart, fLineCount, fParent, fMC);
Cary Clark8032b982017-07-28 11:04:54 -0400587 definition = &fMarkup.front();
588 definition->fName = typeNameBuilder[0];
Cary Clarka560c472017-11-27 10:44:06 -0500589 definition->fFiddle = Definition::NormalizedName(typeNameBuilder[0]);
Cary Clark8032b982017-07-28 11:04:54 -0400590 definition->fContentStart = fChar;
Cary Clarkce101242017-09-01 15:51:02 -0400591 definition->fContentEnd = this->trimmedBracketEnd('\n');
Cary Clark8032b982017-07-28 11:04:54 -0400592 definition->fTerminator = this->lineEnd() - 1;
593 fParent->fChildren.push_back(definition);
594 if (MarkType::kAnchor == markType) {
Cary Clark2be81cf2018-09-13 12:04:30 -0400595 this->parseHashAnchor(definition);
Cary Clark137b8742018-05-30 09:21:49 -0400596 } else if (MarkType::kLine == markType) {
Cary Clark2be81cf2018-09-13 12:04:30 -0400597 this->parseHashLine(definition);
Cary Clark682c58d2018-05-16 07:07:07 -0400598 } else if (IncompleteAllowed(markType)) {
Cary Clark4855f782018-02-06 09:41:53 -0500599 this->skipSpace();
600 fParent->fDeprecated = true;
Cary Clark682c58d2018-05-16 07:07:07 -0400601 fParent->fDetails =
602 this->skipExact("soon") ? Definition::Details::kSoonToBe_Deprecated :
603 this->skipExact("testing") ? Definition::Details::kTestingOnly_Experiment :
Cary Clark137b8742018-05-30 09:21:49 -0400604 this->skipExact("do not use") ? Definition::Details::kDoNotUse_Experiment :
Cary Clark682c58d2018-05-16 07:07:07 -0400605 this->skipExact("not ready") ? Definition::Details::kNotReady_Experiment :
606 Definition::Details::kNone;
Cary Clark4855f782018-02-06 09:41:53 -0500607 this->skipSpace();
608 if ('\n' != this->peek()) {
609 return this->reportError<bool>("unexpected text after #Deprecated");
610 }
611 }
Cary Clark8032b982017-07-28 11:04:54 -0400612 break;
613 case MarkType::kExternal:
614 (void) this->collectExternals(); // FIXME: detect errors in external defs?
615 break;
616 default:
617 SkASSERT(0); // fixme : don't let any types be invisible
618 return true;
619 }
620 if (fParent) {
621 SkASSERT(definition);
622 SkASSERT(definition->fName.length() > 0);
623 }
624 return true;
625}
626
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400627void BmhParser::reportDuplicates(const Definition& def, string dup) const {
Cary Clark73fa9722017-08-29 17:36:51 -0400628 if (MarkType::kExample == def.fMarkType && dup == def.fFiddle) {
629 TextParser reporter(&def);
630 reporter.reportError("duplicate example name");
631 }
632 for (auto& child : def.fChildren ) {
633 reportDuplicates(*child, dup);
634 }
635}
636
Cary Clarkf895a422018-02-27 09:54:21 -0500637
638static Definition* find_fiddle(Definition* def, string name) {
639 if (MarkType::kExample == def->fMarkType && name == def->fFiddle) {
640 return def;
641 }
642 for (auto& child : def->fChildren) {
643 Definition* result = find_fiddle(child, name);
644 if (result) {
645 return result;
646 }
647 }
648 return nullptr;
649}
650
651Definition* BmhParser::findExample(string name) const {
652 for (const auto& topic : fTopicMap) {
653 if (topic.second->fParent) {
654 continue;
655 }
656 Definition* def = find_fiddle(topic.second, name);
657 if (def) {
658 return def;
659 }
660 }
661 return nullptr;
662}
663
Cary Clarkd7895502018-07-18 15:10:08 -0400664static bool check_example_hashes(Definition* def) {
665 if (MarkType::kExample == def->fMarkType) {
666 if (def->fHash.length()) {
667 return true;
668 }
669 for (auto child : def->fChildren) {
670 if (MarkType::kPlatform == child->fMarkType) {
671 if (string::npos != string(child->fContentStart, child->length()).find("!fiddle")) {
672 return true;
673 }
674 }
675 }
676 return def->reportError<bool>("missing hash");
677 }
678 for (auto& child : def->fChildren) {
679 if (!check_example_hashes(child)) {
680 return false;
681 }
682 }
683 return true;
684}
685
686bool BmhParser::checkExampleHashes() const {
687 for (const auto& topic : fTopicMap) {
688 if (!topic.second->fParent && !check_example_hashes(topic.second)) {
689 return false;
690 }
691 }
692 return true;
693}
694
695static void reset_example_hashes(Definition* def) {
696 if (MarkType::kExample == def->fMarkType) {
697 def->fHash.clear();
698 return;
699 }
700 for (auto& child : def->fChildren) {
701 reset_example_hashes(child);
702 }
703}
704
705void BmhParser::resetExampleHashes() {
706 for (const auto& topic : fTopicMap) {
707 if (!topic.second->fParent) {
708 reset_example_hashes(topic.second);
709 }
710 }
711}
712
Cary Clark73fa9722017-08-29 17:36:51 -0400713static void find_examples(const Definition& def, vector<string>* exampleNames) {
714 if (MarkType::kExample == def.fMarkType) {
715 exampleNames->push_back(def.fFiddle);
716 }
717 for (auto& child : def.fChildren ) {
718 find_examples(*child, exampleNames);
719 }
720}
721
Cary Clarkf895a422018-02-27 09:54:21 -0500722bool BmhParser::checkEndMarker(MarkType markType, string match) const {
723 TextParser tp(fFileName, fLine, fChar, fLineCount);
724 tp.skipSpace();
725 if (fMC != tp.next()) {
726 return this->reportError<bool>("mismatched end marker expect #");
727 }
728 const char* nameStart = tp.fChar;
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400729 tp.skipToNonName();
Cary Clarkf895a422018-02-27 09:54:21 -0500730 string markName(nameStart, tp.fChar - nameStart);
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400731 if (kMarkProps[(int) markType].fName != markName) {
Cary Clarkf895a422018-02-27 09:54:21 -0500732 return this->reportError<bool>("expected #XXX ## to match");
733 }
734 tp.skipSpace();
735 nameStart = tp.fChar;
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400736 tp.skipToNonName();
Cary Clarkf895a422018-02-27 09:54:21 -0500737 markName = string(nameStart, tp.fChar - nameStart);
738 if ("" == markName) {
739 if (fMC != tp.next() || fMC != tp.next()) {
740 return this->reportError<bool>("expected ##");
741 }
742 return true;
743 }
744 std::replace(markName.begin(), markName.end(), '-', '_');
745 auto defPos = match.rfind(markName);
746 if (string::npos == defPos) {
747 return this->reportError<bool>("mismatched end marker v1");
748 }
749 if (markName.size() != match.size() - defPos) {
750 return this->reportError<bool>("mismatched end marker v2");
751 }
752 return true;
753}
754
Cary Clark73fa9722017-08-29 17:36:51 -0400755bool BmhParser::checkExamples() const {
756 vector<string> exampleNames;
757 for (const auto& topic : fTopicMap) {
758 if (topic.second->fParent) {
759 continue;
760 }
761 find_examples(*topic.second, &exampleNames);
762 }
763 std::sort(exampleNames.begin(), exampleNames.end());
764 string* last = nullptr;
765 string reported;
766 bool checkOK = true;
767 for (auto& nameIter : exampleNames) {
768 if (last && *last == nameIter && reported != *last) {
769 reported = *last;
770 SkDebugf("%s\n", reported.c_str());
771 for (const auto& topic : fTopicMap) {
772 if (topic.second->fParent) {
773 continue;
774 }
775 this->reportDuplicates(*topic.second, reported);
776 }
777 checkOK = false;
778 }
779 last = &nameIter;
780 }
781 return checkOK;
782}
783
Cary Clarka523d2d2017-08-30 08:58:10 -0400784bool BmhParser::checkParamReturn(const Definition* definition) const {
785 const char* parmEndCheck = definition->fContentEnd;
786 while (parmEndCheck < definition->fTerminator) {
787 if (fMC == parmEndCheck[0]) {
788 break;
789 }
790 if (' ' < parmEndCheck[0]) {
791 this->reportError<bool>(
792 "use full end marker on multiline #Param and #Return");
793 }
794 ++parmEndCheck;
795 }
796 return true;
797}
798
Cary Clark8032b982017-07-28 11:04:54 -0400799bool BmhParser::childOf(MarkType markType) const {
800 auto childError = [this](MarkType markType) -> bool {
801 string errStr = "expected ";
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400802 errStr += kMarkProps[(int) markType].fName;
Cary Clark8032b982017-07-28 11:04:54 -0400803 errStr += " parent";
804 return this->reportError<bool>(errStr.c_str());
805 };
806
807 if (markType == fParent->fMarkType) {
808 return true;
809 }
810 if (this->hasEndToken()) {
811 if (!fParent->fParent) {
812 return this->reportError<bool>("expected grandparent");
813 }
814 if (markType == fParent->fParent->fMarkType) {
815 return true;
816 }
817 }
818 return childError(markType);
819}
820
821string BmhParser::className(MarkType markType) {
Cary Clark8032b982017-07-28 11:04:54 -0400822 const char* end = this->lineEnd();
823 const char* mc = this->strnchr(fMC, end);
Cary Clark73fa9722017-08-29 17:36:51 -0400824 string classID;
Cary Clark186d08f2018-04-03 08:43:27 -0400825 TextParserSave savePlace(this);
Cary Clark73fa9722017-08-29 17:36:51 -0400826 this->skipSpace();
827 const char* wordStart = fChar;
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400828 this->skipToNonName();
Cary Clark73fa9722017-08-29 17:36:51 -0400829 const char* wordEnd = fChar;
830 classID = string(wordStart, wordEnd - wordStart);
831 if (!mc) {
832 savePlace.restore();
833 }
834 string builder;
835 const Definition* parent = this->parentSpace();
836 if (parent && parent->fName != classID) {
837 builder += parent->fName;
838 }
Cary Clark8032b982017-07-28 11:04:54 -0400839 if (mc) {
Cary Clark8032b982017-07-28 11:04:54 -0400840 if (mc + 1 < fEnd && fMC == mc[1]) { // if ##
841 if (markType != fParent->fMarkType) {
842 return this->reportError<string>("unbalanced method");
843 }
Cary Clark73fa9722017-08-29 17:36:51 -0400844 if (builder.length() > 0 && classID.size() > 0) {
Cary Clark8032b982017-07-28 11:04:54 -0400845 if (builder != fParent->fName) {
846 builder += "::";
Cary Clark73fa9722017-08-29 17:36:51 -0400847 builder += classID;
Cary Clark8032b982017-07-28 11:04:54 -0400848 if (builder != fParent->fName) {
849 return this->reportError<string>("name mismatch");
850 }
851 }
852 }
853 this->skipLine();
854 return fParent->fName;
855 }
856 fChar = mc;
857 this->next();
858 }
859 this->skipWhiteSpace();
860 if (MarkType::kEnum == markType && fChar >= end) {
861 fAnonymous = true;
862 builder += "::_anonymous";
863 return uniqueRootName(builder, markType);
864 }
865 builder = this->word(builder, "::");
866 return builder;
867}
868
869bool BmhParser::collectExternals() {
870 do {
871 this->skipWhiteSpace();
872 if (this->eof()) {
873 break;
874 }
875 if (fMC == this->peek()) {
876 this->next();
877 if (this->eof()) {
878 break;
879 }
880 if (fMC == this->peek()) {
881 this->skipLine();
882 break;
883 }
884 if (' ' >= this->peek()) {
885 this->skipLine();
886 continue;
887 }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400888 if (this->startsWith(kMarkProps[(int) MarkType::kExternal].fName)) {
889 this->skipToNonName();
Cary Clark8032b982017-07-28 11:04:54 -0400890 continue;
891 }
892 }
893 this->skipToAlpha();
894 const char* wordStart = fChar;
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400895 this->skipToNonName();
Cary Clark8032b982017-07-28 11:04:54 -0400896 if (fChar - wordStart > 0) {
Cary Clark1a8d7622018-03-05 13:26:16 -0500897 fExternals.emplace_front(MarkType::kExternal, wordStart, fChar, fLineCount, fParent,
898 fMC);
Cary Clark8032b982017-07-28 11:04:54 -0400899 RootDefinition* definition = &fExternals.front();
900 definition->fFileName = fFileName;
901 definition->fName = string(wordStart ,fChar - wordStart);
Cary Clarka560c472017-11-27 10:44:06 -0500902 definition->fFiddle = Definition::NormalizedName(definition->fName);
Cary Clark8032b982017-07-28 11:04:54 -0400903 }
904 } while (!this->eof());
905 return true;
906}
907
Cary Clark1a8d7622018-03-05 13:26:16 -0500908bool BmhParser::dumpExamples(FILE* fiddleOut, Definition& def, bool* continuation) const {
Cary Clark73fa9722017-08-29 17:36:51 -0400909 if (MarkType::kExample == def.fMarkType) {
910 string result;
Cary Clark1a8d7622018-03-05 13:26:16 -0500911 if (!this->exampleToScript(&def, BmhParser::ExampleOptions::kAll, &result)) {
Cary Clark73fa9722017-08-29 17:36:51 -0400912 return false;
913 }
914 if (result.length() > 0) {
Cary Clarkbef063a2017-10-31 15:44:45 -0400915 result += "\n";
916 result += "}";
Cary Clark73fa9722017-08-29 17:36:51 -0400917 if (*continuation) {
918 fprintf(fiddleOut, ",\n");
919 } else {
920 *continuation = true;
921 }
922 fprintf(fiddleOut, "%s", result.c_str());
923 }
924 return true;
925 }
926 for (auto& child : def.fChildren ) {
Cary Clark1a8d7622018-03-05 13:26:16 -0500927 if (!this->dumpExamples(fiddleOut, *child, continuation)) {
Cary Clark73fa9722017-08-29 17:36:51 -0400928 return false;
929 }
930 }
931 return true;
932}
933
934bool BmhParser::dumpExamples(const char* fiddleJsonFileName) const {
Cary Clark5b1f9532018-08-28 14:53:37 -0400935 string oldFiddle(fiddleJsonFileName);
936 string newFiddle(fiddleJsonFileName);
937 newFiddle += "_new";
938 FILE* fiddleOut = fopen(newFiddle.c_str(), "wb");
Cary Clark73fa9722017-08-29 17:36:51 -0400939 if (!fiddleOut) {
Cary Clark5b1f9532018-08-28 14:53:37 -0400940 SkDebugf("could not open output file %s\n", newFiddle.c_str());
Cary Clark73fa9722017-08-29 17:36:51 -0400941 return false;
942 }
943 fprintf(fiddleOut, "{\n");
944 bool continuation = false;
945 for (const auto& topic : fTopicMap) {
946 if (topic.second->fParent) {
947 continue;
948 }
Cary Clark1a8d7622018-03-05 13:26:16 -0500949 this->dumpExamples(fiddleOut, *topic.second, &continuation);
Cary Clark73fa9722017-08-29 17:36:51 -0400950 }
951 fprintf(fiddleOut, "\n}\n");
952 fclose(fiddleOut);
Cary Clark5b1f9532018-08-28 14:53:37 -0400953 if (ParserCommon::WrittenFileDiffers(oldFiddle, newFiddle)) {
954 ParserCommon::CopyToFile(oldFiddle, newFiddle);
955 SkDebugf("wrote %s\n", fiddleJsonFileName);
Cary Clark6a1185a2018-09-04 16:15:11 -0400956 } else {
957 remove(newFiddle.c_str());
Cary Clark5b1f9532018-08-28 14:53:37 -0400958 }
Cary Clark73fa9722017-08-29 17:36:51 -0400959 return true;
960}
961
Cary Clark8032b982017-07-28 11:04:54 -0400962int BmhParser::endHashCount() const {
963 const char* end = fLine + this->lineLength();
964 int count = 0;
965 while (fLine < end && fMC == *--end) {
966 count++;
967 }
968 return count;
969}
970
Cary Clarkce101242017-09-01 15:51:02 -0400971bool BmhParser::endTableColumn(const char* end, const char* terminator) {
972 if (!this->popParentStack(fParent)) {
973 return false;
974 }
975 fWorkingColumn->fContentEnd = end;
976 fWorkingColumn->fTerminator = terminator;
977 fColStart = fChar - 1;
978 this->skipSpace();
979 fTableState = TableState::kColumnStart;
980 return true;
981}
982
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400983static size_t count_indent(string text, size_t test, size_t end) {
Cary Clark1a8d7622018-03-05 13:26:16 -0500984 size_t result = test;
985 while (test < end) {
986 if (' ' != text[test]) {
987 break;
988 }
989 ++test;
990 }
991 return test - result;
992}
993
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400994static void add_code(string text, int pos, int end,
Cary Clark1a8d7622018-03-05 13:26:16 -0500995 size_t outIndent, size_t textIndent, string& example) {
996 do {
997 // fix this to move whole paragraph in, out, but preserve doc indent
998 int nextIndent = count_indent(text, pos, end);
999 size_t len = text.find('\n', pos);
1000 if (string::npos == len) {
1001 len = end;
1002 }
1003 if ((size_t) (pos + nextIndent) < len) {
1004 size_t indent = outIndent + nextIndent;
1005 SkASSERT(indent >= textIndent);
1006 indent -= textIndent;
1007 for (size_t index = 0; index < indent; ++index) {
1008 example += ' ';
1009 }
1010 pos += nextIndent;
1011 while ((size_t) pos < len) {
1012 example += '"' == text[pos] ? "\\\"" :
1013 '\\' == text[pos] ? "\\\\" :
1014 text.substr(pos, 1);
1015 ++pos;
1016 }
1017 example += "\\n";
1018 } else {
1019 pos += nextIndent;
1020 }
1021 if ('\n' == text[pos]) {
1022 ++pos;
1023 }
1024 } while (pos < end);
1025}
1026
Cary Clark61313f32018-10-08 14:57:48 -04001027bool BmhParser::IsExemplary(const Definition* def) {
1028 return kMarkProps[(int) def->fMarkType].fExemplary != Exemplary::kNo;
1029}
1030
Cary Clark1a8d7622018-03-05 13:26:16 -05001031bool BmhParser::exampleToScript(Definition* def, ExampleOptions exampleOptions,
1032 string* result) const {
1033 bool hasFiddle = true;
1034 const Definition* platform = def->hasChild(MarkType::kPlatform);
1035 if (platform) {
1036 TextParser platParse(platform);
1037 hasFiddle = !platParse.strnstr("!fiddle", platParse.fEnd);
1038 }
1039 if (!hasFiddle) {
1040 *result = "";
1041 return true;
1042 }
1043 string text = this->extractText(def, TrimExtract::kNo);
1044 bool textOut = string::npos != text.find("SkDebugf(")
1045 || string::npos != text.find("dump(")
1046 || string::npos != text.find("dumpHex(");
1047 string heightStr = "256";
1048 string widthStr = "256";
1049 string normalizedName(def->fFiddle);
1050 string code;
1051 string imageStr = "0";
1052 string srgbStr = "false";
1053 string durationStr = "0";
1054 for (auto iter : def->fChildren) {
1055 switch (iter->fMarkType) {
1056 case MarkType::kDuration:
1057 durationStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
1058 break;
1059 case MarkType::kHeight:
1060 heightStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
1061 break;
1062 case MarkType::kWidth:
1063 widthStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
1064 break;
1065 case MarkType::kDescription:
1066 // ignore for now
1067 break;
1068 case MarkType::kFunction: {
1069 // emit this, but don't wrap this in draw()
1070 string funcText = this->extractText(&*iter, TrimExtract::kNo);
1071 size_t pos = 0;
1072 while (pos < funcText.length() && ' ' > funcText[pos]) {
1073 ++pos;
1074 }
1075 size_t indent = count_indent(funcText, pos, funcText.length());
1076 add_code(funcText, pos, funcText.length(), 0, indent, code);
1077 code += "\\n";
1078 } break;
1079 case MarkType::kComment:
1080 break;
1081 case MarkType::kImage:
1082 imageStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
1083 break;
1084 case MarkType::kToDo:
1085 break;
1086 case MarkType::kBug:
1087 case MarkType::kMarkChar:
1088 case MarkType::kPlatform:
1089 case MarkType::kPhraseRef:
1090 // ignore for now
1091 break;
1092 case MarkType::kSet:
1093 if ("sRGB" == string(iter->fContentStart,
1094 iter->fContentEnd - iter->fContentStart)) {
1095 srgbStr = "true";
1096 } else {
1097 SkASSERT(0); // more work to do
1098 return false;
1099 }
1100 break;
1101 case MarkType::kStdOut:
1102 textOut = true;
1103 break;
1104 default:
1105 SkASSERT(0); // more coding to do
1106 }
1107 }
1108 string animatedStr = "0" != durationStr ? "true" : "false";
1109 string textOutStr = textOut ? "true" : "false";
1110 size_t pos = 0;
1111 while (pos < text.length() && ' ' > text[pos]) {
1112 ++pos;
1113 }
1114 size_t end = text.length();
1115 size_t outIndent = 0;
1116 size_t textIndent = count_indent(text, pos, end);
1117 if ("" == def->fWrapper) {
1118 this->setWrapper(def);
1119 }
1120 if (def->fWrapper.length() > 0) {
1121 code += def->fWrapper;
1122 code += "\\n";
1123 outIndent = 4;
1124 }
1125 add_code(text, pos, end, outIndent, textIndent, code);
1126 if (def->fWrapper.length() > 0) {
1127 code += "}";
1128 }
1129 string example = "\"" + normalizedName + "\": {\n";
Cary Clark2d4bf5f2018-04-16 08:37:38 -04001130 string filename = def->fileName();
1131 string baseFile = filename.substr(0, filename.length() - 4);
Cary Clark1a8d7622018-03-05 13:26:16 -05001132 if (ExampleOptions::kText == exampleOptions) {
1133 example += " \"code\": \"" + code + "\",\n";
1134 example += " \"hash\": \"" + def->fHash + "\",\n";
1135 example += " \"file\": \"" + baseFile + "\",\n";
1136 example += " \"name\": \"" + def->fName + "\",";
1137 } else {
1138 example += " \"code\": \"" + code + "\",\n";
1139 if (ExampleOptions::kPng == exampleOptions) {
1140 example += " \"width\": " + widthStr + ",\n";
1141 example += " \"height\": " + heightStr + ",\n";
1142 example += " \"hash\": \"" + def->fHash + "\",\n";
1143 example += " \"file\": \"" + baseFile + "\",\n";
1144 example += " \"name\": \"" + def->fName + "\"\n";
1145 example += "}";
1146 } else {
1147 example += " \"options\": {\n";
1148 example += " \"width\": " + widthStr + ",\n";
1149 example += " \"height\": " + heightStr + ",\n";
1150 example += " \"source\": " + imageStr + ",\n";
1151 example += " \"srgb\": " + srgbStr + ",\n";
1152 example += " \"f16\": false,\n";
1153 example += " \"textOnly\": " + textOutStr + ",\n";
1154 example += " \"animated\": " + animatedStr + ",\n";
1155 example += " \"duration\": " + durationStr + "\n";
1156 example += " },\n";
1157 example += " \"fast\": true";
1158 }
1159 }
1160 *result = example;
1161 return true;
1162}
1163
1164string BmhParser::extractText(const Definition* def, TrimExtract trimExtract) const {
1165 string result;
1166 TextParser parser(def);
1167 auto childIter = def->fChildren.begin();
1168 while (!parser.eof()) {
1169 const char* end = def->fChildren.end() == childIter ? parser.fEnd : (*childIter)->fStart;
1170 string fragment(parser.fChar, end - parser.fChar);
1171 trim_end(fragment);
1172 if (TrimExtract::kYes == trimExtract) {
1173 trim_start(fragment);
1174 if (result.length()) {
1175 result += '\n';
1176 result += '\n';
1177 }
1178 }
1179 if (TrimExtract::kYes == trimExtract || has_nonwhitespace(fragment)) {
1180 result += fragment;
1181 }
1182 parser.skipTo(end);
1183 if (def->fChildren.end() != childIter) {
1184 Definition* child = *childIter;
1185 if (MarkType::kPhraseRef == child->fMarkType) {
1186 auto phraseIter = fPhraseMap.find(child->fName);
1187 if (fPhraseMap.end() == phraseIter) {
1188 return def->reportError<string>("missing phrase definition");
1189 }
1190 Definition* phrase = phraseIter->second;
1191 // count indent of last line in result
1192 size_t lastLF = result.rfind('\n');
1193 size_t startPos = string::npos == lastLF ? 0 : lastLF;
1194 size_t lastLen = result.length() - startPos;
1195 size_t indent = count_indent(result, startPos, result.length()) + 4;
1196 string phraseStr = this->extractText(phrase, TrimExtract::kNo);
1197 startPos = 0;
1198 bool firstTime = true;
1199 size_t endPos;
1200 do {
1201 endPos = phraseStr.find('\n', startPos);
1202 size_t len = (string::npos != endPos ? endPos : phraseStr.length()) - startPos;
1203 if (firstTime && lastLen + len + 1 < 100) { // FIXME: make 100 global const or something
1204 result += ' ';
1205 } else {
1206 result += '\n';
1207 result += string(indent, ' ');
1208 }
1209 firstTime = false;
1210 string tmp = phraseStr.substr(startPos, len);
1211 result += tmp;
1212 startPos = endPos + 1;
1213 } while (string::npos != endPos);
1214 result += '\n';
1215 }
1216 parser.skipTo(child->fTerminator);
1217 std::advance(childIter, 1);
1218 }
1219 }
1220 return result;
1221}
1222
1223void BmhParser::setWrapper(Definition* def) const {
1224 const char drawWrapper[] = "void draw(SkCanvas* canvas) {";
1225 const char drawNoCanvas[] = "void draw(SkCanvas* ) {";
1226 string text = this->extractText(def, TrimExtract::kNo);
1227 size_t nonSpace = 0;
1228 while (nonSpace < text.length() && ' ' >= text[nonSpace]) {
1229 ++nonSpace;
1230 }
1231 bool hasFunc = !text.compare(nonSpace, sizeof(drawWrapper) - 1, drawWrapper);
1232 bool noCanvas = !text.compare(nonSpace, sizeof(drawNoCanvas) - 1, drawNoCanvas);
1233 bool hasCanvas = string::npos != text.find("SkCanvas canvas");
1234 SkASSERT(!hasFunc || !noCanvas);
1235 bool preprocessor = text[0] == '#';
1236 bool wrapCode = !hasFunc && !noCanvas && !preprocessor;
1237 if (wrapCode) {
1238 def->fWrapper = hasCanvas ? string(drawNoCanvas) : string(drawWrapper);
1239 }
1240}
1241
Cary Clark0d225392018-06-07 09:59:07 -04001242RootDefinition* BmhParser::findBmhObject(MarkType markType, string typeName) {
1243 const auto& mapIter = std::find_if(fMaps.begin(), fMaps.end(),
1244 [markType](DefinitionMap& defMap){ return markType == defMap.fMarkType; } );
1245 if (mapIter == fMaps.end()) {
1246 return nullptr;
1247 }
1248 return &(*mapIter->fMap)[typeName];
1249}
1250
Ben Wagner63fd7602017-10-09 15:45:33 -04001251// FIXME: some examples may produce different output on different platforms
Cary Clark8032b982017-07-28 11:04:54 -04001252// if the text output can be different, think of how to author that
1253
1254bool BmhParser::findDefinitions() {
1255 bool lineStart = true;
Cary Clarkce101242017-09-01 15:51:02 -04001256 const char* lastChar = nullptr;
1257 const char* lastMC = nullptr;
Cary Clark8032b982017-07-28 11:04:54 -04001258 fParent = nullptr;
1259 while (!this->eof()) {
1260 if (this->peek() == fMC) {
Cary Clarkce101242017-09-01 15:51:02 -04001261 lastMC = fChar;
Cary Clark8032b982017-07-28 11:04:54 -04001262 this->next();
1263 if (this->peek() == fMC) {
1264 this->next();
1265 if (!lineStart && ' ' < this->peek()) {
Cary Clark2be81cf2018-09-13 12:04:30 -04001266 if (!fParent || MarkType::kFormula != fParent->fMarkType) {
1267 return this->reportError<bool>("expected definition");
1268 }
Cary Clark8032b982017-07-28 11:04:54 -04001269 }
1270 if (this->peek() != fMC) {
Cary Clarkce101242017-09-01 15:51:02 -04001271 if (MarkType::kColumn == fParent->fMarkType) {
1272 SkASSERT(TableState::kColumnEnd == fTableState);
1273 if (!this->endTableColumn(lastChar, lastMC)) {
1274 return false;
1275 }
1276 SkASSERT(fRow);
1277 if (!this->popParentStack(fParent)) {
1278 return false;
1279 }
1280 fRow->fContentEnd = fWorkingColumn->fContentEnd;
1281 fWorkingColumn = nullptr;
1282 fRow = nullptr;
1283 fTableState = TableState::kNone;
1284 } else {
1285 vector<string> parentName;
1286 parentName.push_back(fParent->fName);
Cary Clarkf895a422018-02-27 09:54:21 -05001287 if (!this->addDefinition(fChar - 1, true, fParent->fMarkType, parentName,
1288 HasTag::kNo)) {
Cary Clarkce101242017-09-01 15:51:02 -04001289 return false;
1290 }
Cary Clark8032b982017-07-28 11:04:54 -04001291 }
1292 } else {
1293 SkAssertResult(this->next() == fMC);
1294 fMC = this->next(); // change markup character
1295 if (' ' >= fMC) {
1296 return this->reportError<bool>("illegal markup character");
1297 }
Cary Clark1a8d7622018-03-05 13:26:16 -05001298 fMarkup.emplace_front(MarkType::kMarkChar, fChar - 4, fLineCount, fParent, fMC);
Cary Clark8032b982017-07-28 11:04:54 -04001299 Definition* markChar = &fMarkup.front();
1300 markChar->fContentStart = fChar - 1;
1301 this->skipToEndBracket('\n');
1302 markChar->fContentEnd = fChar;
1303 markChar->fTerminator = fChar;
1304 fParent->fChildren.push_back(markChar);
1305 }
1306 } else if (this->peek() >= 'A' && this->peek() <= 'Z') {
1307 const char* defStart = fChar - 1;
1308 MarkType markType = this->getMarkType(MarkLookup::kRequire);
1309 bool hasEnd = this->hasEndToken();
Cary Clark682c58d2018-05-16 07:07:07 -04001310 if (!hasEnd && fParent) {
1311 MarkType parentType = fParent->fMarkType;
Cary Clark2d4bf5f2018-04-16 08:37:38 -04001312 uint64_t parentMask = kMarkProps[(int) markType].fParentMask;
Cary Clark8032b982017-07-28 11:04:54 -04001313 if (parentMask && !(parentMask & (1LL << (int) parentType))) {
1314 return this->reportError<bool>("invalid parent");
1315 }
1316 }
Cary Clark2d4bf5f2018-04-16 08:37:38 -04001317 if (!this->skipName(kMarkProps[(int) markType].fName)) {
Cary Clark8032b982017-07-28 11:04:54 -04001318 return this->reportError<bool>("illegal markup character");
1319 }
1320 if (!this->skipSpace()) {
1321 return this->reportError<bool>("unexpected end");
1322 }
Cary Clark81abc432018-06-25 16:30:08 -04001323 lineStart = '\n' == this->peek();
Cary Clark8032b982017-07-28 11:04:54 -04001324 bool expectEnd = true;
1325 vector<string> typeNameBuilder = this->typeName(markType, &expectEnd);
1326 if (fCloned && MarkType::kMethod != markType && MarkType::kExample != markType
1327 && !fAnonymous) {
1328 return this->reportError<bool>("duplicate name");
1329 }
1330 if (hasEnd && expectEnd) {
Cary Clark137b8742018-05-30 09:21:49 -04001331 if (fMC == this->peek()) {
1332 return this->reportError<bool>("missing body");
1333 }
Cary Clark8032b982017-07-28 11:04:54 -04001334 }
Cary Clarkf895a422018-02-27 09:54:21 -05001335 if (!this->addDefinition(defStart, hasEnd, markType, typeNameBuilder,
1336 HasTag::kYes)) {
Cary Clark8032b982017-07-28 11:04:54 -04001337 return false;
1338 }
1339 continue;
1340 } else if (this->peek() == ' ') {
Cary Clark2be81cf2018-09-13 12:04:30 -04001341 if (!fParent || (MarkType::kFormula != fParent->fMarkType
Cary Clark8032b982017-07-28 11:04:54 -04001342 && MarkType::kLegend != fParent->fMarkType
Cary Clarkab2621d2018-01-30 10:08:57 -05001343 && MarkType::kList != fParent->fMarkType
Cary Clark2be81cf2018-09-13 12:04:30 -04001344 && MarkType::kLine != fParent->fMarkType
1345 && MarkType::kTable != fParent->fMarkType)) {
Cary Clark8032b982017-07-28 11:04:54 -04001346 int endHashes = this->endHashCount();
Cary Clarkce101242017-09-01 15:51:02 -04001347 if (endHashes <= 1) {
Cary Clark8032b982017-07-28 11:04:54 -04001348 if (fParent) {
Cary Clarkce101242017-09-01 15:51:02 -04001349 if (TableState::kColumnEnd == fTableState) {
1350 if (!this->endTableColumn(lastChar, lastMC)) {
1351 return false;
1352 }
1353 } else { // one line comment
1354 fMarkup.emplace_front(MarkType::kComment, fChar - 1, fLineCount,
Cary Clark1a8d7622018-03-05 13:26:16 -05001355 fParent, fMC);
Cary Clarkce101242017-09-01 15:51:02 -04001356 Definition* comment = &fMarkup.front();
1357 comment->fContentStart = fChar - 1;
1358 this->skipToEndBracket('\n');
1359 comment->fContentEnd = fChar;
1360 comment->fTerminator = fChar;
1361 fParent->fChildren.push_back(comment);
1362 }
Cary Clark8032b982017-07-28 11:04:54 -04001363 } else {
1364 fChar = fLine + this->lineLength() - 1;
1365 }
1366 } else { // table row
1367 if (2 != endHashes) {
1368 string errorStr = "expect ";
1369 errorStr += fMC;
1370 errorStr += fMC;
1371 return this->reportError<bool>(errorStr.c_str());
1372 }
1373 if (!fParent || MarkType::kTable != fParent->fMarkType) {
1374 return this->reportError<bool>("missing table");
1375 }
1376 }
Cary Clarkce101242017-09-01 15:51:02 -04001377 } else if (TableState::kNone == fTableState) {
Cary Clarkce101242017-09-01 15:51:02 -04001378 // fixme? no nested tables for now
1379 fColStart = fChar - 1;
Cary Clark1a8d7622018-03-05 13:26:16 -05001380 fMarkup.emplace_front(MarkType::kRow, fColStart, fLineCount, fParent, fMC);
Cary Clarkce101242017-09-01 15:51:02 -04001381 fRow = &fMarkup.front();
1382 fRow->fName = fParent->fName;
1383 this->skipWhiteSpace();
1384 fRow->fContentStart = fChar;
1385 this->setAsParent(fRow);
1386 fTableState = TableState::kColumnStart;
1387 }
1388 if (TableState::kColumnStart == fTableState) {
Cary Clark1a8d7622018-03-05 13:26:16 -05001389 fMarkup.emplace_front(MarkType::kColumn, fColStart, fLineCount, fParent, fMC);
Cary Clarkce101242017-09-01 15:51:02 -04001390 fWorkingColumn = &fMarkup.front();
1391 fWorkingColumn->fName = fParent->fName;
1392 fWorkingColumn->fContentStart = fChar;
1393 this->setAsParent(fWorkingColumn);
1394 fTableState = TableState::kColumnEnd;
1395 continue;
Cary Clark8032b982017-07-28 11:04:54 -04001396 }
Cary Clark1a8d7622018-03-05 13:26:16 -05001397 } else if (this->peek() >= 'a' && this->peek() <= 'z') {
1398 // expect zero or more letters and underscores (no spaces) then hash
1399 const char* phraseNameStart = fChar;
1400 this->skipPhraseName();
1401 string phraseKey = string(phraseNameStart, fChar - phraseNameStart);
Cary Clark682c58d2018-05-16 07:07:07 -04001402 char delimiter = this->next();
1403 vector<string> params;
1404 vector<const char*> paramsLoc;
1405 if (fMC != delimiter) {
1406 if ('(' != delimiter) {
1407 return this->reportError<bool>("expect # after phrase name");
1408 }
1409 // phrase may take comma delimited parameter list
1410 do {
1411 const char* subEnd = this->anyOf(",)\n");
1412 if (!subEnd || '\n' == *subEnd) {
1413 return this->reportError<bool>("unexpected phrase list end");
1414 }
1415 params.push_back(string(fChar, subEnd - fChar));
1416 paramsLoc.push_back(fChar);
1417 this->skipTo(subEnd);
1418
1419 } while (')' != this->next());
Cary Clark1a8d7622018-03-05 13:26:16 -05001420 }
1421 const char* start = phraseNameStart;
1422 SkASSERT('#' == start[-1]);
1423 --start;
1424 if (start > fStart && ' ' >= start[-1]) {
1425 --start; // preserve whether to add whitespace before substitution
1426 }
1427 fMarkup.emplace_front(MarkType::kPhraseRef, start, fLineCount, fParent, fMC);
1428 Definition* markChar = &fMarkup.front();
Cary Clark682c58d2018-05-16 07:07:07 -04001429 this->skipExact("#");
Cary Clark1a8d7622018-03-05 13:26:16 -05001430 markChar->fContentStart = fChar;
Cary Clark1a8d7622018-03-05 13:26:16 -05001431 markChar->fContentEnd = fChar;
1432 markChar->fTerminator = fChar;
1433 markChar->fName = phraseKey;
1434 fParent->fChildren.push_back(markChar);
Cary Clark682c58d2018-05-16 07:07:07 -04001435 int paramLocIndex = 0;
1436 for (auto param : params) {
1437 const char* paramLoc = paramsLoc[paramLocIndex++];
1438 fMarkup.emplace_front(MarkType::kPhraseParam, paramLoc, fLineCount, fParent,
1439 fMC);
1440 Definition* phraseParam = &fMarkup.front();
1441 phraseParam->fContentStart = paramLoc;
1442 phraseParam->fContentEnd = paramLoc + param.length();
1443 phraseParam->fTerminator = paramLoc + param.length();
1444 phraseParam->fName = param;
1445 markChar->fChildren.push_back(phraseParam);
1446 }
Cary Clark8032b982017-07-28 11:04:54 -04001447 }
1448 }
Cary Clarkce101242017-09-01 15:51:02 -04001449 char nextChar = this->next();
Cary Clarkce101242017-09-01 15:51:02 -04001450 if (' ' < nextChar) {
1451 lastChar = fChar;
Cary Clark81abc432018-06-25 16:30:08 -04001452 lineStart = false;
1453 } else if (nextChar == '\n') {
1454 lineStart = true;
Cary Clarkce101242017-09-01 15:51:02 -04001455 }
Cary Clark8032b982017-07-28 11:04:54 -04001456 }
1457 if (fParent) {
Cary Clarka560c472017-11-27 10:44:06 -05001458 return fParent->reportError<bool>("mismatched end");
Cary Clark8032b982017-07-28 11:04:54 -04001459 }
1460 return true;
1461}
1462
1463MarkType BmhParser::getMarkType(MarkLookup lookup) const {
1464 for (int index = 0; index <= Last_MarkType; ++index) {
Cary Clark2d4bf5f2018-04-16 08:37:38 -04001465 int typeLen = strlen(kMarkProps[index].fName);
Cary Clark8032b982017-07-28 11:04:54 -04001466 if (typeLen == 0) {
1467 continue;
1468 }
1469 if (fChar + typeLen >= fEnd || fChar[typeLen] > ' ') {
1470 continue;
1471 }
Cary Clark2d4bf5f2018-04-16 08:37:38 -04001472 int chCompare = strncmp(fChar, kMarkProps[index].fName, typeLen);
Cary Clark8032b982017-07-28 11:04:54 -04001473 if (chCompare < 0) {
1474 goto fail;
1475 }
1476 if (chCompare == 0) {
1477 return (MarkType) index;
1478 }
1479 }
1480fail:
1481 if (MarkLookup::kRequire == lookup) {
1482 return this->reportError<MarkType>("unknown mark type");
1483 }
1484 return MarkType::kNone;
1485}
1486
Cary Clarkab2621d2018-01-30 10:08:57 -05001487 // write #In to show containing #Topic
1488 // write #Line with one liner from Member_Functions, Constructors, Operators if method,
1489 // from Constants if enum, otherwise from #Subtopic containing match
Cary Clark8032b982017-07-28 11:04:54 -04001490bool HackParser::hackFiles() {
1491 string filename(fFileName);
1492 size_t len = filename.length() - 1;
1493 while (len > 0 && (isalnum(filename[len]) || '_' == filename[len] || '.' == filename[len])) {
1494 --len;
1495 }
1496 filename = filename.substr(len + 1);
Cary Clarkab2621d2018-01-30 10:08:57 -05001497 if (filename.substr(0, 2) != "Sk") {
1498 return true;
1499 }
1500 size_t under = filename.find('_');
1501 SkASSERT(under);
1502 string className = filename.substr(0, under);
1503 fOut = fopen(filename.c_str(), "wb");
1504 if (!fOut) {
Cary Clark8032b982017-07-28 11:04:54 -04001505 SkDebugf("could not open output file %s\n", filename.c_str());
1506 return false;
1507 }
Cary Clarkab2621d2018-01-30 10:08:57 -05001508 auto mapEntry = fBmhParser.fClassMap.find(className);
1509 SkASSERT(fBmhParser.fClassMap.end() != mapEntry);
1510 const Definition* classMarkup = &mapEntry->second;
1511 const Definition* root = classMarkup->fParent;
1512 SkASSERT(root);
1513 SkASSERT(root->fTerminator);
1514 SkASSERT('\n' == root->fTerminator[0]);
1515 SkASSERT(!root->fParent);
1516 fStart = root->fStart;
1517 fChar = fStart;
Cary Clark682c58d2018-05-16 07:07:07 -04001518 fClasses = nullptr;
Cary Clark08895c42018-02-01 09:37:32 -05001519 fConstants = nullptr;
Cary Clarkab2621d2018-01-30 10:08:57 -05001520 fConstructors = nullptr;
Cary Clarkab2621d2018-01-30 10:08:57 -05001521 fMemberFunctions = nullptr;
Cary Clark08895c42018-02-01 09:37:32 -05001522 fMembers = nullptr;
1523 fOperators = nullptr;
1524 fRelatedFunctions = nullptr;
Cary Clark682c58d2018-05-16 07:07:07 -04001525 fStructs = nullptr;
Cary Clarkab2621d2018-01-30 10:08:57 -05001526 this->topicIter(root);
Cary Clark08895c42018-02-01 09:37:32 -05001527 fprintf(fOut, "%.*s", (int) (fEnd - fChar), fChar);
Cary Clarkab2621d2018-01-30 10:08:57 -05001528 fclose(fOut);
Cary Clark5b1f9532018-08-28 14:53:37 -04001529 if (ParserCommon::WrittenFileDiffers(filename, root->fFileName)) {
Cary Clark08895c42018-02-01 09:37:32 -05001530 SkDebugf("wrote %s\n", filename.c_str());
1531 } else {
1532 remove(filename.c_str());
1533 }
Cary Clark8032b982017-07-28 11:04:54 -04001534 return true;
1535}
1536
Cary Clarkab2621d2018-01-30 10:08:57 -05001537string HackParser::searchTable(const Definition* tableHolder, const Definition* match) {
1538 if (!tableHolder) {
1539 return "";
1540 }
1541 string bestMatch;
1542 string result;
1543 for (auto table : tableHolder->fChildren) {
1544 if (MarkType::kTable == table->fMarkType) {
1545 for (auto row : table->fChildren) {
1546 if (MarkType::kRow == row->fMarkType) {
1547 const Definition* col0 = row->fChildren[0];
1548 size_t len = col0->fContentEnd - col0->fContentStart;
1549 string method = string(col0->fContentStart, len);
Cary Clark08895c42018-02-01 09:37:32 -05001550 if (len - 2 == method.find("()") && islower(method[0])
1551 && Definition::MethodType::kOperator != match->fMethodType) {
Cary Clarkab2621d2018-01-30 10:08:57 -05001552 method = method.substr(0, len - 2);
1553 }
1554 if (string::npos == match->fName.find(method)) {
1555 continue;
1556 }
1557 if (bestMatch.length() < method.length()) {
1558 bestMatch = method;
1559 const Definition * col1 = row->fChildren[1];
1560 if (col1->fContentEnd <= col1->fContentStart) {
1561 SkASSERT(string::npos != col1->fFileName.find("SkImageInfo"));
1562 result = "incomplete";
1563 } else {
1564 result = string(col1->fContentStart, col1->fContentEnd -
1565 col1->fContentStart);
1566 }
1567 }
1568 }
1569 }
1570 }
1571 }
1572 return result;
1573}
1574
1575// returns true if topic has method
1576void HackParser::topicIter(const Definition* topic) {
Cary Clark682c58d2018-05-16 07:07:07 -04001577 if (string::npos != topic->fName.find(SubtopicKeys::kClasses)) {
1578 SkASSERT(!fClasses);
1579 fClasses = topic;
Cary Clarkab2621d2018-01-30 10:08:57 -05001580 }
Cary Clark682c58d2018-05-16 07:07:07 -04001581 if (string::npos != topic->fName.find(SubtopicKeys::kStructs)) {
1582 SkASSERT(!fStructs);
1583 fStructs = topic;
1584 }
1585 if (string::npos != topic->fName.find(SubtopicKeys::kConstants)) {
Cary Clark08895c42018-02-01 09:37:32 -05001586 SkASSERT(!fConstants);
1587 fConstants = topic;
1588 }
Cary Clark682c58d2018-05-16 07:07:07 -04001589 if (string::npos != topic->fName.find(SubtopicKeys::kConstructors)) {
Cary Clarkab2621d2018-01-30 10:08:57 -05001590 SkASSERT(!fConstructors);
1591 fConstructors = topic;
1592 }
Cary Clark682c58d2018-05-16 07:07:07 -04001593 if (string::npos != topic->fName.find(SubtopicKeys::kMemberFunctions)) {
Cary Clark08895c42018-02-01 09:37:32 -05001594 SkASSERT(!fMemberFunctions);
1595 fMemberFunctions = topic;
1596 }
Cary Clark682c58d2018-05-16 07:07:07 -04001597 if (string::npos != topic->fName.find(SubtopicKeys::kMembers)) {
Cary Clark08895c42018-02-01 09:37:32 -05001598 SkASSERT(!fMembers);
1599 fMembers = topic;
1600 }
Cary Clark682c58d2018-05-16 07:07:07 -04001601 if (string::npos != topic->fName.find(SubtopicKeys::kOperators)) {
Cary Clarkab2621d2018-01-30 10:08:57 -05001602 SkASSERT(!fOperators);
1603 fOperators = topic;
1604 }
Cary Clark682c58d2018-05-16 07:07:07 -04001605 if (string::npos != topic->fName.find(SubtopicKeys::kRelatedFunctions)) {
Cary Clark08895c42018-02-01 09:37:32 -05001606 SkASSERT(!fRelatedFunctions);
1607 fRelatedFunctions = topic;
1608 }
Cary Clarkab2621d2018-01-30 10:08:57 -05001609 for (auto child : topic->fChildren) {
Cary Clark08895c42018-02-01 09:37:32 -05001610 string oneLiner;
1611 bool hasIn = false;
1612 bool hasLine = false;
1613 for (auto part : child->fChildren) {
1614 hasIn |= MarkType::kIn == part->fMarkType;
1615 hasLine |= MarkType::kLine == part->fMarkType;
Cary Clarkab2621d2018-01-30 10:08:57 -05001616 }
Cary Clark08895c42018-02-01 09:37:32 -05001617 switch (child->fMarkType) {
1618 case MarkType::kMethod: {
Cary Clark08895c42018-02-01 09:37:32 -05001619 hasIn |= MarkType::kTopic != topic->fMarkType &&
1620 MarkType::kSubtopic != topic->fMarkType; // don't write #In if parent is class
1621 hasLine |= child->fClone;
1622 if (!hasLine) {
1623 // find member_functions, add entry 2nd column text to #Line
1624 for (auto tableHolder : { fMemberFunctions, fConstructors, fOperators }) {
1625 if (!tableHolder) {
1626 continue;
1627 }
1628 if (Definition::MethodType::kConstructor == child->fMethodType
1629 && fConstructors != tableHolder) {
1630 continue;
1631 }
1632 if (Definition::MethodType::kOperator == child->fMethodType
1633 && fOperators != tableHolder) {
1634 continue;
1635 }
1636 string temp = this->searchTable(tableHolder, child);
1637 if ("" != temp) {
1638 SkASSERT("" == oneLiner || temp == oneLiner);
1639 oneLiner = temp;
1640 }
1641 }
1642 if ("" == oneLiner) {
Cary Clark08895c42018-02-01 09:37:32 -05001643 #ifdef SK_DEBUG
1644 const Definition* rootParent = topic;
1645 while (rootParent->fParent && MarkType::kClass != rootParent->fMarkType
1646 && MarkType::kStruct != rootParent->fMarkType) {
1647 rootParent = rootParent->fParent;
1648 }
1649 #endif
1650 SkASSERT(rootParent);
1651 SkASSERT(MarkType::kClass == rootParent->fMarkType
1652 || MarkType::kStruct == rootParent->fMarkType);
1653 hasLine = true;
1654 }
1655 }
1656
1657 if (hasIn && hasLine) {
1658 continue;
1659 }
1660 const char* start = fChar;
1661 const char* end = child->fContentStart;
1662 fprintf(fOut, "%.*s", (int) (end - start), start);
1663 fChar = end;
1664 // write to method markup header end
1665 if (!hasIn) {
1666 fprintf(fOut, "\n#In %s", topic->fName.c_str());
1667 }
1668 if (!hasLine) {
1669 fprintf(fOut, "\n#Line # %s ##", oneLiner.c_str());
1670 }
1671 } break;
1672 case MarkType::kTopic:
1673 case MarkType::kSubtopic:
1674 this->addOneLiner(fRelatedFunctions, child, hasLine, true);
1675 this->topicIter(child);
1676 break;
1677 case MarkType::kStruct:
Cary Clark682c58d2018-05-16 07:07:07 -04001678 this->addOneLiner(fStructs, child, hasLine, false);
1679 this->topicIter(child);
1680 break;
Cary Clark08895c42018-02-01 09:37:32 -05001681 case MarkType::kClass:
Cary Clark682c58d2018-05-16 07:07:07 -04001682 this->addOneLiner(fClasses, child, hasLine, false);
Cary Clark08895c42018-02-01 09:37:32 -05001683 this->topicIter(child);
1684 break;
1685 case MarkType::kEnum:
1686 case MarkType::kEnumClass:
1687 this->addOneLiner(fConstants, child, hasLine, true);
1688 break;
1689 case MarkType::kMember:
1690 this->addOneLiner(fMembers, child, hasLine, false);
1691 break;
1692 default:
1693 ;
Cary Clarkab2621d2018-01-30 10:08:57 -05001694 }
1695 }
1696}
1697
Cary Clark08895c42018-02-01 09:37:32 -05001698void HackParser::addOneLiner(const Definition* defTable, const Definition* child, bool hasLine,
1699 bool lfAfter) {
1700 if (hasLine) {
1701 return;
1702 }
1703 string oneLiner = this->searchTable(defTable, child);
1704 if ("" == oneLiner) {
1705 return;
1706 }
1707 const char* start = fChar;
1708 const char* end = child->fContentStart;
1709 fprintf(fOut, "%.*s", (int) (end - start), start);
1710 fChar = end;
1711 if (!lfAfter) {
1712 fprintf(fOut, "\n");
1713 }
1714 fprintf(fOut, "#Line # %s ##", oneLiner.c_str());
1715 if (lfAfter) {
1716 fprintf(fOut, "\n");
1717 }
1718}
Cary Clarkab2621d2018-01-30 10:08:57 -05001719
Cary Clark8032b982017-07-28 11:04:54 -04001720bool BmhParser::hasEndToken() const {
Cary Clark2be81cf2018-09-13 12:04:30 -04001721 const char* ptr = fLine;
1722 char test;
1723 do {
1724 if (ptr >= fEnd) {
1725 return false;
1726 }
1727 test = *ptr++;
1728 if ('\n' == test) {
1729 return false;
1730 }
1731 } while (fMC != test || fMC != *ptr);
1732 return true;
Cary Clark8032b982017-07-28 11:04:54 -04001733}
1734
1735string BmhParser::memberName() {
1736 const char* wordStart;
1737 const char* prefixes[] = { "static", "const" };
1738 do {
1739 this->skipSpace();
1740 wordStart = fChar;
Cary Clark2d4bf5f2018-04-16 08:37:38 -04001741 this->skipToNonName();
Cary Clark8032b982017-07-28 11:04:54 -04001742 } while (this->anyOf(wordStart, prefixes, SK_ARRAY_COUNT(prefixes)));
1743 if ('*' == this->peek()) {
1744 this->next();
1745 }
1746 return this->className(MarkType::kMember);
1747}
1748
1749string BmhParser::methodName() {
1750 if (this->hasEndToken()) {
1751 if (!fParent || !fParent->fName.length()) {
1752 return this->reportError<string>("missing parent method name");
1753 }
1754 SkASSERT(fMC == this->peek());
1755 this->next();
1756 SkASSERT(fMC == this->peek());
1757 this->next();
1758 SkASSERT(fMC != this->peek());
1759 return fParent->fName;
1760 }
1761 string builder;
1762 const char* end = this->lineEnd();
1763 const char* paren = this->strnchr('(', end);
1764 if (!paren) {
1765 return this->reportError<string>("missing method name and reference");
1766 }
Cary Clark224c7002018-06-27 11:00:21 -04001767 {
1768 TextParserSave endCheck(this);
1769 while (end < fEnd && !this->strnchr(')', end)) {
1770 fChar = end + 1;
1771 end = this->lineEnd();
1772 }
1773 if (end >= fEnd) {
1774 return this->reportError<string>("missing method end paren");
1775 }
1776 endCheck.restore();
1777 }
Cary Clark8032b982017-07-28 11:04:54 -04001778 const char* nameStart = paren;
1779 char ch;
1780 bool expectOperator = false;
1781 bool isConstructor = false;
1782 const char* nameEnd = nullptr;
1783 while (nameStart > fChar && ' ' != (ch = *--nameStart)) {
1784 if (!isalnum(ch) && '_' != ch) {
1785 if (nameEnd) {
1786 break;
1787 }
1788 expectOperator = true;
1789 continue;
1790 }
1791 if (!nameEnd) {
1792 nameEnd = nameStart + 1;
1793 }
1794 }
1795 if (!nameEnd) {
1796 return this->reportError<string>("unexpected method name char");
1797 }
1798 if (' ' == nameStart[0]) {
1799 ++nameStart;
1800 }
1801 if (nameEnd <= nameStart) {
1802 return this->reportError<string>("missing method name");
1803 }
1804 if (nameStart >= paren) {
1805 return this->reportError<string>("missing method name length");
1806 }
1807 string name(nameStart, nameEnd - nameStart);
1808 bool allLower = true;
1809 for (int index = 0; index < (int) (nameEnd - nameStart); ++index) {
1810 if (!islower(nameStart[index])) {
1811 allLower = false;
1812 break;
1813 }
1814 }
1815 if (expectOperator && "operator" != name) {
1816 return this->reportError<string>("expected operator");
1817 }
1818 const Definition* parent = this->parentSpace();
1819 if (parent && parent->fName.length() > 0) {
Cary Clark224c7002018-06-27 11:00:21 -04001820 size_t parentNameIndex = parent->fName.rfind(':');
1821 parentNameIndex = string::npos == parentNameIndex ? 0 : parentNameIndex + 1;
1822 string parentName = parent->fName.substr(parentNameIndex);
1823 if (parentName == name) {
Cary Clark8032b982017-07-28 11:04:54 -04001824 isConstructor = true;
1825 } else if ('~' == name[0]) {
Cary Clark224c7002018-06-27 11:00:21 -04001826 if (parentName != name.substr(1)) {
Cary Clark8032b982017-07-28 11:04:54 -04001827 return this->reportError<string>("expected destructor");
1828 }
1829 isConstructor = true;
1830 }
1831 builder = parent->fName + "::";
Ben Wagner63fd7602017-10-09 15:45:33 -04001832 }
Cary Clarka560c472017-11-27 10:44:06 -05001833 bool addConst = false;
Cary Clark8032b982017-07-28 11:04:54 -04001834 if (isConstructor || expectOperator) {
1835 paren = this->strnchr(')', end) + 1;
Cary Clark186d08f2018-04-03 08:43:27 -04001836 TextParserSave saveState(this);
Cary Clarka560c472017-11-27 10:44:06 -05001837 this->skipTo(paren);
1838 if (this->skipExact("_const")) {
1839 addConst = true;
1840 }
1841 saveState.restore();
Cary Clark8032b982017-07-28 11:04:54 -04001842 }
1843 builder.append(nameStart, paren - nameStart);
Cary Clarka560c472017-11-27 10:44:06 -05001844 if (addConst) {
1845 builder.append("_const");
1846 }
Cary Clark8032b982017-07-28 11:04:54 -04001847 if (!expectOperator && allLower) {
1848 builder.append("()");
1849 }
1850 int parens = 0;
1851 while (fChar < end || parens > 0) {
1852 if ('(' == this->peek()) {
1853 ++parens;
1854 } else if (')' == this->peek()) {
1855 --parens;
1856 }
1857 this->next();
1858 }
Cary Clark186d08f2018-04-03 08:43:27 -04001859 TextParserSave saveState(this);
Cary Clark8032b982017-07-28 11:04:54 -04001860 this->skipWhiteSpace();
1861 if (this->startsWith("const")) {
1862 this->skipName("const");
1863 } else {
1864 saveState.restore();
1865 }
1866// this->next();
Cary Clark82f1f742018-06-28 08:50:35 -04001867 if (string::npos != builder.find('\n')) {
1868 builder.erase(std::remove(builder.begin(), builder.end(), '\n'), builder.end());
1869 }
Cary Clark8032b982017-07-28 11:04:54 -04001870 return uniqueRootName(builder, MarkType::kMethod);
1871}
1872
1873const Definition* BmhParser::parentSpace() const {
1874 Definition* parent = nullptr;
1875 Definition* test = fParent;
1876 while (test) {
1877 if (MarkType::kClass == test->fMarkType ||
1878 MarkType::kEnumClass == test->fMarkType ||
1879 MarkType::kStruct == test->fMarkType) {
1880 parent = test;
1881 break;
1882 }
1883 test = test->fParent;
1884 }
1885 return parent;
1886}
1887
Cary Clark137b8742018-05-30 09:21:49 -04001888// A full terminal statement is in the form:
1889// \n optional-white-space #MarkType white-space #[# white-space]
1890// \n optional-white-space #MarkType white-space Name white-space #[# white-space]
1891// MarkType must match definition->fMarkType
Cary Clark1a8d7622018-03-05 13:26:16 -05001892const char* BmhParser::checkForFullTerminal(const char* end, const Definition* definition) const {
1893 const char* start = end;
1894 while ('\n' != start[0] && start > fStart) {
1895 --start;
1896 }
1897 SkASSERT (start < end);
1898 // if end is preceeeded by \n#MarkType ## backup to there
1899 TextParser parser(fFileName, start, fChar, fLineCount);
1900 parser.skipWhiteSpace();
1901 if (parser.eof() || fMC != parser.next()) {
1902 return end;
1903 }
Cary Clark2d4bf5f2018-04-16 08:37:38 -04001904 const char* markName = kMarkProps[(int) definition->fMarkType].fName;
Cary Clark1a8d7622018-03-05 13:26:16 -05001905 if (!parser.skipExact(markName)) {
1906 return end;
1907 }
1908 parser.skipWhiteSpace();
Cary Clark137b8742018-05-30 09:21:49 -04001909 TextParser startName(fFileName, definition->fStart, definition->fContentStart,
1910 definition->fLineCount);
1911 if ('#' == startName.next()) {
1912 startName.skipToSpace();
1913 if (!startName.eof() && startName.skipSpace()) {
1914 const char* nameBegin = startName.fChar;
1915 startName.skipToWhiteSpace();
1916 string name(nameBegin, (int) (startName.fChar - nameBegin));
1917 if (fMC != parser.peek() && !parser.skipExact(name.c_str())) {
1918 return end;
1919 }
1920 parser.skipSpace();
Cary Clark1a8d7622018-03-05 13:26:16 -05001921 }
1922 }
Cary Clark137b8742018-05-30 09:21:49 -04001923 if (parser.eof() || fMC != parser.next()) {
Cary Clark1a8d7622018-03-05 13:26:16 -05001924 return end;
1925 }
1926 if (!parser.eof() && fMC != parser.next()) {
1927 return end;
1928 }
Cary Clark137b8742018-05-30 09:21:49 -04001929 SkASSERT(parser.eof());
Cary Clark1a8d7622018-03-05 13:26:16 -05001930 return start;
1931}
1932
Cary Clark2be81cf2018-09-13 12:04:30 -04001933void BmhParser::parseHashAnchor(Definition* definition) {
1934 this->skipToEndBracket(fMC);
1935 fMarkup.emplace_front(MarkType::kLink, fChar, fLineCount, definition, fMC);
1936 SkAssertResult(fMC == this->next());
1937 this->skipWhiteSpace();
1938 Definition* link = &fMarkup.front();
1939 link->fContentStart = fChar;
1940 link->fContentEnd = this->trimmedBracketEnd(fMC);
1941 this->skipToEndBracket(fMC);
1942 SkAssertResult(fMC == this->next());
1943 SkAssertResult(fMC == this->next());
1944 link->fTerminator = fChar;
1945 definition->fContentEnd = link->fContentEnd;
1946 definition->fTerminator = fChar;
1947 definition->fChildren.emplace_back(link);
1948}
1949
1950void BmhParser::parseHashFormula(Definition* definition) {
1951 const char* start = definition->fContentStart;
1952 definition->trimEnd();
1953 const char* end = definition->fContentEnd;
1954 fMarkup.emplace_front(MarkType::kText, start, fLineCount, definition, fMC);
1955 Definition* text = &fMarkup.front();
1956 text->fContentStart = start;
1957 text->fContentEnd = end;
1958 text->fTerminator = definition->fTerminator;
1959 definition->fChildren.emplace_back(text);
1960}
1961
1962void BmhParser::parseHashLine(Definition* definition) {
1963 const char* nextLF = this->strnchr('\n', this->fEnd);
1964 const char* start = fChar;
1965 const char* end = this->trimmedBracketEnd(fMC);
1966 this->skipToEndBracket(fMC, nextLF);
1967 if (fMC != this->next() || fMC != this->next()) {
1968 return this->reportError<void>("expected ## to delineate line");
1969 }
1970 fMarkup.emplace_front(MarkType::kText, start, fLineCount, definition, fMC);
1971 Definition* text = &fMarkup.front();
1972 if (!islower(start[0]) && (!isdigit(start[0])
1973 || MarkType::kConst != definition->fParent->fMarkType)) {
1974 return this->reportError<void>("expect lower case start");
1975 }
1976 string contents = string(start, end - start);
1977 if (string::npos != contents.find('.')) {
1978 return this->reportError<void>("expect phrase, not sentence");
1979 }
1980 size_t firstSpace = contents.find(' ');
1981 if (string::npos == firstSpace || 0 == firstSpace || 's' != start[firstSpace - 1]) {
1982 if (MarkType::kMethod == fParent->fMarkType && "experimental" != contents
1983 && "incomplete" != contents) {
1984 return this->reportError<void>( "expect phrase in third person present"
1985 " tense (1st word should end in 's'");
1986 }
1987 }
1988 text->fContentStart = start;
1989 text->fContentEnd = end;
1990 text->fTerminator = fChar;
1991 definition->fContentEnd = text->fContentEnd;
1992 definition->fTerminator = fChar;
1993 definition->fChildren.emplace_back(text);
1994}
1995
Cary Clark8032b982017-07-28 11:04:54 -04001996bool BmhParser::popParentStack(Definition* definition) {
1997 if (!fParent) {
1998 return this->reportError<bool>("missing parent");
1999 }
2000 if (definition != fParent) {
2001 return this->reportError<bool>("definition end is not parent");
2002 }
2003 if (!definition->fStart) {
2004 return this->reportError<bool>("definition missing start");
2005 }
2006 if (definition->fContentEnd) {
2007 return this->reportError<bool>("definition already ended");
2008 }
Cary Clark1a8d7622018-03-05 13:26:16 -05002009 // more to figure out to handle table columns, at minimum
2010 const char* end = fChar;
2011 if (fMC != end[0]) {
2012 while (end > definition->fContentStart && ' ' >= end[-1]) {
2013 --end;
2014 }
2015 SkASSERT(&end[-1] >= definition->fContentStart && fMC == end[-1]
2016 && (MarkType::kColumn == definition->fMarkType
2017 || (&end[-2] >= definition->fContentStart && fMC == end[-2])));
2018 end -= 2;
2019 }
2020 end = checkForFullTerminal(end, definition);
2021 definition->fContentEnd = end;
Cary Clark8032b982017-07-28 11:04:54 -04002022 definition->fTerminator = fChar;
2023 fParent = definition->fParent;
2024 if (!fParent || (MarkType::kTopic == fParent->fMarkType && !fParent->fParent)) {
2025 fRoot = nullptr;
2026 }
2027 return true;
2028}
2029
2030TextParser::TextParser(const Definition* definition) :
Ben Wagner63fd7602017-10-09 15:45:33 -04002031 TextParser(definition->fFileName, definition->fContentStart, definition->fContentEnd,
Cary Clark8032b982017-07-28 11:04:54 -04002032 definition->fLineCount) {
2033}
2034
Cary Clark4855f782018-02-06 09:41:53 -05002035string TextParser::ReportFilename(string file) {
2036 string fullName;
2037#ifdef SK_BUILD_FOR_WIN
2038 TCHAR pathChars[MAX_PATH];
2039 DWORD pathLen = GetCurrentDirectory(MAX_PATH, pathChars);
2040 for (DWORD index = 0; index < pathLen; ++index) {
2041 fullName += pathChars[index] == (char)pathChars[index] ? (char)pathChars[index] : '?';
2042 }
2043 fullName += '\\';
2044#endif
2045 fullName += file;
2046 return fullName;
2047}
2048
Cary Clark8032b982017-07-28 11:04:54 -04002049void TextParser::reportError(const char* errorStr) const {
2050 this->reportWarning(errorStr);
2051 SkDebugf(""); // convenient place to set a breakpoint
2052}
2053
2054void TextParser::reportWarning(const char* errorStr) const {
Cary Clark61313f32018-10-08 14:57:48 -04002055 const char* lineStart = fLine;
2056 if (lineStart >= fEnd) {
2057 lineStart = fChar;
2058 }
2059 SkASSERT(lineStart < fEnd);
2060 TextParser err(fFileName, lineStart, fEnd, fLineCount);
Cary Clark8032b982017-07-28 11:04:54 -04002061 size_t lineLen = this->lineLength();
Cary Clark61313f32018-10-08 14:57:48 -04002062 ptrdiff_t spaces = fChar - lineStart;
Cary Clark8032b982017-07-28 11:04:54 -04002063 while (spaces > 0 && (size_t) spaces > lineLen) {
2064 ++err.fLineCount;
2065 err.fLine += lineLen;
2066 spaces -= lineLen;
2067 lineLen = err.lineLength();
2068 }
Cary Clark4855f782018-02-06 09:41:53 -05002069 string fullName = this->ReportFilename(fFileName);
2070 SkDebugf("\n%s(%zd): error: %s\n", fullName.c_str(), err.fLineCount, errorStr);
Cary Clark8032b982017-07-28 11:04:54 -04002071 if (0 == lineLen) {
2072 SkDebugf("[blank line]\n");
2073 } else {
2074 while (lineLen > 0 && '\n' == err.fLine[lineLen - 1]) {
2075 --lineLen;
2076 }
2077 SkDebugf("%.*s\n", (int) lineLen, err.fLine);
2078 SkDebugf("%*s^\n", (int) spaces, "");
2079 }
2080}
2081
Cary Clark186d08f2018-04-03 08:43:27 -04002082void TextParser::setForErrorReporting(const Definition* definition, const char* str) {
2083 fFileName = definition->fFileName;
2084 fStart = definition->fContentStart;
2085 fLine = str;
2086 while (fLine > fStart && fLine[-1] != '\n') {
2087 --fLine;
2088 }
2089 fChar = str;
2090 fEnd = definition->fContentEnd;
2091 fLineCount = definition->fLineCount;
2092 const char* lineInc = fStart;
2093 while (lineInc < str) {
2094 fLineCount += '\n' == *lineInc++;
2095 }
2096}
2097
Cary Clark2f466242017-12-11 16:03:17 -05002098string TextParser::typedefName() {
2099 // look for typedef as one of three forms:
2100 // typedef return-type (*NAME)(params);
2101 // typedef alias NAME;
2102 // typedef std::function<alias> NAME;
2103 string builder;
2104 const char* end = this->doubleLF();
2105 if (!end) {
2106 end = fEnd;
2107 }
2108 const char* altEnd = this->strnstr("#Typedef ##", end);
2109 if (altEnd) {
2110 end = this->strnchr('\n', end);
2111 }
2112 if (!end) {
2113 return this->reportError<string>("missing typedef std::function end bracket >");
2114 }
Cary Clark61ca7c52018-01-02 11:34:14 -05002115 bool stdFunction = this->startsWith("std::function");
2116 if (stdFunction) {
Cary Clark2f466242017-12-11 16:03:17 -05002117 if (!this->skipToEndBracket('>')) {
2118 return this->reportError<string>("missing typedef std::function end bracket >");
2119 }
2120 this->next();
2121 this->skipWhiteSpace();
2122 builder += string(fChar, end - fChar);
2123 } else {
2124 const char* paren = this->strnchr('(', end);
2125 if (!paren) {
2126 const char* lastWord = nullptr;
2127 do {
2128 this->skipToWhiteSpace();
2129 if (fChar < end && isspace(fChar[0])) {
Cary Clark682c58d2018-05-16 07:07:07 -04002130 const char* whiteStart = fChar;
Cary Clark2f466242017-12-11 16:03:17 -05002131 this->skipWhiteSpace();
Cary Clark682c58d2018-05-16 07:07:07 -04002132 // FIXME: test should be for fMC
2133 if ('#' == fChar[0]) {
2134 end = whiteStart;
2135 break;
2136 }
Cary Clark2f466242017-12-11 16:03:17 -05002137 lastWord = fChar;
2138 } else {
2139 break;
2140 }
2141 } while (true);
2142 if (!lastWord) {
2143 return this->reportError<string>("missing typedef name");
2144 }
2145 builder += string(lastWord, end - lastWord);
2146 } else {
2147 this->skipTo(paren);
2148 this->next();
2149 if ('*' != this->next()) {
2150 return this->reportError<string>("missing typedef function asterisk");
2151 }
2152 const char* nameStart = fChar;
2153 if (!this->skipToEndBracket(')')) {
2154 return this->reportError<string>("missing typedef function )");
2155 }
2156 builder += string(nameStart, fChar - nameStart);
2157 if (!this->skipToEndBracket('(')) {
2158 return this->reportError<string>("missing typedef params (");
2159 }
2160 if (! this->skipToEndBracket(')')) {
2161 return this->reportError<string>("missing typedef params )");
2162 }
2163 this->skipTo(end);
2164 }
2165 }
2166 return builder;
2167}
2168
Cary Clark8032b982017-07-28 11:04:54 -04002169bool BmhParser::skipNoName() {
2170 if ('\n' == this->peek()) {
2171 this->next();
2172 return true;
2173 }
2174 this->skipWhiteSpace();
2175 if (fMC != this->peek()) {
Cary Clark2be81cf2018-09-13 12:04:30 -04002176 return this->reportError<bool>("expected end mark 1");
Cary Clark8032b982017-07-28 11:04:54 -04002177 }
2178 this->next();
2179 if (fMC != this->peek()) {
Cary Clark2be81cf2018-09-13 12:04:30 -04002180 return this->reportError<bool>("expected end mark 2");
Cary Clark8032b982017-07-28 11:04:54 -04002181 }
2182 this->next();
2183 return true;
2184}
2185
2186bool BmhParser::skipToDefinitionEnd(MarkType markType) {
2187 if (this->eof()) {
2188 return this->reportError<bool>("missing end");
2189 }
2190 const char* start = fLine;
2191 int startLineCount = fLineCount;
2192 int stack = 1;
2193 ptrdiff_t lineLen;
2194 bool foundEnd = false;
2195 do {
2196 lineLen = this->lineLength();
2197 if (fMC != *fChar++) {
2198 continue;
2199 }
2200 if (fMC == *fChar) {
2201 continue;
2202 }
2203 if (' ' == *fChar) {
2204 continue;
2205 }
2206 MarkType nextType = this->getMarkType(MarkLookup::kAllowUnknown);
2207 if (markType != nextType) {
2208 continue;
2209 }
2210 bool hasEnd = this->hasEndToken();
2211 if (hasEnd) {
2212 if (!--stack) {
2213 foundEnd = true;
2214 continue;
2215 }
2216 } else {
2217 ++stack;
2218 }
2219 } while ((void) ++fLineCount, (void) (fLine += lineLen), (void) (fChar = fLine),
2220 !this->eof() && !foundEnd);
2221 if (foundEnd) {
2222 return true;
2223 }
2224 fLineCount = startLineCount;
2225 fLine = start;
2226 fChar = start;
2227 return this->reportError<bool>("unbalanced stack");
2228}
2229
Cary Clarkab2621d2018-01-30 10:08:57 -05002230bool BmhParser::skipToString() {
2231 this->skipSpace();
2232 if (fMC != this->peek()) {
Cary Clark2be81cf2018-09-13 12:04:30 -04002233 return this->reportError<bool>("expected end mark 3");
Cary Clarkab2621d2018-01-30 10:08:57 -05002234 }
2235 this->next();
2236 this->skipSpace();
2237 // body is text from here to double fMC
2238 // no single fMC allowed, no linefeed allowed
2239 return true;
2240}
2241
Cary Clark8032b982017-07-28 11:04:54 -04002242vector<string> BmhParser::topicName() {
2243 vector<string> result;
2244 this->skipWhiteSpace();
2245 const char* lineEnd = fLine + this->lineLength();
2246 const char* nameStart = fChar;
2247 while (fChar < lineEnd) {
2248 char ch = this->next();
2249 SkASSERT(',' != ch);
2250 if ('\n' == ch) {
2251 break;
2252 }
2253 if (fMC == ch) {
2254 break;
2255 }
2256 }
2257 if (fChar - 1 > nameStart) {
2258 string builder(nameStart, fChar - nameStart - 1);
2259 trim_start_end(builder);
2260 result.push_back(builder);
2261 }
2262 if (fChar < lineEnd && fMC == this->peek()) {
2263 this->next();
2264 }
2265 return result;
2266}
2267
2268// typeName parsing rules depend on mark type
2269vector<string> BmhParser::typeName(MarkType markType, bool* checkEnd) {
2270 fAnonymous = false;
2271 fCloned = false;
2272 vector<string> result;
2273 string builder;
2274 if (fParent) {
2275 builder = fParent->fName;
2276 }
2277 switch (markType) {
Cary Clark2d4bf5f2018-04-16 08:37:38 -04002278 case MarkType::kDefine:
Cary Clark8032b982017-07-28 11:04:54 -04002279 case MarkType::kEnum:
2280 // enums may be nameless
2281 case MarkType::kConst:
2282 case MarkType::kEnumClass:
2283 case MarkType::kClass:
2284 case MarkType::kStruct:
Cary Clark8032b982017-07-28 11:04:54 -04002285 // expect name
2286 builder = this->className(markType);
2287 break;
2288 case MarkType::kExample:
2289 // check to see if one already exists -- if so, number this one
2290 builder = this->uniqueName(string(), markType);
2291 this->skipNoName();
2292 break;
2293 case MarkType::kCode:
Cary Clark8032b982017-07-28 11:04:54 -04002294 case MarkType::kDescription:
Cary Clark8032b982017-07-28 11:04:54 -04002295 case MarkType::kExternal:
Cary Clark8032b982017-07-28 11:04:54 -04002296 case MarkType::kFunction:
2297 case MarkType::kLegend:
2298 case MarkType::kList:
2299 case MarkType::kNoExample:
2300 case MarkType::kPrivate:
Cary Clark8032b982017-07-28 11:04:54 -04002301 this->skipNoName();
2302 break;
Cary Clark2be81cf2018-09-13 12:04:30 -04002303 case MarkType::kFormula:
Cary Clarkab2621d2018-01-30 10:08:57 -05002304 case MarkType::kLine:
2305 this->skipToString();
2306 break;
Cary Clark8032b982017-07-28 11:04:54 -04002307 case MarkType::kAlias:
Ben Wagner63fd7602017-10-09 15:45:33 -04002308 case MarkType::kAnchor:
Cary Clark8032b982017-07-28 11:04:54 -04002309 case MarkType::kBug: // fixme: expect number
Cary Clark4855f782018-02-06 09:41:53 -05002310 case MarkType::kDeprecated:
Cary Clark682c58d2018-05-16 07:07:07 -04002311 case MarkType::kDetails:
Cary Clarkac47b882018-01-11 10:35:44 -05002312 case MarkType::kDuration:
Cary Clark682c58d2018-05-16 07:07:07 -04002313 case MarkType::kExperimental:
Cary Clark0d225392018-06-07 09:59:07 -04002314 case MarkType::kFile:
Cary Clark8032b982017-07-28 11:04:54 -04002315 case MarkType::kHeight:
Cary Clarkf895a422018-02-27 09:54:21 -05002316 case MarkType::kIllustration:
Cary Clark8032b982017-07-28 11:04:54 -04002317 case MarkType::kImage:
Cary Clarkab2621d2018-01-30 10:08:57 -05002318 case MarkType::kIn:
Cary Clark154beea2017-10-26 07:58:48 -04002319 case MarkType::kLiteral:
Cary Clark682c58d2018-05-16 07:07:07 -04002320 case MarkType::kNoJustify:
Cary Clark154beea2017-10-26 07:58:48 -04002321 case MarkType::kOutdent:
Cary Clark8032b982017-07-28 11:04:54 -04002322 case MarkType::kPlatform:
Cary Clark08895c42018-02-01 09:37:32 -05002323 case MarkType::kPopulate:
Cary Clark8032b982017-07-28 11:04:54 -04002324 case MarkType::kReturn:
2325 case MarkType::kSeeAlso:
Cary Clark61dfc3a2018-01-03 08:37:53 -05002326 case MarkType::kSet:
Cary Clark8032b982017-07-28 11:04:54 -04002327 case MarkType::kSubstitute:
Cary Clark8032b982017-07-28 11:04:54 -04002328 case MarkType::kToDo:
2329 case MarkType::kVolatile:
2330 case MarkType::kWidth:
2331 *checkEnd = false; // no name, may have text body
2332 break;
2333 case MarkType::kStdOut:
2334 this->skipNoName();
2335 break; // unnamed
2336 case MarkType::kMember:
2337 builder = this->memberName();
2338 break;
2339 case MarkType::kMethod:
2340 builder = this->methodName();
2341 break;
Cary Clarka560c472017-11-27 10:44:06 -05002342 case MarkType::kTypedef:
2343 builder = this->typedefName();
2344 break;
Cary Clark8032b982017-07-28 11:04:54 -04002345 case MarkType::kParam:
Cary Clark1a8d7622018-03-05 13:26:16 -05002346 // fixme: expect camelCase for param
Cary Clark8032b982017-07-28 11:04:54 -04002347 builder = this->word("", "");
2348 this->skipSpace();
2349 *checkEnd = false;
2350 break;
Cary Clark682c58d2018-05-16 07:07:07 -04002351 case MarkType::kPhraseDef: {
2352 const char* nameEnd = this->anyOf("(\n");
2353 builder = string(fChar, nameEnd - fChar);
2354 this->skipLower();
2355 if (fChar != nameEnd) {
2356 this->reportError("expect lower case only");
2357 break;
2358 }
2359 this->skipTo(nameEnd);
2360 *checkEnd = false;
2361 } break;
Cary Clark8032b982017-07-28 11:04:54 -04002362 case MarkType::kTable:
2363 this->skipNoName();
2364 break; // unnamed
2365 case MarkType::kSubtopic:
2366 case MarkType::kTopic:
2367 // fixme: start with cap, allow space, hyphen, stop on comma
2368 // one topic can have multiple type names delineated by comma
2369 result = this->topicName();
2370 if (result.size() == 0 && this->hasEndToken()) {
2371 break;
2372 }
2373 return result;
2374 default:
2375 // fixme: don't allow silent failures
2376 SkASSERT(0);
2377 }
2378 result.push_back(builder);
2379 return result;
2380}
2381
Cary Clarka560c472017-11-27 10:44:06 -05002382string BmhParser::typedefName() {
2383 if (this->hasEndToken()) {
2384 if (!fParent || !fParent->fName.length()) {
2385 return this->reportError<string>("missing parent typedef name");
2386 }
2387 SkASSERT(fMC == this->peek());
2388 this->next();
2389 SkASSERT(fMC == this->peek());
2390 this->next();
2391 SkASSERT(fMC != this->peek());
2392 return fParent->fName;
2393 }
Cary Clarka560c472017-11-27 10:44:06 -05002394 string builder;
Cary Clark2f466242017-12-11 16:03:17 -05002395 const Definition* parent = this->parentSpace();
2396 if (parent && parent->fName.length() > 0) {
2397 builder = parent->fName + "::";
Cary Clarka560c472017-11-27 10:44:06 -05002398 }
Cary Clark2f466242017-12-11 16:03:17 -05002399 builder += TextParser::typedefName();
Cary Clarka560c472017-11-27 10:44:06 -05002400 return uniqueRootName(builder, MarkType::kTypedef);
2401}
2402
Cary Clark2d4bf5f2018-04-16 08:37:38 -04002403string BmhParser::uniqueName(string base, MarkType markType) {
Cary Clark8032b982017-07-28 11:04:54 -04002404 string builder(base);
2405 if (!builder.length()) {
2406 builder = fParent->fName;
2407 }
2408 if (!fParent) {
2409 return builder;
2410 }
2411 int number = 2;
2412 string numBuilder(builder);
2413 do {
Cary Clarkf895a422018-02-27 09:54:21 -05002414 for (auto& iter : fParent->fChildren) {
Cary Clark8032b982017-07-28 11:04:54 -04002415 if (markType == iter->fMarkType) {
2416 if (iter->fName == numBuilder) {
Cary Clarkf895a422018-02-27 09:54:21 -05002417 if (iter->fDeprecated) {
2418 iter->fClone = true;
2419 } else {
2420 fCloned = true;
2421 }
Cary Clark8032b982017-07-28 11:04:54 -04002422 numBuilder = builder + '_' + to_string(number);
2423 goto tryNext;
2424 }
2425 }
2426 }
2427 break;
2428tryNext: ;
2429 } while (++number);
2430 return numBuilder;
2431}
2432
Cary Clark2d4bf5f2018-04-16 08:37:38 -04002433string BmhParser::uniqueRootName(string base, MarkType markType) {
2434 auto checkName = [markType](const Definition& def, string numBuilder) -> bool {
Cary Clark8032b982017-07-28 11:04:54 -04002435 return markType == def.fMarkType && def.fName == numBuilder;
2436 };
2437
2438 string builder(base);
2439 if (!builder.length()) {
2440 builder = fParent->fName;
2441 }
2442 int number = 2;
2443 string numBuilder(builder);
2444 Definition* cloned = nullptr;
2445 do {
2446 if (fRoot) {
2447 for (auto& iter : fRoot->fBranches) {
2448 if (checkName(*iter.second, numBuilder)) {
2449 cloned = iter.second;
2450 goto tryNext;
2451 }
2452 }
2453 for (auto& iter : fRoot->fLeaves) {
2454 if (checkName(iter.second, numBuilder)) {
2455 cloned = &iter.second;
2456 goto tryNext;
2457 }
2458 }
2459 } else if (fParent) {
2460 for (auto& iter : fParent->fChildren) {
2461 if (checkName(*iter, numBuilder)) {
2462 cloned = &*iter;
2463 goto tryNext;
2464 }
2465 }
2466 }
2467 break;
2468tryNext: ;
2469 if ("()" == builder.substr(builder.length() - 2)) {
2470 builder = builder.substr(0, builder.length() - 2);
2471 }
2472 if (MarkType::kMethod == markType) {
2473 cloned->fCloned = true;
Cary Clarkf895a422018-02-27 09:54:21 -05002474 if (cloned->fDeprecated) {
2475 cloned->fClone = true;
2476 } else {
2477 fCloned = true;
2478 }
2479 } else {
2480 fCloned = true;
Cary Clark8032b982017-07-28 11:04:54 -04002481 }
Cary Clark8032b982017-07-28 11:04:54 -04002482 numBuilder = builder + '_' + to_string(number);
2483 } while (++number);
2484 return numBuilder;
2485}
2486
2487void BmhParser::validate() const {
2488 for (int index = 0; index <= (int) Last_MarkType; ++index) {
Cary Clark2d4bf5f2018-04-16 08:37:38 -04002489 SkASSERT(kMarkProps[index].fMarkType == (MarkType) index);
Cary Clark8032b982017-07-28 11:04:54 -04002490 }
2491 const char* last = "";
2492 for (int index = 0; index <= (int) Last_MarkType; ++index) {
Cary Clark2d4bf5f2018-04-16 08:37:38 -04002493 const char* next = kMarkProps[index].fName;
Cary Clark8032b982017-07-28 11:04:54 -04002494 if (!last[0]) {
2495 last = next;
2496 continue;
2497 }
2498 if (!next[0]) {
2499 continue;
2500 }
2501 SkASSERT(strcmp(last, next) < 0);
2502 last = next;
2503 }
2504}
2505
Cary Clark2d4bf5f2018-04-16 08:37:38 -04002506string BmhParser::word(string prefix, string delimiter) {
Cary Clark8032b982017-07-28 11:04:54 -04002507 string builder(prefix);
2508 this->skipWhiteSpace();
2509 const char* lineEnd = fLine + this->lineLength();
2510 const char* nameStart = fChar;
2511 while (fChar < lineEnd) {
2512 char ch = this->next();
2513 if (' ' >= ch) {
2514 break;
2515 }
2516 if (',' == ch) {
2517 return this->reportError<string>("no comma needed");
2518 break;
2519 }
2520 if (fMC == ch) {
2521 return builder;
2522 }
2523 if (!isalnum(ch) && '_' != ch && ':' != ch && '-' != ch) {
2524 return this->reportError<string>("unexpected char");
2525 }
2526 if (':' == ch) {
2527 // expect pair, and expect word to start with Sk
2528 if (nameStart[0] != 'S' || nameStart[1] != 'k') {
2529 return this->reportError<string>("expected Sk");
2530 }
2531 if (':' != this->peek()) {
2532 return this->reportError<string>("expected ::");
2533 }
2534 this->next();
2535 } else if ('-' == ch) {
2536 // expect word not to start with Sk or kX where X is A-Z
2537 if (nameStart[0] == 'k' && nameStart[1] >= 'A' && nameStart[1] <= 'Z') {
2538 return this->reportError<string>("didn't expected kX");
2539 }
2540 if (nameStart[0] == 'S' && nameStart[1] == 'k') {
2541 return this->reportError<string>("expected Sk");
2542 }
2543 }
2544 }
2545 if (prefix.size()) {
2546 builder += delimiter;
2547 }
2548 builder.append(nameStart, fChar - nameStart - 1);
2549 return builder;
2550}
2551
2552// pass one: parse text, collect definitions
2553// pass two: lookup references
2554
Cary Clark8032b982017-07-28 11:04:54 -04002555static int count_children(const Definition& def, MarkType markType) {
2556 int count = 0;
2557 if (markType == def.fMarkType) {
2558 ++count;
2559 }
2560 for (auto& child : def.fChildren ) {
2561 count += count_children(*child, markType);
2562 }
2563 return count;
2564}
2565
2566int main(int argc, char** const argv) {
Cary Clarka560c472017-11-27 10:44:06 -05002567 BmhParser bmhParser(FLAGS_skip);
Cary Clark8032b982017-07-28 11:04:54 -04002568 bmhParser.validate();
2569
2570 SkCommandLineFlags::SetUsage(
Cary Clarka560c472017-11-27 10:44:06 -05002571 "Common Usage: bookmaker -b path/to/bmh_files -i path/to/include.h -t\n"
Cary Clark8032b982017-07-28 11:04:54 -04002572 " bookmaker -b path/to/bmh_files -e fiddle.json\n"
2573 " ~/go/bin/fiddlecli --input fiddle.json --output fiddleout.json\n"
2574 " bookmaker -b path/to/bmh_files -f fiddleout.json -r path/to/md_files\n"
Cary Clark2f466242017-12-11 16:03:17 -05002575 " bookmaker -a path/to/status.json -x\n"
2576 " bookmaker -a path/to/status.json -p\n");
Cary Clark8032b982017-07-28 11:04:54 -04002577 bool help = false;
2578 for (int i = 1; i < argc; i++) {
2579 if (0 == strcmp("-h", argv[i]) || 0 == strcmp("--help", argv[i])) {
2580 help = true;
2581 for (int j = i + 1; j < argc; j++) {
2582 if (SkStrStartsWith(argv[j], '-')) {
2583 break;
2584 }
2585 help = false;
2586 }
2587 break;
2588 }
2589 }
2590 if (!help) {
2591 SkCommandLineFlags::Parse(argc, argv);
2592 } else {
2593 SkCommandLineFlags::PrintUsage();
Cary Clarkac47b882018-01-11 10:35:44 -05002594 const char* const commands[] = { "", "-h", "bmh", "-h", "examples", "-h", "include",
2595 "-h", "fiddle", "-h", "ref", "-h", "status", "-h", "tokens",
Cary Clark8032b982017-07-28 11:04:54 -04002596 "-h", "crosscheck", "-h", "populate", "-h", "spellcheck" };
Cary Clark7265ea32017-09-14 12:12:32 -04002597 SkCommandLineFlags::Parse(SK_ARRAY_COUNT(commands), commands);
Cary Clark8032b982017-07-28 11:04:54 -04002598 return 0;
2599 }
Cary Clark2f466242017-12-11 16:03:17 -05002600 if (FLAGS_bmh.isEmpty() && FLAGS_include.isEmpty() && FLAGS_status.isEmpty()) {
2601 SkDebugf("requires at least one of: -b -i -a\n");
Cary Clark8032b982017-07-28 11:04:54 -04002602 SkCommandLineFlags::PrintUsage();
2603 return 1;
2604 }
Cary Clark2f466242017-12-11 16:03:17 -05002605 if (!FLAGS_bmh.isEmpty() && !FLAGS_status.isEmpty()) {
2606 SkDebugf("requires -b or -a but not both\n");
Cary Clarkbef063a2017-10-31 15:44:45 -04002607 SkCommandLineFlags::PrintUsage();
2608 return 1;
2609 }
Cary Clark2f466242017-12-11 16:03:17 -05002610 if (!FLAGS_include.isEmpty() && !FLAGS_status.isEmpty()) {
2611 SkDebugf("requires -i or -a but not both\n");
2612 SkCommandLineFlags::PrintUsage();
2613 return 1;
2614 }
2615 if (FLAGS_bmh.isEmpty() && FLAGS_status.isEmpty() && FLAGS_catalog) {
2616 SkDebugf("-c requires -b or -a\n");
2617 SkCommandLineFlags::PrintUsage();
2618 return 1;
2619 }
2620 if ((FLAGS_fiddle.isEmpty() || FLAGS_ref.isEmpty()) && FLAGS_catalog) {
2621 SkDebugf("-c requires -f -r\n");
2622 SkCommandLineFlags::PrintUsage();
2623 return 1;
2624 }
2625 if (FLAGS_bmh.isEmpty() && FLAGS_status.isEmpty() && !FLAGS_examples.isEmpty()) {
2626 SkDebugf("-e requires -b or -a\n");
Cary Clark8032b982017-07-28 11:04:54 -04002627 SkCommandLineFlags::PrintUsage();
2628 return 1;
2629 }
Cary Clark2f466242017-12-11 16:03:17 -05002630 if ((FLAGS_include.isEmpty() || FLAGS_bmh.isEmpty()) && FLAGS_status.isEmpty() &&
2631 FLAGS_populate) {
2632 SkDebugf("-p requires -b -i 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_bmh.isEmpty() && FLAGS_status.isEmpty() && !FLAGS_ref.isEmpty()) {
2637 SkDebugf("-r requires -b or -a\n");
Cary Clark8032b982017-07-28 11:04:54 -04002638 SkCommandLineFlags::PrintUsage();
2639 return 1;
2640 }
Cary Clark2f466242017-12-11 16:03:17 -05002641 if (FLAGS_bmh.isEmpty() && FLAGS_status.isEmpty() && !FLAGS_spellcheck.isEmpty()) {
2642 SkDebugf("-s requires -b or -a\n");
Cary Clark8032b982017-07-28 11:04:54 -04002643 SkCommandLineFlags::PrintUsage();
2644 return 1;
2645 }
Cary Clarka560c472017-11-27 10:44:06 -05002646 if ((FLAGS_include.isEmpty() || FLAGS_bmh.isEmpty()) && FLAGS_tokens) {
2647 SkDebugf("-t requires -b -i\n");
Cary Clark8032b982017-07-28 11:04:54 -04002648 SkCommandLineFlags::PrintUsage();
2649 return 1;
2650 }
Cary Clark2f466242017-12-11 16:03:17 -05002651 if ((FLAGS_include.isEmpty() || FLAGS_bmh.isEmpty()) && FLAGS_status.isEmpty() &&
2652 FLAGS_crosscheck) {
2653 SkDebugf("-x requires -b -i or -a\n");
Cary Clark8032b982017-07-28 11:04:54 -04002654 SkCommandLineFlags::PrintUsage();
2655 return 1;
2656 }
Cary Clarkac47b882018-01-11 10:35:44 -05002657 bmhParser.reset();
Cary Clark8032b982017-07-28 11:04:54 -04002658 if (!FLAGS_bmh.isEmpty()) {
Cary Clark2dc84ad2018-01-26 12:56:22 -05002659 if (FLAGS_tokens) {
2660 IncludeParser::RemoveFile(FLAGS_bmh[0], FLAGS_include[0]);
2661 }
Cary Clark186d08f2018-04-03 08:43:27 -04002662 if (!bmhParser.parseFile(FLAGS_bmh[0], ".bmh", ParserCommon::OneFile::kNo)) {
Cary Clark8032b982017-07-28 11:04:54 -04002663 return -1;
2664 }
Cary Clark2f466242017-12-11 16:03:17 -05002665 } else if (!FLAGS_status.isEmpty()) {
Cary Clark2f466242017-12-11 16:03:17 -05002666 if (!bmhParser.parseStatus(FLAGS_status[0], ".bmh", StatusFilter::kInProgress)) {
2667 return -1;
2668 }
Cary Clark8032b982017-07-28 11:04:54 -04002669 }
Cary Clarkab2621d2018-01-30 10:08:57 -05002670 if (FLAGS_hack) {
2671 if (FLAGS_bmh.isEmpty()) {
2672 SkDebugf("-k or --hack requires -b\n");
2673 SkCommandLineFlags::PrintUsage();
2674 return 1;
2675 }
2676 HackParser hacker(bmhParser);
Cary Clark186d08f2018-04-03 08:43:27 -04002677 if (!hacker.parseFile(FLAGS_bmh[0], ".bmh", ParserCommon::OneFile::kNo)) {
Cary Clarkab2621d2018-01-30 10:08:57 -05002678 SkDebugf("hack failed\n");
2679 return -1;
2680 }
2681 return 0;
2682 }
Cary Clarkac47b882018-01-11 10:35:44 -05002683 if (FLAGS_selfcheck && !SelfCheck(bmhParser)) {
2684 return -1;
2685 }
Cary Clark8032b982017-07-28 11:04:54 -04002686 bool done = false;
Cary Clark2f466242017-12-11 16:03:17 -05002687 if (!FLAGS_include.isEmpty() && FLAGS_tokens) {
2688 IncludeParser includeParser;
2689 includeParser.validate();
Cary Clark186d08f2018-04-03 08:43:27 -04002690 if (!includeParser.parseFile(FLAGS_include[0], ".h", ParserCommon::OneFile::kNo)) {
Cary Clark2f466242017-12-11 16:03:17 -05002691 return -1;
2692 }
2693 if (FLAGS_tokens) {
2694 includeParser.fDebugOut = FLAGS_stdout;
Cary Clark2d4bf5f2018-04-16 08:37:38 -04002695 if (includeParser.dumpTokens()) {
Cary Clark2f466242017-12-11 16:03:17 -05002696 bmhParser.fWroteOut = true;
2697 }
2698 done = true;
2699 }
2700 } else if (!FLAGS_include.isEmpty() || !FLAGS_status.isEmpty()) {
2701 if (FLAGS_crosscheck) {
Cary Clark8032b982017-07-28 11:04:54 -04002702 IncludeParser includeParser;
2703 includeParser.validate();
Cary Clark2f466242017-12-11 16:03:17 -05002704 if (!FLAGS_include.isEmpty() &&
Cary Clark186d08f2018-04-03 08:43:27 -04002705 !includeParser.parseFile(FLAGS_include[0], ".h", ParserCommon::OneFile::kNo)) {
Cary Clark8032b982017-07-28 11:04:54 -04002706 return -1;
2707 }
Cary Clark2f466242017-12-11 16:03:17 -05002708 if (!FLAGS_status.isEmpty() && !includeParser.parseStatus(FLAGS_status[0], ".h",
2709 StatusFilter::kCompleted)) {
2710 return -1;
Cary Clark8032b982017-07-28 11:04:54 -04002711 }
Cary Clark2f466242017-12-11 16:03:17 -05002712 if (!includeParser.crossCheck(bmhParser)) {
2713 return -1;
2714 }
2715 done = true;
Cary Clark8032b982017-07-28 11:04:54 -04002716 } else if (FLAGS_populate) {
2717 IncludeWriter includeWriter;
2718 includeWriter.validate();
Cary Clark2f466242017-12-11 16:03:17 -05002719 if (!FLAGS_include.isEmpty() &&
Cary Clark186d08f2018-04-03 08:43:27 -04002720 !includeWriter.parseFile(FLAGS_include[0], ".h", ParserCommon::OneFile::kNo)) {
Cary Clark2f466242017-12-11 16:03:17 -05002721 return -1;
2722 }
2723 if (!FLAGS_status.isEmpty() && !includeWriter.parseStatus(FLAGS_status[0], ".h",
2724 StatusFilter::kCompleted)) {
Cary Clark8032b982017-07-28 11:04:54 -04002725 return -1;
2726 }
Cary Clarkd0530ba2017-09-14 11:25:39 -04002727 includeWriter.fDebugOut = FLAGS_stdout;
Cary Clark8032b982017-07-28 11:04:54 -04002728 if (!includeWriter.populate(bmhParser)) {
2729 return -1;
2730 }
Cary Clarkd0530ba2017-09-14 11:25:39 -04002731 bmhParser.fWroteOut = true;
Cary Clark8032b982017-07-28 11:04:54 -04002732 done = true;
2733 }
2734 }
Cary Clark2f466242017-12-11 16:03:17 -05002735 if (!done && !FLAGS_fiddle.isEmpty() && FLAGS_examples.isEmpty()) {
Cary Clarkbef063a2017-10-31 15:44:45 -04002736 FiddleParser fparser(&bmhParser);
Cary Clarkd7895502018-07-18 15:10:08 -04002737 if (!fparser.parseFromFile(FLAGS_fiddle[0])) {
Cary Clark8032b982017-07-28 11:04:54 -04002738 return -1;
2739 }
2740 }
Cary Clarkbef063a2017-10-31 15:44:45 -04002741 if (!done && FLAGS_catalog && FLAGS_examples.isEmpty()) {
Cary Clark2f466242017-12-11 16:03:17 -05002742 Catalog cparser(&bmhParser);
2743 cparser.fDebugOut = FLAGS_stdout;
Cary Clark5b1f9532018-08-28 14:53:37 -04002744 if (!FLAGS_bmh.isEmpty() && !cparser.openCatalog(FLAGS_bmh[0])) {
Cary Clarkbef063a2017-10-31 15:44:45 -04002745 return -1;
2746 }
Cary Clark5b1f9532018-08-28 14:53:37 -04002747 if (!FLAGS_status.isEmpty() && !cparser.openStatus(FLAGS_status[0])) {
Cary Clarkbef063a2017-10-31 15:44:45 -04002748 return -1;
2749 }
Cary Clark186d08f2018-04-03 08:43:27 -04002750 if (!cparser.parseFile(FLAGS_fiddle[0], ".txt", ParserCommon::OneFile::kNo)) {
Cary Clark2f466242017-12-11 16:03:17 -05002751 return -1;
2752 }
Cary Clark5b1f9532018-08-28 14:53:37 -04002753 if (!cparser.closeCatalog(FLAGS_ref[0])) {
Cary Clarkbef063a2017-10-31 15:44:45 -04002754 return -1;
2755 }
2756 bmhParser.fWroteOut = true;
2757 done = true;
2758 }
Cary Clark8032b982017-07-28 11:04:54 -04002759 if (!done && !FLAGS_ref.isEmpty() && FLAGS_examples.isEmpty()) {
Cary Clark186d08f2018-04-03 08:43:27 -04002760 IncludeParser includeParser;
2761 includeParser.validate();
Cary Clark61313f32018-10-08 14:57:48 -04002762 if (!FLAGS_status.isEmpty() && !includeParser.parseStatus(FLAGS_status[0], ".h",
2763 StatusFilter::kCompleted)) {
2764 return -1;
2765 }
Cary Clark186d08f2018-04-03 08:43:27 -04002766 if (!FLAGS_include.isEmpty() && !includeParser.parseFile(FLAGS_include[0], ".h",
2767 ParserCommon::OneFile::kYes)) {
2768 return -1;
2769 }
Cary Clark61313f32018-10-08 14:57:48 -04002770 includeParser.writeCodeBlock(bmhParser);
2771 MdOut mdOut(bmhParser, includeParser);
Cary Clark9174bda2017-09-19 17:39:32 -04002772 mdOut.fDebugOut = FLAGS_stdout;
Cary Clark682c58d2018-05-16 07:07:07 -04002773 mdOut.fValidate = FLAGS_validate;
Cary Clark61313f32018-10-08 14:57:48 -04002774 if (!FLAGS_bmh.isEmpty() && mdOut.buildReferences(FLAGS_bmh[0], FLAGS_ref[0])) {
Cary Clark2f466242017-12-11 16:03:17 -05002775 bmhParser.fWroteOut = true;
2776 }
2777 if (!FLAGS_status.isEmpty() && mdOut.buildStatus(FLAGS_status[0], FLAGS_ref[0])) {
Cary Clarkd0530ba2017-09-14 11:25:39 -04002778 bmhParser.fWroteOut = true;
2779 }
Cary Clark682c58d2018-05-16 07:07:07 -04002780 if (FLAGS_validate) {
2781 mdOut.checkAnchors();
2782 }
Cary Clark8032b982017-07-28 11:04:54 -04002783 }
Cary Clarkce101242017-09-01 15:51:02 -04002784 if (!done && !FLAGS_spellcheck.isEmpty() && FLAGS_examples.isEmpty()) {
Cary Clark2f466242017-12-11 16:03:17 -05002785 if (!FLAGS_bmh.isEmpty()) {
2786 bmhParser.spellCheck(FLAGS_bmh[0], FLAGS_spellcheck);
2787 }
2788 if (!FLAGS_status.isEmpty()) {
2789 bmhParser.spellStatus(FLAGS_status[0], FLAGS_spellcheck);
2790 }
Cary Clark154beea2017-10-26 07:58:48 -04002791 bmhParser.fWroteOut = true;
Cary Clark8032b982017-07-28 11:04:54 -04002792 done = true;
2793 }
2794 int examples = 0;
2795 int methods = 0;
2796 int topics = 0;
Cary Clark8032b982017-07-28 11:04:54 -04002797 if (!done && !FLAGS_examples.isEmpty()) {
Cary Clark73fa9722017-08-29 17:36:51 -04002798 // check to see if examples have duplicate names
2799 if (!bmhParser.checkExamples()) {
Cary Clark8032b982017-07-28 11:04:54 -04002800 return -1;
2801 }
Cary Clark9174bda2017-09-19 17:39:32 -04002802 bmhParser.fDebugOut = FLAGS_stdout;
Cary Clark73fa9722017-08-29 17:36:51 -04002803 if (!bmhParser.dumpExamples(FLAGS_examples[0])) {
2804 return -1;
Cary Clark8032b982017-07-28 11:04:54 -04002805 }
Cary Clarkd0530ba2017-09-14 11:25:39 -04002806 return 0;
Cary Clark8032b982017-07-28 11:04:54 -04002807 }
Cary Clarkd0530ba2017-09-14 11:25:39 -04002808 if (!bmhParser.fWroteOut) {
2809 for (const auto& topic : bmhParser.fTopicMap) {
2810 if (topic.second->fParent) {
2811 continue;
2812 }
2813 examples += count_children(*topic.second, MarkType::kExample);
2814 methods += count_children(*topic.second, MarkType::kMethod);
2815 topics += count_children(*topic.second, MarkType::kSubtopic);
2816 topics += count_children(*topic.second, MarkType::kTopic);
Cary Clark8032b982017-07-28 11:04:54 -04002817 }
Ben Wagner63fd7602017-10-09 15:45:33 -04002818 SkDebugf("topics=%d classes=%d methods=%d examples=%d\n",
Cary Clarkd0530ba2017-09-14 11:25:39 -04002819 bmhParser.fTopicMap.size(), bmhParser.fClassMap.size(),
2820 methods, examples);
Cary Clark8032b982017-07-28 11:04:54 -04002821 }
Cary Clark8032b982017-07-28 11:04:54 -04002822 return 0;
2823}