1 /*
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"
9 #include "SkOSPath.h"
10
count_indent(const string & text,size_t test,size_t end)11 static size_t count_indent(const string& text, size_t test, size_t end) {
12 size_t result = test;
13 while (test < end) {
14 if (' ' != text[test]) {
15 break;
16 }
17 ++test;
18 }
19 return test - result;
20 }
21
add_code(const string & text,int pos,int end,size_t outIndent,size_t textIndent,string & example)22 static void add_code(const string& text, int pos, int end,
23 size_t outIndent, size_t textIndent, string& example) {
24 do {
25 // fix this to move whole paragraph in, out, but preserve doc indent
26 int nextIndent = count_indent(text, pos, end);
27 size_t len = text.find('\n', pos);
28 if (string::npos == len) {
29 len = end;
30 }
31 if ((size_t) (pos + nextIndent) < len) {
32 size_t indent = outIndent + nextIndent;
33 SkASSERT(indent >= textIndent);
34 indent -= textIndent;
35 for (size_t index = 0; index < indent; ++index) {
36 example += ' ';
37 }
38 pos += nextIndent;
39 while ((size_t) pos < len) {
40 example += '"' == text[pos] ? "\\\"" :
41 '\\' == text[pos] ? "\\\\" :
42 text.substr(pos, 1);
43 ++pos;
44 }
45 example += "\\n";
46 } else {
47 pos += nextIndent;
48 }
49 if ('\n' == text[pos]) {
50 ++pos;
51 }
52 } while (pos < end);
53 }
54
55 #ifdef CONST
56 #undef CONST
57 #endif
58
59 #ifdef FRIEND
60 #undef FRIEND
61 #endif
62
63 #ifdef BLANK
64 #undef BLANK
65 #endif
66
67 #ifdef ANY
68 #undef ANY
69 #endif
70
71 #ifdef DEFOP
72 #undef DEFOP
73 #endif
74
75 #define CONST 1
76 #define STATIC 2
77 #define BLANK 0
78 #define ANY -1
79 #define DEFOP Definition::Operator
80
81 enum class OpType : int8_t {
82 kNone,
83 kVoid,
84 kBool,
85 kChar,
86 kFloat,
87 kInt,
88 kScalar,
89 kSizeT,
90 kThis,
91 kAny,
92 };
93
94 enum class OpMod : int8_t {
95 kNone,
96 kArray,
97 kMove,
98 kPointer,
99 kReference,
100 kAny,
101 };
102
103 const struct OperatorParser {
104 DEFOP fOperator;
105 const char* fSymbol;
106 const char* fName;
107 int8_t fFriend;
108 OpType fReturnType;
109 OpMod fReturnMod;
110 int8_t fConstMethod;
111 struct Param {
112 int8_t fConst;
113 OpType fType;
114 OpMod fMod;
115 } fParams[2];
116 } opData[] = {
117 { DEFOP::kUnknown, "??", "???", BLANK, OpType::kNone, OpMod::kNone, BLANK,
118 { } },
119 { DEFOP::kAdd, "+", "add", BLANK, OpType::kThis, OpMod::kNone, BLANK,
120 {{ CONST, OpType::kThis, OpMod::kReference, },
121 { CONST, OpType::kThis, OpMod::kReference, }}},
122 { DEFOP::kAddTo, "+=", "addto", BLANK, OpType::kVoid, OpMod::kNone, BLANK,
123 {{ CONST, OpType::kThis, OpMod::kReference, }}},
124 { DEFOP::kAddTo, "+=", "addto1", BLANK, OpType::kThis, OpMod::kReference, BLANK,
125 {{ CONST, OpType::kThis, OpMod::kReference, }}},
126 { DEFOP::kAddTo, "+=", "addto2", BLANK, OpType::kThis, OpMod::kReference, BLANK,
127 {{ CONST, OpType::kChar, OpMod::kArray, }}},
128 { DEFOP::kAddTo, "+=", "addto3", BLANK, OpType::kThis, OpMod::kReference, BLANK,
129 {{ CONST, OpType::kChar, OpMod::kNone, }}},
130 { DEFOP::kArray, "[]", "array", BLANK, OpType::kScalar, OpMod::kNone, CONST,
131 {{ BLANK, OpType::kInt, OpMod::kNone, }}},
132 { DEFOP::kArray, "[]", "array1", BLANK, OpType::kScalar, OpMod::kReference, BLANK,
133 {{ BLANK, OpType::kInt, OpMod::kNone, }}},
134 { DEFOP::kArray, "[]", "array2", BLANK, OpType::kChar, OpMod::kNone, CONST,
135 {{ BLANK, OpType::kSizeT, OpMod::kNone, }}},
136 { DEFOP::kArray, "[]", "array3", BLANK, OpType::kChar, OpMod::kReference, BLANK,
137 {{ BLANK, OpType::kSizeT, OpMod::kNone, }}},
138 { DEFOP::kCast, "()", "cast", BLANK, OpType::kAny, OpMod::kAny, ANY,
139 {{ ANY, OpType::kAny, OpMod::kAny, }}},
140 { DEFOP::kCopy, "=", "copy", BLANK, OpType::kThis, OpMod::kReference, BLANK,
141 {{ CONST, OpType::kThis, OpMod::kReference, }}},
142 { DEFOP::kCopy, "=", "copy1", BLANK, OpType::kThis, OpMod::kReference, BLANK,
143 {{ CONST, OpType::kChar, OpMod::kArray, }}},
144 { DEFOP::kDelete, "delete", "delete", BLANK, OpType::kVoid, OpMod::kNone, BLANK,
145 {{ BLANK, OpType::kVoid, OpMod::kPointer, }}},
146 { DEFOP::kDereference, "->", "deref", ANY, OpType::kThis, OpMod::kPointer, CONST,
147 { } },
148 { DEFOP::kDereference, "*", "deref", BLANK, OpType::kThis, OpMod::kReference, CONST,
149 { } },
150 { DEFOP::kEqual, "==", "equal", BLANK, OpType::kBool, OpMod::kNone, BLANK,
151 {{ CONST, OpType::kThis, OpMod::kReference, },
152 { CONST, OpType::kThis, OpMod::kReference, }}},
153 { DEFOP::kEqual, "==", "equal1", BLANK, OpType::kBool, OpMod::kNone, CONST,
154 {{ CONST, OpType::kThis, OpMod::kReference, }}},
155 { DEFOP::kEqual, "==", "equal2", ANY, OpType::kBool, OpMod::kNone, BLANK,
156 {{ CONST, OpType::kThis, OpMod::kReference, },
157 { CONST, OpType::kThis, OpMod::kReference, }}},
158 { DEFOP::kMinus, "-", "minus", BLANK, OpType::kThis, OpMod::kNone, CONST,
159 { } },
160 { DEFOP::kMove, "=", "move", BLANK, OpType::kThis, OpMod::kReference, BLANK,
161 {{ BLANK, OpType::kThis, OpMod::kMove, }}},
162 { DEFOP::kMultiply, "*", "multiply", BLANK, OpType::kThis, OpMod::kNone, CONST,
163 {{ BLANK, OpType::kScalar, OpMod::kNone, }}},
164 { DEFOP::kMultiply, "*", "multiply1", BLANK, OpType::kThis, OpMod::kNone, BLANK,
165 {{ CONST, OpType::kThis, OpMod::kReference, },
166 { CONST, OpType::kThis, OpMod::kReference, }}},
167 { DEFOP::kMultiplyBy, "*=", "multiplyby", BLANK, OpType::kThis, OpMod::kReference, BLANK,
168 {{ BLANK, OpType::kScalar, OpMod::kNone, }}},
169 { DEFOP::kNew, "new", "new", BLANK, OpType::kVoid, OpMod::kPointer, BLANK,
170 {{ BLANK, OpType::kSizeT, OpMod::kNone, }}},
171 { DEFOP::kNotEqual, "!=", "notequal", BLANK, OpType::kBool, OpMod::kNone, BLANK,
172 {{ CONST, OpType::kThis, OpMod::kReference, },
173 { CONST, OpType::kThis, OpMod::kReference, }}},
174 { DEFOP::kNotEqual, "!=", "notequal1", BLANK, OpType::kBool, OpMod::kNone, CONST,
175 {{ CONST, OpType::kThis, OpMod::kReference, }}},
176 { DEFOP::kNotEqual, "!=", "notequal2", ANY, OpType::kBool, OpMod::kNone, BLANK,
177 {{ CONST, OpType::kThis, OpMod::kReference, },
178 { CONST, OpType::kThis, OpMod::kReference, }}},
179 { DEFOP::kSubtract, "-", "subtract", BLANK, OpType::kThis, OpMod::kNone, BLANK,
180 {{ CONST, OpType::kThis, OpMod::kReference, },
181 { CONST, OpType::kThis, OpMod::kReference, }}},
182 { DEFOP::kSubtractFrom, "-=", "subtractfrom", BLANK, OpType::kVoid, OpMod::kNone, BLANK,
183 {{ CONST, OpType::kThis, OpMod::kReference, }}},
184 };
185
lookup_type(const string & typeWord,const string & name)186 OpType lookup_type(const string& typeWord, const string& name) {
187 if (typeWord == name || (typeWord == "SkIVector" && name == "SkIPoint")
188 || (typeWord == "SkVector" && name == "SkPoint")) {
189 return OpType::kThis;
190 }
191 const char* keyWords[] = { "void", "bool", "char", "float", "int", "SkScalar", "size_t" };
192 for (unsigned i = 0; i < SK_ARRAY_COUNT(keyWords); ++i) {
193 if (typeWord == keyWords[i]) {
194 return (OpType) (i + 1);
195 }
196 }
197 return OpType::kNone;
198 }
199
lookup_mod(TextParser & iParser)200 OpMod lookup_mod(TextParser& iParser) {
201 OpMod mod = OpMod::kNone;
202 if ('&' == iParser.peek()) {
203 mod = OpMod::kReference;
204 iParser.next();
205 if ('&' == iParser.peek()) {
206 mod = OpMod::kMove;
207 iParser.next();
208 }
209 } else if ('*' == iParser.peek()) {
210 mod = OpMod::kPointer;
211 iParser.next();
212 }
213 iParser.skipWhiteSpace();
214 return mod;
215 }
216
parseOperator(size_t doubleColons,string & result)217 bool Definition::parseOperator(size_t doubleColons, string& result) {
218 const char operatorStr[] = "operator";
219 size_t opPos = fName.find(operatorStr, doubleColons);
220 if (string::npos == opPos) {
221 return false;
222 }
223 string className(fName, 0, doubleColons - 2);
224 TextParser iParser(fFileName, fStart, fContentStart, fLineCount);
225 SkAssertResult(iParser.skipWord("#Method"));
226 iParser.skipExact("SK_API");
227 iParser.skipWhiteSpace();
228 bool isStatic = iParser.skipExact("static");
229 iParser.skipWhiteSpace();
230 iParser.skipExact("SK_API");
231 iParser.skipWhiteSpace();
232 bool returnsConst = iParser.skipExact("const");
233 if (returnsConst) {
234 SkASSERT(0); // incomplete
235 }
236 SkASSERT(isStatic == false || returnsConst == false);
237 iParser.skipWhiteSpace();
238 const char* returnTypeStart = iParser.fChar;
239 iParser.skipToNonAlphaNum();
240 SkASSERT(iParser.fChar > returnTypeStart);
241 string returnType(returnTypeStart, iParser.fChar - returnTypeStart);
242 OpType returnOpType = lookup_type(returnType, className);
243 iParser.skipWhiteSpace();
244 OpMod returnMod = lookup_mod(iParser);
245 SkAssertResult(iParser.skipExact("operator"));
246 iParser.skipWhiteSpace();
247 fMethodType = Definition::MethodType::kOperator;
248 TextParser::Save save(&iParser);
249 for (auto parser : opData) {
250 save.restore();
251 if (!iParser.skipExact(parser.fSymbol)) {
252 continue;
253 }
254 iParser.skipWhiteSpace();
255 if ('(' != iParser.peek()) {
256 continue;
257 }
258 if (parser.fFriend != ANY && (parser.fFriend == STATIC) != isStatic) {
259 continue;
260 }
261 if (parser.fReturnType != OpType::kAny && parser.fReturnType != returnOpType) {
262 continue;
263 }
264 if (parser.fReturnMod != OpMod::kAny && parser.fReturnMod != returnMod) {
265 continue;
266 }
267 iParser.next(); // skip '('
268 iParser.skipWhiteSpace();
269 int parserCount = (parser.fParams[0].fType != OpType::kNone) +
270 (parser.fParams[1].fType != OpType::kNone);
271 bool countsMatch = true;
272 for (int pIndex = 0; pIndex < 2; ++pIndex) {
273 if (')' == iParser.peek()) {
274 countsMatch = pIndex == parserCount;
275 break;
276 }
277 if (',' == iParser.peek()) {
278 iParser.next();
279 iParser.skipWhiteSpace();
280 }
281 bool paramConst = iParser.skipExact("const");
282 if (parser.fParams[pIndex].fConst != ANY &&
283 paramConst != (parser.fParams[pIndex].fConst == CONST)) {
284 countsMatch = false;
285 break;
286 }
287 iParser.skipWhiteSpace();
288 const char* paramStart = iParser.fChar;
289 iParser.skipToNonAlphaNum();
290 SkASSERT(iParser.fChar > paramStart);
291 string paramType(paramStart, iParser.fChar - paramStart);
292 OpType paramOpType = lookup_type(paramType, className);
293 if (parser.fParams[pIndex].fType != OpType::kAny &&
294 parser.fParams[pIndex].fType != paramOpType) {
295 countsMatch = false;
296 break;
297 }
298 iParser.skipWhiteSpace();
299 OpMod paramMod = lookup_mod(iParser);
300 if (parser.fParams[pIndex].fMod != OpMod::kAny &&
301 parser.fParams[pIndex].fMod != paramMod) {
302 countsMatch = false;
303 break;
304 }
305 iParser.skipToNonAlphaNum();
306 if ('[' == iParser.peek()) {
307 paramMod = OpMod::kArray;
308 SkAssertResult(iParser.skipExact("[]"));
309 }
310 iParser.skipWhiteSpace();
311 }
312 if (!countsMatch) {
313 continue;
314 }
315 if (')' != iParser.peek()) {
316 continue;
317 }
318 iParser.next();
319 bool constMethod = iParser.skipExact("_const");
320 if (parser.fConstMethod != ANY && (parser.fConstMethod == CONST) != constMethod) {
321 continue;
322 }
323 result += parser.fName;
324 result += "_operator";
325 fOperator = parser.fOperator;
326 fOperatorConst = constMethod;
327 return true;
328 }
329 SkASSERT(0); // incomplete
330 return false;
331 #if 0
332 if ('!' == fName[opPos]) {
333 SkASSERT('=' == fName[opPos + 1]);
334 result += "not_equal_operator";
335 } else if ('=' == fName[opPos]) {
336 if ('(' == fName[opPos + 1]) {
337 result += isMove ? "move_" : "copy_";
338 result += "assignment_operator";
339 } else {
340 SkASSERT('=' == fName[opPos + 1]);
341 result += "equal_operator";
342 }
343 } else if ('[' == fName[opPos]) {
344 result += "subscript_operator";
345 const char* end = fContentStart;
346 while (end > fStart && ' ' >= end[-1]) {
347 --end;
348 }
349 string constCheck(fStart, end - fStart);
350 size_t constPos = constCheck.rfind("const");
351 if (constCheck.length() == constPos + 5) {
352 result += "_const";
353 }
354 } else if ('*' == fName[opPos]) {
355 result += "multiply_operator";
356 } else if ('-' == fName[opPos]) {
357 result += "subtract_operator";
358 } else if ('+' == fName[opPos]) {
359 result += "add_operator";
360 } else {
361 SkASSERT(0); // todo: incomplete
362 }
363 #endif
364 return true;
365 }
366
367 #undef CONST
368 #undef FRIEND
369 #undef BLANK
370 #undef DEFOP
371
boilerplateIfDef(Definition * parent)372 bool Definition::boilerplateIfDef(Definition* parent) {
373 const Definition& label = fTokens.front();
374 if (Type::kWord != label.fType) {
375 return false;
376 }
377 fName = string(label.fContentStart, label.fContentEnd - label.fContentStart);
378 return true;
379 }
380
381 // todo: this is matching #ifndef SkXXX_DEFINED for no particular reason
382 // it doesn't do anything useful with arbitrary input, e.g. #ifdef SK_SUPPORT_LEGACY_CANVAS_HELPERS
383 // also doesn't know what to do with SK_REQUIRE_LOCAL_VAR()
boilerplateDef(Definition * parent)384 bool Definition::boilerplateDef(Definition* parent) {
385 if (!this->boilerplateIfDef(parent)) {
386 return false;
387 }
388 const char* s = fName.c_str();
389 const char* e = strchr(s, '_');
390 return true; // fixme: if this is trying to do something useful with define, do it here
391 if (!e) {
392 return false;
393 }
394 string prefix(s, e - s);
395 const char* inName = strstr(parent->fName.c_str(), prefix.c_str());
396 if (!inName) {
397 return false;
398 }
399 if ('/' != inName[-1] && '\\' != inName[-1]) {
400 return false;
401 }
402 if (strcmp(inName + prefix.size(), ".h")) {
403 return false;
404 }
405 return true;
406 }
407
408 // fixme: this will need to be more complicated to handle all of Skia
409 // for now, just handle paint -- maybe fiddle will loosen naming restrictions
setCanonicalFiddle()410 void Definition::setCanonicalFiddle() {
411 fMethodType = Definition::MethodType::kNone;
412 size_t doubleColons = fName.find("::", 0);
413 SkASSERT(string::npos != doubleColons);
414 string base = fName.substr(0, doubleColons);
415 string result = base + "_";
416 doubleColons += 2;
417 if (string::npos != fName.find('~', doubleColons)) {
418 fMethodType = Definition::MethodType::kDestructor;
419 result += "destructor";
420 } else if (!this->parseOperator(doubleColons, result)) {
421 bool isMove = string::npos != fName.find("&&", doubleColons);
422 size_t parens = fName.find("()", doubleColons);
423 if (string::npos != parens) {
424 string methodName = fName.substr(doubleColons, parens - doubleColons);
425 do {
426 size_t nextDouble = methodName.find("::");
427 if (string::npos == nextDouble) {
428 break;
429 }
430 base = methodName.substr(0, nextDouble);
431 result += base + '_';
432 methodName = methodName.substr(nextDouble + 2);
433 doubleColons += nextDouble + 2;
434 } while (true);
435 if (base == methodName) {
436 fMethodType = Definition::MethodType::kConstructor;
437 result += "empty_constructor";
438 } else {
439 result += fName.substr(doubleColons, fName.length() - doubleColons - 2);
440 }
441 } else {
442 size_t openParen = fName.find('(', doubleColons);
443 if (string::npos == openParen) {
444 result += fName.substr(doubleColons);
445 } else {
446 size_t comma = fName.find(',', doubleColons);
447 if (string::npos == comma) {
448 result += isMove ? "move_" : "copy_";
449 }
450 fMethodType = Definition::MethodType::kConstructor;
451 // name them by their param types,
452 // e.g. SkCanvas__int_int_const_SkSurfaceProps_star
453 // TODO: move forward until parens are balanced and terminator =,)
454 TextParser params("", &fName[openParen] + 1, &*fName.end(), 0);
455 bool underline = false;
456 while (!params.eof()) {
457 // SkDEBUGCODE(const char* end = params.anyOf("(),=")); // unused for now
458 // SkASSERT(end[0] != '('); // fixme: put off handling nested parentheseses
459 if (params.startsWith("const") || params.startsWith("int")
460 || params.startsWith("Sk")) {
461 const char* wordStart = params.fChar;
462 params.skipToNonAlphaNum();
463 if (underline) {
464 result += '_';
465 } else {
466 underline = true;
467 }
468 result += string(wordStart, params.fChar - wordStart);
469 } else {
470 params.skipToNonAlphaNum();
471 }
472 if (!params.eof() && '*' == params.peek()) {
473 if (underline) {
474 result += '_';
475 } else {
476 underline = true;
477 }
478 result += "star";
479 params.next();
480 params.skipSpace();
481 }
482 params.skipToAlpha();
483 }
484 }
485 }
486 }
487 fFiddle = Definition::NormalizedName(result);
488 }
489
setWrapper()490 void Definition::setWrapper() {
491 const char drawWrapper[] = "void draw(SkCanvas* canvas) {";
492 const char drawNoCanvas[] = "void draw(SkCanvas* ) {";
493 string text = this->extractText(Definition::TrimExtract::kNo);
494 size_t nonSpace = 0;
495 while (nonSpace < text.length() && ' ' >= text[nonSpace]) {
496 ++nonSpace;
497 }
498 bool hasFunc = !text.compare(nonSpace, sizeof(drawWrapper) - 1, drawWrapper);
499 bool noCanvas = !text.compare(nonSpace, sizeof(drawNoCanvas) - 1, drawNoCanvas);
500 bool hasCanvas = string::npos != text.find("SkCanvas canvas");
501 SkASSERT(!hasFunc || !noCanvas);
502 bool preprocessor = text[0] == '#';
503 bool wrapCode = !hasFunc && !noCanvas && !preprocessor;
504 if (wrapCode) {
505 fWrapper = hasCanvas ? string(drawNoCanvas) : string(drawWrapper);
506 }
507 }
508
exampleToScript(string * result,ExampleOptions exampleOptions) const509 bool Definition::exampleToScript(string* result, ExampleOptions exampleOptions) const {
510 bool hasFiddle = true;
511 const Definition* platform = this->hasChild(MarkType::kPlatform);
512 if (platform) {
513 TextParser platParse(platform);
514 hasFiddle = !platParse.strnstr("!fiddle", platParse.fEnd);
515 }
516 if (!hasFiddle) {
517 *result = "";
518 return true;
519 }
520 string text = this->extractText(Definition::TrimExtract::kNo);
521 bool textOut = string::npos != text.find("SkDebugf(")
522 || string::npos != text.find("dump(")
523 || string::npos != text.find("dumpHex(");
524 string heightStr = "256";
525 string widthStr = "256";
526 string normalizedName(fFiddle);
527 string code;
528 string imageStr = "0";
529 string srgbStr = "false";
530 string durationStr = "0";
531 for (auto const& iter : fChildren) {
532 switch (iter->fMarkType) {
533 case MarkType::kDuration:
534 durationStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
535 break;
536 case MarkType::kHeight:
537 heightStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
538 break;
539 case MarkType::kWidth:
540 widthStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
541 break;
542 case MarkType::kDescription:
543 // ignore for now
544 break;
545 case MarkType::kFunction: {
546 // emit this, but don't wrap this in draw()
547 string funcText(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
548 size_t pos = 0;
549 while (pos < funcText.length() && ' ' > funcText[pos]) {
550 ++pos;
551 }
552 size_t indent = count_indent(funcText, pos, funcText.length());
553 add_code(funcText, pos, funcText.length(), 0, indent, code);
554 code += "\\n";
555 } break;
556 case MarkType::kComment:
557 break;
558 case MarkType::kImage:
559 imageStr = string(iter->fContentStart, iter->fContentEnd - iter->fContentStart);
560 break;
561 case MarkType::kToDo:
562 break;
563 case MarkType::kMarkChar:
564 case MarkType::kPlatform:
565 // ignore for now
566 break;
567 case MarkType::kSet:
568 if ("sRGB" == string(iter->fContentStart,
569 iter->fContentEnd - iter->fContentStart)) {
570 srgbStr = "true";
571 } else {
572 SkASSERT(0); // more work to do
573 return false;
574 }
575 break;
576 case MarkType::kStdOut:
577 textOut = true;
578 break;
579 default:
580 SkASSERT(0); // more coding to do
581 }
582 }
583 string animatedStr = "0" != durationStr ? "true" : "false";
584 string textOutStr = textOut ? "true" : "false";
585 size_t pos = 0;
586 while (pos < text.length() && ' ' > text[pos]) {
587 ++pos;
588 }
589 size_t end = text.length();
590 size_t outIndent = 0;
591 size_t textIndent = count_indent(text, pos, end);
592 if (fWrapper.length() > 0) {
593 code += fWrapper;
594 code += "\\n";
595 outIndent = 4;
596 }
597 add_code(text, pos, end, outIndent, textIndent, code);
598 if (fWrapper.length() > 0) {
599 code += "}";
600 }
601 string example = "\"" + normalizedName + "\": {\n";
602 size_t nameStart = fFileName.find(SkOSPath::SEPARATOR, 0);
603 SkASSERT(string::npos != nameStart);
604 string baseFile = fFileName.substr(nameStart + 1, fFileName.length() - nameStart - 5);
605 if (ExampleOptions::kText == exampleOptions) {
606 example += " \"code\": \"" + code + "\",\n";
607 example += " \"hash\": \"" + fHash + "\",\n";
608 example += " \"file\": \"" + baseFile + "\",\n";
609 example += " \"name\": \"" + fName + "\",";
610 } else {
611 example += " \"code\": \"" + code + "\",\n";
612 if (ExampleOptions::kPng == exampleOptions) {
613 example += " \"width\": " + widthStr + ",\n";
614 example += " \"height\": " + heightStr + ",\n";
615 example += " \"hash\": \"" + fHash + "\",\n";
616 example += " \"file\": \"" + baseFile + "\",\n";
617 example += " \"name\": \"" + fName + "\"\n";
618 example += "}";
619 } else {
620 example += " \"options\": {\n";
621 example += " \"width\": " + widthStr + ",\n";
622 example += " \"height\": " + heightStr + ",\n";
623 example += " \"source\": " + imageStr + ",\n";
624 example += " \"srgb\": " + srgbStr + ",\n";
625 example += " \"f16\": false,\n";
626 example += " \"textOnly\": " + textOutStr + ",\n";
627 example += " \"animated\": " + animatedStr + ",\n";
628 example += " \"duration\": " + durationStr + "\n";
629 example += " },\n";
630 example += " \"fast\": true";
631 }
632 }
633 *result = example;
634 return true;
635 }
636
extractText(TrimExtract trimExtract) const637 string Definition::extractText(TrimExtract trimExtract) const {
638 string result;
639 TextParser parser(fFileName, fContentStart, fContentEnd, fLineCount);
640 int childIndex = 0;
641 char mc = '#';
642 while (parser.fChar < parser.fEnd) {
643 if (TrimExtract::kYes == trimExtract && !parser.skipWhiteSpace()) {
644 break;
645 }
646 if (parser.next() == mc) {
647 if (parser.next() == mc) {
648 if (parser.next() == mc) {
649 mc = parser.next();
650 }
651 } else {
652 // fixme : more work to do if # style comment is in text
653 // if in method definition, could be alternate method name
654 --parser.fChar;
655 if (' ' < parser.fChar[0]) {
656 if (islower(parser.fChar[0])) {
657 result += '\n';
658 parser.skipLine();
659 } else {
660 SkASSERT(isupper(parser.fChar[0]));
661 parser.skipTo(fChildren[childIndex]->fTerminator);
662 if (mc == parser.fChar[0] && mc == parser.fChar[1]) {
663 parser.next();
664 parser.next();
665 }
666 childIndex++;
667 }
668 } else {
669 parser.skipLine();
670 }
671 continue;
672 }
673 } else {
674 --parser.fChar;
675 }
676 const char* end = parser.fEnd;
677 const char* mark = parser.strnchr(mc, end);
678 if (mark) {
679 end = mark;
680 }
681 string fragment(parser.fChar, end - parser.fChar);
682 trim_end(fragment);
683 if (TrimExtract::kYes == trimExtract) {
684 trim_start(fragment);
685 if (result.length()) {
686 result += '\n';
687 result += '\n';
688 }
689 }
690 if (TrimExtract::kYes == trimExtract || has_nonwhitespace(fragment)) {
691 result += fragment;
692 }
693 parser.skipTo(end);
694 }
695 return result;
696 }
697
space_pad(string * str)698 static void space_pad(string* str) {
699 size_t len = str->length();
700 if (len == 0) {
701 return;
702 }
703 char last = (*str)[len - 1];
704 if ('~' == last || ' ' >= last) {
705 return;
706 }
707 *str += ' ';
708 }
709
710 //start here;
711 // see if it possible to abstract this a little bit so it can
712 // additionally be used to find params and return in method prototype that
713 // does not have corresponding doxygen comments
checkMethod() const714 bool Definition::checkMethod() const {
715 SkASSERT(MarkType::kMethod == fMarkType);
716 // if method returns a value, look for a return child
717 // for each parameter, look for a corresponding child
718 const char* end = fContentStart;
719 while (end > fStart && ' ' >= end[-1]) {
720 --end;
721 }
722 TextParser methodParser(fFileName, fStart, end, fLineCount);
723 methodParser.skipWhiteSpace();
724 SkASSERT(methodParser.startsWith("#Method"));
725 methodParser.skipName("#Method");
726 methodParser.skipSpace();
727 string name = this->methodName();
728 if (MethodType::kNone == fMethodType && name.length() > 2 &&
729 "()" == name.substr(name.length() - 2)) {
730 name = name.substr(0, name.length() - 2);
731 }
732 bool expectReturn = this->methodHasReturn(name, &methodParser);
733 bool foundReturn = false;
734 bool foundException = false;
735 for (auto& child : fChildren) {
736 foundException |= MarkType::kDeprecated == child->fMarkType
737 || MarkType::kExperimental == child->fMarkType;
738 if (MarkType::kReturn != child->fMarkType) {
739 if (MarkType::kParam == child->fMarkType) {
740 child->fVisited = false;
741 }
742 continue;
743 }
744 if (!expectReturn) {
745 return methodParser.reportError<bool>("no #Return expected");
746 }
747 if (foundReturn) {
748 return methodParser.reportError<bool>("multiple #Return markers");
749 }
750 foundReturn = true;
751 }
752 if (expectReturn && !foundReturn && !foundException) {
753 return methodParser.reportError<bool>("missing #Return marker");
754 }
755 const char* paren = methodParser.strnchr('(', methodParser.fEnd);
756 if (!paren) {
757 return methodParser.reportError<bool>("missing #Method function definition");
758 }
759 const char* nextEnd = paren;
760 do {
761 string paramName;
762 methodParser.fChar = nextEnd + 1;
763 methodParser.skipSpace();
764 if (!this->nextMethodParam(&methodParser, &nextEnd, ¶mName)) {
765 continue;
766 }
767 bool foundParam = false;
768 for (auto& child : fChildren) {
769 if (MarkType::kParam != child->fMarkType) {
770 continue;
771 }
772 if (paramName != child->fName) {
773 continue;
774 }
775 if (child->fVisited) {
776 return methodParser.reportError<bool>("multiple #Method param with same name");
777 }
778 child->fVisited = true;
779 if (foundParam) {
780 TextParser paramError(child);
781 return methodParser.reportError<bool>("multiple #Param with same name");
782 }
783 foundParam = true;
784
785 }
786 if (!foundParam && !foundException) {
787 return methodParser.reportError<bool>("no #Param found");
788 }
789 if (')' == nextEnd[0]) {
790 break;
791 }
792 } while (')' != nextEnd[0]);
793 for (auto& child : fChildren) {
794 if (MarkType::kParam != child->fMarkType) {
795 continue;
796 }
797 if (!child->fVisited) {
798 TextParser paramError(child);
799 return paramError.reportError<bool>("#Param without param in #Method");
800 }
801 }
802 return true;
803 }
804
crossCheck2(const Definition & includeToken) const805 bool Definition::crossCheck2(const Definition& includeToken) const {
806 TextParser parser(fFileName, fStart, fContentStart, fLineCount);
807 parser.skipExact("#");
808 bool isMethod = parser.skipName("Method");
809 const char* contentEnd;
810 if (isMethod) {
811 contentEnd = fContentStart;
812 } else if (parser.skipName("DefinedBy")) {
813 contentEnd = fContentEnd;
814 while (parser.fChar < contentEnd && ' ' >= contentEnd[-1]) {
815 --contentEnd;
816 }
817 if (parser.fChar < contentEnd - 1 && ')' == contentEnd[-1] && '(' == contentEnd[-2]) {
818 contentEnd -= 2;
819 }
820 } else {
821 return parser.reportError<bool>("unexpected crosscheck marktype");
822 }
823 return crossCheckInside(parser.fChar, contentEnd, includeToken);
824 }
825
crossCheck(const Definition & includeToken) const826 bool Definition::crossCheck(const Definition& includeToken) const {
827 return crossCheckInside(fContentStart, fContentEnd, includeToken);
828 }
829
crossCheckInside(const char * start,const char * end,const Definition & includeToken) const830 bool Definition::crossCheckInside(const char* start, const char* end,
831 const Definition& includeToken) const {
832 TextParser def(fFileName, start, end, fLineCount);
833 TextParser inc("", includeToken.fContentStart, includeToken.fContentEnd, 0);
834 if (inc.startsWith("SK_API")) {
835 inc.skipWord("SK_API");
836 }
837 if (inc.startsWith("friend")) {
838 inc.skipWord("friend");
839 }
840 if (inc.startsWith("SK_API")) {
841 inc.skipWord("SK_API");
842 }
843 inc.skipExact("SkDEBUGCODE(");
844 do {
845 bool defEof;
846 bool incEof;
847 do {
848 defEof = def.eof() || !def.skipWhiteSpace();
849 incEof = inc.eof() || !inc.skipWhiteSpace();
850 if (!incEof && '/' == inc.peek() && (defEof || '/' != def.peek())) {
851 inc.next();
852 if ('*' == inc.peek()) {
853 inc.skipToEndBracket("*/");
854 inc.next();
855 } else if ('/' == inc.peek()) {
856 inc.skipToEndBracket('\n');
857 }
858 } else if (!incEof && '#' == inc.peek() && (defEof || '#' != def.peek())) {
859 inc.next();
860 if (inc.startsWith("if")) {
861 inc.skipToEndBracket("\n");
862 } else if (inc.startsWith("endif")) {
863 inc.skipToEndBracket("\n");
864 } else {
865 SkASSERT(0); // incomplete
866 return false;
867 }
868 } else {
869 break;
870 }
871 inc.next();
872 } while (true);
873 if (defEof || incEof) {
874 if (defEof == incEof || (!defEof && ';' == def.peek())) {
875 return true;
876 }
877 return false; // allow setting breakpoint on failure
878 }
879 char defCh;
880 do {
881 defCh = def.next();
882 char incCh = inc.next();
883 if (' ' >= defCh && ' ' >= incCh) {
884 break;
885 }
886 if (defCh != incCh) {
887 if ('_' != defCh || ' ' != incCh || !fOperatorConst || !def.startsWith("const")) {
888 return false;
889 }
890 }
891 if (';' == defCh) {
892 return true;
893 }
894 } while (!def.eof() && !inc.eof());
895 } while (true);
896 return false;
897 }
898
formatFunction() const899 string Definition::formatFunction() const {
900 const char* end = fContentStart;
901 while (end > fStart && ' ' >= end[-1]) {
902 --end;
903 }
904 TextParser methodParser(fFileName, fStart, end, fLineCount);
905 methodParser.skipWhiteSpace();
906 SkASSERT(methodParser.startsWith("#Method"));
907 methodParser.skipName("#Method");
908 methodParser.skipSpace();
909 const char* lastStart = methodParser.fChar;
910 const int limit = 100; // todo: allow this to be set by caller or in global or something
911 string name = this->methodName();
912 const char* nameInParser = methodParser.strnstr(name.c_str(), methodParser.fEnd);
913 methodParser.skipTo(nameInParser);
914 const char* lastEnd = methodParser.fChar;
915 const char* paren = methodParser.strnchr('(', methodParser.fEnd);
916 size_t indent;
917 if (paren) {
918 indent = (size_t) (paren - lastStart) + 1;
919 } else {
920 indent = (size_t) (lastEnd - lastStart);
921 }
922 // trim indent so longest line doesn't exceed box width
923 TextParser::Save savePlace(&methodParser);
924 const char* saveStart = lastStart;
925 ptrdiff_t maxLine = 0;
926 do {
927 const char* nextStart = lastEnd;
928 const char* delimiter = methodParser.anyOf(",)");
929 const char* nextEnd = delimiter ? delimiter : methodParser.fEnd;
930 if (delimiter) {
931 while (nextStart < nextEnd && ' ' >= nextStart[0]) {
932 ++nextStart;
933 }
934 }
935 while (nextEnd > nextStart && ' ' >= nextEnd[-1]) {
936 --nextEnd;
937 }
938 if (delimiter) {
939 nextEnd += 1;
940 delimiter += 1;
941 }
942 if (lastEnd > lastStart) {
943 maxLine = SkTMax(maxLine, lastEnd - lastStart);
944 }
945 if (delimiter) {
946 methodParser.skipTo(delimiter);
947 }
948 lastStart = nextStart;
949 lastEnd = nextEnd;
950 } while (lastStart < lastEnd);
951 savePlace.restore();
952 lastStart = saveStart;
953 lastEnd = methodParser.fChar;
954 indent = SkTMin(indent, (size_t) (limit - maxLine));
955 // write string wtih trimmmed indent
956 string methodStr;
957 int written = 0;
958 do {
959 const char* nextStart = lastEnd;
960 SkASSERT(written < limit);
961 const char* delimiter = methodParser.anyOf(",)");
962 const char* nextEnd = delimiter ? delimiter : methodParser.fEnd;
963 if (delimiter) {
964 while (nextStart < nextEnd && ' ' >= nextStart[0]) {
965 ++nextStart;
966 }
967 }
968 while (nextEnd > nextStart && ' ' >= nextEnd[-1]) {
969 --nextEnd;
970 }
971 if (delimiter) {
972 nextEnd += 1;
973 delimiter += 1;
974 }
975 if (lastEnd > lastStart) {
976 if (lastStart[0] != ' ') {
977 space_pad(&methodStr);
978 }
979 methodStr += string(lastStart, (size_t) (lastEnd - lastStart));
980 written += (size_t) (lastEnd - lastStart);
981 }
982 if (delimiter) {
983 if (nextEnd - nextStart >= (ptrdiff_t) (limit - written)) {
984 written = indent;
985 methodStr += '\n';
986 methodStr += string(indent, ' ');
987 }
988 methodParser.skipTo(delimiter);
989 }
990 lastStart = nextStart;
991 lastEnd = nextEnd;
992 } while (lastStart < lastEnd);
993 return methodStr;
994 }
995
fiddleName() const996 string Definition::fiddleName() const {
997 string result;
998 size_t start = 0;
999 string parent;
1000 const Definition* parentDef = this;
1001 while ((parentDef = parentDef->fParent)) {
1002 if (MarkType::kClass == parentDef->fMarkType || MarkType::kStruct == parentDef->fMarkType) {
1003 parent = parentDef->fFiddle;
1004 break;
1005 }
1006 }
1007 if (parent.length() && 0 == fFiddle.compare(0, parent.length(), parent)) {
1008 start = parent.length();
1009 while (start < fFiddle.length() && '_' == fFiddle[start]) {
1010 ++start;
1011 }
1012 }
1013 size_t end = fFiddle.find_first_of('(', start);
1014 return fFiddle.substr(start, end - start);
1015 }
1016
hasChild(MarkType markType) const1017 const Definition* Definition::hasChild(MarkType markType) const {
1018 for (auto iter : fChildren) {
1019 if (markType == iter->fMarkType) {
1020 return iter;
1021 }
1022 }
1023 return nullptr;
1024 }
1025
hasParam(const string & ref) const1026 const Definition* Definition::hasParam(const string& ref) const {
1027 SkASSERT(MarkType::kMethod == fMarkType);
1028 for (auto iter : fChildren) {
1029 if (MarkType::kParam != iter->fMarkType) {
1030 continue;
1031 }
1032 if (iter->fName == ref) {
1033 return &*iter;
1034 }
1035
1036 }
1037 return nullptr;
1038 }
1039
hasMatch(const string & name) const1040 bool Definition::hasMatch(const string& name) const {
1041 for (auto child : fChildren) {
1042 if (name == child->fName) {
1043 return true;
1044 }
1045 if (child->hasMatch(name)) {
1046 return true;
1047 }
1048 }
1049 return false;
1050 }
1051
isStructOrClass() const1052 bool Definition::isStructOrClass() const {
1053 if (MarkType::kStruct != fMarkType && MarkType::kClass != fMarkType) {
1054 return false;
1055 }
1056 if (string::npos != fFileName.find("undocumented.bmh")) {
1057 return false;
1058 }
1059 return true;
1060 }
1061
methodHasReturn(const string & name,TextParser * methodParser) const1062 bool Definition::methodHasReturn(const string& name, TextParser* methodParser) const {
1063 if (methodParser->skipExact("static")) {
1064 methodParser->skipWhiteSpace();
1065 }
1066 const char* lastStart = methodParser->fChar;
1067 const char* nameInParser = methodParser->strnstr(name.c_str(), methodParser->fEnd);
1068 methodParser->skipTo(nameInParser);
1069 const char* lastEnd = methodParser->fChar;
1070 const char* returnEnd = lastEnd;
1071 while (returnEnd > lastStart && ' ' == returnEnd[-1]) {
1072 --returnEnd;
1073 }
1074 bool expectReturn = 4 != returnEnd - lastStart || strncmp("void", lastStart, 4);
1075 if (MethodType::kNone != fMethodType && MethodType::kOperator != fMethodType && !expectReturn) {
1076 return methodParser->reportError<bool>("unexpected void");
1077 }
1078 switch (fMethodType) {
1079 case MethodType::kNone:
1080 case MethodType::kOperator:
1081 // either is fine
1082 break;
1083 case MethodType::kConstructor:
1084 expectReturn = true;
1085 break;
1086 case MethodType::kDestructor:
1087 expectReturn = false;
1088 break;
1089 }
1090 return expectReturn;
1091 }
1092
methodName() const1093 string Definition::methodName() const {
1094 string result;
1095 size_t start = 0;
1096 string parent;
1097 const Definition* parentDef = this;
1098 while ((parentDef = parentDef->fParent)) {
1099 if (MarkType::kClass == parentDef->fMarkType || MarkType::kStruct == parentDef->fMarkType) {
1100 parent = parentDef->fName;
1101 break;
1102 }
1103 }
1104 if (parent.length() && 0 == fName.compare(0, parent.length(), parent)) {
1105 start = parent.length();
1106 while (start < fName.length() && ':' == fName[start]) {
1107 ++start;
1108 }
1109 }
1110 if (fClone) {
1111 int lastUnder = fName.rfind('_');
1112 return fName.substr(start, (size_t) (lastUnder - start));
1113 }
1114 size_t end = fName.find_first_of('(', start);
1115 if (string::npos == end) {
1116 return fName.substr(start);
1117 }
1118 return fName.substr(start, end - start);
1119 }
1120
nextMethodParam(TextParser * methodParser,const char ** nextEndPtr,string * paramName) const1121 bool Definition::nextMethodParam(TextParser* methodParser, const char** nextEndPtr,
1122 string* paramName) const {
1123 int parenCount = 0;
1124 TextParser::Save saveState(methodParser);
1125 while (true) {
1126 if (methodParser->eof()) {
1127 return methodParser->reportError<bool>("#Method function missing close paren");
1128 }
1129 char ch = methodParser->peek();
1130 if ('(' == ch) {
1131 ++parenCount;
1132 }
1133 if (parenCount == 0 && (')' == ch || ',' == ch)) {
1134 *nextEndPtr = methodParser->fChar;
1135 break;
1136 }
1137 if (')' == ch) {
1138 if (0 > --parenCount) {
1139 return this->reportError<bool>("mismatched parentheses");
1140 }
1141 }
1142 methodParser->next();
1143 }
1144 saveState.restore();
1145 const char* nextEnd = *nextEndPtr;
1146 const char* paramEnd = nextEnd;
1147 const char* assign = methodParser->strnstr(" = ", paramEnd);
1148 if (assign) {
1149 paramEnd = assign;
1150 }
1151 const char* closeBracket = methodParser->strnstr("]", paramEnd);
1152 if (closeBracket) {
1153 const char* openBracket = methodParser->strnstr("[", paramEnd);
1154 if (openBracket && openBracket < closeBracket) {
1155 while (openBracket < --closeBracket && isdigit(closeBracket[0]))
1156 ;
1157 if (openBracket == closeBracket) {
1158 paramEnd = openBracket;
1159 }
1160 }
1161 }
1162 const char* function = methodParser->strnstr(")(", paramEnd);
1163 if (function) {
1164 paramEnd = function;
1165 }
1166 while (paramEnd > methodParser->fChar && ' ' == paramEnd[-1]) {
1167 --paramEnd;
1168 }
1169 const char* paramStart = paramEnd;
1170 while (paramStart > methodParser->fChar && isalnum(paramStart[-1])) {
1171 --paramStart;
1172 }
1173 if (paramStart > methodParser->fChar && paramStart >= paramEnd) {
1174 return methodParser->reportError<bool>("#Method missing param name");
1175 }
1176 *paramName = string(paramStart, paramEnd - paramStart);
1177 if (!paramName->length()) {
1178 if (')' != nextEnd[0]) {
1179 return methodParser->reportError<bool>("#Method malformed param");
1180 }
1181 return false;
1182 }
1183 return true;
1184 }
1185
NormalizedName(string name)1186 string Definition::NormalizedName(string name) {
1187 string normalizedName = name;
1188 std::replace(normalizedName.begin(), normalizedName.end(), '-', '_');
1189 do {
1190 size_t doubleColon = normalizedName.find("::", 0);
1191 if (string::npos == doubleColon) {
1192 break;
1193 }
1194 normalizedName = normalizedName.substr(0, doubleColon)
1195 + '_' + normalizedName.substr(doubleColon + 2);
1196 } while (true);
1197 return normalizedName;
1198 }
1199
paramsMatch(const string & match,const string & name) const1200 bool Definition::paramsMatch(const string& match, const string& name) const {
1201 TextParser def(fFileName, fStart, fContentStart, fLineCount);
1202 const char* dName = def.strnstr(name.c_str(), fContentStart);
1203 if (!dName) {
1204 return false;
1205 }
1206 def.skipTo(dName);
1207 TextParser m(fFileName, &match.front(), &match.back() + 1, fLineCount);
1208 const char* mName = m.strnstr(name.c_str(), m.fEnd);
1209 if (!mName) {
1210 return false;
1211 }
1212 m.skipTo(mName);
1213 while (!def.eof() && ')' != def.peek() && !m.eof() && ')' != m.peek()) {
1214 const char* ds = def.fChar;
1215 const char* ms = m.fChar;
1216 const char* de = def.anyOf(") \n");
1217 const char* me = m.anyOf(") \n");
1218 def.skipTo(de);
1219 m.skipTo(me);
1220 if (def.fChar - ds != m.fChar - ms) {
1221 return false;
1222 }
1223 if (strncmp(ds, ms, (int) (def.fChar - ds))) {
1224 return false;
1225 }
1226 def.skipWhiteSpace();
1227 m.skipWhiteSpace();
1228 }
1229 return !def.eof() && ')' == def.peek() && !m.eof() && ')' == m.peek();
1230 }
1231
clearVisited()1232 void RootDefinition::clearVisited() {
1233 fVisited = false;
1234 for (auto& leaf : fLeaves) {
1235 leaf.second.fVisited = false;
1236 }
1237 for (auto& branch : fBranches) {
1238 branch.second->clearVisited();
1239 }
1240 }
1241
dumpUnVisited()1242 bool RootDefinition::dumpUnVisited() {
1243 bool success = true;
1244 for (auto& leaf : fLeaves) {
1245 if (!leaf.second.fVisited) {
1246 // FIXME: bugs requiring long tail fixes, suppressed here:
1247 // SkBitmap::validate() is wrapped in SkDEBUGCODE in .h and not parsed
1248 if ("SkBitmap::validate()" == leaf.first) {
1249 continue;
1250 }
1251 // SkPath::pathRefIsValid in #ifdef ; prefer to remove chrome dependency to fix
1252 if ("SkPath::pathRefIsValid" == leaf.first) {
1253 continue;
1254 }
1255 // FIXME: end of long tail bugs
1256 SkDebugf("defined in bmh but missing in include: %s\n", leaf.first.c_str());
1257 success = false;
1258 }
1259 }
1260 for (auto& branch : fBranches) {
1261 success &= branch.second->dumpUnVisited();
1262 }
1263 return success;
1264 }
1265
find(const string & ref,AllowParens allowParens) const1266 const Definition* RootDefinition::find(const string& ref, AllowParens allowParens) const {
1267 const auto leafIter = fLeaves.find(ref);
1268 if (leafIter != fLeaves.end()) {
1269 return &leafIter->second;
1270 }
1271 if (AllowParens::kYes == allowParens && string::npos == ref.find("()")) {
1272 string withParens = ref + "()";
1273 const auto parensIter = fLeaves.find(withParens);
1274 if (parensIter != fLeaves.end()) {
1275 return &parensIter->second;
1276 }
1277 }
1278 const auto branchIter = fBranches.find(ref);
1279 if (branchIter != fBranches.end()) {
1280 const RootDefinition* rootDef = branchIter->second;
1281 return rootDef;
1282 }
1283 const Definition* result = nullptr;
1284 for (const auto& branch : fBranches) {
1285 const RootDefinition* rootDef = branch.second;
1286 result = rootDef->find(ref, allowParens);
1287 if (result) {
1288 break;
1289 }
1290 }
1291 return result;
1292 }
1293