skia2/tools/bookmaker/bookmaker.h
Cary Clark 579985ce4f fix self references
try removing self references in method definitions.
If this creates awkward wording, it can always be allowed
in another CL. Also tighten rules for identifying function
references in include comments.

R=briansoman@google.com, caryclark@google.com
TBR=reed@google.com
Bug: skia:6898
Change-Id: I1a0e6b2a76dacfe71d134deb4589fb74e6611a03
Reviewed-on: https://skia-review.googlesource.com/28624
Commit-Queue: Cary Clark <caryclark@skia.org>
Reviewed-by: Cary Clark <caryclark@skia.org>
2017-07-31 16:49:12 +00:00

1850 lines
53 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.
*/
#ifndef bookmaker_DEFINED
#define bookmaker_DEFINED
#define STDOUT_TO_IDE_OUT 0
#include "SkData.h"
#include <algorithm>
#include <cmath>
#include <cctype>
#include <forward_list>
#include <list>
#include <string>
#include <unordered_map>
#include <vector>
// std::to_string isn't implemented on android
#include <sstream>
template <typename T>
std::string to_string(T value)
{
std::ostringstream os ;
os << value ;
return os.str() ;
}
using std::forward_list;
using std::list;
using std::unordered_map;
using std::string;
using std::vector;
enum class KeyWord {
kNone,
kBool,
kChar,
kClass,
kConst,
kConstExpr,
kDefine,
kDouble,
kElif,
kElse,
kEndif,
kEnum,
kFloat,
kFriend,
kIf,
kIfdef,
kIfndef,
kInclude,
kInline,
kInt,
kOperator,
kPrivate,
kProtected,
kPublic,
kSigned,
kSize_t,
kStatic,
kStruct,
kTemplate,
kTypedef,
kUint32_t,
kUnion,
kUnsigned,
kVoid,
};
enum class MarkType {
kNone,
kAnchor,
kAlias,
kBug,
kClass,
kCode,
kColumn,
kComment,
kConst,
kDefine,
kDefinedBy,
kDeprecated,
kDescription,
kDoxygen,
kEnum,
kEnumClass,
kError,
kExample,
kExperimental,
kExternal,
kFile,
kFormula,
kFunction,
kHeight,
kImage,
kLegend,
kLink,
kList,
kMarkChar,
kMember,
kMethod,
kNoExample,
kParam,
kPlatform,
kPrivate,
kReturn,
kRoot,
kRow,
kSeeAlso,
kStdOut,
kStruct,
kSubstitute,
kSubtopic,
kTable,
kTemplate,
kText,
kTime,
kToDo,
kTopic,
kTrack,
kTypedef,
kUnion,
kVolatile,
kWidth,
};
enum {
Last_MarkType = (int) MarkType::kWidth,
};
enum class Bracket {
kNone,
kParen,
kSquare,
kBrace,
kAngle,
kString,
kChar,
kSlashStar,
kSlashSlash,
kPound,
kColon,
};
enum class Punctuation { // catch-all for misc symbols tracked in C
kNone,
kAsterisk, // for pointer-to
kSemicolon, // e.g., to delinate xxx() const ; const int* yyy()
kLeftBrace,
kColon, // for foo() : bar(1), baz(2) {}
};
static inline bool has_nonwhitespace(const string& s) {
bool nonwhite = false;
for (const char& c : s) {
if (' ' < c) {
nonwhite = true;
break;
}
}
return nonwhite;
}
static inline void trim_end(string &s) {
s.erase(std::find_if(s.rbegin(), s.rend(),
std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
}
static inline void trim_end_spaces(string &s) {
while (s.length() > 0 && ' ' == s.back()) {
s.pop_back();
}
}
static inline void trim_start(string &s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(),
std::not1(std::ptr_fun<int, int>(std::isspace))));
}
static inline void trim_start_end(string& s) {
trim_start(s);
trim_end(s);
}
class NonAssignable {
public:
NonAssignable(NonAssignable const&) = delete;
NonAssignable& operator=(NonAssignable const&) = delete;
NonAssignable() {}
};
class Definition;
class TextParser : public NonAssignable {
TextParser() {} // only for ParserCommon to call
friend class ParserCommon;
public:
enum OneLine {
kNo,
kYes
};
class Save {
public:
Save(TextParser* parser) {
fParser = parser;
fLine = parser->fLine;
fChar = parser->fChar;
fLineCount = parser->fLineCount;
}
void restore() const {
fParser->fLine = fLine;
fParser->fChar = fChar;
fParser->fLineCount = fLineCount;
}
private:
TextParser* fParser;
const char* fLine;
const char* fChar;
int fLineCount;
};
TextParser(const string& fileName, const char* start, const char* end, int lineCount)
: fFileName(fileName)
, fStart(start)
, fLine(start)
, fChar(start)
, fEnd(end)
, fLineCount(lineCount)
{
}
TextParser(const Definition* );
const char* anyOf(const char* str) const {
const char* ptr = fChar;
while (ptr < fEnd) {
if (strchr(str, ptr[0])) {
return ptr;
}
++ptr;
}
return nullptr;
}
const char* anyOf(const char* wordStart, const char* wordList[], size_t wordListCount) const {
const char** wordPtr = wordList;
const char** wordEnd = wordPtr + wordListCount;
const size_t matchLen = fChar - wordStart;
while (wordPtr < wordEnd) {
const char* word = *wordPtr++;
if (strlen(word) == matchLen && !strncmp(wordStart, word, matchLen)) {
return word;
}
}
return nullptr;
}
char backup(const char* pattern) const {
size_t len = strlen(pattern);
const char* start = fChar - len;
if (start <= fStart) {
return '\0';
}
if (strncmp(start, pattern, len)) {
return '\0';
}
return start[-1];
}
bool contains(const char* match, const char* lineEnd, const char** loc) const {
*loc = this->strnstr(match, lineEnd);
return *loc;
}
bool eof() const { return fChar >= fEnd; }
const char* lineEnd() const {
const char* ptr = fChar;
do {
if (ptr >= fEnd) {
return ptr;
}
char test = *ptr++;
if (test == '\n' || test == '\0') {
break;
}
} while (true);
return ptr;
}
ptrdiff_t lineLength() const {
return this->lineEnd() - fLine;
}
bool match(TextParser* );
char next() {
SkASSERT(fChar < fEnd);
char result = *fChar++;
if ('\n' == result) {
++fLineCount;
fLine = fChar;
}
return result;
}
char peek() const { SkASSERT(fChar < fEnd); return *fChar; }
void restorePlace(const TextParser& save) {
fChar = save.fChar;
fLine = save.fLine;
fLineCount = save.fLineCount;
}
void savePlace(TextParser* save) {
save->fChar = fChar;
save->fLine = fLine;
save->fLineCount = fLineCount;
}
void reportError(const char* errorStr) const;
void reportWarning(const char* errorStr) const;
template <typename T> T reportError(const char* errorStr) const {
this->reportError(errorStr);
return T();
}
bool sentenceEnd(const char* check) const {
while (check > fStart) {
--check;
if (' ' < check[0] && '.' != check[0]) {
return false;
}
if ('.' == check[0]) {
return ' ' >= check[1];
}
if ('\n' == check[0] && '\n' == check[1]) {
return true;
}
}
return true;
}
bool skipToEndBracket(char endBracket, const char* end = nullptr) {
if (nullptr == end) {
end = fEnd;
}
while (fChar[0] != endBracket) {
if (fChar >= end) {
return false;
}
(void) this->next();
}
return true;
}
bool skipToEndBracket(const char* endBracket) {
size_t len = strlen(endBracket);
while (strncmp(fChar, endBracket, len)) {
if (fChar >= fEnd) {
return false;
}
(void) this->next();
}
return true;
}
bool skipLine() {
return skipToEndBracket('\n');
}
void skipTo(const char* skip) {
while (fChar < skip) {
this->next();
}
}
void skipToAlpha() {
while (fChar < fEnd && !isalpha(fChar[0])) {
fChar++;
}
}
void skipToAlphaNum() {
while (fChar < fEnd && !isalnum(fChar[0])) {
fChar++;
}
}
bool skipExact(const char* pattern) {
if (!this->startsWith(pattern)) {
return false;
}
this->skipName(pattern);
return true;
}
// differs from skipToNonAlphaNum in that a.b isn't considered a full name,
// since a.b can't be found as a named definition
void skipFullName() {
while (fChar < fEnd && (isalnum(fChar[0])
|| '_' == fChar[0] || '-' == fChar[0]
|| (':' == fChar[0] && fChar +1 < fEnd && ':' == fChar[1]))) {
if (':' == fChar[0] && fChar +1 < fEnd && ':' == fChar[1]) {
fChar++;
}
fChar++;
}
}
bool skipToLineStart() {
if (!this->skipLine()) {
return false;
}
if (!this->eof()) {
return this->skipWhiteSpace();
}
return true;
}
void skipToNonAlphaNum() {
while (fChar < fEnd && (isalnum(fChar[0])
|| '_' == fChar[0] || '-' == fChar[0]
|| (':' == fChar[0] && fChar +1 < fEnd && ':' == fChar[1])
|| ('.' == fChar[0] && fChar + 1 < fEnd && isalpha(fChar[1])))) {
if (':' == fChar[0] && fChar +1 < fEnd && ':' == fChar[1]) {
fChar++;
}
fChar++;
}
}
void skipToSpace() {
while (fChar < fEnd && ' ' != fChar[0]) {
fChar++;
}
}
bool skipName(const char* word) {
size_t len = strlen(word);
if (len < (size_t) (fEnd - fChar) && !strncmp(word, fChar, len)) {
fChar += len;
}
return this->eof() || ' ' >= fChar[0];
}
bool skipSpace() {
while (' ' == this->peek()) {
(void) this->next();
if (fChar >= fEnd) {
return false;
}
}
return true;
}
bool skipWord(const char* word) {
if (!this->skipWhiteSpace()) {
return false;
}
const char* save = fChar;
if (!this->skipName(word)) {
fChar = save;
return false;
}
if (!this->skipWhiteSpace()) {
return false;
}
return true;
}
bool skipWhiteSpace() {
while (' ' >= this->peek()) {
(void) this->next();
if (fChar >= fEnd) {
return false;
}
}
return true;
}
bool startsWith(const char* str) const {
size_t len = strlen(str);
ptrdiff_t lineLen = this->lineLength();
return len <= (size_t) lineLen && 0 == strncmp(str, fChar, len);
}
const char* strnchr(char ch, const char* end) const {
const char* ptr = fChar;
while (ptr < end) {
if (ptr[0] == ch) {
return ptr;
}
++ptr;
}
return nullptr;
}
const char* strnstr(const char *match, const char* end) const {
size_t matchLen = strlen(match);
SkASSERT(matchLen > 0);
ptrdiff_t len = end - fChar;
SkASSERT(len >= 0);
if ((size_t) len < matchLen ) {
return nullptr;
}
size_t count = len - matchLen;
for (size_t index = 0; index <= count; index++) {
if (0 == strncmp(&fChar[index], match, matchLen)) {
return &fChar[index];
}
}
return nullptr;
}
const char* trimmedBracketEnd(const char bracket, OneLine oneLine) const {
int max = (int) (OneLine::kYes == oneLine ? this->lineLength() : fEnd - fChar);
int index = 0;
while (index < max && bracket != fChar[index]) {
++index;
}
SkASSERT(index < max);
while (index > 0 && ' ' >= fChar[index - 1]) {
--index;
}
return fChar + index;
}
const char* trimmedLineEnd() const {
const char* result = this->lineEnd();
while (result > fChar && ' ' >= result[-1]) {
--result;
}
return result;
}
void trimEnd() {
while (fEnd > fStart && ' ' >= fEnd[-1]) {
--fEnd;
}
}
const char* wordEnd() const {
const char* end = fChar;
while (isalnum(end[0]) || '_' == end[0] || '-' == end[0]) {
++end;
}
return end;
}
string fFileName;
const char* fStart;
const char* fLine;
const char* fChar;
const char* fEnd;
size_t fLineCount;
};
class EscapeParser : public TextParser {
public:
EscapeParser(const char* start, const char* end) :
TextParser("", start, end, 0) {
const char* reader = fStart;
fStorage = new char[end - start];
char* writer = fStorage;
while (reader < fEnd) {
char ch = *reader++;
if (ch != '\\') {
*writer++ = ch;
} else {
char ctrl = *reader++;
if (ctrl == 'u') {
unsigned unicode = 0;
for (int i = 0; i < 4; ++i) {
unicode <<= 4;
SkASSERT((reader[0] >= '0' && reader[0] <= '9') ||
(reader[0] >= 'A' && reader[0] <= 'F'));
int nibble = *reader++ - '0';
if (nibble > 9) {
nibble = 'A'- '9' + 1;
}
unicode |= nibble;
}
SkASSERT(unicode < 256);
*writer++ = (unsigned char) unicode;
} else {
SkASSERT(ctrl == 'n');
*writer++ = '\n';
}
}
}
fStart = fLine = fChar = fStorage;
fEnd = writer;
}
virtual ~EscapeParser() {
delete fStorage;
}
private:
char* fStorage;
};
class RootDefinition;
class Definition : public NonAssignable {
public:
enum Type {
kNone,
kWord,
kMark,
kKeyWord,
kBracket,
kPunctuation,
kFileType,
};
enum class TrimExtract {
kNo,
kYes
};
enum class MethodType {
kNone,
kConstructor,
kDestructor,
kOperator,
};
Definition() {}
Definition(const char* start, const char* end, int line, Definition* parent)
: fStart(start)
, fContentStart(start)
, fContentEnd(end)
, fParent(parent)
, fLineCount(line)
, fType(Type::kWord) {
if (parent) {
SkASSERT(parent->fFileName.length() > 0);
fFileName = parent->fFileName;
}
this->setParentIndex();
}
Definition(MarkType markType, const char* start, int line, Definition* parent)
: Definition(markType, start, nullptr, line, parent) {
}
Definition(MarkType markType, const char* start, const char* end, int line, Definition* parent)
: Definition(start, end, line, parent) {
fMarkType = markType;
fType = Type::kMark;
}
Definition(Bracket bracket, const char* start, int lineCount, Definition* parent)
: Definition(start, nullptr, lineCount, parent) {
fBracket = bracket;
fType = Type::kBracket;
}
Definition(KeyWord keyWord, const char* start, const char* end, int lineCount,
Definition* parent)
: Definition(start, end, lineCount, parent) {
fKeyWord = keyWord;
fType = Type::kKeyWord;
}
Definition(Punctuation punctuation, const char* start, int lineCount, Definition* parent)
: Definition(start, nullptr, lineCount, parent) {
fPunctuation = punctuation;
fType = Type::kPunctuation;
}
virtual ~Definition() {}
virtual RootDefinition* asRoot() { SkASSERT(0); return nullptr; }
virtual const RootDefinition* asRoot() const { SkASSERT(0); return nullptr; }
bool 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 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;
}
bool boilerplateEndIf() {
return true;
}
bool checkMethod() const;
void setCanonicalFiddle();
bool crossCheck(const char* tokenName, const Definition& includeToken) const;
bool crossCheck(const Definition& includeToken) const;
bool crossCheckInside(const char* start, const char* end, const Definition& includeToken) const;
bool exampleToScript(string* result) const;
string extractText(TrimExtract trimExtract) const {
string result;
TextParser parser(fFileName, fContentStart, fContentEnd, fLineCount);
int childIndex = 0;
char mc = '#';
while (parser.fChar < parser.fEnd) {
if (TrimExtract::kYes == trimExtract && !parser.skipWhiteSpace()) {
break;
}
if (parser.next() == mc) {
if (parser.next() == mc) {
if (parser.next() == mc) {
mc = parser.next();
}
} else {
// fixme : more work to do if # style comment is in text
// if in method definition, could be alternate method name
--parser.fChar;
if (' ' < parser.fChar[0]) {
if (islower(parser.fChar[0])) {
result += '\n';
parser.skipLine();
} else {
SkASSERT(isupper(parser.fChar[0]));
parser.skipTo(fChildren[childIndex]->fTerminator);
if (mc == parser.fChar[0] && mc == parser.fChar[1]) {
parser.next();
parser.next();
}
childIndex++;
}
} else {
parser.skipLine();
}
continue;
}
} else {
--parser.fChar;
}
const char* end = parser.fEnd;
const char* mark = parser.strnchr(mc, end);
if (mark) {
end = mark;
}
string fragment(parser.fChar, end - parser.fChar);
trim_end(fragment);
if (TrimExtract::kYes == trimExtract) {
trim_start(fragment);
if (result.length()) {
result += '\n';
result += '\n';
}
}
if (TrimExtract::kYes == trimExtract || has_nonwhitespace(fragment)) {
result += fragment;
}
parser.skipTo(end);
}
return result;
}
string fiddleName() const;
string formatFunction() const;
const Definition* hasChild(MarkType markType) const;
const Definition* hasParam(const string& ref) const;
bool isClone() const { return fClone; }
Definition* iRootParent() {
Definition* test = fParent;
while (test) {
if (Type::kKeyWord == test->fType && KeyWord::kClass == test->fKeyWord) {
return test;
}
test = test->fParent;
}
return nullptr;
}
virtual bool isRoot() const { return false; }
int length() const {
return (int) (fContentEnd - fContentStart);
}
bool methodHasReturn(const string& name, TextParser* methodParser) const;
string methodName() const;
bool nextMethodParam(TextParser* methodParser, const char** nextEndPtr,
string* paramName) const;
bool paramsMatch(const string& fullRef, const string& name) const;
string printableName() const {
string result(fName);
std::replace(result.begin(), result.end(), '_', ' ');
return result;
}
virtual RootDefinition* rootParent() { SkASSERT(0); return nullptr; }
void setParentIndex() {
fParentIndex = fParent ? (int) fParent->fTokens.size() : -1;
}
string fText; // if text is constructed instead of in a file, it's put here
const char* fStart = nullptr; // .. in original text file, or the start of fText
const char* fContentStart; // start past optional markup name
string fName;
string fFiddle; // if its a constructor or operator, fiddle name goes here
const char* fContentEnd = nullptr; // the end of the contained text
const char* fTerminator = nullptr; // the end of the markup, normally ##\n or \n
Definition* fParent = nullptr;
list<Definition> fTokens;
vector<Definition*> fChildren;
string fHash; // generated by fiddle
string fFileName;
size_t fLineCount = 0;
int fParentIndex = 0;
MarkType fMarkType = MarkType::kNone;
KeyWord fKeyWord = KeyWord::kNone;
Bracket fBracket = Bracket::kNone;
Punctuation fPunctuation = Punctuation::kNone;
MethodType fMethodType = MethodType::kNone;
Type fType = Type::kNone;
bool fClone = false;
bool fCloned = false;
bool fPrivate = false;
bool fShort = false;
bool fMemberStart = false;
mutable bool fVisited = false;
};
class RootDefinition : public Definition {
public:
RootDefinition() {
}
RootDefinition(MarkType markType, const char* start, int line, Definition* parent)
: Definition(markType, start, line, parent) {
}
RootDefinition(MarkType markType, const char* start, const char* end, int line,
Definition* parent) : Definition(markType, start, end, line, parent) {
}
~RootDefinition() override {
for (auto& iter : fBranches) {
delete iter.second;
}
}
RootDefinition* asRoot() override { return this; }
const RootDefinition* asRoot() const override { return this; }
void clearVisited();
bool dumpUnVisited();
const Definition* find(const string& ref) const;
bool isRoot() const override { return true; }
RootDefinition* rootParent() override { return fRootParent; }
void setRootParent(RootDefinition* rootParent) { fRootParent = rootParent; }
unordered_map<string, RootDefinition*> fBranches;
unordered_map<string, Definition> fLeaves;
private:
RootDefinition* fRootParent = nullptr;
};
struct IClassDefinition : public Definition {
unordered_map<string, Definition*> fEnums;
unordered_map<string, Definition*> fMembers;
unordered_map<string, Definition*> fMethods;
unordered_map<string, Definition*> fStructs;
};
struct Reference {
Reference()
: fLocation(nullptr)
, fDefinition(nullptr) {
}
const char* fLocation; // .. in original text file
const Definition* fDefinition;
};
struct TypeNames {
const char* fName;
MarkType fMarkType;
};
class ParserCommon : public TextParser {
public:
ParserCommon() : TextParser()
, fParent(nullptr)
{
}
virtual ~ParserCommon() {
}
void addDefinition(Definition* def) {
fParent->fChildren.push_back(def);
fParent = def;
}
void indentToColumn(int column) {
SkASSERT(column >= fColumn);
#if STDOUT_TO_IDE_OUT
SkDebugf("%*s", column - fColumn, "");
#endif
fprintf(fOut, "%*s", column - fColumn, "");
fColumn = column;
fSpaces += column - fColumn;
}
bool leadingPunctuation(const char* str, size_t len) const {
if (!fPendingSpace) {
return false;
}
if (len < 2) {
return false;
}
if ('.' != str[0] && ',' != str[0] && ';' != str[0] && ':' != str[0]) {
return false;
}
return ' ' >= str[1];
}
void lf(int count) {
fPendingLF = SkTMax(fPendingLF, count);
this->nl();
}
void lfAlways(int count) {
this->lf(count);
this->writePending();
}
void lfcr() {
this->lf(1);
}
void nl() {
fLinefeeds = 0;
fSpaces = 0;
fColumn = 0;
fPendingSpace = false;
}
bool parseFile(const char* file, const char* suffix);
virtual bool parseFromFile(const char* path) = 0;
bool parseSetup(const char* path);
void popObject() {
fParent->fContentEnd = fParent->fTerminator = fChar;
fParent = fParent->fParent;
}
virtual void reset() = 0;
void resetCommon() {
fLine = fChar = fStart;
fLineCount = 0;
fParent = nullptr;
fIndent = 0;
fOut = nullptr;
fMaxLF = 2;
fPendingLF = 0;
fPendingSpace = false;
nl();
}
void setAsParent(Definition* definition) {
if (fParent) {
fParent->fChildren.push_back(definition);
definition->fParent = fParent;
}
fParent = definition;
}
void singleLF() {
fMaxLF = 1;
}
bool writeBlockTrim(int size, const char* data) {
while (size && ' ' >= data[0]) {
++data;
--size;
}
while (size && ' ' >= data[size - 1]) {
--size;
}
if (size <= 0) {
return false;
}
SkASSERT(size < 8000);
if (size > 3 && !strncmp("#end", data, 4)) {
fMaxLF = 1;
}
if (this->leadingPunctuation(data, (size_t) size)) {
fPendingSpace = false;
}
writePending();
#if STDOUT_TO_IDE_OUT
string check(data, size);
SkDebugf("%s", check.c_str());
#endif
fprintf(fOut, "%.*s", size, data);
int added = 0;
while (size > 0 && '\n' != data[--size]) {
++added;
}
fColumn = size ? added : fColumn + added;
fSpaces = 0;
fLinefeeds = 0;
fMaxLF = added > 2 && !strncmp("#if", &data[size + (size > 0)], 3) ? 1 : 2;
return true;
}
void writeBlock(int size, const char* data) {
SkAssertResult(writeBlockTrim(size, data));
}
void writeCommentHeader() {
this->lf(2);
this->writeString("/**");
this->writeSpace();
}
void writeCommentTrailer() {
this->writeString("*/");
this->lfcr();
}
// write a pending space, so that two consecutive calls
// don't double write, and trailing spaces on lines aren't written
void writeSpace() {
SkASSERT(!fPendingLF);
SkASSERT(!fLinefeeds);
SkASSERT(fColumn > 0);
SkASSERT(!fSpaces);
fPendingSpace = true;
}
void writeString(const char* str) {
SkASSERT(strlen(str) > 0);
SkASSERT(' ' < str[0]);
SkASSERT(' ' < str[strlen(str) - 1]);
if (this->leadingPunctuation(str, strlen(str))) {
fPendingSpace = false;
}
writePending();
#if STDOUT_TO_IDE_OUT
SkDebugf("%s", str);
#endif
SkASSERT(!strchr(str, '\n'));
fprintf(fOut, "%s", str);
fColumn += strlen(str);
fSpaces = 0;
fLinefeeds = 0;
fMaxLF = 2;
}
void writePending() {
fPendingLF = SkTMin(fPendingLF, fMaxLF);
bool wroteLF = false;
while (fLinefeeds < fPendingLF) {
#if STDOUT_TO_IDE_OUT
SkDebugf("\n");
#endif
fprintf(fOut, "\n");
++fLinefeeds;
wroteLF = true;
}
fPendingLF = 0;
if (wroteLF) {
SkASSERT(0 == fColumn);
SkASSERT(fIndent >= fSpaces);
#if STDOUT_TO_IDE_OUT
SkDebugf("%*s", fIndent - fSpaces, "");
#endif
fprintf(fOut, "%*s", fIndent - fSpaces, "");
fColumn = fIndent;
fSpaces = fIndent;
}
if (fPendingSpace) {
#if STDOUT_TO_IDE_OUT
SkDebugf(" ");
#endif
fprintf(fOut, " ");
++fColumn;
fPendingSpace = false;
}
}
unordered_map<string, sk_sp<SkData>> fRawData;
unordered_map<string, vector<char>> fLFOnly;
Definition* fParent;
FILE* fOut;
int fLinefeeds; // number of linefeeds last written, zeroed on non-space
int fMaxLF; // number of linefeeds allowed
int fPendingLF; // number of linefeeds to write (can be suppressed)
int fSpaces; // number of spaces (indent) last written, zeroed on non-space
int fColumn; // current column; number of chars past last linefeed
int fIndent; // desired indention
bool fPendingSpace; // a space should preceed the next string or block
private:
typedef TextParser INHERITED;
};
class BmhParser : public ParserCommon {
public:
enum class MarkLookup {
kRequire,
kAllowUnknown,
};
enum class Resolvable {
kNo, // neither resolved nor output
kYes, // resolved, output
kOut, // not resolved, but output
};
enum class Exemplary {
kNo,
kYes,
kOptional,
};
#define M(mt) (1LL << (int) MarkType::k##mt)
#define M_D M(Description)
#define M_CS M(Class) | M(Struct)
#define M_ST M(Subtopic) | M(Topic)
#define M_CSST M_CS | M_ST
#ifdef M_E
#undef M_E
#endif
#define M_E M(Enum) | M(EnumClass)
#define R_Y Resolvable::kYes
#define R_N Resolvable::kNo
#define R_O Resolvable::kOut
#define E_Y Exemplary::kYes
#define E_N Exemplary::kNo
#define E_O Exemplary::kOptional
BmhParser() : ParserCommon()
, fMaps {
// names without formal definitions (e.g. Column) aren't included
// fill in other names once they're actually used
{ "", nullptr, MarkType::kNone, R_Y, E_N, 0 }
, { "A", nullptr, MarkType::kAnchor, R_Y, E_N, 0 }
, { "Alias", nullptr, MarkType::kAlias, R_N, E_N, 0 }
, { "Bug", nullptr, MarkType::kBug, R_N, E_N, 0 }
, { "Class", &fClassMap, MarkType::kClass, R_Y, E_O, M_CSST | M(Root) }
, { "Code", nullptr, MarkType::kCode, R_Y, E_N, M_CSST | M_E }
, { "", nullptr, MarkType::kColumn, R_Y, E_N, M(Row) }
, { "", nullptr, MarkType::kComment, R_N, E_N, 0 }
, { "Const", &fConstMap, MarkType::kConst, R_Y, E_N, M_E | M_ST }
, { "Define", nullptr, MarkType::kDefine, R_O, E_N, M_ST }
, { "DefinedBy", nullptr, MarkType::kDefinedBy, R_N, E_N, M(Method) }
, { "Deprecated", nullptr, MarkType::kDeprecated, R_Y, E_N, 0 }
, { "Description", nullptr, MarkType::kDescription, R_Y, E_N, M(Example) }
, { "Doxygen", nullptr, MarkType::kDoxygen, R_Y, E_N, 0 }
, { "Enum", &fEnumMap, MarkType::kEnum, R_Y, E_O, M_CSST | M(Root) }
, { "EnumClass", &fClassMap, MarkType::kEnumClass, R_Y, E_O, M_CSST | M(Root) }
, { "Error", nullptr, MarkType::kError, R_N, E_N, M(Example) }
, { "Example", nullptr, MarkType::kExample, R_O, E_N, M_CSST | M_E | M(Method) }
, { "Experimental", nullptr, MarkType::kExperimental, R_Y, E_N, 0 }
, { "External", nullptr, MarkType::kExternal, R_Y, E_N, M(Root) }
, { "File", nullptr, MarkType::kFile, R_N, E_N, M(Track) }
, { "Formula", nullptr, MarkType::kFormula, R_O, E_N, M_ST | M(Method) | M_D }
, { "Function", nullptr, MarkType::kFunction, R_O, E_N, M(Example) }
, { "Height", nullptr, MarkType::kHeight, R_N, E_N, M(Example) }
, { "Image", nullptr, MarkType::kImage, R_N, E_N, M(Example) }
, { "Legend", nullptr, MarkType::kLegend, R_Y, E_N, M(Table) }
, { "", nullptr, MarkType::kLink, R_Y, E_N, M(Anchor) }
, { "List", nullptr, MarkType::kList, R_Y, E_N, M(Method) | M_CSST | M_E | M_D }
, { "", nullptr, MarkType::kMarkChar, R_N, E_N, 0 }
, { "Member", nullptr, MarkType::kMember, R_Y, E_N, M(Class) | M(Struct) }
, { "Method", &fMethodMap, MarkType::kMethod, R_Y, E_Y, M_CSST }
, { "NoExample", nullptr, MarkType::kNoExample, R_Y, E_N, 0 }
, { "Param", nullptr, MarkType::kParam, R_Y, E_N, M(Method) }
, { "Platform", nullptr, MarkType::kPlatform, R_Y, E_N, M(Example) }
, { "Private", nullptr, MarkType::kPrivate, R_N, E_N, 0 }
, { "Return", nullptr, MarkType::kReturn, R_Y, E_N, M(Method) }
, { "", nullptr, MarkType::kRoot, R_Y, E_N, 0 }
, { "", nullptr, MarkType::kRow, R_Y, E_N, M(Table) | M(List) }
, { "SeeAlso", nullptr, MarkType::kSeeAlso, R_Y, E_N, M_CSST | M_E | M(Method) }
, { "StdOut", nullptr, MarkType::kStdOut, R_N, E_N, M(Example) }
, { "Struct", &fClassMap, MarkType::kStruct, R_Y, E_O, M(Class) | M(Root) | M_ST }
, { "Substitute", nullptr, MarkType::kSubstitute, R_N, E_N, M_ST }
, { "Subtopic", nullptr, MarkType::kSubtopic, R_Y, E_Y, M_CSST }
, { "Table", nullptr, MarkType::kTable, R_Y, E_N, M(Method) | M_CSST | M_E }
, { "Template", nullptr, MarkType::kTemplate, R_Y, E_N, 0 }
, { "", nullptr, MarkType::kText, R_Y, E_N, 0 }
, { "Time", nullptr, MarkType::kTime, R_Y, E_N, M(Track) }
, { "ToDo", nullptr, MarkType::kToDo, R_N, E_N, 0 }
, { "Topic", nullptr, MarkType::kTopic, R_Y, E_Y, M_CS | M(Root) | M(Topic) }
, { "Track", nullptr, MarkType::kTrack, R_Y, E_N, M_E | M_ST }
, { "Typedef", &fTypedefMap, MarkType::kTypedef, R_Y, E_N, M(Subtopic) | M(Topic) }
, { "", nullptr, MarkType::kUnion, R_Y, E_N, 0 }
, { "Volatile", nullptr, MarkType::kVolatile, R_N, E_N, M(StdOut) }
, { "Width", nullptr, MarkType::kWidth, R_N, E_N, M(Example) } }
{
this->reset();
}
#undef R_O
#undef R_N
#undef R_Y
#undef M_E
#undef M_CSST
#undef M_ST
#undef M_CS
#undef M_D
#undef M
~BmhParser() override {}
bool addDefinition(const char* defStart, bool hasEnd, MarkType markType,
const vector<string>& typeNameBuilder);
bool childOf(MarkType markType) const;
string className(MarkType markType);
bool collectExternals();
int endHashCount() const;
RootDefinition* findBmhObject(MarkType markType, const string& typeName) {
auto map = fMaps[(int) markType].fBmh;
if (!map) {
return nullptr;
}
return &(*map)[typeName];
}
bool findDefinitions();
MarkType getMarkType(MarkLookup lookup) const;
bool hasEndToken() const;
string memberName();
string methodName();
const Definition* parentSpace() const;
bool parseFromFile(const char* path) override {
if (!INHERITED::parseSetup(path)) {
return false;
}
fCheckMethods = !strstr(path, "undocumented.bmh");
return findDefinitions();
}
bool popParentStack(Definition* definition);
void reset() override {
INHERITED::resetCommon();
fRoot = nullptr;
fMC = '#';
fInChar = false;
fInCharCommentString = false;
fInComment = false;
fInEnum = false;
fInString = false;
fCheckMethods = false;
}
bool skipNoName();
bool skipToDefinitionEnd(MarkType markType);
void spellCheck(const char* match) const;
vector<string> topicName();
vector<string> typeName(MarkType markType, bool* expectEnd);
string uniqueName(const string& base, MarkType markType);
string uniqueRootName(const string& base, MarkType markType);
void validate() const;
string word(const string& prefix, const string& delimiter);
public:
struct DefinitionMap {
const char* fName;
unordered_map<string, RootDefinition>* fBmh;
MarkType fMarkType;
Resolvable fResolve;
Exemplary fExemplary; // worthy of an example
uint64_t fParentMask;
};
DefinitionMap fMaps[Last_MarkType + 1];
forward_list<RootDefinition> fTopics;
forward_list<Definition> fMarkup;
forward_list<RootDefinition> fExternals;
vector<string> fInputFiles;
unordered_map<string, RootDefinition> fClassMap;
unordered_map<string, RootDefinition> fConstMap;
unordered_map<string, RootDefinition> fEnumMap;
unordered_map<string, RootDefinition> fMethodMap;
unordered_map<string, RootDefinition> fTypedefMap;
unordered_map<string, Definition*> fTopicMap;
unordered_map<string, Definition*> fAliasMap;
RootDefinition* fRoot;
mutable char fMC; // markup character
bool fAnonymous;
bool fCloned;
bool fInChar;
bool fInCharCommentString;
bool fInEnum;
bool fInComment;
bool fInString;
bool fCheckMethods;
private:
typedef ParserCommon INHERITED;
};
class IncludeParser : public ParserCommon {
public:
enum class IsStruct {
kNo,
kYes,
};
IncludeParser() : ParserCommon()
, fMaps {
{ nullptr, MarkType::kNone }
, { nullptr, MarkType::kAnchor }
, { nullptr, MarkType::kAlias }
, { nullptr, MarkType::kBug }
, { nullptr, MarkType::kClass }
, { nullptr, MarkType::kCode }
, { nullptr, MarkType::kColumn }
, { nullptr, MarkType::kComment }
, { nullptr, MarkType::kConst }
, { &fIDefineMap, MarkType::kDefine }
, { nullptr, MarkType::kDefinedBy }
, { nullptr, MarkType::kDeprecated }
, { nullptr, MarkType::kDescription }
, { nullptr, MarkType::kDoxygen }
, { &fIEnumMap, MarkType::kEnum }
, { &fIEnumMap, MarkType::kEnumClass }
, { nullptr, MarkType::kError }
, { nullptr, MarkType::kExample }
, { nullptr, MarkType::kExperimental }
, { nullptr, MarkType::kExternal }
, { nullptr, MarkType::kFile }
, { nullptr, MarkType::kFormula }
, { nullptr, MarkType::kFunction }
, { nullptr, MarkType::kHeight }
, { nullptr, MarkType::kImage }
, { nullptr, MarkType::kLegend }
, { nullptr, MarkType::kLink }
, { nullptr, MarkType::kList }
, { nullptr, MarkType::kMarkChar }
, { nullptr, MarkType::kMember }
, { nullptr, MarkType::kMethod }
, { nullptr, MarkType::kNoExample }
, { nullptr, MarkType::kParam }
, { nullptr, MarkType::kPlatform }
, { nullptr, MarkType::kPrivate }
, { nullptr, MarkType::kReturn }
, { nullptr, MarkType::kRoot }
, { nullptr, MarkType::kRow }
, { nullptr, MarkType::kSeeAlso }
, { nullptr, MarkType::kStdOut }
, { &fIStructMap, MarkType::kStruct }
, { nullptr, MarkType::kSubstitute }
, { nullptr, MarkType::kSubtopic }
, { nullptr, MarkType::kTable }
, { &fITemplateMap, MarkType::kTemplate }
, { nullptr, MarkType::kText }
, { nullptr, MarkType::kTime }
, { nullptr, MarkType::kToDo }
, { nullptr, MarkType::kTopic }
, { nullptr, MarkType::kTrack }
, { &fITypedefMap, MarkType::kTypedef }
, { &fIUnionMap, MarkType::kUnion }
, { nullptr, MarkType::kVolatile }
, { nullptr, MarkType::kWidth } }
{
this->reset();
}
~IncludeParser() override {}
void addKeyword(KeyWord keyWord);
void addPunctuation(Punctuation punctuation) {
fParent->fTokens.emplace_back(punctuation, fChar, fLineCount, fParent);
}
void addWord() {
fParent->fTokens.emplace_back(fIncludeWord, fChar, fLineCount, fParent);
fIncludeWord = nullptr;
}
void checkForMissingParams(const vector<string>& methodParams,
const vector<string>& foundParams);
bool checkForWord();
string className() const;
bool crossCheck(BmhParser& );
IClassDefinition* defineClass(const Definition& includeDef, const string& className);
void dumpClassTokens(IClassDefinition& classDef);
void dumpComment(Definition* token);
void dumpTokens();
bool findComments(const Definition& includeDef, Definition* markupDef);
Definition* findIncludeObject(const Definition& includeDef, MarkType markType,
const string& typeName) {
typedef Definition* DefinitionPtr;
unordered_map<string, Definition>* map = fMaps[(int) markType].fInclude;
if (!map) {
return reportError<DefinitionPtr>("invalid mark type");
}
string name = this->uniqueName(*map, typeName);
Definition& markupDef = (*map)[name];
if (markupDef.fStart) {
return reportError<DefinitionPtr>("definition already defined");
}
markupDef.fFileName = fFileName;
markupDef.fStart = includeDef.fStart;
markupDef.fContentStart = includeDef.fStart;
markupDef.fName = name;
markupDef.fContentEnd = includeDef.fContentEnd;
markupDef.fTerminator = includeDef.fTerminator;
markupDef.fParent = fParent;
markupDef.fLineCount = includeDef.fLineCount;
markupDef.fMarkType = markType;
markupDef.fKeyWord = includeDef.fKeyWord;
markupDef.fType = Definition::Type::kMark;
return &markupDef;
}
static KeyWord FindKey(const char* start, const char* end);
void keywordEnd();
void keywordStart(const char* keyword);
bool parseChar();
bool parseComment(const string& filename, const char* start, const char* end, int lineCount,
Definition* markupDef);
bool parseClass(Definition* def, IsStruct);
bool parseDefine();
bool parseEnum(Definition* child, Definition* markupDef);
bool parseFromFile(const char* path) override {
if (!INHERITED::parseSetup(path)) {
return false;
}
string name(path);
return parseInclude(name);
}
bool parseInclude(const string& name);
bool parseMember(Definition* child, Definition* markupDef);
bool parseMethod(Definition* child, Definition* markupDef);
bool parseObject(Definition* child, Definition* markupDef);
bool parseObjects(Definition* parent, Definition* markupDef);
bool parseTemplate();
bool parseTypedef();
bool parseUnion();
void popBracket() {
SkASSERT(Definition::Type::kBracket == fParent->fType);
this->popObject();
Bracket bracket = this->topBracket();
this->setBracketShortCuts(bracket);
}
void pushBracket(Bracket bracket) {
this->setBracketShortCuts(bracket);
fParent->fTokens.emplace_back(bracket, fChar, fLineCount, fParent);
Definition* container = &fParent->fTokens.back();
this->addDefinition(container);
}
void reset() override {
INHERITED::resetCommon();
fRootTopic = nullptr;
fInBrace = nullptr;
fIncludeWord = nullptr;
fPrev = '\0';
fInChar = false;
fInCharCommentString = false;
fInComment = false;
fInEnum = false;
fInFunction = false;
fInString = false;
}
void setBracketShortCuts(Bracket bracket) {
fInComment = Bracket::kSlashSlash == bracket || Bracket::kSlashStar == bracket;
fInString = Bracket::kString == bracket;
fInChar = Bracket::kChar == bracket;
fInCharCommentString = fInChar || fInComment || fInString;
}
Bracket topBracket() const {
Definition* parent = fParent;
while (parent && Definition::Type::kBracket != parent->fType) {
parent = parent->fParent;
}
return parent ? parent->fBracket : Bracket::kNone;
}
template <typename T>
string uniqueName(const unordered_map<string, T>& m, const string& typeName) {
string base(typeName.size() > 0 ? typeName : "_anonymous");
string name(base);
int anonCount = 1;
do {
auto iter = m.find(name);
if (iter == m.end()) {
return name;
}
name = base + '_';
name += to_string(++anonCount);
} while (true);
// should never get here
return string();
}
void validate() const;
protected:
static void ValidateKeyWords();
struct DefinitionMap {
unordered_map<string, Definition>* fInclude;
MarkType fMarkType;
};
DefinitionMap fMaps[Last_MarkType + 1];
unordered_map<string, Definition> fIncludeMap;
unordered_map<string, IClassDefinition> fIClassMap;
unordered_map<string, Definition> fIDefineMap;
unordered_map<string, Definition> fIEnumMap;
unordered_map<string, Definition> fIStructMap;
unordered_map<string, Definition> fITemplateMap;
unordered_map<string, Definition> fITypedefMap;
unordered_map<string, Definition> fIUnionMap;
Definition* fRootTopic;
Definition* fInBrace;
const char* fIncludeWord;
char fPrev;
bool fInChar;
bool fInCharCommentString;
bool fInComment;
bool fInEnum;
bool fInFunction;
bool fInString;
typedef ParserCommon INHERITED;
};
class IncludeWriter : public IncludeParser {
public:
enum class Word {
kStart,
kCap,
kFirst,
kUnderline,
kMixed,
};
enum class PunctuationState {
kStart,
kDelimiter,
kPeriod,
kSpace,
};
enum class Wrote {
kNone,
kLF,
kChars,
};
IncludeWriter() : IncludeParser() {}
~IncludeWriter() override {}
bool contentFree(int size, const char* data) const {
while (size > 0 && data[0] <= ' ') {
--size;
++data;
}
while (size > 0 && data[size - 1] <= ' ') {
--size;
}
return 0 == size;
}
void enumHeaderOut(const RootDefinition* root, const Definition& child);
void enumMembersOut(const RootDefinition* root, const Definition& child);
void enumSizeItems(const Definition& child);
int lookupMethod(const PunctuationState punctuation, const Word word,
const int start, const int run, int lastWrite,
const char* data);
int lookupReference(const PunctuationState punctuation, const Word word,
const int start, const int run, int lastWrite, const char last,
const char* data);
void methodOut(const Definition* method, const Definition& child);
bool populate(Definition* def, RootDefinition* root);
bool populate(BmhParser& bmhParser);
void reset() override {
INHERITED::resetCommon();
fBmhMethod = nullptr;
fBmhParser = nullptr;
fEnumDef = nullptr;
fMethodDef = nullptr;
fStructDef = nullptr;
fAnonymousEnumCount = 1;
fInStruct = false;
}
string resolveMethod(const char* start, const char* end, bool first);
string resolveRef(const char* start, const char* end, bool first);
Wrote rewriteBlock(int size, const char* data);
void structMemberOut(const Definition* memberStart, const Definition& child);
void structOut(const Definition* root, const Definition& child,
const char* commentStart, const char* commentEnd);
void structSizeMembers(Definition& child);
private:
BmhParser* fBmhParser;
Definition* fDeferComment;
const Definition* fBmhMethod;
const Definition* fEnumDef;
const Definition* fMethodDef;
const Definition* fStructDef;
const char* fContinuation; // used to construct paren-qualified method name
int fAnonymousEnumCount;
int fEnumItemValueTab;
int fEnumItemCommentTab;
int fStructMemberTab;
int fStructCommentTab;
bool fInStruct;
typedef IncludeParser INHERITED;
};
class FiddleParser : public ParserCommon {
public:
FiddleParser(BmhParser* bmh) : ParserCommon()
, fBmhParser(bmh) {
this->reset();
}
Definition* findExample(const string& name) const;
bool parseFromFile(const char* path) override {
if (!INHERITED::parseSetup(path)) {
return false;
}
return parseFiddles();
}
void reset() override {
INHERITED::resetCommon();
}
private:
bool parseFiddles();
BmhParser* fBmhParser; // must be writable; writes example hash
typedef ParserCommon INHERITED;
};
class HackParser : public ParserCommon {
public:
HackParser() : ParserCommon() {
this->reset();
}
bool parseFromFile(const char* path) override {
if (!INHERITED::parseSetup(path)) {
return false;
}
return hackFiles();
}
void reset() override {
INHERITED::resetCommon();
}
private:
bool hackFiles();
typedef ParserCommon INHERITED;
};
class MdOut : public ParserCommon {
public:
MdOut(const BmhParser& bmh) : ParserCommon()
, fBmhParser(bmh) {
this->reset();
}
bool buildReferences(const char* path, const char* outDir);
private:
enum class TableState {
kNone,
kRow,
kColumn,
};
string addReferences(const char* start, const char* end, BmhParser::Resolvable );
bool buildRefFromFile(const char* fileName, const char* outDir);
void childrenOut(const Definition* def, const char* contentStart);
const Definition* isDefined(const TextParser& parser, const string& ref, bool report) const;
string linkName(const Definition* ) const;
string linkRef(const string& leadingSpaces, const Definition*, const string& ref) const;
void markTypeOut(Definition* );
void mdHeaderOut(int depth) { mdHeaderOutLF(depth, 2); }
void mdHeaderOutLF(int depth, int lf);
bool parseFromFile(const char* path) override {
return true;
}
void reset() override {
INHERITED::resetCommon();
fMethod = nullptr;
fRoot = nullptr;
fTableState = TableState::kNone;
fHasFiddle = false;
fInDescription = false;
fInList = false;
}
BmhParser::Resolvable resolvable(MarkType markType) {
if ((MarkType::kExample == markType
|| MarkType::kFunction == markType) && fHasFiddle) {
return BmhParser::Resolvable::kNo;
}
return fBmhParser.fMaps[(int) markType].fResolve;
}
void resolveOut(const char* start, const char* end, BmhParser::Resolvable );
const BmhParser& fBmhParser;
Definition* fMethod;
RootDefinition* fRoot;
TableState fTableState;
bool fHasFiddle;
bool fInDescription; // FIXME: for now, ignore unfound camelCase in description since it may
// be defined in example which at present cannot be linked to
bool fInList;
typedef ParserCommon INHERITED;
};
// some methods cannot be trivially parsed; look for class-name / ~ / operator
class MethodParser : public TextParser {
public:
MethodParser(const string& className, const string& fileName,
const char* start, const char* end, int lineCount)
: TextParser(fileName, start, end, lineCount)
, fClassName(className) {
}
void skipToMethodStart() {
if (!fClassName.length()) {
this->skipToAlphaNum();
return;
}
while (!this->eof() && !isalnum(this->peek()) && '~' != this->peek()) {
this->next();
}
}
void skipToMethodEnd() {
if (this->eof()) {
return;
}
if (fClassName.length()) {
if ('~' == this->peek()) {
this->next();
if (!this->startsWith(fClassName.c_str())) {
--fChar;
return;
}
}
if (this->startsWith(fClassName.c_str()) || this->startsWith("operator")) {
const char* ptr = this->anyOf(" (");
if (ptr && '(' == *ptr) {
this->skipToEndBracket(')');
SkAssertResult(')' == this->next());
return;
}
}
}
if (this->startsWith("Sk") && this->wordEndsWith(".h")) { // allow include refs
this->skipToNonAlphaNum();
} else {
this->skipFullName();
}
}
bool wordEndsWith(const char* str) const {
const char* space = this->strnchr(' ', fEnd);
if (!space) {
return false;
}
size_t len = strlen(str);
if (space < fChar + len) {
return false;
}
return !strncmp(str, space - len, len);
}
private:
string fClassName;
typedef TextParser INHERITED;
};
#endif