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 11static 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 22static 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 81enum 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 94enum class OpMod : int8_t { 95 kNone, 96 kArray, 97 kMove, 98 kPointer, 99 kReference, 100 kAny, 101}; 102 103const 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 186OpType 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 200OpMod 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 217bool 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 372bool 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() 384bool 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 410void 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 490void 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 509bool 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 637string 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 698static 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 714bool 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 805bool 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 826bool Definition::crossCheck(const Definition& includeToken) const { 827 return crossCheckInside(fContentStart, fContentEnd, includeToken); 828} 829 830bool 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 899string 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 996string 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 1017const 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 1026const 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 1040bool 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 1052bool 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 1062bool 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 1093string 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 1121bool 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 1186string 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 1200bool 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 1232void 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 1242bool 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 1266const 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