ab2621d3e2
- make descriptions of table entries phrases instead of sentences; lower case start, no ending period (not enforced, yet) - add #Line markup to move one line descriptions to the #Method body. Later, will generate tables like Member_Functions from this - add #In markup to associate a #Method with a #Subtopic. Later, will generate tables of related methods from this - remove return type from operator overloads in tables - add new colorTypes to examples that index into arrays of strings to name them Docs-Preview: https://skia.org/?cl=100422 TBR=caryclark@google.com Bug: skia:6898 Change-Id: I8558048866369f419f1944832b99c05da3fd52bb Reviewed-on: https://skia-review.googlesource.com/100422 Reviewed-by: Cary Clark <caryclark@skia.org> Commit-Queue: Cary Clark <caryclark@skia.org>
483 lines
16 KiB
C++
483 lines
16 KiB
C++
/*
|
|
* Copyright 2018 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"
|
|
|
|
|
|
// Check that mutiple like-named methods are under one Subtopic
|
|
|
|
// Check that all subtopics are in table of contents
|
|
|
|
// Check that SeeAlso reference each other
|
|
|
|
// Would be nice to check if other classes have 'create' methods that are included
|
|
// SkSurface::makeImageSnapShot should be referenced under SkImage 'creators'
|
|
|
|
class SelfChecker {
|
|
public:
|
|
SelfChecker(const BmhParser& bmh)
|
|
: fBmhParser(bmh)
|
|
{}
|
|
|
|
bool check() {
|
|
for (const auto& topic : fBmhParser.fTopicMap) {
|
|
Definition* topicDef = topic.second;
|
|
if (topicDef->fParent) {
|
|
continue;
|
|
}
|
|
if (!topicDef->isRoot()) {
|
|
return fBmhParser.reportError<bool>("expected root topic");
|
|
}
|
|
fRoot = topicDef->asRoot();
|
|
if (!this->checkMethodSummary()) {
|
|
return false;
|
|
}
|
|
if (!this->checkMethodSubtopic()) {
|
|
return false;
|
|
}
|
|
if (!this->checkSubtopicSummary()) {
|
|
return false;
|
|
}
|
|
if (!this->checkConstructorsSummary()) {
|
|
return false;
|
|
}
|
|
if (!this->checkOperatorsSummary()) {
|
|
return false;
|
|
}
|
|
if (!this->checkSeeAlso()) {
|
|
return false;
|
|
}
|
|
if (!this->checkCreators()) {
|
|
return false;
|
|
}
|
|
if (!this->checkRelatedFunctions()) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
protected:
|
|
// Check that all constructors are in a table of contents
|
|
// should be 'creators' instead of constructors?
|
|
bool checkConstructorsSummary() {
|
|
for (auto& rootChild : fRoot->fChildren) {
|
|
if (!this->isStructOrClass(rootChild)) {
|
|
continue;
|
|
}
|
|
auto& cs = rootChild;
|
|
auto constructors = this->findTopic("Constructors", Optional::kYes);
|
|
if (constructors && MarkType::kSubtopic != constructors->fMarkType) {
|
|
return constructors->reportError<bool>("expected #Subtopic Constructors");
|
|
}
|
|
vector<string> constructorEntries;
|
|
if (constructors) {
|
|
if (!this->collectEntries(constructors, &constructorEntries)) {
|
|
return false;
|
|
}
|
|
}
|
|
// mark corresponding methods as visited (may be more than one per entry)
|
|
for (auto& csChild : cs->fChildren) {
|
|
if (MarkType::kMethod != csChild->fMarkType) {
|
|
// only check methods for now
|
|
continue;
|
|
}
|
|
string name;
|
|
if (!this->childName(csChild, &name)) {
|
|
return false;
|
|
}
|
|
string returnType;
|
|
if (Definition::MethodType::kConstructor != csChild->fMethodType &&
|
|
Definition::MethodType::kDestructor != csChild->fMethodType) {
|
|
string makeCheck = name.substr(0, 4);
|
|
if ("Make" != makeCheck && "make" != makeCheck) {
|
|
continue;
|
|
}
|
|
// for now, assume return type of interest is first word to start Sk
|
|
string search(csChild->fStart, csChild->fContentStart - csChild->fStart);
|
|
auto end = search.find(makeCheck);
|
|
if (string::npos == end) {
|
|
return csChild->reportError<bool>("expected Make in content");
|
|
}
|
|
search = search.substr(0, end);
|
|
if (string::npos == search.find(cs->fName)) {
|
|
// if return value doesn't match current struct or class, look in
|
|
// returned struct / class instead
|
|
auto sk = search.find("Sk");
|
|
if (string::npos != sk) {
|
|
// todo: build class name, find it, search for match in its overview
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
if (constructorEntries.end() ==
|
|
std::find(constructorEntries.begin(), constructorEntries.end(), name)) {
|
|
return csChild->reportError<bool>("missing constructor in Constructors");
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool checkCreators() {
|
|
return true;
|
|
}
|
|
|
|
bool checkMethodSubtopic() {
|
|
return true;
|
|
}
|
|
|
|
// Check that summary contains all methods
|
|
bool checkMethodSummary() {
|
|
// look for struct or class in fChildren
|
|
const Definition* cs = this->classOrStruct();
|
|
if (!cs) {
|
|
return true; // topics may not have included classes or structs
|
|
}
|
|
auto memberFunctions = this->findTopic("Member_Functions", Optional::kNo);
|
|
if (MarkType::kSubtopic != memberFunctions->fMarkType) {
|
|
return memberFunctions->reportError<bool>("expected #Subtopic Member_Functions");
|
|
}
|
|
vector<string> methodEntries; // build map of overview entries
|
|
if (!this->collectEntries(memberFunctions, &methodEntries)) {
|
|
return false;
|
|
}
|
|
// mark corresponding methods as visited (may be more than one per entry)
|
|
for (auto& csChild : cs->fChildren) {
|
|
if (MarkType::kMethod != csChild->fMarkType) {
|
|
// only check methods for now
|
|
continue;
|
|
}
|
|
if (Definition::MethodType::kConstructor == csChild->fMethodType) {
|
|
continue;
|
|
}
|
|
if (Definition::MethodType::kDestructor == csChild->fMethodType) {
|
|
continue;
|
|
}
|
|
if (Definition::MethodType::kOperator == csChild->fMethodType) {
|
|
continue;
|
|
}
|
|
string name;
|
|
if (!this->childName(csChild, &name)) {
|
|
return false;
|
|
}
|
|
if (methodEntries.end() ==
|
|
std::find(methodEntries.begin(), methodEntries.end(), name)) {
|
|
return csChild->reportError<bool>("missing method in Member_Functions");
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Check that all operators are in a table of contents
|
|
bool checkOperatorsSummary() {
|
|
const Definition* cs = this->classOrStruct();
|
|
if (!cs) {
|
|
return true; // topics may not have included classes or structs
|
|
}
|
|
const Definition* operators = this->findTopic("Operators", Optional::kYes);
|
|
if (operators && MarkType::kSubtopic != operators->fMarkType) {
|
|
return operators->reportError<bool>("expected #Subtopic Operators");
|
|
}
|
|
vector<string> operatorEntries;
|
|
if (operators) {
|
|
if (!this->collectEntries(operators, &operatorEntries)) {
|
|
return false;
|
|
}
|
|
}
|
|
for (auto& csChild : cs->fChildren) {
|
|
if (Definition::MethodType::kOperator != csChild->fMethodType) {
|
|
continue;
|
|
}
|
|
string name;
|
|
if (!this->childName(csChild, &name)) {
|
|
return false;
|
|
}
|
|
bool found = false;
|
|
for (auto str : operatorEntries) {
|
|
if (string::npos != str.find(name)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
return csChild->reportError<bool>("missing operator in Operators");
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool checkRelatedFunctions() {
|
|
auto related = this->findTopic("Related_Functions", Optional::kYes);
|
|
if (!related) {
|
|
return true;
|
|
}
|
|
vector<string> relatedEntries;
|
|
if (!this->collectEntries(related, &relatedEntries)) {
|
|
return false;
|
|
}
|
|
const Definition* cs = this->classOrStruct();
|
|
vector<string> methodNames;
|
|
if (cs) {
|
|
string prefix = cs->fName + "::";
|
|
for (auto& csChild : cs->fChildren) {
|
|
if (MarkType::kMethod != csChild->fMarkType) {
|
|
// only check methods for now
|
|
continue;
|
|
}
|
|
if (Definition::MethodType::kConstructor == csChild->fMethodType) {
|
|
continue;
|
|
}
|
|
if (Definition::MethodType::kDestructor == csChild->fMethodType) {
|
|
continue;
|
|
}
|
|
if (Definition::MethodType::kOperator == csChild->fMethodType) {
|
|
continue;
|
|
}
|
|
if (csChild->fClone) {
|
|
// FIXME: check to see if all cloned methods are in table
|
|
// since format of clones is in flux, defer this check for now
|
|
continue;
|
|
}
|
|
|
|
SkASSERT(string::npos != csChild->fName.find(prefix));
|
|
string name = csChild->fName.substr(csChild->fName.find(prefix));
|
|
methodNames.push_back(name);
|
|
}
|
|
}
|
|
vector<string> trim = methodNames;
|
|
for (auto entryName : relatedEntries) {
|
|
auto entryDef = this->findTopic(entryName, Optional::kNo);
|
|
if (!entryDef) {
|
|
|
|
}
|
|
vector<string> entries;
|
|
this->collectEntries(entryDef, &entries);
|
|
for (auto entry : entries) {
|
|
auto it = std::find(methodNames.begin(), methodNames.end(), entry);
|
|
if (it == methodNames.end()) {
|
|
return cs->reportError<bool>("missing method");
|
|
}
|
|
it = std::find(trim.begin(), trim.end(), entry);
|
|
if (it != trim.end()) {
|
|
using std::swap;
|
|
swap(*it, trim.back());
|
|
trim.pop_back();
|
|
}
|
|
}
|
|
}
|
|
if (trim.size() > 0) {
|
|
return cs->reportError<bool>("extra method");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool checkSeeAlso() {
|
|
return true;
|
|
}
|
|
|
|
bool checkSubtopicSummary() {
|
|
const auto& cs = this->classOrStruct();
|
|
if (!cs) {
|
|
return true;
|
|
}
|
|
auto overview = this->findOverview(cs);
|
|
if (!overview) {
|
|
return false;
|
|
}
|
|
const Definition* subtopics = this->findTopic("Subtopics", Optional::kNo);
|
|
if (MarkType::kSubtopic != subtopics->fMarkType) {
|
|
return subtopics->reportError<bool>("expected #Subtopic Subtopics");
|
|
}
|
|
const Definition* relatedFunctions = this->findTopic("Related_Functions", Optional::kYes);
|
|
if (relatedFunctions && MarkType::kSubtopic != relatedFunctions->fMarkType) {
|
|
return relatedFunctions->reportError<bool>("expected #Subtopic Related_Functions");
|
|
}
|
|
vector<string> subtopicEntries;
|
|
if (!this->collectEntries(subtopics, &subtopicEntries)) {
|
|
return false;
|
|
}
|
|
if (relatedFunctions && !this->collectEntries(relatedFunctions, &subtopicEntries)) {
|
|
return false;
|
|
}
|
|
for (auto& csChild : cs->fChildren) {
|
|
if (MarkType::kSubtopic != csChild->fMarkType) {
|
|
continue;
|
|
}
|
|
string name;
|
|
if (!this->childName(csChild, &name)) {
|
|
return false;
|
|
}
|
|
bool found = false;
|
|
for (auto str : subtopicEntries) {
|
|
if (string::npos != str.find(name)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
return csChild->reportError<bool>("missing SubTopic in SubTopics");
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool childName(const Definition* def, string* name) {
|
|
auto start = def->fName.find_last_of(':');
|
|
start = string::npos == start ? 0 : start + 1;
|
|
*name = def->fName.substr(start);
|
|
if (def->fClone) {
|
|
auto lastUnderline = name->find_last_of('_');
|
|
if (string::npos == lastUnderline) {
|
|
return def->reportError<bool>("expect _ in name");
|
|
}
|
|
if (lastUnderline + 1 >= name->length()) {
|
|
return def->reportError<bool>("expect char after _ in name");
|
|
}
|
|
for (auto index = lastUnderline + 1; index < name->length(); ++index) {
|
|
if (!isdigit((*name)[index])) {
|
|
return def->reportError<bool>("expect digit after _ in name");
|
|
}
|
|
}
|
|
*name = name->substr(0, lastUnderline);
|
|
bool allLower = true;
|
|
for (auto ch : *name) {
|
|
allLower &= (bool) islower(ch);
|
|
}
|
|
if (allLower) {
|
|
*name += "()";
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
const Definition* classOrStruct() {
|
|
for (auto& rootChild : fRoot->fChildren) {
|
|
if (this->isStructOrClass(rootChild)) {
|
|
return rootChild;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static const Definition* overview_def(const Definition* parent) {
|
|
Definition* overview = nullptr;
|
|
if (parent) {
|
|
for (auto& csChild : parent->fChildren) {
|
|
if ("Overview" == csChild->fName) {
|
|
if (overview) {
|
|
return csChild->reportError<const Definition*>("expected only one Overview");
|
|
}
|
|
overview = csChild;
|
|
}
|
|
}
|
|
}
|
|
return overview;
|
|
}
|
|
|
|
const Definition* findOverview(const Definition* parent) {
|
|
// expect Overview as Topic in every main class or struct
|
|
const Definition* overview = overview_def(parent);
|
|
const Definition* parentOverview = parent ? overview_def(parent->fParent) : nullptr;
|
|
if (overview && parentOverview) {
|
|
return overview->reportError<const Definition*>("expected only one Overview 2");
|
|
}
|
|
overview = overview ? overview : parentOverview;
|
|
if (!overview) {
|
|
return parent->reportError<const Definition*>("missing #Topic Overview");
|
|
}
|
|
return overview;
|
|
}
|
|
|
|
enum class Optional {
|
|
kNo,
|
|
kYes,
|
|
};
|
|
|
|
const Definition* findTopic(string name, Optional optional) {
|
|
string undashed = name;
|
|
std::replace(undashed.begin(), undashed.end(), '-', '_');
|
|
string topicKey = fRoot->fName + '_' + undashed;
|
|
auto topicKeyIter = fBmhParser.fTopicMap.find(topicKey);
|
|
if (fBmhParser.fTopicMap.end() == topicKeyIter) {
|
|
// TODO: remove this and require member functions outside of overview
|
|
topicKey = fRoot->fName + "_Overview_" + undashed; // legacy form for now
|
|
topicKeyIter = fBmhParser.fTopicMap.find(topicKey);
|
|
if (fBmhParser.fTopicMap.end() == topicKeyIter) {
|
|
if (Optional::kNo == optional) {
|
|
return fRoot->reportError<Definition*>("missing subtopic");
|
|
}
|
|
return nullptr;
|
|
}
|
|
}
|
|
return topicKeyIter->second;
|
|
}
|
|
|
|
bool collectEntries(const Definition* entries, vector<string>* strings) {
|
|
const Definition* table = nullptr;
|
|
for (auto& child : entries->fChildren) {
|
|
if (MarkType::kTable == child->fMarkType && child->fName == entries->fName) {
|
|
table = child;
|
|
break;
|
|
}
|
|
}
|
|
if (!table) {
|
|
return entries->reportError<bool>("missing #Table in Overview Subtopic");
|
|
}
|
|
bool expectLegend = true;
|
|
string prior = " "; // expect entries to be alphabetical
|
|
for (auto& row : table->fChildren) {
|
|
if (MarkType::kLegend == row->fMarkType) {
|
|
if (!expectLegend) {
|
|
return row->reportError<bool>("expect #Legend only once");
|
|
}
|
|
// todo: check if legend format matches table's rows' format
|
|
expectLegend = false;
|
|
} else if (expectLegend) {
|
|
return row->reportError<bool>("expect #Legend first");
|
|
}
|
|
if (MarkType::kRow != row->fMarkType) {
|
|
continue; // let anything through for now; can tighten up in the future
|
|
}
|
|
// expect column 0 to point to function name
|
|
Definition* column0 = row->fChildren[0];
|
|
string name = string(column0->fContentStart,
|
|
column0->fContentEnd - column0->fContentStart);
|
|
if (prior > name) {
|
|
return row->reportError<bool>("expect alphabetical order");
|
|
}
|
|
if (prior == name) {
|
|
return row->reportError<bool>("expect unique names");
|
|
}
|
|
// todo: error if name is all lower case and doesn't end in ()
|
|
strings->push_back(name);
|
|
prior = name;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool isStructOrClass(const Definition* definition) const {
|
|
if (MarkType::kStruct != definition->fMarkType &&
|
|
MarkType::kClass != definition->fMarkType) {
|
|
return false;
|
|
}
|
|
if (string::npos != definition->fFileName.find("undocumented.bmh")) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
const BmhParser& fBmhParser;
|
|
RootDefinition* fRoot;
|
|
};
|
|
|
|
bool SelfCheck(const BmhParser& bmh) {
|
|
SelfChecker checker(bmh);
|
|
return checker.check();
|
|
}
|