skia2/tools/bookmaker/definition.cpp
Cary Clark 1a8d762a18 work in imageinfo and phrase substitution
This adds the ability to define long phrases
in one place and refer to those phrases in
many places.

Bookmaker has new syntax to support phrase substitution.
When it encounters

#some_phrase_reference#

It substitutes the body of

#PhraseDef some_phrase_reference
text to substitute when encountering the phrase
##

The phrase label must start with a lowercase letter,
and be bracketed by single hash marks, without spaces
between the label and the hash marks.

Docs-Preview: https://skia.org/?cl=111224
TBR=caryclark@google.com
Bug: skia:6898
Change-Id: I12c57d916ccedbd86b421377d117399150ada72a
Reviewed-on: https://skia-review.googlesource.com/111224
Reviewed-by: Cary Clark <caryclark@skia.org>
Commit-Queue: Cary Clark <caryclark@skia.org>
2018-03-05 18:48:15 +00:00

1127 lines
39 KiB
C++

/*
* Copyright 2017 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "bookmaker.h"
#ifdef CONST
#undef CONST
#endif
#ifdef FRIEND
#undef FRIEND
#endif
#ifdef BLANK
#undef BLANK
#endif
#ifdef ANY
#undef ANY
#endif
#ifdef DEFOP
#undef DEFOP
#endif
#define CONST 1
#define STATIC 2
#define BLANK 0
#define ANY -1
#define DEFOP Definition::Operator
enum class OpType : int8_t {
kNone,
kVoid,
kBool,
kChar,
kFloat,
kInt,
kScalar,
kSizeT,
kThis,
kAny,
};
enum class OpMod : int8_t {
kNone,
kArray,
kMove,
kPointer,
kReference,
kAny,
};
const struct OperatorParser {
DEFOP fOperator;
const char* fSymbol;
const char* fName;
int8_t fFriend;
OpType fReturnType;
OpMod fReturnMod;
int8_t fConstMethod;
struct Param {
int8_t fConst;
OpType fType;
OpMod fMod;
} fParams[2];
} opData[] = {
{ DEFOP::kUnknown, "??", "???", BLANK, OpType::kNone, OpMod::kNone, BLANK,
{ } },
{ DEFOP::kAdd, "+", "add", BLANK, OpType::kThis, OpMod::kNone, BLANK,
{{ CONST, OpType::kThis, OpMod::kReference, },
{ CONST, OpType::kThis, OpMod::kReference, }}},
{ DEFOP::kAddTo, "+=", "addto", BLANK, OpType::kVoid, OpMod::kNone, BLANK,
{{ CONST, OpType::kThis, OpMod::kReference, }}},
{ DEFOP::kAddTo, "+=", "addto1", BLANK, OpType::kThis, OpMod::kReference, BLANK,
{{ CONST, OpType::kThis, OpMod::kReference, }}},
{ DEFOP::kAddTo, "+=", "addto2", BLANK, OpType::kThis, OpMod::kReference, BLANK,
{{ CONST, OpType::kChar, OpMod::kArray, }}},
{ DEFOP::kAddTo, "+=", "addto3", BLANK, OpType::kThis, OpMod::kReference, BLANK,
{{ CONST, OpType::kChar, OpMod::kNone, }}},
{ DEFOP::kArray, "[]", "array", BLANK, OpType::kScalar, OpMod::kNone, CONST,
{{ BLANK, OpType::kInt, OpMod::kNone, }}},
{ DEFOP::kArray, "[]", "array1", BLANK, OpType::kScalar, OpMod::kReference, BLANK,
{{ BLANK, OpType::kInt, OpMod::kNone, }}},
{ DEFOP::kArray, "[]", "array2", BLANK, OpType::kChar, OpMod::kNone, CONST,
{{ BLANK, OpType::kSizeT, OpMod::kNone, }}},
{ DEFOP::kArray, "[]", "array3", BLANK, OpType::kChar, OpMod::kReference, BLANK,
{{ BLANK, OpType::kSizeT, OpMod::kNone, }}},
{ DEFOP::kCast, "()", "cast", BLANK, OpType::kAny, OpMod::kAny, ANY,
{{ ANY, OpType::kAny, OpMod::kAny, }}},
{ DEFOP::kCopy, "=", "copy", BLANK, OpType::kThis, OpMod::kReference, BLANK,
{{ CONST, OpType::kThis, OpMod::kReference, }}},
{ DEFOP::kCopy, "=", "copy1", BLANK, OpType::kThis, OpMod::kReference, BLANK,
{{ CONST, OpType::kChar, OpMod::kArray, }}},
{ DEFOP::kDelete, "delete", "delete", BLANK, OpType::kVoid, OpMod::kNone, BLANK,
{{ BLANK, OpType::kVoid, OpMod::kPointer, }}},
{ DEFOP::kDereference, "->", "deref", ANY, OpType::kThis, OpMod::kPointer, CONST,
{ } },
{ DEFOP::kDereference, "*", "deref", BLANK, OpType::kThis, OpMod::kReference, CONST,
{ } },
{ DEFOP::kEqual, "==", "equal", BLANK, OpType::kBool, OpMod::kNone, BLANK,
{{ CONST, OpType::kThis, OpMod::kReference, },
{ CONST, OpType::kThis, OpMod::kReference, }}},
{ DEFOP::kEqual, "==", "equal1", BLANK, OpType::kBool, OpMod::kNone, CONST,
{{ CONST, OpType::kThis, OpMod::kReference, }}},
{ DEFOP::kEqual, "==", "equal2", ANY, OpType::kBool, OpMod::kNone, BLANK,
{{ CONST, OpType::kThis, OpMod::kReference, },
{ CONST, OpType::kThis, OpMod::kReference, }}},
{ DEFOP::kMinus, "-", "minus", BLANK, OpType::kThis, OpMod::kNone, CONST,
{ } },
{ DEFOP::kMove, "=", "move", BLANK, OpType::kThis, OpMod::kReference, BLANK,
{{ BLANK, OpType::kThis, OpMod::kMove, }}},
{ DEFOP::kMultiply, "*", "multiply", BLANK, OpType::kThis, OpMod::kNone, CONST,
{{ BLANK, OpType::kScalar, OpMod::kNone, }}},
{ DEFOP::kMultiply, "*", "multiply1", BLANK, OpType::kThis, OpMod::kNone, BLANK,
{{ CONST, OpType::kThis, OpMod::kReference, },
{ CONST, OpType::kThis, OpMod::kReference, }}},
{ DEFOP::kMultiplyBy, "*=", "multiplyby", BLANK, OpType::kThis, OpMod::kReference, BLANK,
{{ BLANK, OpType::kScalar, OpMod::kNone, }}},
{ DEFOP::kNew, "new", "new", BLANK, OpType::kVoid, OpMod::kPointer, BLANK,
{{ BLANK, OpType::kSizeT, OpMod::kNone, }}},
{ DEFOP::kNotEqual, "!=", "notequal", BLANK, OpType::kBool, OpMod::kNone, BLANK,
{{ CONST, OpType::kThis, OpMod::kReference, },
{ CONST, OpType::kThis, OpMod::kReference, }}},
{ DEFOP::kNotEqual, "!=", "notequal1", BLANK, OpType::kBool, OpMod::kNone, CONST,
{{ CONST, OpType::kThis, OpMod::kReference, }}},
{ DEFOP::kNotEqual, "!=", "notequal2", ANY, OpType::kBool, OpMod::kNone, BLANK,
{{ CONST, OpType::kThis, OpMod::kReference, },
{ CONST, OpType::kThis, OpMod::kReference, }}},
{ DEFOP::kSubtract, "-", "subtract", BLANK, OpType::kThis, OpMod::kNone, BLANK,
{{ CONST, OpType::kThis, OpMod::kReference, },
{ CONST, OpType::kThis, OpMod::kReference, }}},
{ DEFOP::kSubtractFrom, "-=", "subtractfrom", BLANK, OpType::kVoid, OpMod::kNone, BLANK,
{{ CONST, OpType::kThis, OpMod::kReference, }}},
};
OpType lookup_type(const string& typeWord, const string& name) {
if (typeWord == name || (typeWord == "SkIVector" && name == "SkIPoint")
|| (typeWord == "SkVector" && name == "SkPoint")) {
return OpType::kThis;
}
const char* keyWords[] = { "void", "bool", "char", "float", "int", "SkScalar", "size_t" };
for (unsigned i = 0; i < SK_ARRAY_COUNT(keyWords); ++i) {
if (typeWord == keyWords[i]) {
return (OpType) (i + 1);
}
}
return OpType::kNone;
}
OpMod lookup_mod(TextParser& iParser) {
OpMod mod = OpMod::kNone;
if ('&' == iParser.peek()) {
mod = OpMod::kReference;
iParser.next();
if ('&' == iParser.peek()) {
mod = OpMod::kMove;
iParser.next();
}
} else if ('*' == iParser.peek()) {
mod = OpMod::kPointer;
iParser.next();
}
iParser.skipWhiteSpace();
return mod;
}
bool Definition::parseOperator(size_t doubleColons, string& result) {
const char operatorStr[] = "operator";
size_t opPos = fName.find(operatorStr, doubleColons);
if (string::npos == opPos) {
return false;
}
string className(fName, 0, doubleColons - 2);
TextParser iParser(fFileName, fStart, fContentStart, fLineCount);
SkAssertResult(iParser.skipWord("#Method"));
iParser.skipExact("SK_API");
iParser.skipWhiteSpace();
bool isStatic = iParser.skipExact("static");
iParser.skipWhiteSpace();
iParser.skipExact("SK_API");
iParser.skipWhiteSpace();
bool returnsConst = iParser.skipExact("const");
if (returnsConst) {
SkASSERT(0); // incomplete
}
SkASSERT(isStatic == false || returnsConst == false);
iParser.skipWhiteSpace();
const char* returnTypeStart = iParser.fChar;
iParser.skipToNonAlphaNum();
SkASSERT(iParser.fChar > returnTypeStart);
string returnType(returnTypeStart, iParser.fChar - returnTypeStart);
OpType returnOpType = lookup_type(returnType, className);
iParser.skipWhiteSpace();
OpMod returnMod = lookup_mod(iParser);
SkAssertResult(iParser.skipExact("operator"));
iParser.skipWhiteSpace();
fMethodType = Definition::MethodType::kOperator;
TextParser::Save save(&iParser);
for (auto parser : opData) {
save.restore();
if (!iParser.skipExact(parser.fSymbol)) {
continue;
}
iParser.skipWhiteSpace();
if ('(' != iParser.peek()) {
continue;
}
if (parser.fFriend != ANY && (parser.fFriend == STATIC) != isStatic) {
continue;
}
if (parser.fReturnType != OpType::kAny && parser.fReturnType != returnOpType) {
continue;
}
if (parser.fReturnMod != OpMod::kAny && parser.fReturnMod != returnMod) {
continue;
}
iParser.next(); // skip '('
iParser.skipWhiteSpace();
int parserCount = (parser.fParams[0].fType != OpType::kNone) +
(parser.fParams[1].fType != OpType::kNone);
bool countsMatch = true;
for (int pIndex = 0; pIndex < 2; ++pIndex) {
if (')' == iParser.peek()) {
countsMatch = pIndex == parserCount;
break;
}
if (',' == iParser.peek()) {
iParser.next();
iParser.skipWhiteSpace();
}
bool paramConst = iParser.skipExact("const");
if (parser.fParams[pIndex].fConst != ANY &&
paramConst != (parser.fParams[pIndex].fConst == CONST)) {
countsMatch = false;
break;
}
iParser.skipWhiteSpace();
const char* paramStart = iParser.fChar;
iParser.skipToNonAlphaNum();
SkASSERT(iParser.fChar > paramStart);
string paramType(paramStart, iParser.fChar - paramStart);
OpType paramOpType = lookup_type(paramType, className);
if (parser.fParams[pIndex].fType != OpType::kAny &&
parser.fParams[pIndex].fType != paramOpType) {
countsMatch = false;
break;
}
iParser.skipWhiteSpace();
OpMod paramMod = lookup_mod(iParser);
if (parser.fParams[pIndex].fMod != OpMod::kAny &&
parser.fParams[pIndex].fMod != paramMod) {
countsMatch = false;
break;
}
iParser.skipToNonAlphaNum();
if ('[' == iParser.peek()) {
paramMod = OpMod::kArray;
SkAssertResult(iParser.skipExact("[]"));
}
iParser.skipWhiteSpace();
}
if (!countsMatch) {
continue;
}
if (')' != iParser.peek()) {
continue;
}
iParser.next();
bool constMethod = iParser.skipExact("_const");
if (parser.fConstMethod != ANY && (parser.fConstMethod == CONST) != constMethod) {
continue;
}
result += parser.fName;
result += "_operator";
fOperator = parser.fOperator;
fOperatorConst = constMethod;
return true;
}
SkASSERT(0); // incomplete
return false;
#if 0
if ('!' == fName[opPos]) {
SkASSERT('=' == fName[opPos + 1]);
result += "not_equal_operator";
} else if ('=' == fName[opPos]) {
if ('(' == fName[opPos + 1]) {
result += isMove ? "move_" : "copy_";
result += "assignment_operator";
} else {
SkASSERT('=' == fName[opPos + 1]);
result += "equal_operator";
}
} else if ('[' == fName[opPos]) {
result += "subscript_operator";
const char* end = fContentStart;
while (end > fStart && ' ' >= end[-1]) {
--end;
}
string constCheck(fStart, end - fStart);
size_t constPos = constCheck.rfind("const");
if (constCheck.length() == constPos + 5) {
result += "_const";
}
} else if ('*' == fName[opPos]) {
result += "multiply_operator";
} else if ('-' == fName[opPos]) {
result += "subtract_operator";
} else if ('+' == fName[opPos]) {
result += "add_operator";
} else {
SkASSERT(0); // todo: incomplete
}
#endif
return true;
}
#undef CONST
#undef FRIEND
#undef BLANK
#undef DEFOP
bool Definition::boilerplateIfDef(Definition* parent) {
const Definition& label = fTokens.front();
if (Type::kWord != label.fType) {
return false;
}
fName = string(label.fContentStart, label.fContentEnd - label.fContentStart);
return true;
}
// todo: this is matching #ifndef SkXXX_DEFINED for no particular reason
// it doesn't do anything useful with arbitrary input, e.g. #ifdef SK_SUPPORT_LEGACY_CANVAS_HELPERS
// also doesn't know what to do with SK_REQUIRE_LOCAL_VAR()
bool Definition::boilerplateDef(Definition* parent) {
if (!this->boilerplateIfDef(parent)) {
return false;
}
const char* s = fName.c_str();
const char* e = strchr(s, '_');
return true; // fixme: if this is trying to do something useful with define, do it here
if (!e) {
return false;
}
string prefix(s, e - s);
const char* inName = strstr(parent->fName.c_str(), prefix.c_str());
if (!inName) {
return false;
}
if ('/' != inName[-1] && '\\' != inName[-1]) {
return false;
}
if (strcmp(inName + prefix.size(), ".h")) {
return false;
}
return true;
}
// fixme: this will need to be more complicated to handle all of Skia
// for now, just handle paint -- maybe fiddle will loosen naming restrictions
void Definition::setCanonicalFiddle() {
fMethodType = Definition::MethodType::kNone;
size_t doubleColons = fName.find("::", 0);
SkASSERT(string::npos != doubleColons);
string base = fName.substr(0, doubleColons);
string result = base + "_";
doubleColons += 2;
if (string::npos != fName.find('~', doubleColons)) {
fMethodType = Definition::MethodType::kDestructor;
result += "destructor";
} else if (!this->parseOperator(doubleColons, result)) {
bool isMove = string::npos != fName.find("&&", doubleColons);
size_t parens = fName.find("()", doubleColons);
if (string::npos != parens) {
string methodName = fName.substr(doubleColons, parens - doubleColons);
do {
size_t nextDouble = methodName.find("::");
if (string::npos == nextDouble) {
break;
}
base = methodName.substr(0, nextDouble);
result += base + '_';
methodName = methodName.substr(nextDouble + 2);
doubleColons += nextDouble + 2;
} while (true);
if (base == methodName) {
fMethodType = Definition::MethodType::kConstructor;
result += "empty_constructor";
} else {
result += fName.substr(doubleColons, fName.length() - doubleColons - 2);
}
} else {
size_t openParen = fName.find('(', doubleColons);
if (string::npos == openParen) {
result += fName.substr(doubleColons);
} else {
size_t comma = fName.find(',', doubleColons);
if (string::npos == comma) {
result += isMove ? "move_" : "copy_";
}
fMethodType = Definition::MethodType::kConstructor;
// name them by their param types,
// e.g. SkCanvas__int_int_const_SkSurfaceProps_star
// TODO: move forward until parens are balanced and terminator =,)
TextParser params("", &fName[openParen] + 1, &*fName.end(), 0);
bool underline = false;
while (!params.eof()) {
// SkDEBUGCODE(const char* end = params.anyOf("(),=")); // unused for now
// SkASSERT(end[0] != '('); // fixme: put off handling nested parentheseses
if (params.startsWith("const") || params.startsWith("int")
|| params.startsWith("Sk")) {
const char* wordStart = params.fChar;
params.skipToNonAlphaNum();
if (underline) {
result += '_';
} else {
underline = true;
}
result += string(wordStart, params.fChar - wordStart);
} else {
params.skipToNonAlphaNum();
}
if (!params.eof() && '*' == params.peek()) {
if (underline) {
result += '_';
} else {
underline = true;
}
result += "star";
params.next();
params.skipSpace();
}
params.skipToAlpha();
}
}
}
}
fFiddle = Definition::NormalizedName(result);
}
static void space_pad(string* str) {
size_t len = str->length();
if (len == 0) {
return;
}
char last = (*str)[len - 1];
if ('~' == last || ' ' >= last) {
return;
}
*str += ' ';
}
//start here;
// see if it possible to abstract this a little bit so it can
// additionally be used to find params and return in method prototype that
// does not have corresponding doxygen comments
bool Definition::checkMethod() const {
SkASSERT(MarkType::kMethod == fMarkType);
// if method returns a value, look for a return child
// for each parameter, look for a corresponding child
const char* end = fContentStart;
while (end > fStart && ' ' >= end[-1]) {
--end;
}
TextParser methodParser(fFileName, fStart, end, fLineCount);
methodParser.skipWhiteSpace();
SkASSERT(methodParser.startsWith("#Method"));
methodParser.skipName("#Method");
methodParser.skipSpace();
string name = this->methodName();
if (MethodType::kNone == fMethodType && name.length() > 2 &&
"()" == name.substr(name.length() - 2)) {
name = name.substr(0, name.length() - 2);
}
bool expectReturn = this->methodHasReturn(name, &methodParser);
bool foundReturn = false;
bool foundException = false;
for (auto& child : fChildren) {
foundException |= MarkType::kDeprecated == child->fMarkType
|| MarkType::kExperimental == child->fMarkType;
if (MarkType::kReturn != child->fMarkType) {
if (MarkType::kParam == child->fMarkType) {
child->fVisited = false;
}
continue;
}
if (!expectReturn) {
return methodParser.reportError<bool>("no #Return expected");
}
if (foundReturn) {
return methodParser.reportError<bool>("multiple #Return markers");
}
foundReturn = true;
}
if (expectReturn && !foundReturn && !foundException) {
return methodParser.reportError<bool>("missing #Return marker");
}
const char* paren = methodParser.strnchr('(', methodParser.fEnd);
if (!paren) {
return methodParser.reportError<bool>("missing #Method function definition");
}
const char* nextEnd = paren;
do {
string paramName;
methodParser.fChar = nextEnd + 1;
methodParser.skipSpace();
if (!this->nextMethodParam(&methodParser, &nextEnd, &paramName)) {
continue;
}
bool foundParam = false;
for (auto& child : fChildren) {
if (MarkType::kParam != child->fMarkType) {
continue;
}
if (paramName != child->fName) {
continue;
}
if (child->fVisited) {
return methodParser.reportError<bool>("multiple #Method param with same name");
}
child->fVisited = true;
if (foundParam) {
TextParser paramError(child);
return methodParser.reportError<bool>("multiple #Param with same name");
}
foundParam = true;
}
if (!foundParam && !foundException) {
return methodParser.reportError<bool>("no #Param found");
}
if (')' == nextEnd[0]) {
break;
}
} while (')' != nextEnd[0]);
for (auto& child : fChildren) {
if (MarkType::kParam != child->fMarkType) {
continue;
}
if (!child->fVisited) {
TextParser paramError(child);
return paramError.reportError<bool>("#Param without param in #Method");
}
}
return true;
}
bool Definition::crossCheck2(const Definition& includeToken) const {
TextParser parser(fFileName, fStart, fContentStart, fLineCount);
parser.skipExact("#");
bool isMethod = parser.skipName("Method");
const char* contentEnd;
if (isMethod) {
contentEnd = fContentStart;
} else if (parser.skipName("DefinedBy")) {
contentEnd = fContentEnd;
while (parser.fChar < contentEnd && ' ' >= contentEnd[-1]) {
--contentEnd;
}
if (parser.fChar < contentEnd - 1 && ')' == contentEnd[-1] && '(' == contentEnd[-2]) {
contentEnd -= 2;
}
} else {
return parser.reportError<bool>("unexpected crosscheck marktype");
}
return crossCheckInside(parser.fChar, contentEnd, includeToken);
}
bool Definition::crossCheck(const Definition& includeToken) const {
return crossCheckInside(fContentStart, fContentEnd, includeToken);
}
bool Definition::crossCheckInside(const char* start, const char* end,
const Definition& includeToken) const {
TextParser def(fFileName, start, end, fLineCount);
TextParser inc("", includeToken.fContentStart, includeToken.fContentEnd, 0);
if (inc.startsWith("SK_API")) {
inc.skipWord("SK_API");
}
if (inc.startsWith("friend")) {
inc.skipWord("friend");
}
if (inc.startsWith("SK_API")) {
inc.skipWord("SK_API");
}
inc.skipExact("SkDEBUGCODE(");
do {
bool defEof;
bool incEof;
do {
defEof = def.eof() || !def.skipWhiteSpace();
incEof = inc.eof() || !inc.skipWhiteSpace();
if (!incEof && '/' == inc.peek() && (defEof || '/' != def.peek())) {
inc.next();
if ('*' == inc.peek()) {
inc.skipToEndBracket("*/");
inc.next();
} else if ('/' == inc.peek()) {
inc.skipToEndBracket('\n');
}
} else if (!incEof && '#' == inc.peek() && (defEof || '#' != def.peek())) {
inc.next();
if (inc.startsWith("if")) {
inc.skipToEndBracket("\n");
} else if (inc.startsWith("endif")) {
inc.skipToEndBracket("\n");
} else {
SkASSERT(0); // incomplete
return false;
}
} else {
break;
}
inc.next();
} while (true);
if (defEof || incEof) {
if (defEof == incEof || (!defEof && ';' == def.peek())) {
return true;
}
return false; // allow setting breakpoint on failure
}
char defCh;
do {
defCh = def.next();
char incCh = inc.next();
if (' ' >= defCh && ' ' >= incCh) {
break;
}
if (defCh != incCh) {
if ('_' != defCh || ' ' != incCh || !fOperatorConst || !def.startsWith("const")) {
return false;
}
}
if (';' == defCh) {
return true;
}
} while (!def.eof() && !inc.eof());
} while (true);
return false;
}
string Definition::formatFunction(Format format) const {
const char* end = fContentStart;
while (end > fStart && ' ' >= end[-1]) {
--end;
}
TextParser methodParser(fFileName, fStart, end, fLineCount);
methodParser.skipWhiteSpace();
SkASSERT(methodParser.startsWith("#Method"));
methodParser.skipName("#Method");
methodParser.skipSpace();
const char* lastStart = methodParser.fChar;
const int limit = 100; // todo: allow this to be set by caller or in global or something
string name = this->methodName();
const char* nameInParser = methodParser.strnstr(name.c_str(), methodParser.fEnd);
methodParser.skipTo(nameInParser);
const char* lastEnd = methodParser.fChar;
if (Format::kOmitReturn == format) {
lastStart = lastEnd;
}
const char* paren = methodParser.strnchr('(', methodParser.fEnd);
size_t indent;
if (paren) {
indent = (size_t) (paren - lastStart) + 1;
} else {
indent = (size_t) (lastEnd - lastStart);
}
// trim indent so longest line doesn't exceed box width
TextParser::Save savePlace(&methodParser);
const char* saveStart = lastStart;
ptrdiff_t maxLine = 0;
do {
const char* nextStart = lastEnd;
const char* delimiter = methodParser.anyOf(",)");
const char* nextEnd = delimiter ? delimiter : methodParser.fEnd;
if (delimiter) {
while (nextStart < nextEnd && ' ' >= nextStart[0]) {
++nextStart;
}
}
while (nextEnd > nextStart && ' ' >= nextEnd[-1]) {
--nextEnd;
}
if (delimiter) {
nextEnd += 1;
delimiter += 1;
}
if (lastEnd > lastStart) {
maxLine = SkTMax(maxLine, lastEnd - lastStart);
}
if (delimiter) {
methodParser.skipTo(delimiter);
}
lastStart = nextStart;
lastEnd = nextEnd;
} while (lastStart < lastEnd);
savePlace.restore();
lastStart = saveStart;
lastEnd = methodParser.fChar;
indent = SkTMin(indent, (size_t) (limit - maxLine));
// write string wtih trimmmed indent
string methodStr;
int written = 0;
do {
const char* nextStart = lastEnd;
SkASSERT(written < limit);
const char* delimiter = methodParser.anyOf(",)");
const char* nextEnd = delimiter ? delimiter : methodParser.fEnd;
if (delimiter) {
while (nextStart < nextEnd && ' ' >= nextStart[0]) {
++nextStart;
}
}
while (nextEnd > nextStart && ' ' >= nextEnd[-1]) {
--nextEnd;
}
if (delimiter) {
nextEnd += 1;
delimiter += 1;
}
if (lastEnd > lastStart) {
if (lastStart[0] != ' ') {
space_pad(&methodStr);
}
methodStr += string(lastStart, (size_t) (lastEnd - lastStart));
written += (size_t) (lastEnd - lastStart);
}
if (delimiter) {
if (nextEnd - nextStart >= (ptrdiff_t) (limit - written)) {
written = indent;
if (Format::kIncludeReturn == format) {
methodStr += '\n';
methodStr += string(indent, ' ');
}
}
methodParser.skipTo(delimiter);
}
lastStart = nextStart;
lastEnd = nextEnd;
} while (lastStart < lastEnd);
return methodStr;
}
string Definition::fiddleName() const {
string result;
size_t start = 0;
string parent;
const Definition* parentDef = this;
while ((parentDef = parentDef->fParent)) {
if (MarkType::kClass == parentDef->fMarkType || MarkType::kStruct == parentDef->fMarkType) {
parent = parentDef->fFiddle;
break;
}
}
if (parent.length() && 0 == fFiddle.compare(0, parent.length(), parent)) {
start = parent.length();
while (start < fFiddle.length() && '_' == fFiddle[start]) {
++start;
}
}
size_t end = fFiddle.find_first_of('(', start);
return fFiddle.substr(start, end - start);
}
const Definition* Definition::findClone(string match) const {
for (auto child : fChildren) {
if (!child->fClone) {
continue;
}
if (match == child->fName) {
return child;
}
auto inner = child->findClone(match);
if (inner) {
return inner;
}
}
return nullptr;
}
const Definition* Definition::hasChild(MarkType markType) const {
for (auto iter : fChildren) {
if (markType == iter->fMarkType) {
return iter;
}
}
return nullptr;
}
const Definition* Definition::hasParam(const string& ref) const {
SkASSERT(MarkType::kMethod == fMarkType);
for (auto iter : fChildren) {
if (MarkType::kParam != iter->fMarkType) {
continue;
}
if (iter->fName == ref) {
return &*iter;
}
}
return nullptr;
}
bool Definition::hasMatch(const string& name) const {
for (auto child : fChildren) {
if (name == child->fName) {
return true;
}
if (child->hasMatch(name)) {
return true;
}
}
return false;
}
bool Definition::isStructOrClass() const {
if (MarkType::kStruct != fMarkType && MarkType::kClass != fMarkType) {
return false;
}
if (string::npos != fFileName.find("undocumented.bmh")) {
return false;
}
return true;
}
bool Definition::methodHasReturn(const string& name, TextParser* methodParser) const {
if (methodParser->skipExact("static")) {
methodParser->skipWhiteSpace();
}
const char* lastStart = methodParser->fChar;
const char* nameInParser = methodParser->strnstr(name.c_str(), methodParser->fEnd);
methodParser->skipTo(nameInParser);
const char* lastEnd = methodParser->fChar;
const char* returnEnd = lastEnd;
while (returnEnd > lastStart && ' ' == returnEnd[-1]) {
--returnEnd;
}
bool expectReturn = 4 != returnEnd - lastStart || strncmp("void", lastStart, 4);
if (MethodType::kNone != fMethodType && MethodType::kOperator != fMethodType && !expectReturn) {
return methodParser->reportError<bool>("unexpected void");
}
switch (fMethodType) {
case MethodType::kNone:
case MethodType::kOperator:
// either is fine
break;
case MethodType::kConstructor:
expectReturn = true;
break;
case MethodType::kDestructor:
expectReturn = false;
break;
}
return expectReturn;
}
string Definition::methodName() const {
string result;
size_t start = 0;
string parent;
const Definition* parentDef = this;
while ((parentDef = parentDef->fParent)) {
if (MarkType::kClass == parentDef->fMarkType || MarkType::kStruct == parentDef->fMarkType) {
parent = parentDef->fName;
break;
}
}
if (parent.length() && 0 == fName.compare(0, parent.length(), parent)) {
start = parent.length();
while (start < fName.length() && ':' == fName[start]) {
++start;
}
}
if (fClone) {
int lastUnder = fName.rfind('_');
return fName.substr(start, (size_t) (lastUnder - start));
}
size_t end = fName.find_first_of('(', start);
if (string::npos == end) {
return fName.substr(start);
}
return fName.substr(start, end - start);
}
bool Definition::nextMethodParam(TextParser* methodParser, const char** nextEndPtr,
string* paramName) const {
int parenCount = 0;
TextParser::Save saveState(methodParser);
while (true) {
if (methodParser->eof()) {
return methodParser->reportError<bool>("#Method function missing close paren");
}
char ch = methodParser->peek();
if ('(' == ch) {
++parenCount;
}
if (parenCount == 0 && (')' == ch || ',' == ch)) {
*nextEndPtr = methodParser->fChar;
break;
}
if (')' == ch) {
if (0 > --parenCount) {
return this->reportError<bool>("mismatched parentheses");
}
}
methodParser->next();
}
saveState.restore();
const char* nextEnd = *nextEndPtr;
const char* paramEnd = nextEnd;
const char* assign = methodParser->strnstr(" = ", paramEnd);
if (assign) {
paramEnd = assign;
}
const char* closeBracket = methodParser->strnstr("]", paramEnd);
if (closeBracket) {
const char* openBracket = methodParser->strnstr("[", paramEnd);
if (openBracket && openBracket < closeBracket) {
while (openBracket < --closeBracket && isdigit(closeBracket[0]))
;
if (openBracket == closeBracket) {
paramEnd = openBracket;
}
}
}
const char* function = methodParser->strnstr(")(", paramEnd);
if (function) {
paramEnd = function;
}
while (paramEnd > methodParser->fChar && ' ' == paramEnd[-1]) {
--paramEnd;
}
const char* paramStart = paramEnd;
while (paramStart > methodParser->fChar && isalnum(paramStart[-1])) {
--paramStart;
}
if (paramStart > methodParser->fChar && paramStart >= paramEnd) {
return methodParser->reportError<bool>("#Method missing param name");
}
*paramName = string(paramStart, paramEnd - paramStart);
if (!paramName->length()) {
if (')' != nextEnd[0]) {
return methodParser->reportError<bool>("#Method malformed param");
}
return false;
}
return true;
}
string Definition::NormalizedName(string name) {
string normalizedName = name;
std::replace(normalizedName.begin(), normalizedName.end(), '-', '_');
do {
size_t doubleColon = normalizedName.find("::", 0);
if (string::npos == doubleColon) {
break;
}
normalizedName = normalizedName.substr(0, doubleColon)
+ '_' + normalizedName.substr(doubleColon + 2);
} while (true);
return normalizedName;
}
static string unpreformat(const string& orig) {
string result;
int amp = 0;
for (auto c : orig) {
switch (amp) {
case 0:
if ('&' == c) {
amp = 1;
} else {
amp = 0;
result += c;
}
break;
case 1:
if ('l' == c) {
amp = 2;
} else if ('g' == c) {
amp = 3;
} else {
amp = 0;
result += "&";
result += c;
}
break;
case 2:
if ('t' == c) {
amp = 4;
} else {
amp = 0;
result += "&l";
result += c;
}
break;
case 3:
if ('t' == c) {
amp = 5;
} else {
amp = 0;
result += "&g";
result += c;
}
break;
case 4:
if (';' == c) {
result += '<';
} else {
result += "&lt";
result += c;
}
amp = 0;
break;
case 5:
if (';' == c) {
result += '>';
} else {
result += "&gt";
result += c;
}
amp = 0;
break;
}
}
return result;
}
bool Definition::paramsMatch(const string& matchFormatted, const string& name) const {
string match = unpreformat(matchFormatted);
TextParser def(fFileName, fStart, fContentStart, fLineCount);
const char* dName = def.strnstr(name.c_str(), fContentStart);
if (!dName) {
return false;
}
def.skipTo(dName);
TextParser m(fFileName, &match.front(), &match.back() + 1, fLineCount);
const char* mName = m.strnstr(name.c_str(), m.fEnd);
if (!mName) {
return false;
}
m.skipTo(mName);
while (!def.eof() && ')' != def.peek() && !m.eof() && ')' != m.peek()) {
const char* ds = def.fChar;
const char* ms = m.fChar;
const char* de = def.anyOf(") \n");
const char* me = m.anyOf(") \n");
def.skipTo(de);
m.skipTo(me);
if (def.fChar - ds != m.fChar - ms) {
return false;
}
if (strncmp(ds, ms, (int) (def.fChar - ds))) {
return false;
}
def.skipWhiteSpace();
m.skipWhiteSpace();
}
return !def.eof() && ')' == def.peek() && !m.eof() && ')' == m.peek();
}
void RootDefinition::clearVisited() {
fVisited = false;
for (auto& leaf : fLeaves) {
leaf.second.fVisited = false;
}
for (auto& branch : fBranches) {
branch.second->clearVisited();
}
}
bool RootDefinition::dumpUnVisited() {
bool success = true;
for (auto& leaf : fLeaves) {
if (!leaf.second.fVisited) {
// FIXME: bugs requiring long tail fixes, suppressed here:
// SkBitmap::validate() is wrapped in SkDEBUGCODE in .h and not parsed
if ("SkBitmap::validate()" == leaf.first) {
continue;
}
// SkPath::pathRefIsValid in #ifdef ; prefer to remove chrome dependency to fix
if ("SkPath::pathRefIsValid" == leaf.first) {
continue;
}
// FIXME: end of long tail bugs
SkDebugf("defined in bmh but missing in include: %s\n", leaf.first.c_str());
success = false;
}
}
for (auto& branch : fBranches) {
success &= branch.second->dumpUnVisited();
}
return success;
}
const Definition* RootDefinition::find(const string& ref, AllowParens allowParens) const {
const auto leafIter = fLeaves.find(ref);
if (leafIter != fLeaves.end()) {
return &leafIter->second;
}
if (AllowParens::kYes == allowParens && string::npos == ref.find("()")) {
string withParens = ref + "()";
const auto parensIter = fLeaves.find(withParens);
if (parensIter != fLeaves.end()) {
return &parensIter->second;
}
}
const auto branchIter = fBranches.find(ref);
if (branchIter != fBranches.end()) {
const RootDefinition* rootDef = branchIter->second;
return rootDef;
}
const Definition* result = nullptr;
for (const auto& branch : fBranches) {
const RootDefinition* rootDef = branch.second;
result = rootDef->find(ref, allowParens);
if (result) {
break;
}
}
return result;
}