bookmaker initial checkin

bookmaker is a tool that generates documentation
backends from a canonical markup. Documentation for
bookmaker itself is evolving at docs/usingBookmaker.bmh,
which is visible online at skia.org/user/api/bmh_usingBookmaker

Change-Id: Ic76ddf29134895b5c2ebfbc84603e40ff08caf09
Reviewed-on: https://skia-review.googlesource.com/28000
Commit-Queue: Cary Clark <caryclark@google.com>
Reviewed-by: Cary Clark <caryclark@google.com>
This commit is contained in:
Cary Clark 2017-07-28 11:04:54 -04:00 committed by Skia Commit-Bot
parent acaa607328
commit 8032b983fa
16 changed files with 26251 additions and 0 deletions

View File

@ -1213,6 +1213,23 @@ if (skia_enable_tools) {
} }
} }
test_app("bookmaker") {
sources = [
"tools/bookmaker/bookmaker.cpp",
"tools/bookmaker/fiddleParser.cpp",
"tools/bookmaker/includeParser.cpp",
"tools/bookmaker/includeWriter.cpp",
"tools/bookmaker/mdOut.cpp",
"tools/bookmaker/parserCommon.cpp",
"tools/bookmaker/spellCheck.cpp",
]
deps = [
":flags",
":skia",
":tool_utils",
]
}
import("gn/samples.gni") import("gn/samples.gni")
test_lib("samples") { test_lib("samples") {
public_include_dirs = [ "samplecode" ] public_include_dirs = [ "samplecode" ]

5721
docs/SkCanvas.bmh Normal file

File diff suppressed because it is too large Load Diff

5280
docs/SkPaint.bmh Normal file

File diff suppressed because it is too large Load Diff

5801
docs/SkPath.bmh Normal file

File diff suppressed because it is too large Load Diff

88
docs/markup.bmh Normal file
View File

@ -0,0 +1,88 @@
#Topic Bookmaker_Markup
# redefine markup character so examples below will not be parsed
###$
Text, except for the single markup character, requires no annotation.
# comments are preceded by a hash symbol and whitespace
# comments may terminated by linefeed or double hash ## <- end of comment
Keywords are preceded by a single hash symbol without whitespace.
#Keyword
Keywords are terminated by double hash and may be labeled
## <- end of #keyword
#Keyword
#Keyword ## <- alternate labeled end of #Keyword
Tables use single hash symbols to delimit columns, and double to end row.
#Table
#Legend
# first column in table # next column in table ##
## <- end of #Legend
# a row # another row ##
# another row # another row ##
#Table ## <- or, just ##
$Table
$Legend
$ first column in table $ next column in table $$
$$
$ a row $ another row $$
$ another row $ another row $$
$Table $$
The markup character is initially # at the start of any .bmh file
###x <- redefine the markup character as 'x'
xxx# <- restore the default markup character
anchor, ala HTML
anchors may start anywhere in the line
#A text #_reference ##
class description
#Class SkClassName
description
methods
##
if the example is not named, it inherits the name of its container
#Example
#Description
##
#Image
#Width
#Height
code...
#StdOut
expected example output
##
##
#Enum __required_reference
description
#Code
##
#Example
##
#Enum ##
method description
the _method_reference must be unique within the class
#Method type name(params..)
description
#Param name description ##
#Return return ##
#Example
##
#SeeAlso ##
##
#ToDo description ##
$ restore markup character
$$$#
##

8
docs/overview.bmh Normal file
View File

@ -0,0 +1,8 @@
overview
--------------------------------------------------------------------------------
Skia draws 2D primitives, paths and bitmaps, using the styles in the SkPaint, to
the device contained by the SkCanvas.
--------------------------------------------------------------------------------

528
docs/undocumented.bmh Normal file
View File

@ -0,0 +1,528 @@
# external references that will be documented eventually ...
#External
DirectWrite TrueType Windows Linux Android
FreeType FreeType-based Harfbuzz
PostScript PostScript_arct
OS_X Core_Graphics Core_Text iOS
LCD RGB
Premultiplied Unpremultiplied
Unicode Unicode5 UTF-8 UTF-16 UTF-32 ASCII Unichar
HTML_Canvas HTML_Canvas_arcTo
API
CPU
GPU GPU-backed GPU_Context OpenGL Vulkan
NULL
RFC
Bezier Coons
SkUserConfig.h # not external, but still thinking about how markup refers to this
Skia # ditto
SK_USE_FREETYPE_EMBOLDEN # ditto
SK_SUPPORT_LEGACY_PAINT_TEXTDECORATION # ditto
SK_BUILD_FOR_ANDROID_FRAMEWORK # ditto
Developer_Mode # ditto
Draw_Layer # ditto
Raster_Engine # ditto
# FreeType related
FT_LOAD_TARGET_LIGHT
FT_LOAD_TARGET_NORMAL
FT_LOAD_TARGET_LCD
FT_LOAD_TARGET_LCD_V
FT_LOAD_NO_HINTING
FT_Load_Glyph
#External ##
#Topic Arc
#Substitute arcs
#Topic ##
#Topic BBH_Factory
#Class SkBBHFactory
##
##
#Topic Bitmap
#Class SkBitmap
#Subtopic Row_Bytes
##
#Class ##
##
#Topic Blend_Mode
#EnumClass SkBlendMode
#Const kSrc 1
##
#Const kSrcOver 3
##
#Const kPlus 12
##
#EnumClass ##
#Topic ##
#Topic Circle
#Substitute circles
#Topic ##
#Topic Clip_Op
#EnumClass SkClipOp
#Const kDifference 0
##
#Const kIntersect 1
##
##
##
#Topic Color
#Typedef SkColor
#Typedef ##
# fixme: defines, not methods, need new markup type
#Method int SkColorGetA(color)
##
#Method int SkColorGetR(color)
##
#Method int SkColorGetG(color)
##
#Method int SkColorGetB(color)
##
#Method int SkColorSetARGB(a, r, g, b)
##
#Const SK_ColorBLACK 0xFF000000
##
#Const SK_ColorBLUE 0xFF0000FF
##
#Const SK_ColorGREEN 0xFF00FF00
##
#Const SK_ColorRED 0xFFFF0000
##
#Const SK_ColorWHITE 0xFFFFFFFF
##
#Subtopic Alpha
#Substitute alpha
#Subtopic ##
#Subtopic RGB
#Substitute RGB
#Subtopic Red
#Substitute red
#Subtopic ##
#Subtopic Blue
#Substitute blue
#Subtopic ##
#Subtopic Green
#Substitute green
#Subtopic ##
#Subtopic ##
#Subtopic ARGB
#Substitute ARGB
#Subtopic ##
#Subtopic RBG
#Substitute RBG
#Subtopic ##
#Subtopic RGB-565
#Substitute RGB-565
#Alias Color_RGB-565 # quit changing - to _ !
#Subtopic ##
#Topic ##
#Topic Color_Filter
#Class SkColorFilter
#Class ##
#Topic ##
#Topic Color_Space
##
#Topic Curve
#Alias Curves
##
#Topic Data
##
#Topic Device
#Class SkBaseDevice
##
#Topic ##
#Topic Document
#Class SkDocument
#Method SkCanvas* beginPage(SkScalar width, SkScalar height,
const SkRect* content = NULL)
##
##
#Subtopic PDF
##
##
#Topic Draw_Filter
#Class SkDrawFilter
##
##
#Topic Draw_Looper
#Class SkDrawLooper
#Class ##
#Topic ##
#Topic Drawable
#Class SkDrawable
#Method void draw(SkCanvas*, const SkMatrix* = NULL)
##
##
##
#Topic Dump_Canvas
#Class SkDumpCanvas
##
#Topic ##
#Topic Filter_Quality
#Enum SkFilterQuality
#Const kNone_SkFilterQuality 0
##
#Const kLow_SkFilterQuality 1
##
#Const kMedium_SkFilterQuality 2
##
#Const kHigh_SkFilterQuality 3
##
#Enum ##
#Topic ##
#Topic Font
#Subtopic Advance
#Subtopic ##
#Subtopic Engine
##
#Topic ##
#Topic Font_Manager
#Topic ##
#Topic Glyph
##
#Topic Image
#Subtopic Alpha_Type
#Enum SkAlphaType
#Const kPremul_SkAlphaType 2
##
##
#Subtopic ##
#Subtopic Color_Type
#Enum SkColorType
#Const kUnknown_SkColorType 0
##
#Const kAlpha_8_SkColorType 1
##
#Const kRGB_565_SkColorType 2
##
#Const kARGB_4444_SkColorType 3
##
#Const kRGBA_8888_SkColorType 4
##
#Const kBGRA_8888_SkColorType 5
##
#Const kIndex_8_SkColorType 6
##
#Const kGray_8_SkColorType 7
##
#Const kRGBA_F16_SkColorType 8
##
#ToDo this is a lie; need to not require values for consts ##
#Const kN32_SkColorType 4
##
#Enum ##
#Subtopic ##
#Subtopic Info
#Struct SkImageInfo
#Method SkImageInfo()
##
##
#Subtopic ##
#Class SkImage
#Method sk_sp<SkShader> makeShader(SkShader::TileMode, SkShader::TileMode,
const SkMatrix* localMatrix = nullptr) const
##
##
#Topic ##
#Topic Image_Filter
#Subtopic Scaling
#Subtopic ##
#Class SkImageFilter
#Class ##
#Topic ##
#Topic Image_Scaling
##
#Topic IRect
#Struct SkIRect
##
##
#Topic Line
#Substitute lines
#Alias Lines
#Topic ##
#Topic Mask
#Topic ##
#Topic Mask_Alpha
#Topic ##
#Topic Mask_Filter
#Class SkMaskFilter
#Class ##
#Topic ##
#Topic Matrix
#Struct SkMatrix
#Struct ##
#Topic ##
#Topic Nine_Patch
##
#Topic Number_Types
#Typedef SkGlyphID
#Typedef ##
#Typedef SkScalar
#Typedef ##
#Const SK_ScalarMax
to be written
##
#Const SK_ScalarInfinity
to be written
##
#Const SK_ScalarNegativeInfinity
to be written
##
#Const SK_ScalarNaN
to be written
##
#Typedef SkUnichar
#Typedef ##
#Typedef U8CPU
#Typedef ##
#Topic ##
#Topic Oval
#Substitute ovals
#Topic ##
#Topic Paint_Defaults
#Const SkPaintDefaults_Flags 0
##
#Const SkPaintDefaults_Hinting 2
##
#Const SkPaintDefaults_TextSize 12
##
#Const SkPaintDefaults_MiterLimit 4
##
#Topic ##
#Topic Patch
#Substitute patches
#Topic ##
#Topic Path_Effect
#Class SkPathEffect
#Class ##
#Topic ##
#Topic Path_Measure
#Class SkPathMeasure
#Method void dump() const
##
##
##
#Topic PathOps
#Method bool SK_API Op(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result)
##
#Topic ##
#Topic Picture
#Subtopic Recorder
#Class SkPictureRecorder
#Method SkCanvas* beginRecording(const SkRect& bounds,
SkBBHFactory* bbhFactory = NULL,
uint32_t recordFlags = 0)
##
##
##
##
#Topic Pixel
#Subtopic Storage
##
##
#Topic Pixmap
#Class SkPixmap
##
##
#Topic Point
#Alias Points
#Struct SkPoint
#Method bool equalsWithinTolerance(const SkPoint& p) const
##
#Struct ##
#Subtopic Array
#Substitute SkPoint arrays
#Subtopic ##
#Topic ##
#Topic Raster_Handle_Allocator
#Class SkRasterHandleAllocator
#Struct Rec
##
#Method static std::unique_ptr<SkCanvas> MakeCanvas(std::unique_ptr<SkRasterHandleAllocator>, const SkImageInfo&, const Rec* rec = nullptr)
##
##
##
#Topic Rasterizer
#Class SkRasterizer
#Class ##
#Subtopic Layer
#Subtopic ##
#Topic ##
#Topic Rect
#Alias Rects
#Struct SkRect
#Method static constexpr SkRect SK_WARN_UNUSED_RESULT MakeEmpty()
##
#Method void dump() const
##
#Method void dumpHex() const
##
#Struct ##
#Topic ##
#Topic Reference_Count
#Substitute SkRefCnt
#Class sk_sp
#Class ##
#Topic ##
#Topic Region
#Class SkRegion
##
#Topic ##
#Topic Round_Rect
#Class SkRRect
#Method void dump() const
##
#Method void dumpHex() const
##
##
#Topic ##
#Topic RSXform
#Struct SkRSXform
##
##
#Topic Shader
#Class SkShader
#Enum TileMode
#Const kClamp_TileMode 0
##
##
#Method static sk_sp<SkShader> MakeBitmapShader(const SkBitmap& src, TileMode tmx, TileMode tmy,
const SkMatrix* localMatrix = nullptr)
##
#Class ##
#Subtopic Gradient
#Subtopic ##
#Topic ##
#Topic Sprite
#Substitute sprites
#Topic ##
#Topic Stream
#Class SkFlattenable
#Class ##
#Topic ##
#Topic String
#Class SkString
#Class ##
#Topic ##
#Topic Surface
#Class SkSurface
#Method static sk_sp<SkSurface> MakeRasterDirect(const SkImageInfo&, void* pixels, size_t rowBytes,
const SkSurfaceProps* = nullptr)
##
##
#Subtopic Properties
#Class SkSurfaceProps
#Enum InitType
#Const kLegacyFontHost_InitType 0
##
##
##
##
#Subtopic GPU
#Alias GPU_Surface
##
#Subtopic Raster
#Alias Raster_Surface
##
##
#Topic SVG
#Subtopic Canvas
##
#Subtopic Arc
##
##
#Topic Text
#Topic ##
#Topic Text_Blob
#Class SkTextBlob
#Class ##
#Topic ##
#Topic Typeface
#Class SkTypeface
#Class ##
#Topic ##
#Topic Vector
#Struct SkVector
##
##
#Topic Vertices
#Substitute vertices
#Subtopic Colors
##
#Subtopic Texs
##
#Topic ##
#Topic Read_Buffer
#Struct SkReadBuffer
#Struct ##
##
#Topic Write_Buffer
#Struct SkWriteBuffer
#Struct ##
#Topic ##

95
docs/usingBookmaker.bmh Normal file
View File

@ -0,0 +1,95 @@
#External
SkXXX
bmh_SkXXX
CL
C
Visual_Studio
##
#Topic Bookmaker
How to use the Bookmaker utility.
Get the fiddle command line interface tool.
#Code
$ go get go.skia.org/infra/fiddle/go/fiddlecli
##
Get the Bookmaker CL and build it.
#Code
$ git cl patch 9919
$ ninja -C out/dir bookmaker
##
Generate an starter Bookmaker file from an existing include.
This writes SkXXX.bmh in the current directory, which is
out/dir/obj/ from an IDE.
#Code
$ ./out/dir/bookmaker -t -i include/core/SkXXX.h
##
Use your favorite editor to fill out SkXXX.bmh.
Generate fiddle.json from all examples, including the ones you just wrote.
Error checking is syntatic: starting keywords are closed, keywords have the
correct parents.
If you run Bookmaker inside Visual_Studio, you can click on errors and it
will take you to the source line in question.
#Code
$ ./out/dir/bookmaker -e fiddle.json -b current_directory
##
Once complete, run fiddlecli to generate the example hashes.
Errors are contained by the output but aren't reported yet.
#Code
$ $GOPATH/bin/fiddlecli --input fiddle.json --output fiddleout.json
##
Generate bmh_SkXXX.md from SkXXX.bmh and fiddleout.json.
Error checking includes: undefined references, fiddle compiler errors,
missing or mismatched printf output.
Again, you can click on any errors inside Visual_Studio.
#Code
$ ./out/dir/bookmaker -r site/user/api -b current_directory -f fiddleout.json
##
The original include may have changed since you started creating the markdown.
Check to see if it is up to date.
This reports if a method no longer exists or its parameters have changed.
#Code
$ ./out/dir/bookmaker -x -b current_directory/SkXXX.bmh -i include/core/SkXXX.h
##
#Topic Bugs
#List
overaggressive reference finding in code block
missing examples
redundant examples -- got tired so used the same one more than once
some examples need vertical resizing
list doesn't work (ironic, huh)
##
##
#Topic To_Do
#List
check that all methods have one line descriptions in overview
see also -- anything that can be done automatically? maybe any ref shows up everywhere
index by example png
generate pdf or pdf-like out
generate b/w out instead of color -- have b/w versions of examples?
formalize voice / syntax for parts of topic and method
write bmh data back into include
have a way to write one block that covers multiple nearly indentical methods?
may want to do this for pdf view as well
write a one-method-per-page online view?
##
##
#Topic Bookmaker ##

File diff suppressed because it is too large Load Diff

1844
tools/bookmaker/bookmaker.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,231 @@
/*
* Copyright 2017 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "bookmaker.h"
static Definition* find_fiddle(Definition* def, const string& name) {
if (MarkType::kExample == def->fMarkType && name == def->fFiddle) {
return def;
}
for (auto& child : def->fChildren) {
Definition* result = find_fiddle(child, name);
if (result) {
return result;
}
}
return nullptr;
}
Definition* FiddleParser::findExample(const string& name) const {
for (const auto& topic : fBmhParser->fTopicMap) {
if (topic.second->fParent) {
continue;
}
Definition* def = find_fiddle(topic.second, name);
if (def) {
return def;
}
}
return nullptr;
}
bool FiddleParser::parseFiddles() {
if (!this->skipExact("{\n")) {
return false;
}
while (!this->eof()) {
if (!this->skipExact(" \"")) {
return false;
}
const char* nameLoc = fChar;
if (!this->skipToEndBracket("\"")) {
return false;
}
string name(nameLoc, fChar - nameLoc);
if (!this->skipExact("\": {\n")) {
return false;
}
if (!this->skipExact(" \"compile_errors\": [")) {
return false;
}
if (']' != this->peek()) {
// report compiler errors
int brackets = 1;
const char* errorStart = fChar;
do {
if ('[' == this->peek()) {
++brackets;
} else if (']' == this->peek()) {
--brackets;
}
} while (!this->eof() && this->next() && brackets > 0);
SkDebugf("fiddle compile error in %s: %.*s\n", name.c_str(), (int) (fChar - errorStart),
errorStart);
}
if (!this->skipExact("],\n")) {
return false;
}
if (!this->skipExact(" \"runtime_error\": \"")) {
return false;
}
if ('"' != this->peek()) {
const char* errorStart = fChar;
if (!this->skipToEndBracket('"')) {
return false;
}
SkDebugf("fiddle runtime error in %s: %.*s\n", name.c_str(), (int) (fChar - errorStart),
errorStart);
}
if (!this->skipExact("\",\n")) {
return false;
}
if (!this->skipExact(" \"fiddleHash\": \"")) {
return false;
}
const char* hashStart = fChar;
if (!this->skipToEndBracket('"')) {
return false;
}
Definition* example = this->findExample(name);
if (!example) {
SkDebugf("missing example %s\n", name.c_str());
}
string hash(hashStart, fChar - hashStart);
if (example) {
example->fHash = hash;
}
if (!this->skipExact("\",\n")) {
return false;
}
if (!this->skipExact(" \"text\": \"")) {
return false;
}
if ('"' != this->peek()) {
const char* stdOutStart = fChar;
do {
if ('\\' == this->peek()) {
this->next();
} else if ('"' == this->peek()) {
break;
}
} while (!this->eof() && this->next());
const char* stdOutEnd = fChar;
if (example) {
bool foundStdOut = false;
for (auto& textOut : example->fChildren) {
if (MarkType::kStdOut != textOut->fMarkType) {
continue;
}
foundStdOut = true;
bool foundVolatile = false;
for (auto& stdOutChild : textOut->fChildren) {
if (MarkType::kVolatile == stdOutChild->fMarkType) {
foundVolatile = true;
break;
}
}
TextParser bmh(textOut);
EscapeParser fiddle(stdOutStart, stdOutEnd);
do {
bmh.skipWhiteSpace();
fiddle.skipWhiteSpace();
const char* bmhEnd = bmh.trimmedLineEnd();
const char* fiddleEnd = fiddle.trimmedLineEnd();
ptrdiff_t bmhLen = bmhEnd - bmh.fChar;
SkASSERT(bmhLen > 0);
ptrdiff_t fiddleLen = fiddleEnd - fiddle.fChar;
SkASSERT(fiddleLen > 0);
if (bmhLen != fiddleLen) {
if (!foundVolatile) {
SkDebugf("mismatched stdout len in %s\n", name.c_str());
}
} else if (strncmp(bmh.fChar, fiddle.fChar, fiddleLen)) {
if (!foundVolatile) {
SkDebugf("mismatched stdout text in %s\n", name.c_str());
}
}
bmh.skipToLineStart();
fiddle.skipToLineStart();
} while (!bmh.eof() && !fiddle.eof());
if (!foundStdOut) {
SkDebugf("bmh %s missing stdout\n", name.c_str());
} else if (!bmh.eof() || !fiddle.eof()) {
if (!foundVolatile) {
SkDebugf("%s mismatched stdout eof\n", name.c_str());
}
}
}
}
}
if (!this->skipExact("\"\n")) {
return false;
}
if (!this->skipExact(" }")) {
return false;
}
if ('\n' == this->peek()) {
break;
}
if (!this->skipExact(",\n")) {
return false;
}
}
#if 0
// compare the text output with the expected output in the markup tree
this->skipToSpace();
SkASSERT(' ' == fChar[0]);
this->next();
const char* nameLoc = fChar;
this->skipToNonAlphaNum();
const char* nameEnd = fChar;
string name(nameLoc, nameEnd - nameLoc);
const Definition* example = this->findExample(name);
if (!example) {
return this->reportError<bool>("missing stdout name");
}
SkASSERT(':' == fChar[0]);
this->next();
this->skipSpace();
const char* stdOutLoc = fChar;
do {
this->skipToLineStart();
} while (!this->eof() && !this->startsWith("fiddles.htm:"));
const char* stdOutEnd = fChar;
for (auto& textOut : example->fChildren) {
if (MarkType::kStdOut != textOut->fMarkType) {
continue;
}
TextParser bmh(textOut);
TextParser fiddle(fFileName, stdOutLoc, stdOutEnd, fLineCount);
do {
bmh.skipWhiteSpace();
fiddle.skipWhiteSpace();
const char* bmhEnd = bmh.trimmedLineEnd();
const char* fiddleEnd = fiddle.trimmedLineEnd();
ptrdiff_t bmhLen = bmhEnd - bmh.fChar;
SkASSERT(bmhLen > 0);
ptrdiff_t fiddleLen = fiddleEnd - fiddle.fChar;
SkASSERT(fiddleLen > 0);
if (bmhLen != fiddleLen) {
return this->reportError<bool>("mismatched stdout len");
}
if (strncmp(bmh.fChar, fiddle.fChar, fiddleLen)) {
return this->reportError<bool>("mismatched stdout text");
}
bmh.skipToLineStart();
fiddle.skipToLineStart();
} while (!bmh.eof() && !fiddle.eof());
if (!bmh.eof() || (!fiddle.eof() && !fiddle.startsWith("</pre>"))) {
return this->reportError<bool>("mismatched stdout eof");
}
break;
}
}
}
#endif
return true;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

929
tools/bookmaker/mdOut.cpp Normal file
View File

@ -0,0 +1,929 @@
/*
* Copyright 2017 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "bookmaker.h"
#include "SkOSFile.h"
#include "SkOSPath.h"
static void add_ref(const string& leadingSpaces, const string& ref, string* result) {
*result += leadingSpaces + ref;
}
// 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;
string ref;
string leadingSpaces;
do {
const char* base = t.fChar;
t.skipWhiteSpace();
const char* wordStart = t.fChar;
t.skipToMethodStart();
const char* start = t.fChar;
if (wordStart < start) {
if (lineStart) {
lineStart = false;
} else {
wordStart = base;
}
result += string(wordStart, start - wordStart);
if ('\n' != result.back()) {
while (start > wordStart && '\n' == start[-1]) {
result += '\n';
--start;
}
}
}
if (lineStart) {
lineStart = false;
} else {
leadingSpaces = string(base, wordStart - base);
}
t.skipToMethodEnd();
if (base == t.fChar) {
break;
}
if (start >= t.fChar) {
continue;
}
if (!t.eof() && '"' == t.peek() && start > wordStart && '"' == start[-1]) {
continue;
}
ref = string(start, t.fChar - start);
if (const Definition* def = this->isDefined(t, ref,
BmhParser::Resolvable::kOut != resolvable)) {
SkASSERT(def->fFiddle.length());
if (!t.eof() && '(' == t.peek() && t.strnchr(')', t.fEnd)) {
if (!t.skipToEndBracket(')')) {
t.reportError("missing close paren");
return result;
}
t.next();
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
// otherwise flag as error
int suffix = '2';
bool foundMatch = false;
const Definition* altDef = def;
while (altDef && suffix <= '9') {
if ((foundMatch = altDef->paramsMatch(fullRef, ref))) {
def = altDef;
ref = fullRef;
break;
}
string altTest = ref + '_';
altTest += suffix++;
altDef = this->isDefined(t, altTest, false);
}
if (suffix > '9') {
t.reportError("too many alts");
return result;
}
if (!foundMatch) {
if (!(def = this->isDefined(t, fullRef, true))) {
return result;
}
ref = fullRef;
}
}
result += linkRef(leadingSpaces, def, ref);
continue;
}
if (!t.eof() && '(' == t.peek()) {
if (!t.skipToEndBracket(')')) {
t.reportError("missing close paren");
return result;
}
t.next();
ref = string(start, t.fChar - start);
if (const Definition* def = this->isDefined(t, ref, true)) {
SkASSERT(def->fFiddle.length());
result += linkRef(leadingSpaces, def, ref);
continue;
}
}
// class, struct, and enum start with capitals
// methods may start with upper (static) or lower (most)
// see if this should have been a findable reference
// look for Sk / sk / SK ..
if (!ref.compare(0, 2, "Sk") && ref != "Skew" && ref != "Skews" &&
ref != "Skip" && ref != "Skips") {
t.reportError("missed Sk prefixed");
return result;
}
if (!ref.compare(0, 2, "SK")) {
if (BmhParser::Resolvable::kOut != resolvable) {
t.reportError("missed SK prefixed");
}
return result;
}
if (!isupper(start[0])) {
// TODO:
// look for all lowercase w/o trailing parens as mistaken method matches
// will also need to see if Example Description matches var in example
const Definition* def;
if (fMethod && (def = fMethod->hasParam(ref))) {
result += linkRef(leadingSpaces, def, ref);
continue;
} else if (!fInDescription && ref[0] != '0'
&& string::npos != ref.find_first_of("ABCDEFGHIJKLMNOPQRSTUVWXYZ")) {
// FIXME: see isDefined(); check to see if fXX is a member of xx.fXX
if (('f' != ref[0] && string::npos == ref.find("()"))
|| '.' != t.backup(ref.c_str())) {
if (BmhParser::Resolvable::kOut != resolvable) {
t.reportError("missed camelCase");
return result;
}
}
}
add_ref(leadingSpaces, ref, &result);
continue;
}
auto topicIter = fBmhParser.fTopicMap.find(ref);
if (topicIter != fBmhParser.fTopicMap.end()) {
result += linkRef(leadingSpaces, topicIter->second, ref);
continue;
}
bool startsSentence = t.sentenceEnd(start);
if (!t.eof() && ' ' != t.peek()) {
add_ref(leadingSpaces, ref, &result);
continue;
}
if (t.fChar + 1 >= t.fEnd || (!isupper(t.fChar[1]) && startsSentence)) {
add_ref(leadingSpaces, ref, &result);
continue;
}
if (isupper(t.fChar[1]) && startsSentence) {
TextParser next(t.fFileName, &t.fChar[1], t.fEnd, t.fLineCount);
string nextWord(next.fChar, next.wordEnd() - next.fChar);
if (this->isDefined(t, nextWord, true)) {
add_ref(leadingSpaces, ref, &result);
continue;
}
}
Definition* test = fRoot;
do {
if (!test->isRoot()) {
continue;
}
for (string prefix : { "_", "::" } ) {
RootDefinition* root = test->asRoot();
string prefixed = root->fName + prefix + ref;
if (const Definition* def = root->find(prefixed)) {
result += linkRef(leadingSpaces, def, ref);
goto found;
}
}
} while ((test = test->fParent));
found:
if (!test) {
if (BmhParser::Resolvable::kOut != resolvable) {
t.reportError("undefined reference");
}
}
} while (!t.eof());
return result;
}
bool MdOut::buildReferences(const char* fileOrPath, const char* outDir) {
if (!sk_isdir(fileOrPath)) {
if (!this->buildRefFromFile(fileOrPath, outDir)) {
SkDebugf("failed to parse %s\n", fileOrPath);
return false;
}
} else {
SkOSFile::Iter it(fileOrPath, ".bmh");
for (SkString file; it.next(&file); ) {
SkString p = SkOSPath::Join(fileOrPath, file.c_str());
const char* hunk = p.c_str();
if (!SkStrEndsWith(hunk, ".bmh")) {
continue;
}
if (SkStrEndsWith(hunk, "markup.bmh")) { // don't look inside this for now
continue;
}
if (!this->buildRefFromFile(hunk, outDir)) {
SkDebugf("failed to parse %s\n", hunk);
return false;
}
}
}
return true;
}
bool MdOut::buildRefFromFile(const char* name, const char* outDir) {
fFileName = string(name);
string filename(name);
if (filename.substr(filename.length() - 4) == ".bmh") {
filename = filename.substr(0, filename.length() - 4);
}
size_t start = filename.length();
while (start > 0 && (isalnum(filename[start - 1]) || '_' == filename[start - 1])) {
--start;
}
string match = filename.substr(start);
string header = match;
filename = "bmh_" + match + ".md";
match += ".bmh";
fOut = nullptr;
for (const auto& topic : fBmhParser.fTopicMap) {
Definition* topicDef = topic.second;
if (topicDef->fParent) {
continue;
}
if (!topicDef->isRoot()) {
return this->reportError<bool>("expected root topic");
}
fRoot = topicDef->asRoot();
if (string::npos == fRoot->fFileName.rfind(match)) {
continue;
}
if (!fOut) {
string fullName(outDir);
if ('/' != fullName.back()) {
fullName += '/';
}
fullName += filename;
fOut = fopen(fullName.c_str(), "wb");
if (!fOut) {
SkDebugf("could not open output file %s\n", fullName.c_str());
return false;
}
fprintf(fOut, "Experimental %s", header.c_str());
this->lfAlways(1);
fprintf(fOut, "===");
}
this->markTypeOut(topicDef);
}
if (fOut) {
this->writePending();
fclose(fOut);
fOut = nullptr;
}
return true;
}
void MdOut::childrenOut(const Definition* def, const char* start) {
const char* end;
fLineCount = def->fLineCount;
if (def->isRoot()) {
fRoot = const_cast<RootDefinition*>(def->asRoot());
}
BmhParser::Resolvable resolvable = this->resolvable(def->fMarkType);
for (auto& child : def->fChildren) {
end = child->fStart;
if (BmhParser::Resolvable::kNo != resolvable) {
this->resolveOut(start, end, resolvable);
}
this->markTypeOut(child);
start = child->fTerminator;
}
if (BmhParser::Resolvable::kNo != resolvable) {
end = def->fContentEnd;
this->resolveOut(start, end, resolvable);
}
}
const Definition* MdOut::isDefined(const TextParser& parser, const string& ref, bool report) const {
auto rootIter = fBmhParser.fClassMap.find(ref);
if (rootIter != fBmhParser.fClassMap.end()) {
return &rootIter->second;
}
auto typedefIter = fBmhParser.fTypedefMap.find(ref);
if (typedefIter != fBmhParser.fTypedefMap.end()) {
return &typedefIter->second;
}
auto enumIter = fBmhParser.fEnumMap.find(ref);
if (enumIter != fBmhParser.fEnumMap.end()) {
return &enumIter->second;
}
auto constIter = fBmhParser.fConstMap.find(ref);
if (constIter != fBmhParser.fConstMap.end()) {
return &constIter->second;
}
auto methodIter = fBmhParser.fMethodMap.find(ref);
if (methodIter != fBmhParser.fMethodMap.end()) {
return &methodIter->second;
}
auto aliasIter = fBmhParser.fAliasMap.find(ref);
if (aliasIter != fBmhParser.fAliasMap.end()) {
return aliasIter->second;
}
for (const auto& external : fBmhParser.fExternals) {
if (external.fName == ref) {
return &external;
}
}
if (fRoot) {
if (ref == fRoot->fName) {
return fRoot;
}
if (const Definition* definition = fRoot->find(ref)) {
return definition;
}
Definition* test = fRoot;
do {
if (!test->isRoot()) {
continue;
}
RootDefinition* root = test->asRoot();
for (auto& leaf : root->fBranches) {
if (ref == leaf.first) {
return leaf.second;
}
const Definition* definition = leaf.second->find(ref);
if (definition) {
return definition;
}
}
for (string prefix : { "::", "_" } ) {
string prefixed = root->fName + prefix + ref;
if (const Definition* definition = root->find(prefixed)) {
return definition;
}
if (isupper(prefixed[0])) {
auto topicIter = fBmhParser.fTopicMap.find(prefixed);
if (topicIter != fBmhParser.fTopicMap.end()) {
return topicIter->second;
}
}
}
} while ((test = test->fParent));
}
size_t doubleColon = ref.find("::");
if (string::npos != doubleColon) {
string className = ref.substr(0, doubleColon);
auto classIter = fBmhParser.fClassMap.find(className);
if (classIter != fBmhParser.fClassMap.end()) {
const RootDefinition& classDef = classIter->second;
const Definition* result = classDef.find(ref);
if (result) {
return result;
}
}
}
if (!ref.compare(0, 2, "SK") || !ref.compare(0, 3, "sk_")
|| (('k' == ref[0] || 'g' == ref[0] || 'f' == ref[0]) &&
ref.length() > 1 && isupper(ref[1]))) {
// try with a prefix
if ('k' == ref[0]) {
for (auto const& iter : fBmhParser.fEnumMap) {
if (iter.second.find(ref)) {
return &iter.second;
}
}
}
if ('f' == ref[0]) {
// FIXME : find def associated with prior, e.g.: r.fX where 'SkPoint r' was earlier
// need to have pushed last resolve on stack to do this
// for now, just try to make sure that it's there and error if not
if ('.' != parser.backup(ref.c_str())) {
parser.reportError("fX member undefined");
return nullptr;
}
} else {
if (report) {
parser.reportError("SK undefined");
}
return nullptr;
}
}
if (isupper(ref[0])) {
auto topicIter = fBmhParser.fTopicMap.find(ref);
if (topicIter != fBmhParser.fTopicMap.end()) {
return topicIter->second;
}
size_t pos = ref.find('_');
if (string::npos != pos) {
// see if it is defined by another base class
string className(ref, 0, pos);
auto classIter = fBmhParser.fClassMap.find(className);
if (classIter != fBmhParser.fClassMap.end()) {
if (const Definition* definition = classIter->second.find(ref)) {
return definition;
}
}
auto enumIter = fBmhParser.fEnumMap.find(className);
if (enumIter != fBmhParser.fEnumMap.end()) {
if (const Definition* definition = enumIter->second.find(ref)) {
return definition;
}
}
if (report) {
parser.reportError("_ undefined");
}
return nullptr;
}
}
return nullptr;
}
string MdOut::linkName(const Definition* ref) const {
string result = ref->fName;
size_t under = result.find('_');
if (string::npos != under) {
string classPart = result.substr(0, under);
string namePart = result.substr(under + 1, result.length());
if (fRoot && (fRoot->fName == classPart
|| (fRoot->fParent && fRoot->fParent->fName == classPart))) {
result = namePart;
}
}
return result;
}
// for now, hard-code to html links
// def should not include SkXXX_
string MdOut::linkRef(const string& leadingSpaces, const Definition* def,
const string& ref) const {
string buildup;
const string* str = &def->fFiddle;
SkASSERT(str->length() > 0);
size_t under = str->find('_');
Definition* curRoot = fRoot;
string classPart = string::npos != under ? str->substr(0, under) : *str;
bool classMatch = curRoot->fName == classPart;
while (curRoot->fParent) {
curRoot = curRoot->fParent;
classMatch |= curRoot->fName == classPart;
}
const Definition* defRoot;
do {
defRoot = def;
if (!(def = def->fParent)) {
break;
}
classMatch |= def != defRoot && def->fName == classPart;
} while (true);
string namePart = string::npos != under ? str->substr(under + 1, str->length()) : *str;
SkASSERT(fRoot);
SkASSERT(fRoot->fFileName.length());
if (false && classMatch) {
str = &namePart;
} else if (true || (curRoot != defRoot && defRoot->isRoot())) {
string filename = defRoot->asRoot()->fFileName;
if (filename.substr(filename.length() - 4) == ".bmh") {
filename = filename.substr(0, filename.length() - 4);
}
size_t start = filename.length();
while (start > 0 && (isalnum(filename[start - 1]) || '_' == filename[start - 1])) {
--start;
}
buildup = "bmh_" + filename.substr(start) + "?cl=9919#"
+ (classMatch ? namePart : *str);
str = &buildup;
}
string refOut(ref);
std::replace(refOut.begin(), refOut.end(), '_', ' ');
if (ref.length() > 2 && islower(ref[0]) && "()" == ref.substr(ref.length() - 2)) {
refOut = refOut.substr(0, refOut.length() - 2);
}
return leadingSpaces + "<a href=\"" + *str + "\">" + refOut + "</a>";
}
void MdOut::markTypeOut(Definition* def) {
string printable = def->printableName();
const char* textStart = def->fContentStart;
if (MarkType::kParam != def->fMarkType && MarkType::kConst != def->fMarkType &&
(!def->fParent || MarkType::kConst != def->fParent->fMarkType) &&
TableState::kNone != fTableState) {
this->writePending();
fprintf(fOut, "</table>");
this->lf(2);
fTableState = TableState::kNone;
}
switch (def->fMarkType) {
case MarkType::kAlias:
break;
case MarkType::kAnchor:
break;
case MarkType::kBug:
break;
case MarkType::kClass:
this->mdHeaderOut(1);
fprintf(fOut, "<a name=\"%s\"></a> Class %s", this->linkName(def).c_str(),
def->fName.c_str());
this->lf(1);
break;
case MarkType::kCode:
this->lfAlways(2);
fprintf(fOut, "<pre style=\"padding: 1em 1em 1em 1em;"
"width: 44em; background-color: #f0f0f0\">");
this->lf(1);
break;
case MarkType::kColumn:
this->writePending();
if (fInList) {
fprintf(fOut, " <td>");
} else {
fprintf(fOut, "| ");
}
break;
case MarkType::kComment:
break;
case MarkType::kConst: {
if (TableState::kNone == fTableState) {
this->mdHeaderOut(3);
fprintf(fOut, "Constants\n"
"\n"
"<table>");
fTableState = TableState::kRow;
this->lf(1);
}
if (TableState::kRow == fTableState) {
this->writePending();
fprintf(fOut, " <tr>");
this->lf(1);
fTableState = TableState::kColumn;
}
this->writePending();
fprintf(fOut, " <td><a name=\"%s\"></a> <code><strong>%s </strong></code></td>",
def->fName.c_str(), def->fName.c_str());
const char* lineEnd = strchr(textStart, '\n');
SkASSERT(lineEnd < def->fTerminator);
SkASSERT(lineEnd > textStart);
SkASSERT((int) (lineEnd - textStart) == lineEnd - textStart);
fprintf(fOut, "<td>%.*s</td>", (int) (lineEnd - textStart), textStart);
fprintf(fOut, "<td>");
textStart = lineEnd;
} break;
case MarkType::kDefine:
break;
case MarkType::kDefinedBy:
break;
case MarkType::kDeprecated:
break;
case MarkType::kDescription:
fInDescription = true;
this->writePending();
fprintf(fOut, "<div>");
break;
case MarkType::kDoxygen:
break;
case MarkType::kEnum:
case MarkType::kEnumClass:
this->mdHeaderOut(2);
fprintf(fOut, "<a name=\"%s\"></a> Enum %s", def->fName.c_str(), def->fName.c_str());
this->lf(2);
break;
case MarkType::kError:
break;
case MarkType::kExample: {
this->mdHeaderOut(3);
fprintf(fOut, "Example\n"
"\n");
fHasFiddle = true;
const Definition* platform = def->hasChild(MarkType::kPlatform);
if (platform) {
TextParser platParse(platform);
fHasFiddle = !platParse.strnstr("!fiddle", platParse.fEnd);
}
if (fHasFiddle) {
fprintf(fOut, "<div><fiddle-embed name=\"%s\">", def->fHash.c_str());
} else {
fprintf(fOut, "<pre style=\"padding: 1em 1em 1em 1em;"
"width: 44em; background-color: #f0f0f0\">");
this->lf(1);
}
} break;
case MarkType::kExperimental:
break;
case MarkType::kExternal:
break;
case MarkType::kFile:
break;
case MarkType::kFormula:
break;
case MarkType::kFunction:
break;
case MarkType::kHeight:
break;
case MarkType::kImage:
break;
case MarkType::kLegend:
break;
case MarkType::kLink:
break;
case MarkType::kList:
fInList = true;
this->lfAlways(2);
fprintf(fOut, "<table>");
this->lf(1);
break;
case MarkType::kMarkChar:
fBmhParser.fMC = def->fContentStart[0];
break;
case MarkType::kMember: {
TextParser tp(def->fFileName, def->fStart, def->fContentStart, def->fLineCount);
tp.skipExact("#Member");
tp.skipWhiteSpace();
const char* end = tp.trimmedBracketEnd('\n', TextParser::OneLine::kYes);
this->lfAlways(2);
fprintf(fOut, "<code><strong>%.*s</strong></code>", (int) (end - tp.fChar), tp.fChar);
this->lf(2);
} break;
case MarkType::kMethod: {
string method_name = def->methodName();
string formattedStr = def->formatFunction();
if (!def->isClone()) {
this->lfAlways(2);
fprintf(fOut, "<a name=\"%s\"></a>", def->fiddleName().c_str());
this->mdHeaderOutLF(2, 1);
fprintf(fOut, "%s", method_name.c_str());
this->lf(2);
}
// TODO: put in css spec that we can define somewhere else (if markup supports that)
// TODO: 50em below should match limt = 80 in formatFunction()
this->writePending();
fprintf(fOut, "<pre style=\"padding: 1em 1em 1em 1em;"
"width: 50em; background-color: #f0f0f0\">\n"
"%s\n"
"</pre>", formattedStr.c_str());
this->lf(2);
fTableState = TableState::kNone;
fMethod = def;
} break;
case MarkType::kNoExample:
break;
case MarkType::kParam: {
if (TableState::kNone == fTableState) {
this->mdHeaderOut(3);
fprintf(fOut,
"Parameters\n"
"\n"
"<table>"
);
this->lf(1);
fTableState = TableState::kRow;
}
if (TableState::kRow == fTableState) {
fprintf(fOut, " <tr>");
this->lf(1);
fTableState = TableState::kColumn;
}
TextParser paramParser(def->fFileName, def->fStart, def->fContentStart,
def->fLineCount);
paramParser.skipWhiteSpace();
SkASSERT(paramParser.startsWith("#Param"));
paramParser.next(); // skip hash
paramParser.skipToNonAlphaNum(); // skip Param
paramParser.skipSpace();
const char* paramName = paramParser.fChar;
paramParser.skipToSpace();
fprintf(fOut,
" <td><code><strong>%.*s </strong></code></td> <td>",
(int) (paramParser.fChar - paramName), paramName);
} break;
case MarkType::kPlatform:
break;
case MarkType::kPrivate:
break;
case MarkType::kReturn:
this->mdHeaderOut(3);
fprintf(fOut, "Return Value");
this->lf(2);
break;
case MarkType::kRow:
if (fInList) {
fprintf(fOut, " <tr>");
this->lf(1);
}
break;
case MarkType::kSeeAlso:
this->mdHeaderOut(3);
fprintf(fOut, "See Also");
this->lf(2);
break;
case MarkType::kStdOut: {
TextParser code(def);
this->mdHeaderOut(4);
fprintf(fOut,
"Example Output\n"
"\n"
"~~~~");
this->lfAlways(1);
code.skipSpace();
while (!code.eof()) {
const char* end = code.trimmedLineEnd();
fprintf(fOut, "%.*s\n", (int) (end - code.fChar), code.fChar);
code.skipToLineStart();
}
fprintf(fOut, "~~~~");
this->lf(2);
} break;
case MarkType::kStruct:
fRoot = def->asRoot();
this->mdHeaderOut(1);
fprintf(fOut, "<a name=\"%s\"></a> Struct %s", def->fName.c_str(), def->fName.c_str());
this->lf(1);
break;
case MarkType::kSubstitute:
break;
case MarkType::kSubtopic:
this->mdHeaderOut(2);
fprintf(fOut, "<a name=\"%s\"></a> %s", def->fName.c_str(), printable.c_str());
this->lf(2);
break;
case MarkType::kTable:
this->lf(2);
break;
case MarkType::kTemplate:
break;
case MarkType::kText:
break;
case MarkType::kTime:
break;
case MarkType::kToDo:
break;
case MarkType::kTopic:
this->mdHeaderOut(1);
fprintf(fOut, "<a name=\"%s\"></a> %s", this->linkName(def).c_str(),
printable.c_str());
this->lf(1);
break;
case MarkType::kTrack:
// don't output children
return;
case MarkType::kTypedef:
break;
case MarkType::kUnion:
break;
case MarkType::kVolatile:
break;
case MarkType::kWidth:
break;
default:
SkDebugf("fatal error: MarkType::k%s unhandled in %s()\n",
fBmhParser.fMaps[(int) def->fMarkType].fName, __func__);
SkASSERT(0); // handle everything
break;
}
this->childrenOut(def, textStart);
switch (def->fMarkType) { // post child work, at least for tables
case MarkType::kCode:
this->writePending();
fprintf(fOut, "</pre>");
this->lf(2);
break;
case MarkType::kColumn:
if (fInList) {
this->writePending();
fprintf(fOut, "</td>");
this->lf(1);
} else {
fprintf(fOut, " ");
}
break;
case MarkType::kDescription:
this->writePending();
fprintf(fOut, "</div>");
fInDescription = false;
break;
case MarkType::kEnum:
case MarkType::kEnumClass:
this->lfAlways(2);
break;
case MarkType::kExample:
this->writePending();
if (fHasFiddle) {
fprintf(fOut, "</fiddle-embed></div>");
} else {
fprintf(fOut, "</pre>");
}
this->lf(2);
break;
case MarkType::kList:
fInList = false;
this->writePending();
fprintf(fOut, "</table>");
this->lf(2);
break;
case MarkType::kLegend: {
SkASSERT(def->fChildren.size() == 1);
const Definition* row = def->fChildren[0];
SkASSERT(MarkType::kRow == row->fMarkType);
size_t columnCount = row->fChildren.size();
SkASSERT(columnCount > 0);
this->writePending();
for (size_t index = 0; index < columnCount; ++index) {
fprintf(fOut, "| --- ");
}
fprintf(fOut, " |");
this->lf(1);
} break;
case MarkType::kMethod:
fMethod = nullptr;
this->lfAlways(2);
fprintf(fOut, "---");
this->lf(2);
break;
case MarkType::kConst:
case MarkType::kParam:
SkASSERT(TableState::kColumn == fTableState);
fTableState = TableState::kRow;
this->writePending();
fprintf(fOut, "</td>\n");
fprintf(fOut, " </tr>");
this->lf(1);
break;
case MarkType::kReturn:
case MarkType::kSeeAlso:
this->lf(2);
break;
case MarkType::kRow:
if (fInList) {
fprintf(fOut, " </tr>");
} else {
fprintf(fOut, "|");
}
this->lf(1);
break;
case MarkType::kStruct:
fRoot = fRoot->rootParent();
break;
case MarkType::kTable:
this->lf(2);
break;
case MarkType::kPrivate:
break;
default:
break;
}
}
void MdOut::mdHeaderOutLF(int depth, int lf) {
this->lfAlways(lf);
for (int index = 0; index < depth; ++index) {
fprintf(fOut, "#");
}
fprintf(fOut, " ");
}
void MdOut::resolveOut(const char* start, const char* end, BmhParser::Resolvable resolvable) {
// FIXME: this needs the markdown character present when the def was defined,
// not the last markdown character the parser would have seen...
while (fBmhParser.fMC == end[-1]) {
--end;
}
if (start >= end) {
return;
}
string resolved = this->addReferences(start, end, resolvable);
trim_end_spaces(resolved);
if (resolved.length()) {
TextParser paragraph(fFileName, &*resolved.begin(), &*resolved.end(), fLineCount);
TextParser original(fFileName, start, end, fLineCount);
while (!original.eof() && '\n' == original.peek()) {
original.next();
}
original.skipSpace();
while (!paragraph.eof()) {
paragraph.skipWhiteSpace();
const char* contentStart = paragraph.fChar;
paragraph.skipToEndBracket('\n');
ptrdiff_t lineLength = paragraph.fChar - contentStart;
if (lineLength) {
this->writePending();
fprintf(fOut, "%.*s", (int) lineLength, contentStart);
}
int linefeeds = 0;
while (lineLength > 0 && '\n' == contentStart[--lineLength]) {
++linefeeds;
}
if (lineLength > 0) {
this->nl();
}
fLinefeeds += linefeeds;
if (paragraph.eof()) {
break;
}
if ('\n' == paragraph.next()) {
linefeeds = 1;
if (!paragraph.eof() && '\n' == paragraph.peek()) {
linefeeds = 2;
}
this->lf(linefeeds);
}
}
#if 0
while (end > start && end[0] == '\n') {
fprintf(fOut, "\n");
--end;
}
#endif
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright 2017 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "bookmaker.h"
bool ParserCommon::parseSetup(const char* path) {
this->reset();
sk_sp<SkData> data = SkData::MakeFromFileName(path);
if (nullptr == data.get()) {
SkDebugf("%s missing\n", path);
return false;
}
const char* rawText = (const char*) data->data();
bool hasCR = false;
size_t dataSize = data->size();
for (size_t index = 0; index < dataSize; ++index) {
if ('\r' == rawText[index]) {
hasCR = true;
break;
}
}
string name(path);
if (hasCR) {
vector<char> lfOnly;
for (size_t index = 0; index < dataSize; ++index) {
char ch = rawText[index];
if ('\r' == rawText[index]) {
ch = '\n';
if ('\n' == rawText[index + 1]) {
++index;
}
}
lfOnly.push_back(ch);
}
fLFOnly[name] = lfOnly;
dataSize = lfOnly.size();
rawText = &fLFOnly[name].front();
}
fRawData[name] = data;
fStart = rawText;
fLine = rawText;
fChar = rawText;
fEnd = rawText + dataSize;
fFileName = string(path);
fLineCount = 1;
return true;
}

View File

@ -0,0 +1,455 @@
/*
* Copyright 2017 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "bookmaker.h"
#include "SkOSFile.h"
#include "SkOSPath.h"
/*
things to do
if cap word is beginning of sentence, add it to table as lower-case
word must have only a single initial capital
if word is camel cased, look for :: matches on suffix
when function crosses lines, whole thing isn't seen as a 'word' e.g., search for largeArc in path
words in external not seen
*/
struct CheckEntry {
string fFile;
int fLine;
int fCount;
};
class SpellCheck : public ParserCommon {
public:
SpellCheck(const BmhParser& bmh) : ParserCommon()
, fBmhParser(bmh) {
this->reset();
}
bool check(const char* match);
void report();
private:
enum class TableState {
kNone,
kRow,
kColumn,
};
bool check(Definition* );
bool checkable(MarkType markType);
void childCheck(const Definition* def, const char* start);
void leafCheck(const char* start, const char* end);
bool parseFromFile(const char* path) override { return true; }
void printCheck(const string& str);
void reset() override {
INHERITED::resetCommon();
fMethod = nullptr;
fRoot = nullptr;
fTableState = TableState::kNone;
fInCode = false;
fInConst = false;
fInDescription = false;
fInStdOut = false;
}
void wordCheck(const string& str);
void wordCheck(ptrdiff_t len, const char* ch);
unordered_map<string, CheckEntry> fCode;
unordered_map<string, CheckEntry> fColons;
unordered_map<string, CheckEntry> fDigits;
unordered_map<string, CheckEntry> fDots;
unordered_map<string, CheckEntry> fParens; // also hold destructors, operators
unordered_map<string, CheckEntry> fUnderscores;
unordered_map<string, CheckEntry> fWords;
const BmhParser& fBmhParser;
Definition* fMethod;
RootDefinition* fRoot;
TableState fTableState;
bool fInCode;
bool fInConst;
bool fInDescription;
bool fInStdOut;
typedef ParserCommon INHERITED;
};
/* This doesn't perform a traditional spell or grammar check, although
maybe it should. Instead it looks for words used uncommonly and lower
case words that match capitalized words that are not sentence starters.
It also looks for articles preceeding capitalized words and their
modifiers to try to maintain a consistent voice.
Maybe also look for passive verbs (e.g. 'is') and suggest active ones?
*/
void BmhParser::spellCheck(const char* match) const {
SpellCheck checker(*this);
checker.check(match);
checker.report();
}
bool SpellCheck::check(const char* match) {
for (const auto& topic : fBmhParser.fTopicMap) {
Definition* topicDef = topic.second;
if (topicDef->fParent) {
continue;
}
if (!topicDef->isRoot()) {
return this->reportError<bool>("expected root topic");
}
fRoot = topicDef->asRoot();
if (string::npos == fRoot->fFileName.rfind(match)) {
continue;
}
this->check(topicDef);
}
return true;
}
bool SpellCheck::check(Definition* def) {
fFileName = def->fFileName;
fLineCount = def->fLineCount;
string printable = def->printableName();
const char* textStart = def->fContentStart;
if (MarkType::kParam != def->fMarkType && MarkType::kConst != def->fMarkType &&
TableState::kNone != fTableState) {
fTableState = TableState::kNone;
}
switch (def->fMarkType) {
case MarkType::kAlias:
break;
case MarkType::kAnchor:
break;
case MarkType::kBug:
break;
case MarkType::kClass:
this->wordCheck(def->fName);
break;
case MarkType::kCode:
fInCode = true;
break;
case MarkType::kColumn:
break;
case MarkType::kComment:
break;
case MarkType::kConst: {
fInConst = true;
if (TableState::kNone == fTableState) {
fTableState = TableState::kRow;
}
if (TableState::kRow == fTableState) {
fTableState = TableState::kColumn;
}
this->wordCheck(def->fName);
const char* lineEnd = strchr(textStart, '\n');
this->wordCheck(lineEnd - textStart, textStart);
textStart = lineEnd;
} break;
case MarkType::kDefine:
break;
case MarkType::kDefinedBy:
break;
case MarkType::kDeprecated:
break;
case MarkType::kDescription:
fInDescription = true;
break;
case MarkType::kDoxygen:
break;
case MarkType::kEnum:
case MarkType::kEnumClass:
this->wordCheck(def->fName);
break;
case MarkType::kError:
break;
case MarkType::kExample:
break;
case MarkType::kExternal:
break;
case MarkType::kFile:
break;
case MarkType::kFormula:
break;
case MarkType::kFunction:
break;
case MarkType::kHeight:
break;
case MarkType::kImage:
break;
case MarkType::kLegend:
break;
case MarkType::kList:
break;
case MarkType::kMember:
break;
case MarkType::kMethod: {
string method_name = def->methodName();
string formattedStr = def->formatFunction();
if (!def->isClone()) {
this->wordCheck(method_name);
}
fTableState = TableState::kNone;
fMethod = def;
} break;
case MarkType::kParam: {
if (TableState::kNone == fTableState) {
fTableState = TableState::kRow;
}
if (TableState::kRow == fTableState) {
fTableState = TableState::kColumn;
}
TextParser paramParser(def->fFileName, def->fStart, def->fContentStart,
def->fLineCount);
paramParser.skipWhiteSpace();
SkASSERT(paramParser.startsWith("#Param"));
paramParser.next(); // skip hash
paramParser.skipToNonAlphaNum(); // skip Param
paramParser.skipSpace();
const char* paramName = paramParser.fChar;
paramParser.skipToSpace();
fInCode = true;
this->wordCheck(paramParser.fChar - paramName, paramName);
fInCode = false;
} break;
case MarkType::kPlatform:
break;
case MarkType::kReturn:
break;
case MarkType::kRow:
break;
case MarkType::kSeeAlso:
break;
case MarkType::kStdOut: {
fInStdOut = true;
TextParser code(def);
code.skipSpace();
while (!code.eof()) {
const char* end = code.trimmedLineEnd();
this->wordCheck(end - code.fChar, code.fChar);
code.skipToLineStart();
}
fInStdOut = false;
} break;
case MarkType::kStruct:
fRoot = def->asRoot();
this->wordCheck(def->fName);
break;
case MarkType::kSubtopic:
this->printCheck(printable);
break;
case MarkType::kTable:
break;
case MarkType::kTemplate:
break;
case MarkType::kText:
break;
case MarkType::kTime:
break;
case MarkType::kToDo:
break;
case MarkType::kTopic:
this->printCheck(printable);
break;
case MarkType::kTrack:
// don't output children
return true;
case MarkType::kTypedef:
break;
case MarkType::kUnion:
break;
case MarkType::kWidth:
break;
default:
SkASSERT(0); // handle everything
break;
}
this->childCheck(def, textStart);
switch (def->fMarkType) { // post child work, at least for tables
case MarkType::kCode:
fInCode = false;
break;
case MarkType::kColumn:
break;
case MarkType::kDescription:
fInDescription = false;
break;
case MarkType::kEnum:
case MarkType::kEnumClass:
break;
case MarkType::kExample:
break;
case MarkType::kLegend:
break;
case MarkType::kMethod:
fMethod = nullptr;
break;
case MarkType::kConst:
fInConst = false;
case MarkType::kParam:
SkASSERT(TableState::kColumn == fTableState);
fTableState = TableState::kRow;
break;
case MarkType::kReturn:
case MarkType::kSeeAlso:
break;
case MarkType::kRow:
break;
case MarkType::kStruct:
fRoot = fRoot->rootParent();
break;
case MarkType::kTable:
break;
default:
break;
}
return true;
}
bool SpellCheck::checkable(MarkType markType) {
return BmhParser::Resolvable::kYes == fBmhParser.fMaps[(int) markType].fResolve;
}
void SpellCheck::childCheck(const Definition* def, const char* start) {
const char* end;
fLineCount = def->fLineCount;
if (def->isRoot()) {
fRoot = const_cast<RootDefinition*>(def->asRoot());
}
for (auto& child : def->fChildren) {
end = child->fStart;
if (this->checkable(def->fMarkType)) {
this->leafCheck(start, end);
}
this->check(child);
start = child->fTerminator;
}
if (this->checkable(def->fMarkType)) {
end = def->fContentEnd;
this->leafCheck(start, end);
}
}
void SpellCheck::leafCheck(const char* start, const char* end) {
TextParser text("", start, end, fLineCount);
do {
const char* lineStart = text.fChar;
text.skipToAlpha();
if (text.eof()) {
break;
}
const char* wordStart = text.fChar;
text.fChar = lineStart;
text.skipTo(wordStart); // advances line number
text.skipToNonAlphaNum();
fLineCount = text.fLineCount;
string word(wordStart, text.fChar - wordStart);
wordCheck(word);
} while (!text.eof());
}
void SpellCheck::printCheck(const string& str) {
string word;
for (std::stringstream stream(str); stream >> word; ) {
wordCheck(word);
}
}
void SpellCheck::report() {
for (auto iter : fWords) {
if (string::npos != iter.second.fFile.find("undocumented.bmh")) {
continue;
}
if (string::npos != iter.second.fFile.find("markup.bmh")) {
continue;
}
if (string::npos != iter.second.fFile.find("usingBookmaker.bmh")) {
continue;
}
if (iter.second.fCount == 1) {
SkDebugf("%s %s %d\n", iter.first.c_str(), iter.second.fFile.c_str(),
iter.second.fLine);
}
}
}
void SpellCheck::wordCheck(const string& str) {
bool hasColon = false;
bool hasDot = false;
bool hasParen = false;
bool hasUnderscore = false;
bool sawDash = false;
bool sawDigit = false;
bool sawSpecial = false;
SkASSERT(str.length() > 0);
SkASSERT(isalpha(str[0]) || '~' == str[0]);
for (char ch : str) {
if (isalpha(ch) || '-' == ch) {
sawDash |= '-' == ch;
continue;
}
bool isColon = ':' == ch;
hasColon |= isColon;
bool isDot = '.' == ch;
hasDot |= isDot;
bool isParen = '(' == ch || ')' == ch || '~' == ch || '=' == ch || '!' == ch;
hasParen |= isParen;
bool isUnderscore = '_' == ch;
hasUnderscore |= isUnderscore;
if (isColon || isDot || isUnderscore || isParen) {
continue;
}
if (isdigit(ch)) {
sawDigit = true;
continue;
}
if ('&' == ch || ',' == ch || ' ' == ch) {
sawSpecial = true;
continue;
}
SkASSERT(0);
}
if (sawSpecial && !hasParen) {
SkASSERT(0);
}
bool inCode = fInCode;
if (hasUnderscore && isupper(str[0]) && ('S' != str[0] || 'K' != str[1])
&& !hasColon && !hasDot && !hasParen && !fInStdOut && !inCode && !fInConst
&& !sawDigit && !sawSpecial && !sawDash) {
std::istringstream ss(str);
string token;
while (std::getline(ss, token, '_')) {
this->wordCheck(token);
}
return;
}
if (!hasColon && !hasDot && !hasParen && !hasUnderscore
&& !fInStdOut && !inCode && !fInConst && !sawDigit
&& islower(str[0]) && isupper(str[1])) {
inCode = true;
}
auto& mappy = hasColon ? fColons :
hasDot ? fDots :
hasParen ? fParens :
hasUnderscore ? fUnderscores :
fInStdOut || inCode || fInConst ? fCode :
sawDigit ? fDigits : fWords;
auto iter = mappy.find(str);
if (mappy.end() != iter) {
iter->second.fCount += 1;
} else {
CheckEntry* entry = &mappy[str];
entry->fFile = fFileName;
entry->fLine = fLineCount;
entry->fCount = 1;
}
}
void SpellCheck::wordCheck(ptrdiff_t len, const char* ch) {
leafCheck(ch, ch + len);
}