skia2/tools/bookmaker/selfCheck.cpp
Cary Clark ab2621d3e2 generate tables instead of manual entry
- 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>
2018-01-30 15:32:15 +00:00

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();
}