blob: 228f898fe168b51d09548179ab4645546fb36496 [file] [log] [blame]
Cary Clarka560c472017-11-27 10:44:06 -05001/*
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 Clark2d4bf5f2018-04-16 08:37:38 -04009#include "SkOSPath.h"
Cary Clarka560c472017-11-27 10:44:06 -050010
11#ifdef CONST
12#undef CONST
13#endif
14
15#ifdef FRIEND
16#undef FRIEND
17#endif
18
19#ifdef BLANK
20#undef BLANK
21#endif
22
23#ifdef ANY
24#undef ANY
25#endif
26
27#ifdef DEFOP
28#undef DEFOP
29#endif
30
31#define CONST 1
32#define STATIC 2
33#define BLANK 0
34#define ANY -1
35#define DEFOP Definition::Operator
36
37enum class OpType : int8_t {
38 kNone,
39 kVoid,
40 kBool,
41 kChar,
42 kFloat,
43 kInt,
44 kScalar,
45 kSizeT,
46 kThis,
47 kAny,
48};
49
50enum class OpMod : int8_t {
51 kNone,
52 kArray,
53 kMove,
54 kPointer,
55 kReference,
56 kAny,
57};
58
59const struct OperatorParser {
60 DEFOP fOperator;
61 const char* fSymbol;
62 const char* fName;
63 int8_t fFriend;
64 OpType fReturnType;
65 OpMod fReturnMod;
66 int8_t fConstMethod;
67 struct Param {
68 int8_t fConst;
69 OpType fType;
70 OpMod fMod;
71 } fParams[2];
72} opData[] = {
73 { DEFOP::kUnknown, "??", "???", BLANK, OpType::kNone, OpMod::kNone, BLANK,
74 { } },
75 { DEFOP::kAdd, "+", "add", BLANK, OpType::kThis, OpMod::kNone, BLANK,
76 {{ CONST, OpType::kThis, OpMod::kReference, },
77 { CONST, OpType::kThis, OpMod::kReference, }}},
78 { DEFOP::kAddTo, "+=", "addto", BLANK, OpType::kVoid, OpMod::kNone, BLANK,
79 {{ CONST, OpType::kThis, OpMod::kReference, }}},
80 { DEFOP::kAddTo, "+=", "addto1", BLANK, OpType::kThis, OpMod::kReference, BLANK,
81 {{ CONST, OpType::kThis, OpMod::kReference, }}},
82 { DEFOP::kAddTo, "+=", "addto2", BLANK, OpType::kThis, OpMod::kReference, BLANK,
83 {{ CONST, OpType::kChar, OpMod::kArray, }}},
84 { DEFOP::kAddTo, "+=", "addto3", BLANK, OpType::kThis, OpMod::kReference, BLANK,
85 {{ CONST, OpType::kChar, OpMod::kNone, }}},
86 { DEFOP::kArray, "[]", "array", BLANK, OpType::kScalar, OpMod::kNone, CONST,
87 {{ BLANK, OpType::kInt, OpMod::kNone, }}},
88 { DEFOP::kArray, "[]", "array1", BLANK, OpType::kScalar, OpMod::kReference, BLANK,
89 {{ BLANK, OpType::kInt, OpMod::kNone, }}},
90 { DEFOP::kArray, "[]", "array2", BLANK, OpType::kChar, OpMod::kNone, CONST,
91 {{ BLANK, OpType::kSizeT, OpMod::kNone, }}},
92 { DEFOP::kArray, "[]", "array3", BLANK, OpType::kChar, OpMod::kReference, BLANK,
93 {{ BLANK, OpType::kSizeT, OpMod::kNone, }}},
94 { DEFOP::kCast, "()", "cast", BLANK, OpType::kAny, OpMod::kAny, ANY,
95 {{ ANY, OpType::kAny, OpMod::kAny, }}},
96 { DEFOP::kCopy, "=", "copy", BLANK, OpType::kThis, OpMod::kReference, BLANK,
97 {{ CONST, OpType::kThis, OpMod::kReference, }}},
98 { DEFOP::kCopy, "=", "copy1", BLANK, OpType::kThis, OpMod::kReference, BLANK,
99 {{ CONST, OpType::kChar, OpMod::kArray, }}},
100 { DEFOP::kDelete, "delete", "delete", BLANK, OpType::kVoid, OpMod::kNone, BLANK,
101 {{ BLANK, OpType::kVoid, OpMod::kPointer, }}},
102 { DEFOP::kDereference, "->", "deref", ANY, OpType::kThis, OpMod::kPointer, CONST,
103 { } },
104 { DEFOP::kDereference, "*", "deref", BLANK, OpType::kThis, OpMod::kReference, CONST,
105 { } },
106 { DEFOP::kEqual, "==", "equal", BLANK, OpType::kBool, OpMod::kNone, BLANK,
107 {{ CONST, OpType::kThis, OpMod::kReference, },
108 { CONST, OpType::kThis, OpMod::kReference, }}},
109 { DEFOP::kEqual, "==", "equal1", BLANK, OpType::kBool, OpMod::kNone, CONST,
110 {{ CONST, OpType::kThis, OpMod::kReference, }}},
111 { DEFOP::kEqual, "==", "equal2", ANY, OpType::kBool, OpMod::kNone, BLANK,
112 {{ CONST, OpType::kThis, OpMod::kReference, },
113 { CONST, OpType::kThis, OpMod::kReference, }}},
114 { DEFOP::kMinus, "-", "minus", BLANK, OpType::kThis, OpMod::kNone, CONST,
115 { } },
116 { DEFOP::kMove, "=", "move", BLANK, OpType::kThis, OpMod::kReference, BLANK,
117 {{ BLANK, OpType::kThis, OpMod::kMove, }}},
118 { DEFOP::kMultiply, "*", "multiply", BLANK, OpType::kThis, OpMod::kNone, CONST,
119 {{ BLANK, OpType::kScalar, OpMod::kNone, }}},
120 { DEFOP::kMultiply, "*", "multiply1", BLANK, OpType::kThis, OpMod::kNone, BLANK,
121 {{ CONST, OpType::kThis, OpMod::kReference, },
122 { CONST, OpType::kThis, OpMod::kReference, }}},
123 { DEFOP::kMultiplyBy, "*=", "multiplyby", BLANK, OpType::kThis, OpMod::kReference, BLANK,
124 {{ BLANK, OpType::kScalar, OpMod::kNone, }}},
125 { DEFOP::kNew, "new", "new", BLANK, OpType::kVoid, OpMod::kPointer, BLANK,
126 {{ BLANK, OpType::kSizeT, OpMod::kNone, }}},
127 { DEFOP::kNotEqual, "!=", "notequal", BLANK, OpType::kBool, OpMod::kNone, BLANK,
128 {{ CONST, OpType::kThis, OpMod::kReference, },
129 { CONST, OpType::kThis, OpMod::kReference, }}},
130 { DEFOP::kNotEqual, "!=", "notequal1", BLANK, OpType::kBool, OpMod::kNone, CONST,
131 {{ CONST, OpType::kThis, OpMod::kReference, }}},
132 { DEFOP::kNotEqual, "!=", "notequal2", ANY, OpType::kBool, OpMod::kNone, BLANK,
133 {{ CONST, OpType::kThis, OpMod::kReference, },
134 { CONST, OpType::kThis, OpMod::kReference, }}},
135 { DEFOP::kSubtract, "-", "subtract", BLANK, OpType::kThis, OpMod::kNone, BLANK,
136 {{ CONST, OpType::kThis, OpMod::kReference, },
137 { CONST, OpType::kThis, OpMod::kReference, }}},
138 { DEFOP::kSubtractFrom, "-=", "subtractfrom", BLANK, OpType::kVoid, OpMod::kNone, BLANK,
139 {{ CONST, OpType::kThis, OpMod::kReference, }}},
140};
141
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400142OpType lookup_type(string typeWord, string name) {
Cary Clarka560c472017-11-27 10:44:06 -0500143 if (typeWord == name || (typeWord == "SkIVector" && name == "SkIPoint")
144 || (typeWord == "SkVector" && name == "SkPoint")) {
145 return OpType::kThis;
146 }
147 const char* keyWords[] = { "void", "bool", "char", "float", "int", "SkScalar", "size_t" };
148 for (unsigned i = 0; i < SK_ARRAY_COUNT(keyWords); ++i) {
149 if (typeWord == keyWords[i]) {
150 return (OpType) (i + 1);
151 }
152 }
153 return OpType::kNone;
154}
155
156OpMod lookup_mod(TextParser& iParser) {
157 OpMod mod = OpMod::kNone;
158 if ('&' == iParser.peek()) {
159 mod = OpMod::kReference;
160 iParser.next();
161 if ('&' == iParser.peek()) {
162 mod = OpMod::kMove;
163 iParser.next();
164 }
165 } else if ('*' == iParser.peek()) {
166 mod = OpMod::kPointer;
167 iParser.next();
168 }
169 iParser.skipWhiteSpace();
170 return mod;
171}
172
173bool Definition::parseOperator(size_t doubleColons, string& result) {
174 const char operatorStr[] = "operator";
175 size_t opPos = fName.find(operatorStr, doubleColons);
176 if (string::npos == opPos) {
177 return false;
178 }
179 string className(fName, 0, doubleColons - 2);
180 TextParser iParser(fFileName, fStart, fContentStart, fLineCount);
181 SkAssertResult(iParser.skipWord("#Method"));
182 iParser.skipExact("SK_API");
183 iParser.skipWhiteSpace();
184 bool isStatic = iParser.skipExact("static");
185 iParser.skipWhiteSpace();
186 iParser.skipExact("SK_API");
187 iParser.skipWhiteSpace();
188 bool returnsConst = iParser.skipExact("const");
189 if (returnsConst) {
190 SkASSERT(0); // incomplete
191 }
192 SkASSERT(isStatic == false || returnsConst == false);
193 iParser.skipWhiteSpace();
194 const char* returnTypeStart = iParser.fChar;
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400195 iParser.skipToNonName();
Cary Clarka560c472017-11-27 10:44:06 -0500196 SkASSERT(iParser.fChar > returnTypeStart);
197 string returnType(returnTypeStart, iParser.fChar - returnTypeStart);
198 OpType returnOpType = lookup_type(returnType, className);
199 iParser.skipWhiteSpace();
200 OpMod returnMod = lookup_mod(iParser);
201 SkAssertResult(iParser.skipExact("operator"));
202 iParser.skipWhiteSpace();
203 fMethodType = Definition::MethodType::kOperator;
Cary Clark186d08f2018-04-03 08:43:27 -0400204 TextParserSave save(&iParser);
Cary Clarka560c472017-11-27 10:44:06 -0500205 for (auto parser : opData) {
206 save.restore();
207 if (!iParser.skipExact(parser.fSymbol)) {
208 continue;
209 }
210 iParser.skipWhiteSpace();
211 if ('(' != iParser.peek()) {
212 continue;
213 }
214 if (parser.fFriend != ANY && (parser.fFriend == STATIC) != isStatic) {
215 continue;
216 }
217 if (parser.fReturnType != OpType::kAny && parser.fReturnType != returnOpType) {
218 continue;
219 }
220 if (parser.fReturnMod != OpMod::kAny && parser.fReturnMod != returnMod) {
221 continue;
222 }
223 iParser.next(); // skip '('
224 iParser.skipWhiteSpace();
225 int parserCount = (parser.fParams[0].fType != OpType::kNone) +
226 (parser.fParams[1].fType != OpType::kNone);
227 bool countsMatch = true;
228 for (int pIndex = 0; pIndex < 2; ++pIndex) {
229 if (')' == iParser.peek()) {
230 countsMatch = pIndex == parserCount;
231 break;
232 }
233 if (',' == iParser.peek()) {
234 iParser.next();
235 iParser.skipWhiteSpace();
236 }
237 bool paramConst = iParser.skipExact("const");
238 if (parser.fParams[pIndex].fConst != ANY &&
239 paramConst != (parser.fParams[pIndex].fConst == CONST)) {
240 countsMatch = false;
241 break;
242 }
243 iParser.skipWhiteSpace();
244 const char* paramStart = iParser.fChar;
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400245 iParser.skipToNonName();
Cary Clarka560c472017-11-27 10:44:06 -0500246 SkASSERT(iParser.fChar > paramStart);
247 string paramType(paramStart, iParser.fChar - paramStart);
248 OpType paramOpType = lookup_type(paramType, className);
249 if (parser.fParams[pIndex].fType != OpType::kAny &&
250 parser.fParams[pIndex].fType != paramOpType) {
251 countsMatch = false;
252 break;
253 }
254 iParser.skipWhiteSpace();
255 OpMod paramMod = lookup_mod(iParser);
256 if (parser.fParams[pIndex].fMod != OpMod::kAny &&
257 parser.fParams[pIndex].fMod != paramMod) {
258 countsMatch = false;
259 break;
260 }
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400261 iParser.skipToNonName();
Cary Clarka560c472017-11-27 10:44:06 -0500262 if ('[' == iParser.peek()) {
263 paramMod = OpMod::kArray;
264 SkAssertResult(iParser.skipExact("[]"));
265 }
266 iParser.skipWhiteSpace();
267 }
268 if (!countsMatch) {
269 continue;
270 }
271 if (')' != iParser.peek()) {
272 continue;
273 }
274 iParser.next();
275 bool constMethod = iParser.skipExact("_const");
276 if (parser.fConstMethod != ANY && (parser.fConstMethod == CONST) != constMethod) {
277 continue;
278 }
279 result += parser.fName;
280 result += "_operator";
281 fOperator = parser.fOperator;
282 fOperatorConst = constMethod;
283 return true;
284 }
285 SkASSERT(0); // incomplete
286 return false;
287#if 0
288 if ('!' == fName[opPos]) {
289 SkASSERT('=' == fName[opPos + 1]);
290 result += "not_equal_operator";
291 } else if ('=' == fName[opPos]) {
292 if ('(' == fName[opPos + 1]) {
293 result += isMove ? "move_" : "copy_";
294 result += "assignment_operator";
295 } else {
296 SkASSERT('=' == fName[opPos + 1]);
297 result += "equal_operator";
298 }
299 } else if ('[' == fName[opPos]) {
300 result += "subscript_operator";
301 const char* end = fContentStart;
302 while (end > fStart && ' ' >= end[-1]) {
303 --end;
304 }
305 string constCheck(fStart, end - fStart);
306 size_t constPos = constCheck.rfind("const");
307 if (constCheck.length() == constPos + 5) {
308 result += "_const";
309 }
310 } else if ('*' == fName[opPos]) {
311 result += "multiply_operator";
312 } else if ('-' == fName[opPos]) {
313 result += "subtract_operator";
314 } else if ('+' == fName[opPos]) {
315 result += "add_operator";
316 } else {
317 SkASSERT(0); // todo: incomplete
318 }
319#endif
320 return true;
321}
322
323#undef CONST
324#undef FRIEND
325#undef BLANK
326#undef DEFOP
327
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400328bool Definition::boilerplateIfDef() {
Cary Clarka560c472017-11-27 10:44:06 -0500329 const Definition& label = fTokens.front();
330 if (Type::kWord != label.fType) {
331 return false;
332 }
333 fName = string(label.fContentStart, label.fContentEnd - label.fContentStart);
334 return true;
335}
336
Cary Clarka560c472017-11-27 10:44:06 -0500337
338// fixme: this will need to be more complicated to handle all of Skia
339// for now, just handle paint -- maybe fiddle will loosen naming restrictions
340void Definition::setCanonicalFiddle() {
341 fMethodType = Definition::MethodType::kNone;
342 size_t doubleColons = fName.find("::", 0);
343 SkASSERT(string::npos != doubleColons);
344 string base = fName.substr(0, doubleColons);
345 string result = base + "_";
346 doubleColons += 2;
347 if (string::npos != fName.find('~', doubleColons)) {
348 fMethodType = Definition::MethodType::kDestructor;
349 result += "destructor";
350 } else if (!this->parseOperator(doubleColons, result)) {
351 bool isMove = string::npos != fName.find("&&", doubleColons);
352 size_t parens = fName.find("()", doubleColons);
353 if (string::npos != parens) {
354 string methodName = fName.substr(doubleColons, parens - doubleColons);
355 do {
356 size_t nextDouble = methodName.find("::");
357 if (string::npos == nextDouble) {
358 break;
359 }
360 base = methodName.substr(0, nextDouble);
361 result += base + '_';
362 methodName = methodName.substr(nextDouble + 2);
363 doubleColons += nextDouble + 2;
364 } while (true);
365 if (base == methodName) {
366 fMethodType = Definition::MethodType::kConstructor;
367 result += "empty_constructor";
368 } else {
369 result += fName.substr(doubleColons, fName.length() - doubleColons - 2);
370 }
371 } else {
372 size_t openParen = fName.find('(', doubleColons);
373 if (string::npos == openParen) {
374 result += fName.substr(doubleColons);
Cary Clark682c58d2018-05-16 07:07:07 -0400375 // see if it is a constructor -- if second to last delimited name equals last
376 size_t nextColons = fName.find("::", doubleColons);
377 if (string::npos != nextColons) {
378 nextColons += 2;
379 if (!strncmp(&fName[doubleColons], &fName[nextColons],
380 nextColons - doubleColons - 2)) {
381 fMethodType = Definition::MethodType::kConstructor;
382 }
383 }
Cary Clarka560c472017-11-27 10:44:06 -0500384 } else {
385 size_t comma = fName.find(',', doubleColons);
386 if (string::npos == comma) {
387 result += isMove ? "move_" : "copy_";
388 }
389 fMethodType = Definition::MethodType::kConstructor;
390 // name them by their param types,
391 // e.g. SkCanvas__int_int_const_SkSurfaceProps_star
392 // TODO: move forward until parens are balanced and terminator =,)
393 TextParser params("", &fName[openParen] + 1, &*fName.end(), 0);
394 bool underline = false;
395 while (!params.eof()) {
396// SkDEBUGCODE(const char* end = params.anyOf("(),=")); // unused for now
397// SkASSERT(end[0] != '('); // fixme: put off handling nested parentheseses
398 if (params.startsWith("const") || params.startsWith("int")
399 || params.startsWith("Sk")) {
400 const char* wordStart = params.fChar;
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400401 params.skipToNonName();
Cary Clarka560c472017-11-27 10:44:06 -0500402 if (underline) {
403 result += '_';
404 } else {
405 underline = true;
406 }
407 result += string(wordStart, params.fChar - wordStart);
408 } else {
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400409 params.skipToNonName();
Cary Clarka560c472017-11-27 10:44:06 -0500410 }
411 if (!params.eof() && '*' == params.peek()) {
412 if (underline) {
413 result += '_';
414 } else {
415 underline = true;
416 }
417 result += "star";
418 params.next();
419 params.skipSpace();
420 }
421 params.skipToAlpha();
422 }
423 }
424 }
425 }
426 fFiddle = Definition::NormalizedName(result);
427}
428
Cary Clarka560c472017-11-27 10:44:06 -0500429static void space_pad(string* str) {
430 size_t len = str->length();
431 if (len == 0) {
432 return;
433 }
434 char last = (*str)[len - 1];
435 if ('~' == last || ' ' >= last) {
436 return;
437 }
438 *str += ' ';
439}
440
441//start here;
442// see if it possible to abstract this a little bit so it can
443// additionally be used to find params and return in method prototype that
444// does not have corresponding doxygen comments
445bool Definition::checkMethod() const {
446 SkASSERT(MarkType::kMethod == fMarkType);
447 // if method returns a value, look for a return child
448 // for each parameter, look for a corresponding child
449 const char* end = fContentStart;
450 while (end > fStart && ' ' >= end[-1]) {
451 --end;
452 }
453 TextParser methodParser(fFileName, fStart, end, fLineCount);
454 methodParser.skipWhiteSpace();
455 SkASSERT(methodParser.startsWith("#Method"));
456 methodParser.skipName("#Method");
457 methodParser.skipSpace();
458 string name = this->methodName();
459 if (MethodType::kNone == fMethodType && name.length() > 2 &&
460 "()" == name.substr(name.length() - 2)) {
461 name = name.substr(0, name.length() - 2);
462 }
463 bool expectReturn = this->methodHasReturn(name, &methodParser);
464 bool foundReturn = false;
465 bool foundException = false;
466 for (auto& child : fChildren) {
467 foundException |= MarkType::kDeprecated == child->fMarkType
468 || MarkType::kExperimental == child->fMarkType;
469 if (MarkType::kReturn != child->fMarkType) {
470 if (MarkType::kParam == child->fMarkType) {
471 child->fVisited = false;
472 }
473 continue;
474 }
475 if (!expectReturn) {
476 return methodParser.reportError<bool>("no #Return expected");
477 }
478 if (foundReturn) {
479 return methodParser.reportError<bool>("multiple #Return markers");
480 }
481 foundReturn = true;
482 }
483 if (expectReturn && !foundReturn && !foundException) {
484 return methodParser.reportError<bool>("missing #Return marker");
485 }
486 const char* paren = methodParser.strnchr('(', methodParser.fEnd);
487 if (!paren) {
488 return methodParser.reportError<bool>("missing #Method function definition");
489 }
490 const char* nextEnd = paren;
491 do {
492 string paramName;
493 methodParser.fChar = nextEnd + 1;
494 methodParser.skipSpace();
495 if (!this->nextMethodParam(&methodParser, &nextEnd, &paramName)) {
496 continue;
497 }
498 bool foundParam = false;
499 for (auto& child : fChildren) {
500 if (MarkType::kParam != child->fMarkType) {
501 continue;
502 }
503 if (paramName != child->fName) {
504 continue;
505 }
506 if (child->fVisited) {
507 return methodParser.reportError<bool>("multiple #Method param with same name");
508 }
509 child->fVisited = true;
510 if (foundParam) {
511 TextParser paramError(child);
512 return methodParser.reportError<bool>("multiple #Param with same name");
513 }
514 foundParam = true;
515
516 }
517 if (!foundParam && !foundException) {
518 return methodParser.reportError<bool>("no #Param found");
519 }
520 if (')' == nextEnd[0]) {
521 break;
522 }
523 } while (')' != nextEnd[0]);
524 for (auto& child : fChildren) {
525 if (MarkType::kParam != child->fMarkType) {
526 continue;
527 }
528 if (!child->fVisited) {
529 TextParser paramError(child);
530 return paramError.reportError<bool>("#Param without param in #Method");
531 }
532 }
Cary Clark80247e52018-07-11 16:18:41 -0400533 // check after end of #Line and before next child for description
534 const char* descStart = fContentStart;
535 const char* descEnd = nullptr;
Cary Clarkab5c9af2018-07-12 16:24:53 -0400536 const Definition* defEnd = nullptr;
537 const Definition* priorDef = nullptr;
Cary Clark80247e52018-07-11 16:18:41 -0400538 for (auto& child : fChildren) {
539 if (MarkType::kAnchor == child->fMarkType) {
540 continue;
541 }
542 if (MarkType::kCode == child->fMarkType) {
Cary Clarkab5c9af2018-07-12 16:24:53 -0400543 priorDef = child;
Cary Clark80247e52018-07-11 16:18:41 -0400544 continue;
545 }
546 if (MarkType::kDeprecated == child->fMarkType) {
547 return true;
548 }
549 if (MarkType::kExperimental == child->fMarkType) {
550 return true;
551 }
552 if (MarkType::kFormula == child->fMarkType) {
553 continue;
554 }
555 if (MarkType::kList == child->fMarkType) {
Cary Clarkab5c9af2018-07-12 16:24:53 -0400556 priorDef = child;
Cary Clark80247e52018-07-11 16:18:41 -0400557 continue;
558 }
559 if (MarkType::kMarkChar == child->fMarkType) {
560 continue;
561 }
562 if (MarkType::kPhraseRef == child->fMarkType) {
563 continue;
564 }
565 if (MarkType::kPrivate == child->fMarkType) {
566 return true;
567 }
568 TextParser emptyCheck(fFileName, descStart, child->fStart, child->fLineCount);
569 if (!emptyCheck.eof() && emptyCheck.skipWhiteSpace()) {
570 descStart = emptyCheck.fChar;
571 emptyCheck.trimEnd();
Cary Clarkab5c9af2018-07-12 16:24:53 -0400572 defEnd = priorDef;
Cary Clark80247e52018-07-11 16:18:41 -0400573 descEnd = emptyCheck.fEnd;
574 break;
575 }
576 descStart = child->fTerminator;
Cary Clarkab5c9af2018-07-12 16:24:53 -0400577 priorDef = nullptr;
Cary Clark80247e52018-07-11 16:18:41 -0400578 }
579 if (!descEnd) {
580 return methodParser.reportError<bool>("missing description");
581 }
582 TextParser description(fFileName, descStart, descEnd, fLineCount);
583 // expect first word capitalized and pluralized. expect a trailing period
584 SkASSERT(descStart < descEnd);
585 if (!isupper(descStart[0])) {
586 description.reportWarning("expected capital");
587 } else if ('.' != descEnd[-1]) {
Cary Clarkab5c9af2018-07-12 16:24:53 -0400588 if (!defEnd || defEnd->fTerminator != descEnd) {
589 description.reportWarning("expected period");
590 }
Cary Clark80247e52018-07-11 16:18:41 -0400591 } else {
592 if (!description.startsWith("For use by Android")) {
593 description.skipToSpace();
594 if (',' == description.fChar[-1]) {
595 --description.fChar;
596 }
597 if ('s' != description.fChar[-1]) {
598 description.reportWarning("expected plural");
599 }
600 }
601 }
Cary Clarka560c472017-11-27 10:44:06 -0500602 return true;
603}
604
605bool Definition::crossCheck2(const Definition& includeToken) const {
606 TextParser parser(fFileName, fStart, fContentStart, fLineCount);
607 parser.skipExact("#");
608 bool isMethod = parser.skipName("Method");
609 const char* contentEnd;
610 if (isMethod) {
611 contentEnd = fContentStart;
612 } else if (parser.skipName("DefinedBy")) {
613 contentEnd = fContentEnd;
614 while (parser.fChar < contentEnd && ' ' >= contentEnd[-1]) {
615 --contentEnd;
616 }
617 if (parser.fChar < contentEnd - 1 && ')' == contentEnd[-1] && '(' == contentEnd[-2]) {
618 contentEnd -= 2;
619 }
620 } else {
621 return parser.reportError<bool>("unexpected crosscheck marktype");
622 }
623 return crossCheckInside(parser.fChar, contentEnd, includeToken);
624}
625
626bool Definition::crossCheck(const Definition& includeToken) const {
627 return crossCheckInside(fContentStart, fContentEnd, includeToken);
628}
629
Cary Clarkab5c9af2018-07-12 16:24:53 -0400630const char* Definition::methodEnd() const {
631 const char defaultTag[] = " = default";
632 size_t tagSize = sizeof(defaultTag) - 1;
633 const char* tokenEnd = fContentEnd - tagSize;
634 if (tokenEnd <= fContentStart || strncmp(tokenEnd, defaultTag, tagSize)) {
635 tokenEnd = fContentEnd;
636 }
637 return tokenEnd;
638}
639
Cary Clarka560c472017-11-27 10:44:06 -0500640bool Definition::crossCheckInside(const char* start, const char* end,
641 const Definition& includeToken) const {
642 TextParser def(fFileName, start, end, fLineCount);
Cary Clarkab5c9af2018-07-12 16:24:53 -0400643 const char* tokenEnd = includeToken.methodEnd();
644 TextParser inc("", includeToken.fContentStart, tokenEnd, 0);
Cary Clarka560c472017-11-27 10:44:06 -0500645 if (inc.startsWith("SK_API")) {
646 inc.skipWord("SK_API");
647 }
648 if (inc.startsWith("friend")) {
649 inc.skipWord("friend");
650 }
651 if (inc.startsWith("SK_API")) {
652 inc.skipWord("SK_API");
653 }
654 inc.skipExact("SkDEBUGCODE(");
655 do {
656 bool defEof;
657 bool incEof;
658 do {
659 defEof = def.eof() || !def.skipWhiteSpace();
660 incEof = inc.eof() || !inc.skipWhiteSpace();
661 if (!incEof && '/' == inc.peek() && (defEof || '/' != def.peek())) {
662 inc.next();
663 if ('*' == inc.peek()) {
664 inc.skipToEndBracket("*/");
665 inc.next();
666 } else if ('/' == inc.peek()) {
667 inc.skipToEndBracket('\n');
668 }
669 } else if (!incEof && '#' == inc.peek() && (defEof || '#' != def.peek())) {
670 inc.next();
671 if (inc.startsWith("if")) {
672 inc.skipToEndBracket("\n");
673 } else if (inc.startsWith("endif")) {
674 inc.skipToEndBracket("\n");
675 } else {
676 SkASSERT(0); // incomplete
677 return false;
678 }
679 } else {
680 break;
681 }
682 inc.next();
683 } while (true);
684 if (defEof || incEof) {
685 if (defEof == incEof || (!defEof && ';' == def.peek())) {
686 return true;
687 }
688 return false; // allow setting breakpoint on failure
689 }
690 char defCh;
691 do {
692 defCh = def.next();
693 char incCh = inc.next();
694 if (' ' >= defCh && ' ' >= incCh) {
695 break;
696 }
697 if (defCh != incCh) {
698 if ('_' != defCh || ' ' != incCh || !fOperatorConst || !def.startsWith("const")) {
699 return false;
700 }
701 }
702 if (';' == defCh) {
703 return true;
704 }
705 } while (!def.eof() && !inc.eof());
706 } while (true);
707 return false;
708}
709
Cary Clark78de7512018-02-07 07:27:09 -0500710string Definition::formatFunction(Format format) const {
Cary Clarka560c472017-11-27 10:44:06 -0500711 const char* end = fContentStart;
712 while (end > fStart && ' ' >= end[-1]) {
713 --end;
714 }
715 TextParser methodParser(fFileName, fStart, end, fLineCount);
716 methodParser.skipWhiteSpace();
717 SkASSERT(methodParser.startsWith("#Method"));
718 methodParser.skipName("#Method");
719 methodParser.skipSpace();
720 const char* lastStart = methodParser.fChar;
721 const int limit = 100; // todo: allow this to be set by caller or in global or something
722 string name = this->methodName();
Cary Clarka560c472017-11-27 10:44:06 -0500723 const char* nameInParser = methodParser.strnstr(name.c_str(), methodParser.fEnd);
724 methodParser.skipTo(nameInParser);
725 const char* lastEnd = methodParser.fChar;
Cary Clark78de7512018-02-07 07:27:09 -0500726 if (Format::kOmitReturn == format) {
727 lastStart = lastEnd;
728 }
Cary Clarka560c472017-11-27 10:44:06 -0500729 const char* paren = methodParser.strnchr('(', methodParser.fEnd);
730 size_t indent;
731 if (paren) {
732 indent = (size_t) (paren - lastStart) + 1;
733 } else {
734 indent = (size_t) (lastEnd - lastStart);
735 }
736 // trim indent so longest line doesn't exceed box width
Cary Clark186d08f2018-04-03 08:43:27 -0400737 TextParserSave savePlace(&methodParser);
Cary Clarka560c472017-11-27 10:44:06 -0500738 const char* saveStart = lastStart;
739 ptrdiff_t maxLine = 0;
740 do {
741 const char* nextStart = lastEnd;
742 const char* delimiter = methodParser.anyOf(",)");
743 const char* nextEnd = delimiter ? delimiter : methodParser.fEnd;
744 if (delimiter) {
745 while (nextStart < nextEnd && ' ' >= nextStart[0]) {
746 ++nextStart;
747 }
748 }
749 while (nextEnd > nextStart && ' ' >= nextEnd[-1]) {
750 --nextEnd;
751 }
752 if (delimiter) {
753 nextEnd += 1;
754 delimiter += 1;
755 }
756 if (lastEnd > lastStart) {
757 maxLine = SkTMax(maxLine, lastEnd - lastStart);
758 }
759 if (delimiter) {
760 methodParser.skipTo(delimiter);
761 }
762 lastStart = nextStart;
763 lastEnd = nextEnd;
764 } while (lastStart < lastEnd);
765 savePlace.restore();
766 lastStart = saveStart;
767 lastEnd = methodParser.fChar;
768 indent = SkTMin(indent, (size_t) (limit - maxLine));
Cary Clark682c58d2018-05-16 07:07:07 -0400769 // write string with trimmmed indent
Cary Clarka560c472017-11-27 10:44:06 -0500770 string methodStr;
771 int written = 0;
772 do {
773 const char* nextStart = lastEnd;
774 SkASSERT(written < limit);
775 const char* delimiter = methodParser.anyOf(",)");
776 const char* nextEnd = delimiter ? delimiter : methodParser.fEnd;
777 if (delimiter) {
778 while (nextStart < nextEnd && ' ' >= nextStart[0]) {
779 ++nextStart;
780 }
781 }
782 while (nextEnd > nextStart && ' ' >= nextEnd[-1]) {
783 --nextEnd;
784 }
785 if (delimiter) {
786 nextEnd += 1;
787 delimiter += 1;
788 }
789 if (lastEnd > lastStart) {
790 if (lastStart[0] != ' ') {
791 space_pad(&methodStr);
792 }
793 methodStr += string(lastStart, (size_t) (lastEnd - lastStart));
794 written += (size_t) (lastEnd - lastStart);
795 }
796 if (delimiter) {
797 if (nextEnd - nextStart >= (ptrdiff_t) (limit - written)) {
798 written = indent;
Cary Clark78de7512018-02-07 07:27:09 -0500799 if (Format::kIncludeReturn == format) {
800 methodStr += '\n';
801 methodStr += string(indent, ' ');
802 }
Cary Clarka560c472017-11-27 10:44:06 -0500803 }
804 methodParser.skipTo(delimiter);
805 }
806 lastStart = nextStart;
807 lastEnd = nextEnd;
808 } while (lastStart < lastEnd);
809 return methodStr;
810}
811
812string Definition::fiddleName() const {
813 string result;
814 size_t start = 0;
815 string parent;
816 const Definition* parentDef = this;
817 while ((parentDef = parentDef->fParent)) {
818 if (MarkType::kClass == parentDef->fMarkType || MarkType::kStruct == parentDef->fMarkType) {
819 parent = parentDef->fFiddle;
820 break;
821 }
822 }
823 if (parent.length() && 0 == fFiddle.compare(0, parent.length(), parent)) {
824 start = parent.length();
825 while (start < fFiddle.length() && '_' == fFiddle[start]) {
826 ++start;
827 }
828 }
829 size_t end = fFiddle.find_first_of('(', start);
830 return fFiddle.substr(start, end - start);
831}
832
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400833string Definition::fileName() const {
834 size_t nameStart = fFileName.rfind(SkOSPath::SEPARATOR);
835 if (SkOSPath::SEPARATOR != '/') {
836 size_t altNameStart = fFileName.rfind('/');
837 nameStart = string::npos == nameStart ? altNameStart :
838 string::npos != altNameStart && altNameStart > nameStart ? altNameStart : nameStart;
839 }
840 SkASSERT(string::npos != nameStart);
841 string baseFile = fFileName.substr(nameStart + 1);
842 return baseFile;
843}
844
Cary Clark4855f782018-02-06 09:41:53 -0500845const Definition* Definition::findClone(string match) const {
846 for (auto child : fChildren) {
847 if (!child->fClone) {
848 continue;
849 }
850 if (match == child->fName) {
851 return child;
852 }
853 auto inner = child->findClone(match);
854 if (inner) {
855 return inner;
856 }
857 }
858 return nullptr;
859}
860
Cary Clarka560c472017-11-27 10:44:06 -0500861const Definition* Definition::hasChild(MarkType markType) const {
862 for (auto iter : fChildren) {
863 if (markType == iter->fMarkType) {
864 return iter;
865 }
866 }
867 return nullptr;
868}
869
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400870const Definition* Definition::hasParam(string ref) const {
Cary Clarka560c472017-11-27 10:44:06 -0500871 SkASSERT(MarkType::kMethod == fMarkType);
872 for (auto iter : fChildren) {
873 if (MarkType::kParam != iter->fMarkType) {
874 continue;
875 }
876 if (iter->fName == ref) {
877 return &*iter;
878 }
879
880 }
881 return nullptr;
882}
883
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400884bool Definition::hasMatch(string name) const {
Cary Clarkac47b882018-01-11 10:35:44 -0500885 for (auto child : fChildren) {
886 if (name == child->fName) {
887 return true;
888 }
889 if (child->hasMatch(name)) {
890 return true;
891 }
892 }
893 return false;
894}
895
Cary Clark682c58d2018-05-16 07:07:07 -0400896string Definition::incompleteMessage(DetailsType detailsType) const {
Cary Clark137b8742018-05-30 09:21:49 -0400897 SkASSERT(!IncompleteAllowed(fMarkType));
898 auto iter = std::find_if(fChildren.begin(), fChildren.end(),
899 [](const Definition* test) { return IncompleteAllowed(test->fMarkType); });
900 SkASSERT(fChildren.end() != iter);
901 SkASSERT(Details::kNone == (*iter)->fDetails);
902 string message = MarkType::kExperimental == (*iter)->fMarkType ?
Cary Clark682c58d2018-05-16 07:07:07 -0400903 "Experimental." : "Deprecated.";
Cary Clark137b8742018-05-30 09:21:49 -0400904 if (Details::kDoNotUse_Experiment == fDetails) {
Cary Clark682c58d2018-05-16 07:07:07 -0400905 message += " Do not use.";
Cary Clark137b8742018-05-30 09:21:49 -0400906 } else if (Details::kNotReady_Experiment == fDetails) {
Cary Clark682c58d2018-05-16 07:07:07 -0400907 message += " Not ready for general use.";
Cary Clark137b8742018-05-30 09:21:49 -0400908 } else if (Details::kSoonToBe_Deprecated == fDetails) {
909 message = "To be deprecated soon.";
910 } else if (Details::kTestingOnly_Experiment == fDetails) {
Cary Clark682c58d2018-05-16 07:07:07 -0400911 message += " For testing only.";
912 }
913 if (DetailsType::kPhrase == detailsType) {
914 message = message.substr(0, message.length() - 1); // remove trailing period
915 std::replace(message.begin(), message.end(), '.', ':');
916 std::transform(message.begin(), message.end(), message.begin(), ::tolower);
917 }
918 return message;
919}
920
Cary Clarkab2621d2018-01-30 10:08:57 -0500921bool Definition::isStructOrClass() const {
922 if (MarkType::kStruct != fMarkType && MarkType::kClass != fMarkType) {
923 return false;
924 }
925 if (string::npos != fFileName.find("undocumented.bmh")) {
926 return false;
927 }
928 return true;
929}
930
Cary Clark2d4bf5f2018-04-16 08:37:38 -0400931bool Definition::methodHasReturn(string name, TextParser* methodParser) const {
Cary Clarka560c472017-11-27 10:44:06 -0500932 if (methodParser->skipExact("static")) {
933 methodParser->skipWhiteSpace();
934 }
Cary Clark80247e52018-07-11 16:18:41 -0400935 if (methodParser->skipExact("virtual")) {
936 methodParser->skipWhiteSpace();
937 }
Cary Clarka560c472017-11-27 10:44:06 -0500938 const char* lastStart = methodParser->fChar;
939 const char* nameInParser = methodParser->strnstr(name.c_str(), methodParser->fEnd);
940 methodParser->skipTo(nameInParser);
941 const char* lastEnd = methodParser->fChar;
942 const char* returnEnd = lastEnd;
943 while (returnEnd > lastStart && ' ' == returnEnd[-1]) {
944 --returnEnd;
945 }
946 bool expectReturn = 4 != returnEnd - lastStart || strncmp("void", lastStart, 4);
947 if (MethodType::kNone != fMethodType && MethodType::kOperator != fMethodType && !expectReturn) {
948 return methodParser->reportError<bool>("unexpected void");
949 }
950 switch (fMethodType) {
951 case MethodType::kNone:
952 case MethodType::kOperator:
953 // either is fine
954 break;
955 case MethodType::kConstructor:
956 expectReturn = true;
957 break;
958 case MethodType::kDestructor:
959 expectReturn = false;
960 break;
961 }
962 return expectReturn;
963}
964
965string Definition::methodName() const {
966 string result;
967 size_t start = 0;
968 string parent;
969 const Definition* parentDef = this;
970 while ((parentDef = parentDef->fParent)) {
971 if (MarkType::kClass == parentDef->fMarkType || MarkType::kStruct == parentDef->fMarkType) {
972 parent = parentDef->fName;
973 break;
974 }
975 }
976 if (parent.length() && 0 == fName.compare(0, parent.length(), parent)) {
977 start = parent.length();
978 while (start < fName.length() && ':' == fName[start]) {
979 ++start;
980 }
981 }
982 if (fClone) {
983 int lastUnder = fName.rfind('_');
984 return fName.substr(start, (size_t) (lastUnder - start));
985 }
986 size_t end = fName.find_first_of('(', start);
987 if (string::npos == end) {
988 return fName.substr(start);
989 }
990 return fName.substr(start, end - start);
991}
992
993bool Definition::nextMethodParam(TextParser* methodParser, const char** nextEndPtr,
994 string* paramName) const {
995 int parenCount = 0;
Cary Clark186d08f2018-04-03 08:43:27 -0400996 TextParserSave saveState(methodParser);
Cary Clarka560c472017-11-27 10:44:06 -0500997 while (true) {
998 if (methodParser->eof()) {
999 return methodParser->reportError<bool>("#Method function missing close paren");
1000 }
1001 char ch = methodParser->peek();
1002 if ('(' == ch) {
1003 ++parenCount;
1004 }
1005 if (parenCount == 0 && (')' == ch || ',' == ch)) {
1006 *nextEndPtr = methodParser->fChar;
1007 break;
1008 }
1009 if (')' == ch) {
1010 if (0 > --parenCount) {
1011 return this->reportError<bool>("mismatched parentheses");
1012 }
1013 }
1014 methodParser->next();
1015 }
1016 saveState.restore();
1017 const char* nextEnd = *nextEndPtr;
1018 const char* paramEnd = nextEnd;
1019 const char* assign = methodParser->strnstr(" = ", paramEnd);
1020 if (assign) {
1021 paramEnd = assign;
1022 }
1023 const char* closeBracket = methodParser->strnstr("]", paramEnd);
1024 if (closeBracket) {
1025 const char* openBracket = methodParser->strnstr("[", paramEnd);
1026 if (openBracket && openBracket < closeBracket) {
1027 while (openBracket < --closeBracket && isdigit(closeBracket[0]))
1028 ;
1029 if (openBracket == closeBracket) {
1030 paramEnd = openBracket;
1031 }
1032 }
1033 }
1034 const char* function = methodParser->strnstr(")(", paramEnd);
1035 if (function) {
1036 paramEnd = function;
1037 }
1038 while (paramEnd > methodParser->fChar && ' ' == paramEnd[-1]) {
1039 --paramEnd;
1040 }
1041 const char* paramStart = paramEnd;
1042 while (paramStart > methodParser->fChar && isalnum(paramStart[-1])) {
1043 --paramStart;
1044 }
1045 if (paramStart > methodParser->fChar && paramStart >= paramEnd) {
1046 return methodParser->reportError<bool>("#Method missing param name");
1047 }
1048 *paramName = string(paramStart, paramEnd - paramStart);
1049 if (!paramName->length()) {
1050 if (')' != nextEnd[0]) {
1051 return methodParser->reportError<bool>("#Method malformed param");
1052 }
1053 return false;
1054 }
1055 return true;
1056}
1057
1058string Definition::NormalizedName(string name) {
1059 string normalizedName = name;
1060 std::replace(normalizedName.begin(), normalizedName.end(), '-', '_');
1061 do {
1062 size_t doubleColon = normalizedName.find("::", 0);
1063 if (string::npos == doubleColon) {
1064 break;
1065 }
1066 normalizedName = normalizedName.substr(0, doubleColon)
1067 + '_' + normalizedName.substr(doubleColon + 2);
1068 } while (true);
1069 return normalizedName;
1070}
1071
Cary Clark2d4bf5f2018-04-16 08:37:38 -04001072static string unpreformat(string orig) {
Cary Clark78de7512018-02-07 07:27:09 -05001073 string result;
1074 int amp = 0;
1075 for (auto c : orig) {
1076 switch (amp) {
1077 case 0:
1078 if ('&' == c) {
1079 amp = 1;
1080 } else {
1081 amp = 0;
1082 result += c;
1083 }
1084 break;
1085 case 1:
1086 if ('l' == c) {
1087 amp = 2;
1088 } else if ('g' == c) {
1089 amp = 3;
1090 } else {
1091 amp = 0;
1092 result += "&";
1093 result += c;
1094 }
1095 break;
1096 case 2:
1097 if ('t' == c) {
1098 amp = 4;
1099 } else {
1100 amp = 0;
1101 result += "&l";
1102 result += c;
1103 }
1104 break;
1105 case 3:
1106 if ('t' == c) {
1107 amp = 5;
1108 } else {
1109 amp = 0;
1110 result += "&g";
1111 result += c;
1112 }
1113 break;
1114 case 4:
1115 if (';' == c) {
1116 result += '<';
1117 } else {
1118 result += "&lt";
1119 result += c;
1120 }
1121 amp = 0;
1122 break;
1123 case 5:
1124 if (';' == c) {
1125 result += '>';
1126 } else {
1127 result += "&gt";
1128 result += c;
1129 }
1130 amp = 0;
1131 break;
1132 }
1133 }
1134 return result;
1135}
1136
Cary Clark2d4bf5f2018-04-16 08:37:38 -04001137bool Definition::paramsMatch(string matchFormatted, string name) const {
Cary Clark78de7512018-02-07 07:27:09 -05001138 string match = unpreformat(matchFormatted);
Cary Clarka560c472017-11-27 10:44:06 -05001139 TextParser def(fFileName, fStart, fContentStart, fLineCount);
1140 const char* dName = def.strnstr(name.c_str(), fContentStart);
1141 if (!dName) {
1142 return false;
1143 }
1144 def.skipTo(dName);
1145 TextParser m(fFileName, &match.front(), &match.back() + 1, fLineCount);
1146 const char* mName = m.strnstr(name.c_str(), m.fEnd);
1147 if (!mName) {
1148 return false;
1149 }
1150 m.skipTo(mName);
1151 while (!def.eof() && ')' != def.peek() && !m.eof() && ')' != m.peek()) {
1152 const char* ds = def.fChar;
1153 const char* ms = m.fChar;
1154 const char* de = def.anyOf(") \n");
1155 const char* me = m.anyOf(") \n");
1156 def.skipTo(de);
1157 m.skipTo(me);
1158 if (def.fChar - ds != m.fChar - ms) {
1159 return false;
1160 }
1161 if (strncmp(ds, ms, (int) (def.fChar - ds))) {
1162 return false;
1163 }
1164 def.skipWhiteSpace();
1165 m.skipWhiteSpace();
1166 }
1167 return !def.eof() && ')' == def.peek() && !m.eof() && ')' == m.peek();
1168}
1169
1170void RootDefinition::clearVisited() {
1171 fVisited = false;
1172 for (auto& leaf : fLeaves) {
1173 leaf.second.fVisited = false;
1174 }
1175 for (auto& branch : fBranches) {
1176 branch.second->clearVisited();
1177 }
1178}
1179
Cary Clarkf5404bb2018-01-05 12:10:09 -05001180bool RootDefinition::dumpUnVisited() {
1181 bool success = true;
Cary Clarka560c472017-11-27 10:44:06 -05001182 for (auto& leaf : fLeaves) {
1183 if (!leaf.second.fVisited) {
Cary Clarka560c472017-11-27 10:44:06 -05001184 // FIXME: bugs requiring long tail fixes, suppressed here:
1185 // SkBitmap::validate() is wrapped in SkDEBUGCODE in .h and not parsed
1186 if ("SkBitmap::validate()" == leaf.first) {
1187 continue;
1188 }
Cary Clarka560c472017-11-27 10:44:06 -05001189 // SkPath::pathRefIsValid in #ifdef ; prefer to remove chrome dependency to fix
1190 if ("SkPath::pathRefIsValid" == leaf.first) {
1191 continue;
1192 }
1193 // FIXME: end of long tail bugs
1194 SkDebugf("defined in bmh but missing in include: %s\n", leaf.first.c_str());
Cary Clarkf5404bb2018-01-05 12:10:09 -05001195 success = false;
Cary Clarka560c472017-11-27 10:44:06 -05001196 }
1197 }
1198 for (auto& branch : fBranches) {
Cary Clarkf5404bb2018-01-05 12:10:09 -05001199 success &= branch.second->dumpUnVisited();
Cary Clarka560c472017-11-27 10:44:06 -05001200 }
Cary Clarkf5404bb2018-01-05 12:10:09 -05001201 return success;
Cary Clarka560c472017-11-27 10:44:06 -05001202}
1203
Cary Clark682c58d2018-05-16 07:07:07 -04001204Definition* RootDefinition::find(string ref, AllowParens allowParens) {
Cary Clarka560c472017-11-27 10:44:06 -05001205 const auto leafIter = fLeaves.find(ref);
1206 if (leafIter != fLeaves.end()) {
1207 return &leafIter->second;
1208 }
1209 if (AllowParens::kYes == allowParens && string::npos == ref.find("()")) {
1210 string withParens = ref + "()";
1211 const auto parensIter = fLeaves.find(withParens);
1212 if (parensIter != fLeaves.end()) {
1213 return &parensIter->second;
1214 }
1215 }
1216 const auto branchIter = fBranches.find(ref);
1217 if (branchIter != fBranches.end()) {
Cary Clark682c58d2018-05-16 07:07:07 -04001218 RootDefinition* rootDef = branchIter->second;
Cary Clarka560c472017-11-27 10:44:06 -05001219 return rootDef;
1220 }
Cary Clark682c58d2018-05-16 07:07:07 -04001221 Definition* result = nullptr;
Cary Clarka560c472017-11-27 10:44:06 -05001222 for (const auto& branch : fBranches) {
Cary Clark682c58d2018-05-16 07:07:07 -04001223 RootDefinition* rootDef = branch.second;
Cary Clarka560c472017-11-27 10:44:06 -05001224 result = rootDef->find(ref, allowParens);
1225 if (result) {
1226 break;
1227 }
1228 }
1229 return result;
1230}