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, &paramName)) {
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