• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2018 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "bookmaker.h"
9 
10 
11 // Check that mutiple like-named methods are under one Subtopic
12 
13 // Check that all subtopics are in table of contents
14 
15 // Check that SeeAlso reference each other
16 
17 // Would be nice to check if other classes have 'create' methods that are included
18 //          SkSurface::makeImageSnapShot should be referenced under SkImage 'creators'
19 
20 class SelfChecker {
21 public:
SelfChecker(const BmhParser & bmh)22     SelfChecker(const BmhParser& bmh)
23         : fBmhParser(bmh)
24         {}
25 
check()26     bool check() {
27         for (const auto& topic : fBmhParser.fTopicMap) {
28             Definition* topicDef = topic.second;
29             if (topicDef->fParent) {
30                 continue;
31             }
32             if (!topicDef->isRoot()) {
33                 return fBmhParser.reportError<bool>("expected root topic");
34             }
35             fRoot = topicDef->asRoot();
36             if (!this->checkMethodSummary()) {
37                 return false;
38             }
39             if (!this->checkMethodSubtopic()) {
40                 return false;
41             }
42             if (!this->checkSubtopicSummary()) {
43                 return false;
44             }
45             if (!this->checkConstructorsSummary()) {
46                 return false;
47             }
48             if (!this->checkOperatorsSummary()) {
49                 return false;
50             }
51             if (!this->checkSeeAlso()) {
52                 return false;
53             }
54             if (!this->checkCreators()) {
55                 return false;
56             }
57 			if (!this->checkRelatedFunctions()) {
58 				return false;
59 			}
60         }
61         return true;
62     }
63 
64 protected:
65     // Check that all constructors are in a table of contents
66     //          should be 'creators' instead of constructors?
checkConstructorsSummary()67     bool checkConstructorsSummary() {
68         for (auto& rootChild : fRoot->fChildren) {
69             if (!rootChild->isStructOrClass()) {
70                 continue;
71             }
72             auto& cs = rootChild;
73 			auto constructors = this->findTopic("Constructors", Optional::kYes);
74 			if (constructors && MarkType::kSubtopic != constructors->fMarkType) {
75                 return constructors->reportError<bool>("expected #Subtopic Constructors");
76             }
77             vector<string> constructorEntries;
78             if (constructors) {
79                 if (!this->collectEntries(constructors, &constructorEntries)) {
80                     return false;
81                 }
82             }
83             // mark corresponding methods as visited (may be more than one per entry)
84             for (auto& csChild : cs->fChildren) {
85                 if (MarkType::kMethod != csChild->fMarkType) {
86                     // only check methods for now
87                     continue;
88                 }
89                 string name;
90                 if (!this->childName(csChild, &name)) {
91                     return false;
92                 }
93                 string returnType;
94                 if (Definition::MethodType::kConstructor != csChild->fMethodType &&
95                         Definition::MethodType::kDestructor != csChild->fMethodType) {
96                     string makeCheck = name.substr(0, 4);
97                     if ("Make" != makeCheck && "make" != makeCheck) {
98                         continue;
99                     }
100                     // for now, assume return type of interest is first word to start Sk
101                     string search(csChild->fStart, csChild->fContentStart - csChild->fStart);
102                     auto end = search.find(makeCheck);
103                     if (string::npos == end) {
104                         return csChild->reportError<bool>("expected Make in content");
105                     }
106                     search = search.substr(0, end);
107                     if (string::npos == search.find(cs->fName)) {
108                         // if return value doesn't match current struct or class, look in
109                         // returned struct / class instead
110                         auto sk = search.find("Sk");
111                         if (string::npos != sk) {
112                             // todo: build class name, find it, search for match in its overview
113                             continue;
114                         }
115                     }
116                 }
117                 if (constructorEntries.end() ==
118                         std::find(constructorEntries.begin(), constructorEntries.end(), name)) {
119                     return csChild->reportError<bool>("missing constructor in Constructors");
120                 }
121             }
122         }
123         return true;
124     }
125 
checkCreators()126     bool checkCreators() {
127         return true;
128     }
129 
checkMethodSubtopic()130     bool checkMethodSubtopic() {
131         return true;
132     }
133 
134     // Check that summary contains all methods
checkMethodSummary()135     bool checkMethodSummary() {
136         // look for struct or class in fChildren
137 		const Definition* cs = this->classOrStruct();
138 		if (!cs) {
139 			return true;  // topics may not have included classes or structs
140 		}
141 		auto memberFunctions = this->findTopic("Member_Functions", Optional::kNo);
142         if (MarkType::kSubtopic != memberFunctions->fMarkType) {
143             return memberFunctions->reportError<bool>("expected #Subtopic Member_Functions");
144         }
145         vector<string> methodEntries; // build map of overview entries
146         if (!this->collectEntries(memberFunctions, &methodEntries)) {
147             return false;
148         }
149         // mark corresponding methods as visited (may be more than one per entry)
150         for (auto& csChild : cs->fChildren) {
151             if (MarkType::kMethod != csChild->fMarkType) {
152                 // only check methods for now
153                 continue;
154             }
155             if (Definition::MethodType::kConstructor == csChild->fMethodType) {
156                 continue;
157             }
158             if (Definition::MethodType::kDestructor == csChild->fMethodType) {
159                 continue;
160             }
161             if (Definition::MethodType::kOperator == csChild->fMethodType) {
162                 continue;
163             }
164             string name;
165             if (!this->childName(csChild, &name)) {
166                 return false;
167             }
168             if (methodEntries.end() ==
169                     std::find(methodEntries.begin(), methodEntries.end(), name)) {
170                 return csChild->reportError<bool>("missing method in Member_Functions");
171             }
172         }
173         return true;
174     }
175 
176     // Check that all operators are in a table of contents
checkOperatorsSummary()177     bool checkOperatorsSummary() {
178 		const Definition* cs = this->classOrStruct();
179 		if (!cs) {
180 			return true;  // topics may not have included classes or structs
181 		}
182         const Definition* operators = this->findTopic("Operators", Optional::kYes);
183         if (operators && MarkType::kSubtopic != operators->fMarkType) {
184             return operators->reportError<bool>("expected #Subtopic Operators");
185         }
186         vector<string> operatorEntries;
187         if (operators) {
188             if (!this->collectEntries(operators, &operatorEntries)) {
189                 return false;
190             }
191         }
192         for (auto& csChild : cs->fChildren) {
193             if (Definition::MethodType::kOperator != csChild->fMethodType) {
194                 continue;
195             }
196             string name;
197             if (!this->childName(csChild, &name)) {
198                 return false;
199             }
200             bool found = false;
201             for (auto str : operatorEntries) {
202                 if (string::npos != str.find(name)) {
203                     found = true;
204                     break;
205                 }
206             }
207             if (!found) {
208                 return csChild->reportError<bool>("missing operator in Operators");
209             }
210         }
211         return true;
212     }
213 
checkRelatedFunctions()214 	bool checkRelatedFunctions() {
215 		auto related = this->findTopic("Related_Functions", Optional::kYes);
216 		if (!related) {
217 			return true;
218 		}
219 		vector<string> relatedEntries;
220 		if (!this->collectEntries(related, &relatedEntries)) {
221 			return false;
222 		}
223 		const Definition* cs = this->classOrStruct();
224 		vector<string> methodNames;
225 		if (cs) {
226 			string prefix = cs->fName + "::";
227 			for (auto& csChild : cs->fChildren) {
228 				if (MarkType::kMethod != csChild->fMarkType) {
229 					// only check methods for now
230 					continue;
231 				}
232 				if (Definition::MethodType::kConstructor == csChild->fMethodType) {
233 					continue;
234 				}
235 				if (Definition::MethodType::kDestructor == csChild->fMethodType) {
236 					continue;
237 				}
238 				if (Definition::MethodType::kOperator == csChild->fMethodType) {
239 					continue;
240 				}
241 				if (csChild->fClone) {
242 					// FIXME: check to see if all cloned methods are in table
243 					// since format of clones is in flux, defer this check for now
244 					continue;
245 				}
246 
247 				SkASSERT(string::npos != csChild->fName.find(prefix));
248 				string name = csChild->fName.substr(csChild->fName.find(prefix));
249 				methodNames.push_back(name);
250 			}
251 		}
252 		vector<string> trim = methodNames;
253 		for (auto entryName : relatedEntries) {
254 			auto entryDef = this->findTopic(entryName, Optional::kNo);
255 			if (!entryDef) {
256 
257 			}
258 			vector<string> entries;
259 			this->collectEntries(entryDef, &entries);
260 			for (auto entry : entries) {
261 				auto it = std::find(methodNames.begin(), methodNames.end(), entry);
262 				if (it == methodNames.end()) {
263 					return cs->reportError<bool>("missing method");
264 				}
265 				it = std::find(trim.begin(), trim.end(), entry);
266 				if (it != trim.end()) {
267 					using std::swap;
268 					swap(*it, trim.back());
269 					trim.pop_back();
270 				}
271 			}
272 		}
273 		if (trim.size() > 0) {
274 			return cs->reportError<bool>("extra method");
275 		}
276 		return true;
277 	}
278 
checkSeeAlso()279     bool checkSeeAlso() {
280         return true;
281     }
282 
checkSubtopicSummary()283     bool checkSubtopicSummary() {
284         const auto& cs = this->classOrStruct();
285 		if (!cs) {
286 			return true;
287 		}
288         auto overview = this->findOverview(cs);
289         if (!overview) {
290             return false;
291         }
292         const Definition* subtopics = this->findTopic("Subtopics", Optional::kNo);
293         if (MarkType::kSubtopic != subtopics->fMarkType) {
294             return subtopics->reportError<bool>("expected #Subtopic Subtopics");
295         }
296 		const Definition* relatedFunctions = this->findTopic("Related_Functions", Optional::kYes);
297 		if (relatedFunctions && MarkType::kSubtopic != relatedFunctions->fMarkType) {
298             return relatedFunctions->reportError<bool>("expected #Subtopic Related_Functions");
299         }
300         vector<string> subtopicEntries;
301         if (!this->collectEntries(subtopics, &subtopicEntries)) {
302             return false;
303         }
304         if (relatedFunctions && !this->collectEntries(relatedFunctions, &subtopicEntries)) {
305             return false;
306         }
307         for (auto& csChild : cs->fChildren) {
308             if (MarkType::kSubtopic != csChild->fMarkType) {
309                 continue;
310             }
311             string name;
312             if (!this->childName(csChild, &name)) {
313                 return false;
314             }
315             bool found = false;
316             for (auto str : subtopicEntries) {
317                 if (string::npos != str.find(name)) {
318                     found = true;
319                     break;
320                 }
321             }
322             if (!found) {
323                 return csChild->reportError<bool>("missing SubTopic in SubTopics");
324             }
325         }
326         return true;
327     }
328 
childName(const Definition * def,string * name)329     bool childName(const Definition* def, string* name) {
330         auto start = def->fName.find_last_of(':');
331         start = string::npos == start ? 0 : start + 1;
332         *name = def->fName.substr(start);
333         if (def->fClone) {
334             auto lastUnderline = name->find_last_of('_');
335             if (string::npos == lastUnderline) {
336                 return def->reportError<bool>("expect _ in name");
337             }
338             if (lastUnderline + 1 >= name->length()) {
339                 return def->reportError<bool>("expect char after _ in name");
340             }
341             for (auto index = lastUnderline + 1; index < name->length(); ++index) {
342                 if (!isdigit((*name)[index])) {
343                     return def->reportError<bool>("expect digit after _ in name");
344                 }
345             }
346             *name = name->substr(0, lastUnderline);
347             bool allLower = true;
348             for (auto ch : *name) {
349                 allLower &= (bool) islower(ch);
350             }
351             if (allLower) {
352                 *name += "()";
353             }
354         }
355         return true;
356     }
357 
classOrStruct()358 	const Definition* classOrStruct() {
359 		for (auto& rootChild : fRoot->fChildren) {
360 			if (rootChild->isStructOrClass()) {
361 				return rootChild;
362 			}
363 		}
364 		return nullptr;
365 	}
366 
overview_def(const Definition * parent)367 	static const Definition* overview_def(const Definition* parent) {
368 		Definition* overview = nullptr;
369 		if (parent) {
370 			for (auto& csChild : parent->fChildren) {
371 				if ("Overview" == csChild->fName) {
372 					if (overview) {
373 						return csChild->reportError<const Definition*>("expected only one Overview");
374 					}
375 					overview = csChild;
376 				}
377 			}
378 		}
379 		return overview;
380 	}
381 
findOverview(const Definition * parent)382     const Definition* findOverview(const Definition* parent) {
383         // expect Overview as Topic in every main class or struct
384         const Definition* overview = overview_def(parent);
385 		const Definition* parentOverview = parent ? overview_def(parent->fParent) : nullptr;
386 		if (overview && parentOverview) {
387 			return overview->reportError<const Definition*>("expected only one Overview 2");
388 		}
389 		overview = overview ? overview : parentOverview;
390         if (!overview) {
391             return parent->reportError<const Definition*>("missing #Topic Overview");
392         }
393         return overview;
394     }
395 
396 	enum class Optional {
397 		kNo,
398 		kYes,
399 	};
400 
findTopic(string name,Optional optional)401 	const Definition* findTopic(string name, Optional optional) {
402 		string undashed = name;
403 		std::replace(undashed.begin(), undashed.end(), '-', '_');
404 		string topicKey = fRoot->fName + '_' + undashed;
405 		auto topicKeyIter = fBmhParser.fTopicMap.find(topicKey);
406 		if (fBmhParser.fTopicMap.end() == topicKeyIter) {
407 			// TODO: remove this and require member functions outside of overview
408 			topicKey = fRoot->fName + "_Overview_" + undashed;  // legacy form for now
409 			topicKeyIter = fBmhParser.fTopicMap.find(topicKey);
410 			if (fBmhParser.fTopicMap.end() == topicKeyIter) {
411 				if (Optional::kNo == optional) {
412 					return fRoot->reportError<Definition*>("missing subtopic");
413 				}
414 				return nullptr;
415 			}
416 		}
417 		return topicKeyIter->second;
418 	}
419 
collectEntries(const Definition * entries,vector<string> * strings)420     bool collectEntries(const Definition* entries, vector<string>* strings) {
421         const Definition* table = nullptr;
422         for (auto& child : entries->fChildren) {
423             if (MarkType::kTable == child->fMarkType && child->fName == entries->fName) {
424                 table = child;
425                 break;
426             }
427         }
428         if (!table) {
429             return entries->reportError<bool>("missing #Table in Overview Subtopic");
430         }
431         bool expectLegend = true;
432         string prior = " ";  // expect entries to be alphabetical
433         for (auto& row : table->fChildren) {
434             if (MarkType::kLegend == row->fMarkType) {
435                 if (!expectLegend) {
436                     return row->reportError<bool>("expect #Legend only once");
437                 }
438                 // todo: check if legend format matches table's rows' format
439                 expectLegend = false;
440             } else if (expectLegend) {
441                 return row->reportError<bool>("expect #Legend first");
442             }
443             if (MarkType::kRow != row->fMarkType) {
444                 continue;  // let anything through for now; can tighten up in the future
445             }
446             // expect column 0 to point to function name
447             Definition* column0 = row->fChildren[0];
448             string name = string(column0->fContentStart,
449                     column0->fContentEnd - column0->fContentStart);
450             if (prior > name) {
451                 return row->reportError<bool>("expect alphabetical order");
452             }
453             if (prior == name) {
454                 return row->reportError<bool>("expect unique names");
455             }
456             // todo: error if name is all lower case and doesn't end in ()
457             strings->push_back(name);
458             prior = name;
459         }
460         return true;
461     }
462 
463 private:
464     const BmhParser& fBmhParser;
465     RootDefinition* fRoot;
466 };
467 
SelfCheck(const BmhParser & bmh)468 bool SelfCheck(const BmhParser& bmh) {
469     SelfChecker checker(bmh);
470     return checker.check();
471 }
472