Generate bookmaker indices

First cut at splitting bookmaker documentation into reference
and overview. Reference starts with a hyperlinked index,
generated from a public include.

This moves towards typing once, minimizing the information
duplicated in the .h file and the .bmh file.

TBR=caryclark@google.com
Docs-Preview: https://skia.org/?cl=154630
Change-Id: I836622db9b1786bd28c0bce2536cd3caef6e5a32
Reviewed-on: https://skia-review.googlesource.com/c/154630
Commit-Queue: Cary Clark <caryclark@skia.org>
Auto-Submit: Cary Clark <caryclark@skia.org>
Reviewed-by: Cary Clark <caryclark@skia.org>
diff --git a/tools/bookmaker/bookmaker.cpp b/tools/bookmaker/bookmaker.cpp
index 411e192..08577b9 100644
--- a/tools/bookmaker/bookmaker.cpp
+++ b/tools/bookmaker/bookmaker.cpp
@@ -43,20 +43,6 @@
 subclasses (e.g. Iter in SkPath) need to check for #Line and generate overview
      subclass methods should also disallow #In
 
-There are a number of formatting bugs with ad hoc patches where a substitution doesn't keep
-the space before or after, or the linefeeds before or after. The rules are not very good either.
-Linefeeds in the bmh file are intended to be respected, but #Formula tends to start on a new line
-even if the contents is intended to be inlined. Probably need to require it to be, e.g.:
-
-    array length must be #Formula # (fXCount + 1) * (fYCount + 1) ##.
-
-where there is always a space between prior words and formula (i.e., between "be" and "(fXCount";
-and, an absense of a space after ## denotes no space between "+ 1)" and ".". These rules preserve
-that # commands are always preceded by a whitespace character. Similarly, #PhraseDef/Ref
-need to be inline or create new paragraphs. #phrase_ref# is sufficiently flexible that it can be
-treated as a word without trailing whitespace, adapting the whitespace of its context. It also must
-always have leading whitespace.
-
 It's awkward that phrase param is a child of the phrase def. Since phrase refs may also be children,
 there is special case code to skip phrase def when looking for additional substitutions in the
 phrase def. Could put it in the token list instead I guess, or make a definition subclass used
@@ -68,6 +54,13 @@
 Or, only allow #Line and moderate text description in #Const. Put more verbose text, example,
 seealso, in subsequent #SubTopic. Alpha_Type does this and it looks good.
 
+IPoint is awkward. SkPoint and SkIPoint are named things; Point is a topic, which
+refers to float points or integer points. There needn't be an IPoint topic.
+One way to resolve this would be to combine SkPoint_Reference and SkIPoint_Reference into
+Point_Reference that then contains both structs (or just move SKIPoint into SkPoint_Reference).
+Most Point references would be replaced with SkPoint / SkIPoint (if that's what they mean),
+or remain Point if the text indicates the concept rather one of the C structs.
+
 see head of selfCheck.cpp for additional todos
 see head of spellCheck.cpp for additional todos
  */
@@ -140,7 +133,7 @@
 , { "List",         MarkType::kList,         R_Y, E_N, M(Method) | M_CSST | M_E | M_D }
 , { "Literal",      MarkType::kLiteral,      R_N, E_N, M(Code) }
 , { "",             MarkType::kMarkChar,     R_N, E_N, 0 }
-, { "Member",       MarkType::kMember,       R_Y, E_N, M_CSST }
+, { "Member",       MarkType::kMember,       R_Y, E_O, M_CSST }
 , { "Method",       MarkType::kMethod,       R_Y, E_Y, M_CSST }
 , { "NoExample",    MarkType::kNoExample,    R_N, E_N, M_CSST | M_E | M_MD }
 , { "NoJustify",    MarkType::kNoJustify,    R_N, E_N, M(Const) | M(Member) }
@@ -150,7 +143,7 @@
 , { "",             MarkType::kPhraseParam,  R_Y, E_N, 0 }
 , { "",             MarkType::kPhraseRef,    R_N, E_N, 0 }
 , { "Platform",     MarkType::kPlatform,     R_N, E_N, M(Example) | M(NoExample) }
-, { "Populate",     MarkType::kPopulate,     R_N, E_N, M(Subtopic) }
+, { "Populate",     MarkType::kPopulate,     R_N, E_N, M_CS | M(Code) }
 , { "Private",      MarkType::kPrivate,      R_N, E_N, M_CSST | M_MDCM | M_E }
 , { "Return",       MarkType::kReturn,       R_Y, E_N, M(Method) }
 , { "",             MarkType::kRow,          R_Y, E_N, M(Table) | M(List) }
@@ -159,14 +152,15 @@
 , { "StdOut",       MarkType::kStdOut,       R_N, E_N, M(Example) | M(NoExample) }
 , { "Struct",       MarkType::kStruct,       R_Y, E_O, M(Class) | M_ST }
 , { "Substitute",   MarkType::kSubstitute,   R_N, E_N, M(Alias) | M_ST }
-, { "Subtopic",     MarkType::kSubtopic,     R_Y, E_Y, M_CSST }
+, { "Subtopic",     MarkType::kSubtopic,     R_Y, E_Y, M_CSST | M_E }
 , { "Table",        MarkType::kTable,        R_Y, E_N, M(Method) | M_CSST | M_E }
 , { "Template",     MarkType::kTemplate,     R_Y, E_N, M_CSST }
 , { "",             MarkType::kText,         R_N, E_N, 0 }
 , { "ToDo",         MarkType::kToDo,         R_N, E_N, 0 }
 , { "Topic",        MarkType::kTopic,        R_Y, E_Y, 0 }
-, { "Typedef",      MarkType::kTypedef,      R_Y, E_N, M_CSST | M_E }
+, { "Typedef",      MarkType::kTypedef,      R_Y, E_O, M_CSST | M_E }
 , { "Union",        MarkType::kUnion,        R_Y, E_N, M_CSST }
+, { "Using",        MarkType::kUsing,        R_Y, E_O, M_CSST }
 , { "Volatile",     MarkType::kVolatile,     R_N, E_N, M(StdOut) }
 , { "Width",        MarkType::kWidth,        R_N, E_N, M(Example) | M(NoExample) }
 };
@@ -234,7 +228,8 @@
                     if (!hasEnd && fRoot->find(name, RootDefinition::AllowParens::kNo)) {
                         return this->reportError<bool>("duplicate symbol");
                     }
-                    if (MarkType::kStruct == markType || MarkType::kClass == markType) {
+                    if (MarkType::kStruct == markType || MarkType::kClass == markType
+                            || MarkType::kEnumClass == markType) {
                         // if class or struct, build fRoot hierarchy
                         // and change isDefined to search all parents of fRoot
                         SkASSERT(!hasEnd);
@@ -1029,6 +1024,10 @@
     } while (pos < end);
 }
 
+bool BmhParser::IsExemplary(const Definition* def) {
+    return kMarkProps[(int) def->fMarkType].fExemplary != Exemplary::kNo;
+}
+
 bool BmhParser::exampleToScript(Definition* def, ExampleOptions exampleOptions,
         string* result) const {
     bool hasFiddle = true;
@@ -2053,10 +2052,14 @@
 }
 
 void TextParser::reportWarning(const char* errorStr) const {
-    SkASSERT(fLine < fEnd);
-    TextParser err(fFileName, fLine, fEnd, fLineCount);
+    const char* lineStart = fLine;
+    if (lineStart >= fEnd) {
+        lineStart = fChar;
+    }
+    SkASSERT(lineStart < fEnd);
+    TextParser err(fFileName, lineStart, fEnd, fLineCount);
     size_t lineLen = this->lineLength();
-    ptrdiff_t spaces = fChar - fLine;
+    ptrdiff_t spaces = fChar - lineStart;
     while (spaces > 0 && (size_t) spaces > lineLen) {
         ++err.fLineCount;
         err.fLine += lineLen;
@@ -2756,15 +2759,19 @@
     if (!done && !FLAGS_ref.isEmpty() && FLAGS_examples.isEmpty()) {
         IncludeParser includeParser;
         includeParser.validate();
+        if (!FLAGS_status.isEmpty() && !includeParser.parseStatus(FLAGS_status[0], ".h",
+                StatusFilter::kCompleted)) {
+            return -1;
+        }
         if (!FLAGS_include.isEmpty() && !includeParser.parseFile(FLAGS_include[0], ".h",
                 ParserCommon::OneFile::kYes)) {
             return -1;
         }
-        MdOut mdOut(bmhParser);
+        includeParser.writeCodeBlock(bmhParser);
+        MdOut mdOut(bmhParser, includeParser);
         mdOut.fDebugOut = FLAGS_stdout;
         mdOut.fValidate = FLAGS_validate;
-        if (!FLAGS_bmh.isEmpty() && mdOut.buildReferences(includeParser,
-                FLAGS_bmh[0], FLAGS_ref[0])) {
+        if (!FLAGS_bmh.isEmpty() && mdOut.buildReferences(FLAGS_bmh[0], FLAGS_ref[0])) {
             bmhParser.fWroteOut = true;
         }
         if (!FLAGS_status.isEmpty() && mdOut.buildStatus(FLAGS_status[0], FLAGS_ref[0])) {
diff --git a/tools/bookmaker/bookmaker.h b/tools/bookmaker/bookmaker.h
index 3951dce..d8b2042 100644
--- a/tools/bookmaker/bookmaker.h
+++ b/tools/bookmaker/bookmaker.h
@@ -81,6 +81,7 @@
     kUintPtr_t,
     kUnion,
     kUnsigned,
+    kUsing,
     kVoid,
 };
 
@@ -144,6 +145,7 @@
     kTopic,
     kTypedef,
     kUnion,
+    kUsing,
     kVolatile,
     kWidth,
 };
@@ -300,8 +302,11 @@
     }
 
     bool contains(const char* match, const char* lineEnd, const char** loc) const {
-        *loc = this->strnstr(match, lineEnd);
-        return *loc;
+        const char* result = this->strnstr(match, lineEnd);
+        if (loc) {
+            *loc = result;
+        }
+        return result;
     }
 
     // either /n/n or /n# will stop parsing a typedef
@@ -406,6 +411,18 @@
 
     void setForErrorReporting(const Definition* , const char* );
 
+    bool skipToBalancedEndBracket(char startB, char endB) {
+        SkASSERT(fChar < fEnd);
+        SkASSERT(startB == fChar[0]);
+        int startCount = 0;
+        do {
+            char test = this->next();
+            startCount += startB == test;
+            startCount -= endB  == test;
+        } while (startCount && fChar < fEnd);
+        return !startCount;
+    }
+
     bool skipToEndBracket(char endBracket, const char* end = nullptr) {
         if (nullptr == end) {
             end = fEnd;
@@ -446,10 +463,13 @@
         }
     }
 
-    void skipToAlphaNum() {
+    // returns true if saw close brace
+    bool skipToAlphaNum() {
+        bool sawCloseBrace = false;
         while (fChar < fEnd && !isalnum(fChar[0])) {
-            fChar++;
+            sawCloseBrace |= '}' == *fChar++;
         }
+        return sawCloseBrace;
     }
 
     bool skipExact(const char* pattern) {
@@ -473,6 +493,15 @@
         }
     }
 
+    int skipToLineBalance(char open, char close) {
+        int match = 0;
+        while (!this->eof() && '\n' != fChar[0]) {
+            match += open == this->peek();
+            match -= close == this->next();
+        }
+        return match;
+    }
+
     bool skipToLineStart() {
         if (!this->skipLine()) {
             return false;
@@ -483,6 +512,11 @@
         return true;
     }
 
+    void skipToLineStart(int* indent, bool* sawReturn) {
+        SkAssertResult(this->skipLine());
+        this->skipWhiteSpace(indent, sawReturn);
+    }
+
     void skipLower() {
         while (fChar < fEnd && (islower(fChar[0]) || '_' == fChar[0])) {
             fChar++;
@@ -570,6 +604,18 @@
         return true;
     }
 
+    void skipWhiteSpace(int* indent, bool* skippedReturn) {
+        while (' ' >= this->peek()) {
+            *indent = *skippedReturn ? *indent + 1 : 1;
+            if ('\n' == this->peek()) {
+                *skippedReturn |= true;
+                *indent = 0;
+            }
+            (void) this->next();
+            SkASSERT(fChar < fEnd);
+        }
+    }
+
     bool startsWith(const char* str) const {
         size_t len = strlen(str);
         ptrdiff_t lineLen = fEnd - fChar;
@@ -992,6 +1038,7 @@
     const char* fContentStart;  // start past optional markup name
     string fName;
     string fFiddle;  // if its a constructor or operator, fiddle name goes here
+    string fCode;  // suitable for autogeneration of #Code blocks in bmh
     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;
@@ -1023,17 +1070,17 @@
 
 class SubtopicKeys {
 public:
-    static constexpr const char* kClasses = "Class";
-    static constexpr const char* kConstants = "Constant";
-    static constexpr const char* kConstructors = "Constructor";
-    static constexpr const char* kDefines = "Define";
-    static constexpr const char* kMemberFunctions = "Member_Function";
-    static constexpr const char* kMembers = "Member";
-    static constexpr const char* kOperators = "Operator";
+    static constexpr const char* kClasses = "Classes";
+    static constexpr const char* kConstants = "Constants";
+    static constexpr const char* kConstructors = "Constructors";
+    static constexpr const char* kDefines = "Defines";
+    static constexpr const char* kMemberFunctions = "Member_Functions";
+    static constexpr const char* kMembers = "Members";
+    static constexpr const char* kOperators = "Operators";
     static constexpr const char* kOverview = "Overview";
-    static constexpr const char* kRelatedFunctions = "Related_Function";
-    static constexpr const char* kStructs = "Struct";
-    static constexpr const char* kTypedefs = "Typedef";
+    static constexpr const char* kRelatedFunctions = "Related_Functions";
+    static constexpr const char* kStructs = "Structs";
+    static constexpr const char* kTypedefs = "Typedefs";
 
     static const char* kGeneratedSubtopics[];
 };
@@ -1255,6 +1302,7 @@
         fPendingSpace = 0;
         fOutdentNext = false;
         fWritingIncludes = false;
+        fDebugWriteCodeBlock = false;
         nl();
    }
 
@@ -1270,6 +1318,10 @@
         fMaxLF = 1;
     }
 
+    void stringAppend(string& result, char ch) const;
+    void stringAppend(string& result, string str) const;
+    void stringAppend(string& result, const Definition* ) const;
+
     void writeBlock(int size, const char* data) {
         SkAssertResult(writeBlockTrim(size, data));
     }
@@ -1341,6 +1393,7 @@
     bool fWroteSomething; // used to detect empty content; an alternative source is preferable
     bool fReturnOnWrite; // used to detect non-empty content; allowing early return
     bool fWritingIncludes; // set true when writing includes to check >100 columns
+    mutable bool fDebugWriteCodeBlock;
 
 private:
     typedef TextParser INHERITED;
@@ -1350,6 +1403,7 @@
     const Json::Value& fObject;
     Json::Value::iterator fIter;
     string fName;
+    StatusFilter fStatusFilter;
 };
 
 class JsonCommon : public ParserCommon {
@@ -1373,7 +1427,7 @@
     StatusIter(const char* statusFile, const char* suffix, StatusFilter);
     ~StatusIter() override {}
     string baseDir();
-    bool next(string* file);
+    bool next(string* file, StatusFilter* filter);
 private:
     const char* fSuffix;
     StatusFilter fFilter;
@@ -1463,6 +1517,7 @@
     Definition* findExample(string name) const;
     MarkType getMarkType(MarkLookup lookup) const;
     bool hasEndToken() const;
+    static bool IsExemplary(const Definition* );
     string memberName();
     string methodName();
     const Definition* parentSpace() const;
@@ -1568,6 +1623,49 @@
         kYes,
     };
 
+    enum class Elided {
+        kNo,
+        kYes,
+    };
+
+    struct CheckCode {
+        enum class State {
+            kNone,
+            kClassDeclaration,
+            kConstructor,
+            kForwardDeclaration,
+            kMethod,
+        };
+
+        void reset() {
+            fInDebugCode = nullptr;
+            fPrivateBrace = 0;
+            fBraceCount = 0;
+            fIndent = 0;
+            fDoubleReturn = 0;
+            fState = State::kNone;
+            fPrivateProtected = false;
+            fTypedefReturn = false;
+            fSkipAPI = false;
+            fSkipInline = false;
+            fSkipWarnUnused = false;
+            fWriteReturn = false;
+        }
+
+        const char* fInDebugCode;
+        int fPrivateBrace;
+        int fBraceCount;
+        int fIndent;
+        int fDoubleReturn;
+        State fState;
+        bool fPrivateProtected;
+        bool fTypedefReturn;
+        bool fSkipAPI;
+        bool fSkipInline;
+        bool fSkipWarnUnused;
+        bool fWriteReturn;
+    };
+
     IncludeParser() : ParserCommon()
         , fMaps {
           { &fIConstMap,    MarkType::kConst }
@@ -1596,11 +1694,35 @@
         fIncludeWord = nullptr;
     }
 
+    bool advanceInclude(TextParser& i);
     bool inAlignAs() const;
     void checkForMissingParams(const vector<string>& methodParams,
                                const vector<string>& foundParams);
     bool checkForWord();
     string className() const;
+
+    string codeBlock(const Definition& def, bool inProgress) const {
+        return codeBlock(def.fMarkType, def.fName, inProgress);
+    }
+
+    string codeBlock(MarkType markType, string name, bool inProgress) const {
+        if (MarkType::kClass == markType || MarkType::kStruct == markType) {
+            auto map = fIClassMap.find(name);
+            SkASSERT(fIClassMap.end() != map || inProgress);
+            return fIClassMap.end() != map ? map->second.fCode : "";
+        }
+        if (MarkType::kEnum == markType || MarkType::kEnumClass == markType) {
+            auto map = fIEnumMap.find(name);
+            SkASSERT(fIEnumMap.end() != map);
+            return map->second->fCode;
+        }
+        SkASSERT(0);
+        return "";
+    }
+
+    void codeBlockAppend(string& result, char ch) const;
+    void codeBlockSpaces(string& result, int indent) const;
+
     bool crossCheck(BmhParser& );
     IClassDefinition* defineClass(const Definition& includeDef, string className);
     void dumpClassTokens(IClassDefinition& classDef);
@@ -1616,11 +1738,13 @@
     bool dumpTokens(string skClassName, string globalFileName, long int* globalTell);
     void dumpTypedef(const Definition& , string className);
 
+    string elidedCodeBlock(const Definition& );
     bool findComments(const Definition& includeDef, Definition* markupDef);
     Definition* findIncludeObject(const Definition& includeDef, MarkType markType,
                                   string typeName);
     static KeyWord FindKey(const char* start, const char* end);
     Bracket grandParentBracket() const;
+    const Definition* include(string ) const;
     bool isClone(const Definition& token);
     bool isConstructor(const Definition& token, string className);
     bool isInternalName(const Definition& token);
@@ -1651,6 +1775,7 @@
     bool parseObjects(Definition* parent, Definition* markupDef);
     bool parseTemplate(Definition* child, Definition* markupDef);
     bool parseTypedef(Definition* child, Definition* markupDef);
+    bool parseUsing();
     bool parseUnion();
 
     void popBracket() {
@@ -1724,6 +1849,9 @@
     }
 
     void validate() const;
+    void writeCodeBlock(const BmhParser& );
+    string writeCodeBlock(const Definition&, MarkType );
+    string writeCodeBlock(TextParser& i, MarkType , int indent);
 
     void writeDefinition(const Definition& def) {
         if (def.length() > 1) {
@@ -1877,6 +2005,7 @@
     unordered_map<string, Definition*> fITemplateMap;
     unordered_map<string, Definition*> fITypedefMap;
     unordered_map<string, Definition*> fIUnionMap;
+    CheckCode fCheck;
     Definition* fRootTopic;
     Definition* fConstExpr;
     Definition* fInBrace;
@@ -1886,6 +2015,7 @@
     const Definition* fAttrDeprecated;
     int fPriorIndex;
     const char* fIncludeWord;
+    Elided fElided;
     char fPrev;
     bool fInChar;
     bool fInCharCommentString;
@@ -2251,18 +2381,20 @@
 class MdOut : public ParserCommon {
 public:
     struct SubtopicDescriptions {
-        string fName;
+        string fSingular;
+        string fPlural;
         string fOneLiner;
         string fDetails;
     };
 
-    MdOut(BmhParser& bmh) : ParserCommon()
-        , fBmhParser(bmh) {
+    MdOut(BmhParser& bmh, IncludeParser& inc) : ParserCommon()
+        , fBmhParser(bmh)
+        , fIncludeParser(inc) {
         this->reset();
         this->addPopulators();
     }
 
-    bool buildReferences(const IncludeParser& , const char* docDir, const char* mdOutDirOrFile);
+    bool buildReferences(const char* docDir, const char* mdOutDirOrFile);
     bool buildStatus(const char* docDir, const char* mdOutDir);
     void checkAnchors();
 
@@ -2278,6 +2410,7 @@
         MarkType fMarkType;
     };
 
+    void addCodeBlock(const Definition* def, string& str) const;
     void addPopulators();
     string addReferences(const char* start, const char* end, BmhParser::Resolvable );
     string anchorDef(string def, string name);
@@ -2327,6 +2460,8 @@
         fResolveAndIndent = false;
         fLiteralAndIndent = false;
         fLastDef = nullptr;
+        fParamEnd = nullptr;
+        fInProgress = false;
     }
 
     BmhParser::Resolvable resolvable(const Definition* definition) const {
@@ -2346,10 +2481,14 @@
     }
 
     void resolveOut(const char* start, const char* end, BmhParser::Resolvable );
+    void rowOut(string col1, const Definition* col2);
     void rowOut(const char * name, string description, bool literalName);
 
     void subtopicOut(string name);
     void subtopicsOut(Definition* def);
+    void subtopicOut(string key, const vector<Definition*>& data, const Definition* csParent,
+        const Definition* topicParent, bool showClones);
+    bool subtopicRowOut(string keyName, const Definition* entry);
     void summaryOut(const Definition* def, MarkType , string name);
     string tableDataCodeDef(const Definition* def);
     string tableDataCodeDef(string def, string name);
@@ -2357,14 +2496,16 @@
     string tableDataCodeLocalRef(string ref, string name);
     string tableDataCodeRef(const Definition* ref);
     string tableDataCodeRef(string ref, string name);
+    void writeSubtopicTableHeader(string key);
 
     vector<const Definition*> fClassStack;
     unordered_map<string, vector<AnchorDef> > fAllAnchorDefs;
     unordered_map<string, vector<string> > fAllAnchorRefs;
 
     BmhParser& fBmhParser;
+    IncludeParser& fIncludeParser;
     const Definition* fEnumClass;
-     const Definition* fLastDef;
+    const Definition* fLastDef;
     Definition* fMethod;
     RootDefinition* fRoot;  // used in generating populated tables; always struct or class
     RootDefinition* fSubtopic; // used in resolving symbols
@@ -2372,6 +2513,7 @@
     TableState fTableState;
     unordered_map<string, SubtopicDescriptions> fPopulators;
     unordered_map<string, string> fPhraseParams;
+    const char* fParamEnd;
     bool fAddRefFailed;
     bool fHasFiddle;
     bool fInDescription;   // FIXME: for now, ignore unfound camelCase in description since it may
@@ -2381,6 +2523,7 @@
     bool fResolveAndIndent;
     bool fOddRow;
     bool fHasDetails;
+    bool fInProgress;
     typedef ParserCommon INHERITED;
 };
 
@@ -2391,8 +2534,7 @@
     MethodParser(string className, string fileName,
             const char* start, const char* end, int lineCount)
         : TextParser(fileName, start, end, lineCount)
-        , fClassName(className)
-        , fLocalName(className) {
+        , fClassName(className) {
         size_t doubleColons = className.find_last_of("::");
         if (string::npos != doubleColons) {
             fLocalName = className.substr(doubleColons + 1);
@@ -2402,37 +2544,53 @@
 
     ~MethodParser() override {}
 
-    void skipToMethodStart() {
-        if (!fClassName.length()) {
-            this->skipToAlphaNum();
-            return;
+    string localName() const {
+        return fLocalName;
+    }
+
+    void setLocalName(string name) {
+        if (name == fClassName) {
+            fLocalName = "";
+        } else {
+            fLocalName = name;
         }
+    }
+
+    // returns true if close brace was skipped
+    int skipToMethodStart() {
+        if (!fClassName.length()) {
+            return this->skipToAlphaNum();
+        }
+        int braceCount = 0;
         while (!this->eof() && !isalnum(this->peek()) && '~' != this->peek()) {
+            braceCount += '{' == this->peek();
+            braceCount -= '}' == this->peek();
             this->next();
         }
+        return braceCount;
     }
 
     void skipToMethodEnd(BmhParser::Resolvable resolvable) {
         if (this->eof()) {
             return;
         }
-        if (fLocalName.length()) {
-            if ('~' == this->peek()) {
-                this->next();
-                if (!this->startsWith(fLocalName.c_str())) {
-                    --fChar;
-                    return;
-                }
+        string name = fLocalName.length() ? fLocalName : fClassName;
+        if ('~' == this->peek()) {
+            this->next();
+            if (!this->startsWith(name.c_str())) {
+                --fChar;
+                return;
             }
-            if (BmhParser::Resolvable::kSimple != resolvable
-                    && (this->startsWith(fLocalName.c_str()) || this->startsWith("operator"))) {
-                const char* ptr = this->anyOf("\n (");
-                if (ptr && '(' ==  *ptr && strncmp(ptr, "(...", 4)) {
-                    this->skipToEndBracket(')');
-                    SkAssertResult(')' == this->next());
-                    this->skipExact("_const");
-                    return;
-                }
+        }
+        if (BmhParser::Resolvable::kSimple != resolvable
+                && (this->startsWith(name.c_str()) || this->startsWith("operator"))) {
+            const char* ptr = this->anyOf("\n (");
+            if (ptr && '(' ==  *ptr && strncmp(ptr, "(...", 4)) {
+                this->skipToEndBracket(')');
+                SkAssertResult(')' == this->next());
+                this->skipExact("_const") || (BmhParser::Resolvable::kCode == resolvable
+                        && this->skipExact(" const"));
+                return;
             }
         }
         if (this->startsWith("Sk") && this->wordEndsWith(".h")) {  // allow include refs
diff --git a/tools/bookmaker/cataloger.cpp b/tools/bookmaker/cataloger.cpp
index c027d58..fe0084d 100644
--- a/tools/bookmaker/cataloger.cpp
+++ b/tools/bookmaker/cataloger.cpp
@@ -51,10 +51,9 @@
 
 bool Catalog::openStatus(const char* statusFile) {
     StatusIter iter(statusFile, ".bmh", StatusFilter::kInProgress);
-    string unused;
     // FIXME: iterate through only chosen files by setting fDocsDir to iter
     // read one file to find directory
-    if (!iter.next(&unused)) {
+    if (!iter.next(nullptr, nullptr)) {
         return false;
     }
     return openCatalog(iter.baseDir().c_str());
diff --git a/tools/bookmaker/definition.cpp b/tools/bookmaker/definition.cpp
index c148357..4f841e4 100644
--- a/tools/bookmaker/definition.cpp
+++ b/tools/bookmaker/definition.cpp
@@ -39,7 +39,6 @@
     kVoid,
     kBool,
     kChar,
-    kFloat,
     kInt,
     kScalar,
     kSizeT,
@@ -117,7 +116,9 @@
                                     {{ 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,
+    { DEFOP::kMultiply, "*", "multiply1", BLANK, OpType::kThis, OpMod::kNone,         CONST,
+                                    {{ CONST,  OpType::kThis,   OpMod::kReference, }}},
+    { DEFOP::kMultiply, "*", "multiply2", 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,
@@ -144,7 +145,10 @@
                          || (typeWord == "SkVector" && name == "SkPoint")) {
         return OpType::kThis;
     }
-    const char* keyWords[] = { "void", "bool", "char", "float", "int", "SkScalar", "size_t" };
+    if ("float" == typeWord || "double" == typeWord) {
+        return OpType::kScalar;
+    }
+    const char* keyWords[] = { "void", "bool", "char", "int", "SkScalar", "size_t" };
     for (unsigned i = 0; i < SK_ARRAY_COUNT(keyWords); ++i) {
         if (typeWord == keyWords[i]) {
             return (OpType) (i + 1);
@@ -655,6 +659,9 @@
     if (inc.startsWith("SK_API")) {
         inc.skipWord("SK_API");
     }
+    if (inc.startsWith("inline")) {
+        inc.skipWord("inline");
+    }
     if (inc.startsWith("friend")) {
         inc.skipWord("friend");
     }
@@ -700,6 +707,9 @@
         char defCh;
         do {
             defCh = def.next();
+            if (inc.skipExact("SK_WARN_UNUSED_RESULT")) {
+                inc.skipSpace();
+            }
             char incCh = inc.next();
             if (' ' >= defCh && ' ' >= incCh) {
                 break;
@@ -800,8 +810,12 @@
             if (lastStart[0] != ' ') {
                 space_pad(&methodStr);
             }
-            methodStr += string(lastStart, (size_t) (lastEnd - lastStart));
-            written += (size_t) (lastEnd - lastStart);
+            string addon(lastStart, (size_t) (lastEnd - lastStart));
+            if ("_const" == addon) {
+                addon = "const";
+            }
+            methodStr += addon;
+            written += addon.length();
         }
         if (delimiter) {
             if (nextEnd - nextStart >= (ptrdiff_t) (limit - written)) {
diff --git a/tools/bookmaker/includeParser.cpp b/tools/bookmaker/includeParser.cpp
index eca7296..50ba723 100644
--- a/tools/bookmaker/includeParser.cpp
+++ b/tools/bookmaker/includeParser.cpp
@@ -55,6 +55,7 @@
     { "uintptr_t",  KeyWord::kUintPtr_t,    KeyProperty::kNumber         },
     { "union",      KeyWord::kUnion,        KeyProperty::kObject         },
     { "unsigned",   KeyWord::kUnsigned,     KeyProperty::kNumber         },
+    { "using",      KeyWord::kUsing,        KeyProperty::kObject         },
     { "void",       KeyWord::kVoid,         KeyProperty::kNumber         },
 };
 
@@ -65,7 +66,8 @@
     for (size_t index = 0; index < kKeyWordCount; ) {
         if (start[ch] > kKeyWords[index].fName[ch]) {
             ++index;
-            if (ch > 0 && kKeyWords[index - 1].fName[ch - 1] < kKeyWords[index].fName[ch - 1]) {
+            if (ch > 0 && (index == kKeyWordCount ||
+                    kKeyWords[index - 1].fName[ch - 1] < kKeyWords[index].fName[ch - 1])) {
                 return KeyWord::kNone;
             }
             continue;
@@ -104,6 +106,394 @@
     }
 }
 
+static bool looks_like_method(const TextParser& tp) {
+    TextParser t(tp.fFileName, tp.fLine, tp.fChar, tp.fLineCount);
+    t.skipSpace();
+    if (!t.skipExact("struct") && !t.skipExact("class") && !t.skipExact("enum class")
+            && !t.skipExact("enum")) {
+        return true;
+    }
+    t.skipSpace();
+    if (t.skipExact("SK_API")) {
+        t.skipSpace();
+    }
+    if (!isupper(t.peek())) {
+        return true;
+    }
+    return nullptr != t.strnchr('(', t.fEnd);
+}
+
+static bool looks_like_forward_declaration(const TextParser& tp) {
+    TextParser t(tp.fFileName, tp.fChar, tp.lineEnd(), tp.fLineCount);
+    t.skipSpace();
+    if (!t.skipExact("struct") && !t.skipExact("class") && !t.skipExact("enum class")
+            && !t.skipExact("enum")) {
+        return false;
+    }
+    t.skipSpace();
+    if (t.skipExact("SK_API")) {
+        t.skipSpace();
+    }
+    if (!isupper(t.peek())) {
+        return false;
+    }
+    t.skipToNonAlphaNum();
+    if (t.eof() || ';' != t.next()) {
+        return false;
+    }
+    if (t.eof() || '\n' != t.next()) {
+        return false;
+    }
+    return t.eof();
+}
+
+static bool looks_like_constructor(const TextParser& tp) {
+    TextParser t(tp.fFileName, tp.fLine, tp.lineEnd(), tp.fLineCount);
+    t.skipSpace();
+    if (!isupper(t.peek())) {
+        if (':' == t.next() && ' ' >= t.peek()) {
+            return true;
+        }
+        return false;
+    }
+    t.skipToNonAlphaNum();
+    if ('(' != t.peek()) {
+        return false;
+    }
+    if (!t.skipToEndBracket(')')) {
+        return false;
+    }
+    SkAssertResult(')' == t.next());
+    t.skipSpace();
+    return tp.fChar == t.fChar;
+}
+
+static bool looks_like_class_decl(const TextParser& tp) {
+    TextParser t(tp.fFileName, tp.fLine, tp.fChar, tp.fLineCount);
+    t.skipSpace();
+    if (!t.skipExact("class")) {
+        return false;
+    }
+    t.skipSpace();
+    if (t.skipExact("SK_API")) {
+        t.skipSpace();
+    }
+    if (!isupper(t.peek())) {
+        return false;
+    }
+    t.skipToNonAlphaNum();
+    return !t.skipToEndBracket('(');
+}
+
+static bool looks_like_const(const TextParser& tp) {
+    TextParser t(tp.fFileName, tp.fChar, tp.lineEnd(), tp.fLineCount);
+    if (!t.startsWith("static constexpr ")) {
+        return false;
+    }
+    if (t.skipToEndBracket(" k")) {
+        SkAssertResult(t.skipExact(" k"));
+    } else if (t.skipToEndBracket(" SK_")) {
+        SkAssertResult(t.skipExact(" SK_"));
+    } else {
+        return false;
+    }
+    if (!isupper(t.peek())) {
+        return false;
+    }
+    return t.skipToEndBracket(" = ");
+}
+
+static bool looks_like_member(const TextParser& tp) {
+    TextParser t(tp.fFileName, tp.fChar, tp.lineEnd(), tp.fLineCount);
+    const char* end = t.anyOf("(;");
+    if (!end || '(' == *end) {
+        return false;
+    }
+    bool foundMember = false;
+    do {
+        const char* next = t.anyOf(" ;");
+        if (';' == *next) {
+            break;
+        }
+        t.skipTo(next);
+        t.skipSpace();
+        foundMember = 'f' == t.fChar[0] && isupper(t.fChar[1]);
+    } while (true);
+    return foundMember;
+}
+
+static void skip_constructor_initializers(TextParser& t) {
+    SkAssertResult(':' == t.next());
+    do {
+        t.skipWhiteSpace();
+        t.skipToNonAlphaNum();
+        t.skipWhiteSpace();
+        if ('{' == t.peek()) {
+            t.skipToBalancedEndBracket('{', '}');
+        }
+        do {
+            const char* limiter = t.anyOf("(,{");
+            t.skipTo(limiter);
+            if ('(' != t.peek()) {
+                break;
+            }
+            t.skipToBalancedEndBracket('(', ')');
+        } while (true);
+        if ('{' == t.peek()) {
+            return;
+        }
+        SkAssertResult(',' == t.next());
+    } while (true);
+}
+
+static const char kInline[] = "inline ";
+static const char kSK_API[] = "SK_API ";
+static const char kSK_WARN_UNUSED_RESULT[] = "SK_WARN_UNUSED_RESULT ";
+
+bool IncludeParser::advanceInclude(TextParser& i) {
+    i.skipWhiteSpace(&fCheck.fIndent, &fCheck.fWriteReturn);
+    if (fCheck.fPrivateBrace) {
+        if (i.startsWith("};")) {
+            if (fCheck.fPrivateBrace == fCheck.fBraceCount) {
+                fCheck.fPrivateBrace = 0;
+                fCheck.fDoubleReturn = true;
+            } else {
+                i.skipExact("};");
+                fCheck.fBraceCount -= 1;
+            }
+            return false;
+        }
+        if (i.startsWith("public:")) {
+            if (fCheck.fBraceCount <= fCheck.fPrivateBrace) {
+                fCheck.fPrivateBrace = 0;
+                if (fCheck.fPrivateProtected) {
+                    i.skipExact("public:");
+                }
+            } else {
+                i.skipExact("public:");
+            }
+        } else {
+            fCheck.fBraceCount += i.skipToLineBalance('{', '}');
+        }
+        return false;
+    } else if (i.startsWith("};")) {
+        fCheck.fDoubleReturn = 2;
+    }
+    if (i.skipExact(kInline)) {
+        fCheck.fSkipInline = true;
+        return false;
+    }
+    if (i.skipExact(kSK_API)) {
+        fCheck.fSkipAPI = true;
+        return false;
+    }
+    if (i.skipExact(kSK_WARN_UNUSED_RESULT)) {
+        fCheck.fSkipWarnUnused = true;
+        return false;
+    }
+    if (i.skipExact("SK_ATTR_DEPRECATED")) {
+        i.skipToLineStart(&fCheck.fIndent, &fCheck.fWriteReturn);
+        return false;
+    }
+    if (i.skipExact("SkDEBUGCODE")) {
+        i.skipWhiteSpace();
+        if ('(' != i.peek()) {
+            i.reportError("expected open paren");
+        }
+        TextParserSave save(&i);
+        SkAssertResult(i.skipToBalancedEndBracket('(', ')'));
+        fCheck.fInDebugCode = i.fChar - 1;
+        save.restore();
+        SkAssertResult('(' == i.next());
+    }
+    if ('{' == i.peek()) {
+        if (looks_like_method(i)) {
+            fCheck.fState = CheckCode::State::kMethod;
+            if (!i.skipToBalancedEndBracket('{', '}')) {
+                i.reportError("unbalanced open brace");
+            }
+            i.skipToLineStart(&fCheck.fIndent, &fCheck.fWriteReturn);
+            return false;
+        } else if (looks_like_class_decl(i)) {
+            fCheck.fState = CheckCode::State::kClassDeclaration;
+            fCheck.fPrivateBrace = fCheck.fBraceCount + 1;
+            fCheck.fPrivateProtected = false;
+        }
+    }
+    if (':' == i.peek() && looks_like_constructor(i)) {
+        fCheck.fState = CheckCode::State::kConstructor;
+        skip_constructor_initializers(i);
+        return false;
+    }
+    if ('#' == i.peek()) {
+        i.skipToLineStart(&fCheck.fIndent, &fCheck.fWriteReturn);
+        return false;
+    }
+    if (i.startsWith("//")) {
+        i.skipToLineStart(&fCheck.fIndent, &fCheck.fWriteReturn);
+        return false;
+    }
+    if (i.startsWith("/*")) {
+        i.skipToEndBracket("*/");
+        i.skipToLineStart(&fCheck.fIndent, &fCheck.fWriteReturn);
+        return false;
+    }
+    if (looks_like_forward_declaration(i)) {
+        fCheck.fState = CheckCode::State::kForwardDeclaration;
+        i.skipToLineStart(&fCheck.fIndent, &fCheck.fWriteReturn);
+        return false;
+    }
+    if (i.skipExact("private:") || i.skipExact("protected:")) {
+        if (!fCheck.fBraceCount) {
+            i.reportError("expect private in brace");
+        }
+        fCheck.fPrivateBrace = fCheck.fBraceCount;
+        fCheck.fPrivateProtected = true;
+        return false;
+    }
+    const char* funcEnd = i.anyOf("(\n");
+    if (funcEnd && '(' == funcEnd[0] && '_' == *i.anyOf("_(")
+            && (i.contains("internal_", funcEnd, nullptr)
+            || i.contains("private_", funcEnd, nullptr)
+            || i.contains("legacy_", funcEnd, nullptr)
+            || i.contains("temporary_", funcEnd, nullptr))) {
+        i.skipTo(funcEnd);
+        if (!i.skipToBalancedEndBracket('(', ')')) {
+            i.reportError("unbalanced open parent");
+        }
+        i.skipSpace();
+        i.skipExact("const ");
+        i.skipSpace();
+        if (';' == i.peek()) {
+            i.next();
+        }
+        fCheck.fState = CheckCode::State::kNone;
+        return false;
+    }
+    return true;
+}
+
+void IncludeParser::codeBlockAppend(string& result, char ch) const {
+    if (Elided::kYes == fElided && fCheck.fBraceCount) {
+        return;
+    }
+    this->stringAppend(result, ch);
+}
+
+void IncludeParser::codeBlockSpaces(string& result, int indent) const {
+    if (!indent) {
+        return;
+    }
+    if (Elided::kYes == fElided && fCheck.fBraceCount) {
+        return;
+    }
+    SkASSERT(indent > 0);
+    if (fDebugWriteCodeBlock) {
+        SkDebugf("%*c", indent, ' ');
+    }
+    result.append(indent, ' ');
+}
+
+string IncludeParser::writeCodeBlock(const Definition& iDef, MarkType markType) {
+    TextParser i(&iDef);
+    fElided = Elided::kNo;
+    const char* before = iDef.fContentStart;
+    while (' ' == *--before)
+        ;
+    int startIndent = iDef.fContentStart - before - 1;
+    return writeCodeBlock(i, markType, startIndent);
+}
+
+string IncludeParser::writeCodeBlock(TextParser& i, MarkType markType, int startIndent) {
+    string result;
+    char last;
+    int lastIndent = 0;
+    bool lastDoubleMeUp = false;
+    fCheck.reset();
+    this->codeBlockSpaces(result, startIndent);
+    do {
+        if (!this->advanceInclude(i)) {
+            continue;
+        }
+        do {
+            last = i.peek();
+            SkASSERT(' ' < last);
+            if (fCheck.fInDebugCode == i.fChar) {
+                fCheck.fInDebugCode = nullptr;
+                i.next();   // skip close paren
+                break;
+            }
+            if (CheckCode::State::kMethod == fCheck.fState) {
+                this->codeBlockAppend(result, ';');
+                fCheck.fState = CheckCode::State::kNone;
+            }
+            if (fCheck.fWriteReturn) {
+                this->codeBlockAppend(result, '\n');
+                bool doubleMeUp = i.startsWith("typedef ") || looks_like_const(i)
+                        || (!strncmp("struct ", i.fStart, 7) && looks_like_member(i));
+                if ((!--fCheck.fDoubleReturn && !i.startsWith("};")) || i.startsWith("enum ")
+                        || i.startsWith("typedef ") || doubleMeUp || fCheck.fTypedefReturn
+                        || (fCheck.fIndent && (i.startsWith("class ") || i.startsWith("struct ")))) {
+                    if (lastIndent > 0 && (!doubleMeUp || !lastDoubleMeUp)) {
+                        this->codeBlockAppend(result, '\n');
+                    }
+                    fCheck.fTypedefReturn = false;
+                    lastDoubleMeUp = doubleMeUp;
+                }
+                if (doubleMeUp) {
+                    fCheck.fTypedefReturn = true;
+                }
+                lastIndent = fCheck.fIndent;
+            }
+            if (fCheck.fIndent) {
+                size_t indent = fCheck.fIndent;
+                if (fCheck.fSkipInline && indent > sizeof(kInline)) {
+                    indent -= sizeof(kInline) - 1;
+                }
+                if (fCheck.fSkipAPI && indent > sizeof(kSK_API)) {
+                    indent -= sizeof(kSK_API) - 1;
+                }
+                if (fCheck.fSkipWarnUnused && indent > sizeof(kSK_WARN_UNUSED_RESULT)) {
+                    indent -= sizeof(kSK_WARN_UNUSED_RESULT) - 1;
+                }
+
+                this->codeBlockSpaces(result, indent);
+            }
+            this->codeBlockAppend(result, last);
+            fCheck.fWriteReturn = false;
+            fCheck.fIndent = 0;
+            fCheck.fBraceCount += '{' == last;
+            fCheck.fBraceCount -= '}' == last;
+            if (';' == last) {
+                fCheck.fSkipInline = false;
+                fCheck.fSkipAPI = false;
+                fCheck.fSkipWarnUnused = false;
+            }
+            if (fCheck.fBraceCount < 0) {
+                i.reportError("unbalanced close brace");
+                return result;
+            }
+            i.next();
+        } while (!i.eof() && ' ' < i.peek() && !i.startsWith("//"));
+    } while (!i.eof());
+    if (CheckCode::State::kMethod == fCheck.fState) {
+        this->codeBlockAppend(result, ';');
+    }
+    bool elidedTemplate = Elided::kYes == fElided && !strncmp(i.fStart, "template ", 9);
+    bool elidedTClass = elidedTemplate && MarkType::kClass == markType;
+    if (fCheck.fWriteReturn || elidedTClass) {
+        this->codeBlockAppend(result, '\n');
+    }
+    if ((MarkType::kFunction != markType && lastIndent > startIndent) || elidedTClass) {
+        this->codeBlockAppend(result, '}');
+    }
+    this->codeBlockAppend(result, ';');
+    if (MarkType::kFunction != markType || elidedTemplate) {
+        this->codeBlockAppend(result, '\n');
+    }
+    return result;
+}
+
 void IncludeParser::checkForMissingParams(const vector<string>& methodParams,
         const vector<string>& foundParams) {
     for (auto& methodParam : methodParams) {
@@ -220,6 +610,22 @@
     return result;
 }
 
+void IncludeParser::writeCodeBlock(const BmhParser& bmhParser) {
+    for (auto& classMapper : fIClassMap) {
+        string className = classMapper.first;
+        auto finder = bmhParser.fClassMap.find(className);
+        if (bmhParser.fClassMap.end() != finder) {
+            classMapper.second.fCode = this->writeCodeBlock(classMapper.second, MarkType::kClass);
+            continue;
+        }
+        SkASSERT(string::npos != className.find("::"));
+    }
+    for (auto& enumMapper : fIEnumMap) {
+            enumMapper.second->fCode = this->writeCodeBlock(*enumMapper.second,
+                    enumMapper.second->fMarkType);
+    }
+}
+
 #include <sstream>
 #include <iostream>
 
@@ -231,8 +637,6 @@
             SkASSERT(string::npos != className.find("::"));
             continue;
         }
-        RootDefinition* root = &finder->second;
-        root->clearVisited();
     }
     for (auto& classMapper : fIClassMap) {
         string className = classMapper.first;
@@ -421,24 +825,34 @@
                         }
                     }
                     def->fVisited = true;
+                    bool hasCode = false;
+                    bool hasPopulate = true;
                     for (auto& child : def->fChildren) {
                         if (MarkType::kCode == child->fMarkType) {
-                            def = child;
+                            hasPopulate = std::any_of(child->fChildren.begin(),
+                                    child->fChildren.end(), [](auto grandChild){
+                                    return MarkType::kPopulate == grandChild->fMarkType; });
+                            if (!hasPopulate) {
+                                def = child;
+                            }
+                            hasCode = true;
                             break;
                         }
                     }
-                    if (MarkType::kCode != def->fMarkType) {
+                    if (!hasCode) {
                         if (!root->fDeprecated) {
                             SkDebugf("enum code missing from bmh: %s\n", fullName.c_str());
                             fFailed = true;
                         }
                         break;
                     }
-                    if (def->crossCheck(token)) {
-                        def->fVisited = true;
-                    } else {
-                        SkDebugf("enum differs from bmh: %s\n", def->fName.c_str());
-                        fFailed = true;
+                    if (!hasPopulate) {
+                        if (def->crossCheck(token)) {
+                            def->fVisited = true;
+                        } else {
+                            SkDebugf("enum differs from bmh: %s\n", def->fName.c_str());
+                            fFailed = true;
+                        }
                     }
                     for (auto& child : token.fChildren) {
                         string constName = MarkType::kEnumClass == token.fMarkType ?
@@ -839,9 +1253,7 @@
     this->lf(2);
     this->dumpComment(token);
     for (auto& child : token.fChildren) {
-    //     start here;
-        // get comments before
-        // or after const values
+        // TODO: get comments before or after const values
         this->writeTag("Const");
         this->writeSpace();
         this->writeString(child->fName);
@@ -1327,6 +1739,23 @@
     this->dumpComment(token);
 }
 
+string IncludeParser::elidedCodeBlock(const Definition& iDef) {
+    SkASSERT(KeyWord::kStruct == iDef.fKeyWord || KeyWord::kClass == iDef.fKeyWord
+            || KeyWord::kTemplate == iDef.fKeyWord);
+    TextParser i(&iDef);
+    fElided = Elided::kYes;
+    MarkType markType = MarkType::kClass;
+    if (KeyWord::kTemplate == iDef.fKeyWord) {  // may be function
+        for (auto child : iDef.fChildren) {
+            if (MarkType::kMethod == child->fMarkType) {
+                markType = MarkType::kFunction;
+                break;
+            }
+        }
+    }
+    return this->writeCodeBlock(i, markType, 0);
+}
+
 bool IncludeParser::findComments(const Definition& includeDef, Definition* markupDef) {
     // add comment preceding class, if any
     const Definition* parent = includeDef.fParent;
@@ -1436,6 +1865,17 @@
     return false;
 }
 
+const Definition* IncludeParser::include(string match) const {
+    for (auto& entry : fIncludeMap) {
+        if (string::npos == entry.first.find(match)) {
+            continue;
+        }
+        return &entry.second;
+    }
+    SkASSERT(0);
+    return nullptr;
+}
+
 // caller just returns, so report error here
 bool IncludeParser::parseClass(Definition* includeDef, IsStruct isStruct) {
     SkASSERT(includeDef->fTokens.size() > 0);
@@ -1881,8 +2321,10 @@
         IClassDefinition& classDef = fIClassMap[markupDef->fName];
         SkASSERT(classDef.fStart);
         string uniqueName = this->uniqueName(classDef.fEnums, nameStr);
+        string fullName = markupChild->fName;
         markupChild->fName = uniqueName;
         classDef.fEnums[uniqueName] = markupChild;
+        fIEnumMap[fullName] = markupChild;
     }
     return true;
 }
@@ -2017,7 +2459,8 @@
     }
     tokenIter->fName = nameStr;     // simple token stream, OK if name is duplicate
     tokenIter->fMarkType = MarkType::kMethod;
-    tokenIter->fPrivate = string::npos != nameStr.find("::");
+    tokenIter->fPrivate = string::npos != nameStr.find("::")
+            && KeyWord::kTemplate != child->fParent->fKeyWord;
     auto testIter = child->fParent->fTokens.begin();
     SkASSERT(child->fParentIndex > 0);
     std::advance(testIter, child->fParentIndex - 1);
@@ -2035,8 +2478,11 @@
         end = testIter->fContentEnd - 1;
     } else {
         end = testIter->fContentEnd;
-        while (testIter != child->fParent->fTokens.end()) {
-            testIter = std::next(testIter);
+        do {
+            std::advance(testIter, 1);
+            if (testIter == child->fParent->fTokens.end()) {
+                break;
+            }
             switch (testIter->fType) {
                 case Definition::Type::kPunctuation:
                     SkASSERT(Punctuation::kSemicolon == testIter->fPunctuation
@@ -2054,7 +2500,7 @@
                     continue;
             }
             break;
-        }
+        } while (true);
     }
     while (end > start && ' ' >= end[-1]) {
         --end;
@@ -2154,6 +2600,11 @@
                         return child->reportError<bool>("failed to parse union");
                     }
                     break;
+                case KeyWord::kUsing:
+                    if (!this->parseUsing()) {
+                        return child->reportError<bool>("failed to parse using");
+                    }
+                    break;
                 default:
                     return child->reportError<bool>("unhandled keyword");
             }
@@ -2316,7 +2767,12 @@
 }
 
 bool IncludeParser::parseUnion() {
+    // incomplete
+    return true;
+}
 
+bool IncludeParser::parseUsing() {
+    // incomplete
     return true;
 }
 
@@ -2684,6 +3140,7 @@
                 if (KeyWord::kEnum == fParent->fKeyWord) {
                     fInEnum = false;
                 }
+                parentIsClass |= KeyWord::kStruct == fParent->fKeyWord;
                 this->popObject();
                 if (parentIsClass && fParent && KeyWord::kTemplate == fParent->fKeyWord) {
                     this->popObject();
diff --git a/tools/bookmaker/includeWriter.cpp b/tools/bookmaker/includeWriter.cpp
index 7f3a2d4..5891e95 100644
--- a/tools/bookmaker/includeWriter.cpp
+++ b/tools/bookmaker/includeWriter.cpp
@@ -450,14 +450,17 @@
     }
     Definition* codeBlock = nullptr;
     const char* commentStart = nullptr;
+    bool firstCodeBlocks = true;
     bool wroteHeader = false;
     bool lastAnchor = false;
     SkDEBUGCODE(bool foundConst = false);
     for (auto test : enumDef->fChildren) {
-        if (MarkType::kCode == test->fMarkType && !codeBlock) {
+        if (MarkType::kCode == test->fMarkType && firstCodeBlocks) {
             codeBlock = test;
             commentStart = codeBlock->fTerminator;
             continue;
+        } else if (codeBlock) {
+            firstCodeBlocks = false;
         }
         if (!codeBlock) {
             continue;
@@ -515,8 +518,8 @@
             break;
         }
     }
-    SkASSERT(codeBlock);
-    SkASSERT(foundConst);
+ //   SkASSERT(codeBlock);
+ //   SkASSERT(foundConst);
     if (wroteHeader) {
         this->indentOut();
         this->lfcr();
@@ -1506,6 +1509,11 @@
                 fContinuation = nullptr;
                 continue;
             }
+            if (KeyWord::kTemplate == child.fParent->fKeyWord) {
+                // incomplete; no support to template specialization in public includes
+                fContinuation = nullptr;
+                continue;
+            }
             return child.reportError<bool>("method not found");
         }
         if (Bracket::kSlashSlash == child.fBracket || Bracket::kSlashStar == child.fBracket) {
@@ -1518,6 +1526,10 @@
             if (this->isInternalName(child)) {
                 continue;
             }
+            if (KeyWord::kTemplate == child.fParent->fKeyWord) {
+                // todo: support template specializations
+                continue;
+            }
             const char* bodyEnd = fDeferComment ? fDeferComment->fContentStart - 1 :
                     fAttrDeprecated ? fAttrDeprecated->fContentStart - 1 :
                     child.fContentStart;
@@ -1547,7 +1559,7 @@
                 inConstructor = false;
                 method = &mapFind->second;
                 methodName = child.fName;
-            } else {
+            } else if (root) {
                 methodName = root->fName + "::" + child.fName;
                 size_t lastName = root->fName.rfind(':');
                 lastName = string::npos == lastName ? 0 : lastName + 1;
@@ -1657,7 +1669,22 @@
                                 if (MarkType::kTopic == parent->fMarkType ||
                                         MarkType::kSubtopic == parent->fMarkType) {
                                     const char* commentStart = root->fContentStart;
+                                    unsigned index = 0;
                                     const char* commentEnd = root->fChildren[0]->fStart;
+                                    int line = 1;
+                                    do {
+                                        TextParser parser(root->fFileName, commentStart, commentEnd, line);
+                                        if (!parser.eof()) {
+                                            parser.skipWhiteSpace();
+                                        }
+                                        if (!parser.eof()) {
+                                            break;
+                                        }
+                                        commentStart = root->fChildren[index]->fTerminator;
+                                        ++index;
+                                        SkASSERT(index < root->fChildren.size());
+                                        commentEnd = root->fChildren[index]->fStart;
+                                    } while (true);
                                     this->structOut(root, *root, commentStart, commentEnd);
                                 } else {
                                     SkASSERT(0); // incomplete
@@ -1784,6 +1811,7 @@
                 case KeyWord::kInline:
                 case KeyWord::kSK_API:
                 case KeyWord::kTemplate:
+                case KeyWord::kUsing:
                     break;
                 case KeyWord::kTypedef:
                     SkASSERT(!memberStart);
@@ -2255,6 +2283,7 @@
                 } else if (!first) {
                     this->fChar = start;
                     this->fLine = start;
+                    this->fEnd = end;
                     this->reportError("reference unfound");
                     return "";
                 }
@@ -2290,6 +2319,10 @@
         }
         if (!substitute.length()) {
             for (auto child : rootDef->fChildren) {
+                if (MarkType::kSubstitute == child->fMarkType) {
+                    substitute = string(child->fContentStart, child->length());
+                    break;
+                }
                 // there may be more than one
                 // if so, it's a bug since it's unknown which is the right one
                 if (MarkType::kClass == child->fMarkType ||
diff --git a/tools/bookmaker/mdOut.cpp b/tools/bookmaker/mdOut.cpp
index c79d8b2..21aa939 100644
--- a/tools/bookmaker/mdOut.cpp
+++ b/tools/bookmaker/mdOut.cpp
@@ -17,8 +17,8 @@
     fprintf(fOut, __VA_ARGS__)
 
 const char* SubtopicKeys::kGeneratedSubtopics[] = {
-    kClasses, kConstants, kConstructors, kDefines,
-    kMemberFunctions, kMembers, kOperators, kRelatedFunctions, kStructs, kTypedefs,
+    kConstants, kDefines, kTypedefs, kMembers, kClasses, kStructs, kConstructors,
+    kOperators, kMemberFunctions, kRelatedFunctions
 };
 
 const char* kConstTableStyle =
@@ -46,10 +46,10 @@
                                              kTH_Left   "Details</th>"                          "\n"
                                              kTH_Left   "Description</th>" "</tr>";
 const char* kAllMemberTableHeader = "  <tr>" kTH_Left   "Type</th>"                             "\n"
-                                             kTH_Left   "Name</th>"                             "\n"
+                                             kTH_Left   "Member</th>"                           "\n"
                                              kTH_Left   "Description</th>" "</tr>";
 const char* kSubMemberTableHeader = "  <tr>" kTH_Left   "Type</th>"                             "\n"
-                                             kTH_Left   "Name</th>"                             "\n"
+                                             kTH_Left   "Member</th>"                           "\n"
                                              kTH_Left   "Details</th>"                          "\n"
                                              kTH_Left   "Description</th>" "</tr>";
 const char* kTopicsTableHeader    = "  <tr>" kTH_Left   "Topic</th>"                            "\n"
@@ -162,7 +162,6 @@
 }
 
 #undef kConstTDBase
-#undef kTH_Left
 #undef kTH_Center
 
 static void add_ref(string leadingSpaces, string ref, string* result) {
@@ -204,56 +203,50 @@
 
 // detail strings are preceded by an example comment to check readability
 void MdOut::addPopulators() {
-    fPopulators[SubtopicKeys::kClasses].fName = "Class Declarations";
-    fPopulators[SubtopicKeys::kClasses].fOneLiner = "embedded class members";
-    fPopulators[SubtopicKeys::kClasses].fDetails =
-            /* SkImageInfo */ "uses C++ classes to declare the public data"
-            " structures and interfaces.";
-    fPopulators[SubtopicKeys::kConstants].fName = "Constants";
-    fPopulators[SubtopicKeys::kConstants].fOneLiner = "enum and enum class, and their const values";
-    fPopulators[SubtopicKeys::kConstants].fDetails =
-            /* SkImageInfo */ "related constants are defined by <code>enum</code>,"
-            " <code>enum class</code>,  <code>#define</code>, <code>const</code>,"
-            " and <code>constexpr</code>.";
-    fPopulators[SubtopicKeys::kConstructors].fName = "Constructors";
-    fPopulators[SubtopicKeys::kConstructors].fOneLiner = "functions that construct";
-    fPopulators[SubtopicKeys::kConstructors].fDetails =
+    auto populator = [this](string key, string singular, string plural, string oneLiner,
+            string details) -> void {
+        fPopulators[key].fSingular = singular;
+        fPopulators[key].fPlural = plural;
+        fPopulators[key].fOneLiner = oneLiner;
+        fPopulators[key].fDetails = details;
+    };
+    populator(SubtopicKeys::kClasses, "Class", "Class Declarations",
+            "embedded class members",
+            /* SkImageInfo */ "uses <code>class</code> to declare the public data structures"
+                              " and interfaces.");
+    populator(SubtopicKeys::kConstants, "Constant", "Constants",
+            "enum and enum class, and their const values",
+            /* SkImageInfo */ "defines related constants are using <code>enum</code>,"
+                              " <code>enum class</code>,  <code>#define</code>,"
+                              " <code>const</code>, and <code>constexpr</code>.");
+    populator(SubtopicKeys::kConstructors, "Constructor", "Constructors",
+            "functions that construct",
             /* SkImageInfo */ "can be constructed or initialized by these functions,"
-            " including C++ class constructors.";
-    fPopulators[SubtopicKeys::kDefines].fName = "Defines";
-    fPopulators[SubtopicKeys::kDefines].fOneLiner = "preprocessor definitions of functions, values";
-    fPopulators[SubtopicKeys::kDefines].fDetails =
+                              " including <code>class</code> constructors.");
+    populator(SubtopicKeys::kDefines, "Define", "Defines",
+            "preprocessor definitions of functions, values",
             /* SkImageInfo */ "uses preprocessor definitions to inline code and constants,"
-            " and to abstract platform-specific functionality.";
-    fPopulators[SubtopicKeys::kMemberFunctions].fName = "Functions";
-    fPopulators[SubtopicKeys::kMemberFunctions].fOneLiner = "global and class member functions";
-    fPopulators[SubtopicKeys::kMemberFunctions].fDetails =
-            /* SkImageInfo */ "member functions read and modify the structure properties.";
-    fPopulators[SubtopicKeys::kMembers].fName = "Members";
-    fPopulators[SubtopicKeys::kMembers].fOneLiner = "member values";
-    fPopulators[SubtopicKeys::kMembers].fDetails =
-            /* SkImageInfo */ "members may be read and written directly without using"
-            " a member function.";
-    fPopulators[SubtopicKeys::kOperators].fName = "Operators";
-    fPopulators[SubtopicKeys::kOperators].fOneLiner = "operator overloading methods";
-    fPopulators[SubtopicKeys::kOperators].fDetails =
-            /* SkImageInfo */ "operators inline class member functions with arithmetic"
-            " equivalents.";
-    fPopulators[SubtopicKeys::kRelatedFunctions].fName = "Related Functions";
-    fPopulators[SubtopicKeys::kRelatedFunctions].fOneLiner =
-            "similar member functions grouped together";
-    fPopulators[SubtopicKeys::kRelatedFunctions].fDetails =
-            /* SkImageInfo */ "global, <code>struct</code>, and <code>class</code> related member"
-            " functions share a topic.";
-    fPopulators[SubtopicKeys::kStructs].fName = "Struct Declarations";
-    fPopulators[SubtopicKeys::kStructs].fOneLiner = "embedded struct members";
-    fPopulators[SubtopicKeys::kStructs].fDetails =
-            /* SkImageInfo */ "uses C++ structs to declare the public data"
-            " structures and interfaces.";
-    fPopulators[SubtopicKeys::kTypedefs].fName = "Typedef Declarations";
-    fPopulators[SubtopicKeys::kTypedefs].fOneLiner = "types defined by other types";
-    fPopulators[SubtopicKeys::kTypedefs].fDetails =
-            /* SkImageInfo */ " <code>typedef</code> define a data type.";
+                              " and to abstract platform-specific functionality.");
+    populator(SubtopicKeys::kMemberFunctions, "Member Function", "Member Functions",
+            "static and local functions",
+            /* SkImageInfo */ "uses member functions to read and modify structure properties.");
+    populator(SubtopicKeys::kMembers, "Member", "Members",
+            "member values",
+            /* SkImageInfo */ "contains members that may be read and written directly without using"
+                              " a member function.");
+    populator(SubtopicKeys::kOperators, "Operator", "Operators",
+            "operator overloading functions",
+            /* SkImageInfo */ "defines member functions with arithmetic equivalents.");
+    populator(SubtopicKeys::kRelatedFunctions, "Related Function", "Related Functions",
+            "similar functions grouped together",
+            /* SkImageInfo */ "defines related functions that share a topic.");
+    populator(SubtopicKeys::kStructs, "Struct", "Struct Declarations",
+            "embedded struct members",
+            /* SkImageInfo */ "uses <code>struct</code> to declare the public data"
+                              " structures and interfaces.");
+    populator(SubtopicKeys::kTypedefs, "Typedef", "Typedef Declarations",
+            "types defined in terms of other types",
+            /* SkImageInfo */ "uses <code>typedef</code> to define a data type.");
 }
 
 Definition* MdOut::checkParentsForMatch(Definition* test, string ref) const {
@@ -283,15 +276,50 @@
             || BmhParser::Resolvable::kCode == resolvable;
 }
 
+static void fixup_const_function_name(string* ref) {
+    const char spaceConst[] = " const";
+    size_t spacePos = ref->rfind(spaceConst);
+    if (string::npos != spacePos && spacePos == ref->length() - sizeof(spaceConst) + 1) {
+        *ref = ref->substr(0, spacePos) + "_const";
+    }
+}
+
+struct BraceState {
+    BraceState(RootDefinition* root, string name, const char* ch, KeyWord last, KeyWord keyWord,
+            int count)
+        : fRoot(root)
+        , fName(name)
+        , fChar(ch)
+        , fLastKey(last)
+        , fKeyWord(keyWord)
+        , fBraceCount(count) {
+    }
+
+    RootDefinition* fRoot;
+    string fName;
+    const char* fChar;
+    KeyWord fLastKey;
+    KeyWord fKeyWord;
+    int fBraceCount;
+};
+
 // FIXME: preserve inter-line spaces and don't add new ones
 string MdOut::addReferences(const char* refStart, const char* refEnd,
         BmhParser::Resolvable resolvable) {
     string result;
     MethodParser t(fRoot ? fRoot->fName : string(), fFileName, refStart, refEnd, fLineCount);
     bool lineStart = true;
+//    bool structClassSet = false;
     string ref;
     string leadingSpaces;
     int distFromParam = 99;
+    const char* lastLine = nullptr;
+    vector<BraceState> braceStack;
+    KeyWord lastKeyWord = KeyWord::kNone;
+    KeyWord keyWord = KeyWord::kNone;
+    if (BmhParser::Resolvable::kCode == resolvable) {
+        braceStack.push_back({fRoot, fRoot->fName, t.fChar, lastKeyWord, keyWord, 0});
+    }
     do {
         ++distFromParam;
         const char* base = t.fChar;
@@ -303,8 +331,53 @@
             t.next();
             continue;
         }
-        t.skipToMethodStart();
+        if (BmhParser::Resolvable::kCode == resolvable) {
+            int priorBrace = braceStack.back().fBraceCount;
+            int braceCount = priorBrace + t.skipToMethodStart();
+            if (braceCount > priorBrace) {
+                string name;
+                if (KeyWord::kClass == keyWord || KeyWord::kStruct == keyWord) {
+                    name = ref;
+                } else if (KeyWord::kClass == lastKeyWord && KeyWord::kPublic == keyWord) {
+                    SkASSERT(lastLine);
+                    TextParser parser(t.fFileName, lastLine, t.fChar, t.fLineCount);
+                    parser.skipSpace();
+                    SkAssertResult(parser.skipExact("class "));
+                    parser.skipSpace();
+                    const char* nameStart = parser.fChar;
+                    parser.skipToSpace();
+                    name = string(nameStart, parser.fChar - nameStart);
+                }
+                if ("" != name) {
+                    const Definition* classDef = this->isDefined(t, name, resolvable);
+                    if (!classDef) {
+                        string className = fRoot->csParent()->fName;
+                        string withRoot = className + "::" + name;
+                        classDef = this->isDefined(t, withRoot, resolvable);
+                        SkASSERT(classDef);
+                    }
+                    if (classDef->isRoot()) {
+                        fRoot = const_cast<Definition*>(classDef)->asRoot();
+                        t.setLocalName(name);
+                    }
+                }
+                braceStack.push_back({fRoot, name, t.fChar, lastKeyWord, keyWord, braceCount});
+            } else if (braceCount < priorBrace) {
+                lastKeyWord = braceStack.back().fLastKey;
+                keyWord = braceStack.back().fKeyWord;
+                braceStack.pop_back();
+                if (KeyWord::kClass == keyWord || KeyWord::kStruct == keyWord) {
+                    fRoot = braceStack.back().fRoot;
+                    t.setLocalName(braceStack.back().fName);
+                }
+            }
+        } else {
+            (void) t.skipToMethodStart();
+        }
         const char* start = t.fChar;
+        if (fParamEnd <= start) {
+            fParamEnd = nullptr;
+        }
         if (wordStart < start) {
             if (lineStart) {
                 lineStart = false;
@@ -356,6 +429,17 @@
             continue;
         }
         ref = string(start, t.fChar - start);
+        if (fParamEnd
+                && islower(start[0]) && ('k' != start[0] || !isupper(start[1]))) {
+            if (' ' == start[-1] && ' ' != result.back()) {
+                result += ' ';
+            }
+            result += ref;
+            continue;
+        }
+        if (BmhParser::Resolvable::kCode == resolvable) {
+            fixup_const_function_name(&ref);
+        }
         if (const Definition* def = this->isDefined(t, ref, resolvable)) {
             if (MarkType::kExternal == def->fMarkType) {
                 (void) this->anchorRef("undocumented#" + ref, "");   // for anchor validate
@@ -365,15 +449,19 @@
             SkASSERT(def->fFiddle.length());
             if (BmhParser::Resolvable::kSimple != resolvable
                     && !t.eof() && '(' == t.peek() && t.strnchr(')', t.fEnd)) {
-                if (!t.skipToEndBracket(')')) {
+                TextParserSave tSave(&t);
+                if (!t.skipToBalancedEndBracket('(', ')')) {
+                    tSave.restore();
                     t.reportError("missing close paren");
                     fAddRefFailed = true;
                     return result;
                 }
-                t.next();
+//                t.next();
+                SkASSERT(')' == t.fChar[-1]);
+                fParamEnd = t.fChar - 1;    // don't resolve lower case words til param end
                 string fullRef = string(start, t.fChar - start);
                 // if _2 etc alternates are defined, look for paren match
-                // may ignore () if ref is all lower case
+                // may ignore () if ref is all lower case and resolvable is not code
                 // otherwise flag as error
                 int suffix = '2';
                 bool foundMatch = false;
@@ -409,6 +497,8 @@
                     }
                     ref = fullRef;
                 }
+                ref = ref.substr(0, ref.find('('));
+                tSave.restore();
 			} else if (BmhParser::Resolvable::kClone != resolvable &&
 					all_lower(ref) && (t.eof() || '(' != t.peek())) {
 				add_ref(leadingSpaces, ref, &result);
@@ -420,6 +510,9 @@
                 return result;
             }
 			result += linkRef(leadingSpaces, def, ref, resolvable);
+            if (!t.eof() && '(' == t.peek()) {
+                result += t.next();  // skip open paren
+            }
             continue;
         }
         if (!t.eof() && '(' == t.peek()) {
@@ -492,6 +585,17 @@
                     }
                 }
             }
+            KeyWord newKeyWord = IncludeParser::FindKey(start, start + ref.length());
+            lastLine = nullptr;
+            if (KeyWord::kPrivate != newKeyWord && KeyWord::kProtected != newKeyWord
+                    && KeyWord::kPublic != newKeyWord) {
+                lastKeyWord = keyWord;
+                keyWord = newKeyWord;
+            } else if (BmhParser::Resolvable::kCode == resolvable && ':' != t.peek()) {
+                lastLine = t.fLine;
+                lastKeyWord = keyWord;
+                keyWord = newKeyWord;
+            }
             add_ref(leadingSpaces, ref, &result);
             continue;
         }
@@ -536,16 +640,16 @@
     return result;
 }
 
-bool MdOut::buildReferences(const IncludeParser& includeParser, const char* docDir,
-        const char* mdFileOrPath) {
+bool MdOut::buildReferences(const char* docDir, const char* mdFileOrPath) {
     if (!sk_isdir(mdFileOrPath)) {
         SkDebugf("must pass directory %s\n", mdFileOrPath);
         SkDebugf("pass -i SkXXX.h to build references for a single include\n");
         return false;
     }
+    fInProgress = true;
     SkOSFile::Iter it(docDir, ".bmh");
     for (SkString file; it.next(&file); ) {
-        if (!includeParser.references(file)) {
+        if (!fIncludeParser.references(file)) {
             continue;
         }
         SkString p = SkOSPath::Join(docDir, file.c_str());
@@ -559,9 +663,11 @@
 
 bool MdOut::buildStatus(const char* statusFile, const char* outDir) {
     StatusIter iter(statusFile, ".bmh", StatusFilter::kInProgress);
-    for (string file; iter.next(&file); ) {
+    StatusFilter filter;
+    for (string file; iter.next(&file, &filter); ) {
         SkString p = SkOSPath::Join(iter.baseDir().c_str(), file.c_str());
         const char* hunk = p.c_str();
+        fInProgress = StatusFilter::kInProgress == filter;
         if (!this->buildRefFromFile(hunk, outDir)) {
             SkDebugf("failed to parse %s\n", hunk);
             return false;
@@ -630,9 +736,10 @@
                 header.replace(underscorePos, 1, " ");
             }
             SkASSERT(string::npos == header.find('_'));
-            FPRINTF("%s", header.c_str());
+            this->writeString(header);
             this->lfAlways(1);
-            FPRINTF("===");
+            this->writeString("===");
+            this->lfAlways(1);
         }
         const Definition* prior = nullptr;
         this->markTypeOut(topicDef, &prior);
@@ -1190,10 +1297,11 @@
         buildup = '#' + parent->fFiddle + '_' + ref;
     }
     string refOut(ref);
-    if (!globalEnumMember) {
+    if (!globalEnumMember && BmhParser::Resolvable::kCode != resolvable) {
         std::replace(refOut.begin(), refOut.end(), '_', ' ');
     }
-    if (ref.length() > 2 && islower(ref[0]) && "()" == ref.substr(ref.length() - 2)) {
+    if (ref.length() > 2 && islower(ref[0]) && "()" == ref.substr(ref.length() - 2)
+            && BmhParser::Resolvable::kCode != resolvable) {
         refOut = refOut.substr(0, refOut.length() - 2);
     }
     string result = leadingSpaces + this->anchorRef(buildup, refOut);
@@ -1228,6 +1336,131 @@
     return markType != def->fMarkType && *prior && markType == (*prior)->fMarkType;
 }
 
+// Recursively build string with declarative code. Skip structs, classes, that
+// have been built directly.
+void MdOut::addCodeBlock(const Definition* def, string& result) const {
+    const Definition* last = nullptr;
+    bool wroteFunction = false;
+    for (auto member : def->fChildren) {
+        const Definition* prior = last;
+        const char* priorTerminator = nullptr;
+        if (prior) {
+            priorTerminator = prior->fTerminator ? prior->fTerminator : prior->fContentEnd;
+        }
+        last = member;
+        if (KeyWord::kIfndef == member->fKeyWord) {
+            this->addCodeBlock(member, result);
+            continue;
+        }
+        if (KeyWord::kClass == member->fKeyWord || KeyWord::kStruct == member->fKeyWord
+                || KeyWord::kTemplate == member->fKeyWord) {
+            if (!member->fChildren.size()) {
+                continue;
+            }
+            // todo: Make sure this was written non-elided somewhere else
+            // todo: provide indent value?
+            string block = fIncludeParser.elidedCodeBlock(*member);
+            // add italic link for elided body
+            size_t brace = block.find('{');
+            if (string::npos != brace) {
+                string name = member->fName;
+                if ("" == name) {
+                    for (auto child : member->fChildren) {
+                        if ("" != (name = child->fName)) {
+                            break;
+                        }
+                    }
+                }
+                SkASSERT("" != name);
+                string body = "\n    // <i>" + name + " interface</i>";
+                block = block.substr(0, brace + 1) + body + block.substr(brace + 1);
+            }
+            this->stringAppend(result, block);
+            continue;
+        }
+        if (KeyWord::kEnum == member->fKeyWord) {
+            if (member->fChildren.empty()) {
+                continue;
+            }
+            auto tokenIter = member->fTokens.begin();
+            if (KeyWord::kEnum == member->fKeyWord && KeyWord::kClass == tokenIter->fKeyWord) {
+                tokenIter = tokenIter->fTokens.begin();
+            }
+            while (Definition::Type::kWord != tokenIter->fType) {
+                std::advance(tokenIter, 1);
+            }
+            const auto& token = *tokenIter;
+            string name = string(token.fContentStart, token.length());
+            SkASSERT(name.length() > 0);
+            MarkType markType = KeyWord::kClass == member->fKeyWord
+                    || KeyWord::kStruct == member->fKeyWord ? MarkType::kClass : MarkType::kEnum;
+            // find bmh def or just find name of class / struct / enum ? (what if enum is nameless?)
+            if (wroteFunction) {
+                this->stringAppend(result, '\n');
+                wroteFunction = false;
+            }
+            this->stringAppend(result,
+                    fIncludeParser.codeBlock(markType, name, fInProgress));
+            this->stringAppend(result, '\n');
+            continue;
+        }
+        // Global function declarations are not preparsed very well;
+        // make do by using the prior position to find the start
+        if (Bracket::kParen == member->fBracket && prior) {
+            TextParser function(member->fFileName, priorTerminator, member->fTerminator + 1,
+                    member->fLineCount);
+            this->stringAppend(result,
+                    fIncludeParser.writeCodeBlock(function, MarkType::kFunction, 0));
+            this->stringAppend(result, '\n');
+            wroteFunction = true;
+            continue;
+        }
+        if (KeyWord::kTypedef == member->fKeyWord) {
+            this->stringAppend(result, member);
+            this->stringAppend(result, ';');
+            this->stringAppend(result, '\n');
+            continue;
+        }
+        if (KeyWord::kDefine == member->fKeyWord) {
+            string body(member->fContentStart, member->length());
+            if (string::npos != body.find('(')) {
+                this->stringAppend(result, body);
+                this->stringAppend(result, '\n');
+            }
+            continue;
+        }
+        if (KeyWord::kConstExpr == member->fKeyWord) {
+            this->stringAppend(result, member);
+            auto nextMember = def->fTokens.begin();
+            unsigned tokenPos = member->fParentIndex + 1;
+            SkASSERT(tokenPos < def->fTokens.size());
+            std::advance(nextMember, tokenPos);
+            while (member->fContentEnd >= nextMember->fContentStart) {
+                std::advance(nextMember, 1);
+                SkASSERT(++tokenPos < def->fTokens.size());
+            }
+            while (Punctuation::kSemicolon != nextMember->fPunctuation) {
+                std::advance(nextMember, 1);
+                SkASSERT(++tokenPos < def->fTokens.size());
+            }
+            TextParser between(member->fFileName, member->fContentEnd,
+                    nextMember->fContentStart, member->fLineCount);
+            between.skipWhiteSpace();
+            if ('=' == between.peek()) {
+                this->stringAppend(result, ' ');
+                string middle(between.fChar, nextMember->fContentStart);
+                this->stringAppend(result, middle);
+                last = nullptr;
+            } else {
+                SkAssertResult(';' == between.peek());
+            }
+            this->stringAppend(result, ';');
+            this->stringAppend(result, '\n');
+            continue;
+        }
+    }
+}
+
 void MdOut::markTypeOut(Definition* def, const Definition** prior) {
     string printable = def->printableName();
     const char* textStart = def->fContentStart;
@@ -1263,40 +1496,14 @@
         case MarkType::kClass:
         case MarkType::kStruct: {
             fRoot = def->asRoot();
-            this->mdHeaderOut(1);
+        //    this->mdHeaderOut(1);
+            this->lfAlways(1);
             if (MarkType::kStruct == def->fMarkType) {
-                this->htmlOut(anchorDef(def->fFiddle, "Struct " + def->fName));
+                this->htmlOut(anchorDef(def->fFiddle, ""));
             } else {
-                this->htmlOut(anchorDef(this->linkName(def), "Class " + def->fName));
+                this->htmlOut(anchorDef(this->linkName(def), ""));
             }
-            this->lf(1);
-            if (string::npos != fRoot->fFileName.find("undocumented")) {
-                break;
-            }
-            // if class or struct contains constants, and doesn't contain subtopic kConstant, add it
-            // and add a child populate
-            const Definition* subtopic = def->subtopicParent();
-            const Definition* topic = def->topicParent();
-            for (auto item : SubtopicKeys::kGeneratedSubtopics) {
-                string subname;
-                if (subtopic != topic) {
-                    subname = subtopic->fName + '_';
-                }
-                subname += item;
-                if (fRoot->populator(item).fMembers.size()
-                        && !std::any_of(fRoot->fChildren.begin(), fRoot->fChildren.end(),
-                        [subname](const Definition* child) {
-                            return MarkType::kSubtopic == child->fMarkType
-                                    && subname == child->fName;
-                        } )) {
-                    // generate subtopic
-                    this->mdHeaderOut(2);
-                    this->htmlOut(anchorDef(subname, item));
-                    this->lf(2);
-                    // generate populate
-                    this->subtopicOut(item);
-                }
-            }
+        //    this->lf(1);
             } break;
         case MarkType::kCode:
             this->lfAlways(2);
@@ -1325,10 +1532,11 @@
             if (TableState::kNone == fTableState) {
                 SkASSERT(!*prior || (isConst && MarkType::kConst != (*prior)->fMarkType)
                         || (!isConst && MarkType::kMember != (*prior)->fMarkType));
-                this->mdHeaderOut(3);
-                FPRINTF("%s", this->fPopulators[isConst ? SubtopicKeys::kConstants :
-                        SubtopicKeys::kMembers].fName.c_str());
-                this->lfAlways(2);
+                if (isConst) {
+                    this->mdHeaderOut(3);
+                    this->writeString(this->fPopulators[SubtopicKeys::kConstants].fPlural);
+                    this->lfAlways(2);
+                }
                 FPRINTF("%s", kTableDeclaration);
                 fTableState = TableState::kRow;
                 fOddRow = true;
@@ -1512,7 +1720,7 @@
             string method_name = def->methodName();
             string formattedStr = def->formatFunction(Definition::Format::kIncludeReturn);
 			this->lfAlways(2);
-            this->htmlOut(anchorDef(def->fFiddle, ""));
+            this->htmlOut(this->anchorDef(def->fFiddle, ""));
 			if (!def->isClone()) {
                 this->mdHeaderOutLF(2, 1);
                 FPRINTF("%s", method_name.c_str());
@@ -1637,16 +1845,88 @@
             break;
         case MarkType::kPlatform:
             break;
-        case MarkType::kPopulate: {
-            SkASSERT(MarkType::kSubtopic == def->fParent->fMarkType);
-            string name = def->fParent->fName;
-            if (string::npos != name.find(SubtopicKeys::kOverview)) {
-                this->subtopicsOut(def->fParent);
+        case MarkType::kPopulate:
+            if (MarkType::kSubtopic == def->fParent->fMarkType) {
+                string name = def->fParent->fName;
+                if (string::npos != name.find(SubtopicKeys::kOverview)) {
+                    this->subtopicsOut(def->fParent);
+                } else {
+                    this->subtopicOut(name);
+                }
+            } else if (MarkType::kClass == def->fParent->fMarkType
+                    || MarkType::kStruct == def->fParent->fMarkType) {
+                Definition* parent = def->fParent;
+ //               SkASSERT(parent->csParent());
+                if (!parent->csParent()) {
+                    this->subtopicsOut(def->fParent);
+                }
+                // if class or struct contains constants, and doesn't contain subtopic kConstant,
+                //add it and add a child populate
+                const Definition* subtopic = parent->subtopicParent();
+                const Definition* topic = parent->topicParent();
+                for (auto item : SubtopicKeys::kGeneratedSubtopics) {
+                    if (SubtopicKeys::kRelatedFunctions == item) {
+                        continue;
+                    }
+                    if (SubtopicKeys::kMemberFunctions == item) {
+                        continue;
+                    }
+                    string subname;
+                    if (subtopic != topic) {
+                        subname = subtopic->fName + '_';
+                    }
+                    subname += item;
+                    if (fRoot->populator(item).fMembers.size()
+                            && !std::any_of(fRoot->fChildren.begin(), fRoot->fChildren.end(),
+                            [subname](const Definition* child) {
+                                return MarkType::kSubtopic == child->fMarkType
+                                        && subname == child->fName;
+                            } )) {
+                        // generate subtopic
+                        this->mdHeaderOut(2);
+                        string itemStr(item);
+                        std::replace(itemStr.begin(), itemStr.end(), '_', ' ');
+                        this->htmlOut(anchorDef(subname, itemStr));
+                        this->lf(2);
+                        // generate populate
+                        this->subtopicOut(item);
+                    }
+                }
             } else {
-                this->subtopicOut(name);
+                Definition* parent = def->fParent;
+                SkASSERT(parent && MarkType::kCode == parent->fMarkType);
+                // find include matching code parent
+                Definition* grand = parent->fParent;
+                SkASSERT(grand);
+                if (MarkType::kClass == grand->fMarkType
+                        || MarkType::kStruct == grand->fMarkType
+                        || MarkType::kEnum == grand->fMarkType
+                        || MarkType::kEnumClass == grand->fMarkType) {
+                    string codeBlock = fIncludeParser.codeBlock(*grand, fInProgress);
+                    this->resolveOut(codeBlock.c_str(), codeBlock.c_str() + codeBlock.length(),
+                            this->resolvable(parent));
+                } else {
+                    SkASSERT(MarkType::kTopic == grand->fMarkType);
+                    // use bmh file name to find include file name
+                    size_t start = grand->fFileName.rfind("Sk");
+                    SkASSERT(start != string::npos && start >= 0);
+                    size_t end = grand->fFileName.rfind("_Reference");
+                    SkASSERT(end != string::npos && end > start);
+                    string incName(grand->fFileName.substr(start, end - start));
+                    const Definition* includeDef = fIncludeParser.include(incName + ".h");
+                    SkASSERT(includeDef);
+                    string codeBlock;
+                    this->addCodeBlock(includeDef, codeBlock);
+                    this->resolveOut(codeBlock.c_str(), codeBlock.c_str() + codeBlock.length(),
+                            this->resolvable(parent));
+                }
             }
-            } break;
+            break;
         case MarkType::kPrivate:
+            this->writeString("Private:");
+            this->writeSpace();
+            this->writeBlock(def->length(), def->fContentStart);
+            this->lf(2);
             break;
         case MarkType::kReturn:
             this->mdHeaderOut(3);
@@ -1701,12 +1981,12 @@
             // if a subtopic child is const, generate short table of const name, value, line desc
             if (std::any_of(def->fChildren.begin(), def->fChildren.end(),
                     [](Definition* child){return MarkType::kConst == child->fMarkType;})) {
-                this->summaryOut(def, MarkType::kConst, fPopulators[SubtopicKeys::kConstants].fName);
+                this->summaryOut(def, MarkType::kConst, fPopulators[SubtopicKeys::kConstants].fPlural);
             }
             // if a subtopic child is member, generate short table of const name, value, line desc
             if (std::any_of(def->fChildren.begin(), def->fChildren.end(),
                     [](Definition* child){return MarkType::kMember == child->fMarkType;})) {
-                this->summaryOut(def, MarkType::kMember, fPopulators[SubtopicKeys::kMembers].fName);
+                this->summaryOut(def, MarkType::kMember, fPopulators[SubtopicKeys::kMembers].fPlural);
             }
             break;
         case MarkType::kTable:
@@ -1738,9 +2018,9 @@
             if (!isUndocumented) {
                 this->populateTables(def, fRoot);
             }
-            this->mdHeaderOut(1);
-            this->htmlOut(anchorDef(this->linkName(def), printable));
-            this->lf(1);
+//            this->mdHeaderOut(1);
+//            this->htmlOut(anchorDef(this->linkName(def), printable));
+//            this->lf(1);
             } break;
         case MarkType::kTypedef:
             this->mdHeaderOut(2);
@@ -2097,6 +2377,29 @@
     }
 }
 
+void MdOut::rowOut(string col1, const Definition* col2) {
+    FPRINTF("%s", fOddRow ? kTR_Dark.c_str() : "  <tr>");
+    this->lfAlways(1);
+    FPRINTF("%s", kTD_Left.c_str());
+    if ("" != col1) {
+        this->writeString(col1);
+    }
+    FPRINTF("</td>");
+    this->lfAlways(1);
+    FPRINTF("%s", kTD_Left.c_str());
+    TextParser parser(col2->fFileName, col2->fStart, col2->fContentStart, col2->fLineCount);
+    parser.skipExact("#Method");
+    parser.skipSpace();
+    parser.trimEnd();
+    string methodName(parser.fChar, parser.fEnd - parser.fChar);
+    this->htmlOut(this->anchorRef("#" + col2->fFiddle, methodName));
+    this->htmlOut("</td>");
+    this->lfAlways(1);
+    FPRINTF("  </tr>");
+    this->lfAlways(1);
+    fOddRow = !fOddRow;
+}
+
 void MdOut::rowOut(const char* name, string description, bool literalName) {
     FPRINTF("%s", fOddRow ? kTR_Dark.c_str() : "  <tr>");
     this->lfAlways(1);
@@ -2131,9 +2434,16 @@
     this->lfAlways(1);
     fOddRow = true;
     for (auto item : SubtopicKeys::kGeneratedSubtopics) {
+        if (SubtopicKeys::kMemberFunctions == item) {
+            continue;
+        }
         for (auto entry : fRoot->populator(item).fMembers) {
             if ((csParent && entry->csParent() == csParent)
                     || entry->subtopicParent() == subtopicParent) {
+                if (SubtopicKeys::kRelatedFunctions == item) {
+                    (void) subtopicRowOut(entry->fName, entry); // report all errors
+                    continue;
+                }
                 auto popItem = fPopulators.find(item);
                 string description = popItem->second.fOneLiner;
                 if (SubtopicKeys::kConstructors == item) {
@@ -2143,7 +2453,7 @@
                 if (subtopicParent != topicParent) {
                     subtopic = subtopicParent->fName + '_';
                 }
-                string link = this->anchorLocalRef(subtopic + item, popItem->second.fName);
+                string link = this->anchorLocalRef(subtopic + item, popItem->second.fPlural);
                 this->rowOut(link.c_str(), description, true);
                 break;
             }
@@ -2167,7 +2477,7 @@
     this->lfAlways(1);
     if (fPopulators.end() != fPopulators.find(name)) {
         const SubtopicDescriptions& tableDescriptions = this->populator(name);
-        this->anchorDef(name, tableDescriptions.fName);
+        this->anchorDef(name, tableDescriptions.fPlural);
         this->lfAlways(1);
         if (tableDescriptions.fDetails.length()) {
             string details = csParent->fName;
@@ -2179,15 +2489,29 @@
         this->anchorDef(name, name);
         this->lfAlways(1);
     }
-    FPRINTF("%s", kTableDeclaration);
+    if (SubtopicKeys::kMembers == name) {
+        return; // members output their own table
+    }
+    const RootDefinition::SubtopicContents& tableContents = fRoot->populator(name.c_str());
+    if (SubtopicKeys::kTypedefs == name && fSubtopic && MarkType::kTopic == fSubtopic->fMarkType) {
+        topicParent = fSubtopic;
+    }
+    this->subtopicOut(name, tableContents.fMembers, csParent, topicParent,
+            tableContents.fShowClones);
+}
+
+void MdOut::subtopicOut(string key, const vector<Definition*>& data, const Definition* csParent,
+        const Definition* topicParent, bool showClones) {
+    this->writeString(kTableDeclaration);
     this->lfAlways(1);
-    FPRINTF("%s", kTopicsTableHeader);
+    this->writeSubtopicTableHeader(key);
     this->lfAlways(1);
     fOddRow = true;
     std::map<string, const Definition*> items;
-    const RootDefinition::SubtopicContents& tableContents = fRoot->populator(name.c_str());
-    auto& data = tableContents.fMembers;
     for (auto entry : data) {
+        if (!BmhParser::IsExemplary(entry)) {
+            continue;
+        }
         if (entry->csParent() != csParent && entry->topicParent() != topicParent) {
             continue;
         }
@@ -2198,55 +2522,77 @@
             start = entry->fName.substr(0, start - 1).rfind("::");
         }
         string entryName = entry->fName.substr(string::npos == start ? 0 : start + 1);
+//        fixup_const_function_name(&entry->fName);
         items[entryName] = entry;
     }
     for (auto entry : items) {
         if (entry.second->fDeprecated) {
             continue;
         }
-        const Definition* oneLiner = nullptr;
-        for (auto child : entry.second->fChildren) {
-            if (MarkType::kLine == child->fMarkType) {
-                oneLiner = child;
-                break;
-            }
+        if (!this->subtopicRowOut(entry.first, entry.second)) {
+            return;
         }
-        if (!oneLiner) {
-            TextParser parser(entry.second->fFileName, entry.second->fStart,
-                    entry.second->fContentStart, entry.second->fLineCount);
-            parser.reportError("missing #Line");
-            continue;
-        }
-        string keyName = entry.first;
-        TextParser dummy(entry.second); // for reporting errors, which we won't do
-        if (!this->isDefined(dummy, keyName, BmhParser::Resolvable::kOut)) {
-            keyName = entry.second->fName;
-            size_t doubleColon = keyName.find("::");
-            SkASSERT(string::npos != doubleColon);
-            keyName = keyName.substr(doubleColon + 2);
-        }
-        this->rowOut(keyName.c_str(), string(oneLiner->fContentStart,
-                oneLiner->fContentEnd - oneLiner->fContentStart), false);
-        if (tableContents.fShowClones && entry.second->fCloned) {
+        if (showClones && entry.second->fCloned) {
             int cloneNo = 2;
             string builder = entry.second->fName;
             if ("()" == builder.substr(builder.length() - 2)) {
                 builder = builder.substr(0, builder.length() - 2);
             }
             builder += '_';
-            this->rowOut("",
-                    preformat(entry.second->formatFunction(Definition::Format::kOmitReturn)), true);
+            this->rowOut("overloads", entry.second);
             do {
                 string match = builder + to_string(cloneNo);
                 auto child = csParent->findClone(match);
                 if (!child) {
                     break;
                 }
-                this->rowOut("",
-                        preformat(child->formatFunction(Definition::Format::kOmitReturn)), true);
+                this->rowOut("", child);
             } while (++cloneNo);
         }
     }
     FPRINTF("</table>");
     this->lf(2);
 }
+
+bool MdOut::subtopicRowOut(string keyName, const Definition* entry) {
+    const Definition* oneLiner = nullptr;
+    for (auto child : entry->fChildren) {
+        if (MarkType::kLine == child->fMarkType) {
+            oneLiner = child;
+            break;
+        }
+    }
+    if (!oneLiner) {
+        TextParser parser(entry->fFileName, entry->fStart,
+                entry->fContentStart, entry->fLineCount);
+        return parser.reportError<bool>("missing #Line");
+    }
+    TextParser dummy(entry); // for reporting errors, which we won't do
+    if (!this->isDefined(dummy, keyName, BmhParser::Resolvable::kOut)) {
+        keyName = entry->fName;
+        size_t doubleColon = keyName.find("::");
+        SkASSERT(string::npos != doubleColon);
+        keyName = keyName.substr(doubleColon + 2);
+    }
+    this->rowOut(keyName.c_str(), string(oneLiner->fContentStart,
+            oneLiner->fContentEnd - oneLiner->fContentStart), false);
+    return true;
+}
+
+void MdOut::writeSubtopicTableHeader(string key) {
+    this->htmlOut("<tr>");
+    this->htmlOut(kTH_Left);
+    if (fPopulators.end() != fPopulators.find(key)) {
+        this->writeString(fPopulators[key].fSingular);
+    } else {
+        this->writeString("Function");
+    }
+    this->htmlOut("</th>");
+    this->lf(1);
+    this->htmlOut(kTH_Left);
+    this->writeString("Description");
+    this->htmlOut("</th>");
+    this->htmlOut("</tr>");
+}
+
+#undef kTH_Left
diff --git a/tools/bookmaker/parserCommon.cpp b/tools/bookmaker/parserCommon.cpp
index 20e5604..5500a1b 100644
--- a/tools/bookmaker/parserCommon.cpp
+++ b/tools/bookmaker/parserCommon.cpp
@@ -84,7 +84,7 @@
     if (iter.empty()) {
         return false;
     }
-    for (string file; iter.next(&file); ) {
+    for (string file; iter.next(&file, nullptr); ) {
         SkString p = SkOSPath::Join(iter.baseDir().c_str(), file.c_str());
         const char* hunk = p.c_str();
         if (!this->parseFromFile(hunk)) {
@@ -137,6 +137,35 @@
     return true;
 }
 
+void ParserCommon::stringAppend(string& result, char ch) const {
+    if (fDebugWriteCodeBlock) {
+        SkDebugf("%c", ch);
+    }
+    result += ch;
+}
+
+void ParserCommon::stringAppend(string& result, string str) const {
+    string condense;
+    char last = result.size() ? result.back() : '\n';
+    for (auto c : str) {
+        if (' ' == c && ' ' == last) {
+            continue;
+        }
+        condense += c;
+        if ('\n' != last || ' ' != c) {
+            last = c;
+        }
+    }
+    if (fDebugWriteCodeBlock) {
+        SkDebugf("%s", condense.c_str());
+    }
+    result += condense;
+}
+
+void ParserCommon::stringAppend(string& result, const Definition* def) const {
+    this->stringAppend(result, string(def->fContentStart, def->length()));
+}
+
 bool ParserCommon::writeBlockIndent(int size, const char* data, bool ignoreIdent) {
     bool wroteSomething = false;
     while (size && ' ' >= data[size - 1]) {
@@ -387,8 +416,10 @@
 
 // FIXME: need to compare fBlockName against fFilter
 // need to compare fSuffix against next value returned
-bool StatusIter::next(string* str) {
+bool StatusIter::next(string* strPtr, StatusFilter *filter) {
+    string str;
     JsonStatus* status;
+    StatusFilter blockType = StatusFilter::kCompleted;
     do {
         do {
             if (fStack.empty()) {
@@ -402,7 +433,7 @@
         } while (true);
         if (1 == fStack.size()) {
             do {
-                StatusFilter blockType = StatusFilter::kUnknown;
+                blockType = StatusFilter::kUnknown;
                 for (unsigned index = 0; index < SK_ARRAY_COUNT(block_names); ++index) {
                     if (status->fIter.key().asString() == block_names[index]) {
                         blockType = (StatusFilter) index;
@@ -423,7 +454,8 @@
             JsonStatus block = {
                 *status->fIter,
                 status->fIter->begin(),
-                status->fIter.key().asString()
+                status->fIter.key().asString(),
+                blockType
             };
             fStack.emplace_back(block);
             status = &(&fStack.back())[-1];
@@ -431,9 +463,15 @@
             status = &fStack.back();
             continue;
         }
-        *str = status->fIter->asString();
+        str = status->fIter->asString();
+        if (strPtr) {
+            *strPtr = str;
+        }
+        if (filter) {
+            *filter = status->fStatusFilter;
+        }
         status->fIter++;
-        if (str->length() - strlen(fSuffix) == str->find(fSuffix)) {
+        if (str.length() - strlen(fSuffix) == str.find(fSuffix)) {
             return true;
         }
     } while (true);
@@ -452,7 +490,7 @@
         SkDebugf("file %s:\n", path);
         return this->reportError<bool>("file not parsable");
     }
-    JsonStatus block = { fRoot, fRoot.begin(), "" };
+    JsonStatus block = { fRoot, fRoot.begin(), "", StatusFilter::kUnknown };
     fStack.emplace_back(block);
     return true;
 }
diff --git a/tools/bookmaker/spellCheck.cpp b/tools/bookmaker/spellCheck.cpp
index 23222a4..f5786b9 100644
--- a/tools/bookmaker/spellCheck.cpp
+++ b/tools/bookmaker/spellCheck.cpp
@@ -112,7 +112,7 @@
     SpellCheck checker(*this);
     StatusIter iter(statusFile, ".bmh", StatusFilter::kInProgress);
     string file;
-    iter.next(&file);
+    iter.next(&file, nullptr);
     string match = iter.baseDir();
     checker.check(match.c_str());
     checker.report(report);