skia2/tools/bookmaker/parserCommon.cpp
Cary Clark 2be81cf973 Condense embedded formulas.
Bookmaker delimits formulas and equations to allow
representing variables and symbols without tripping
up reference lookup, spell checking, and comment
generation.

Before, formulas were represented with:
some text
#Formula
(x + y, 0)
##
, and more text

This made it difficult to know when spacing should
be preserved before and after the formula. Now,
formulas are represented with:
some text #Formula # (x + y, 0) ##, and more text

The presence or absence of a space between ## and ,
is now significant (before it was not).

Also, formulas are bracketed by <code> in markdown
generation, so that variables stand out better.
See:
https://skia.org/user/api/SkBlendMode_Reference?cl=152781#Dst_Out
for an example.

Also fixed 100 column offenders and added a code
check to identify them. For the moment, 100 column
offenders are outed with SkDebugf but their presence
does not cause bookmaker to fail.

TBR=caryclark@google.com

Docs-Preview: https://skia.org/?cl=152781
Bug: skia:6898
Change-Id: If92a65a234f5d616bf4485984a8d219a6f04821a
Reviewed-on: https://skia-review.googlesource.com/152781
Commit-Queue: Cary Clark <caryclark@skia.org>
Auto-Submit: Cary Clark <caryclark@skia.org>
Reviewed-by: Cary Clark <caryclark@skia.org>
2018-09-13 16:50:25 +00:00

460 lines
13 KiB
C++

/*
* 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 debug_out(int len, const char* data) {
// convenient place to intercept arbitrary output
SkDebugf("%.*s", len, data);
}
void ParserCommon::checkLineLength(size_t len, const char* str) {
if (!fWritingIncludes) {
return;
}
int column = fColumn;
const char* lineStart = str;
for (size_t index = 0; index < len; ++index) {
if ('\n' == str[index]) {
if (column > 100) {
SkDebugf("> 100 columns in %s line %d\n", fFileName.c_str(), fLinesWritten);
SkDebugf("%.*s\n", &str[index + 1] - lineStart, lineStart);
SkDebugf(""); // convenient place to set breakpoints
}
fLinesWritten++;
column = 0;
lineStart = &str[index + 1];
} else {
column++;
}
}
}
void ParserCommon::CopyToFile(string oldFile, string newFile) {
int bufferSize;
char* buffer = ParserCommon::ReadToBuffer(newFile, &bufferSize);
FILE* oldOut = fopen(oldFile.c_str(), "wb");
if (!oldOut) {
SkDebugf("could not open file %s\n", oldFile.c_str());
return;
}
fwrite(buffer, 1, bufferSize, oldOut);
fclose(oldOut);
remove(newFile.c_str());
}
bool ParserCommon::parseFile(const char* fileOrPath, const char* suffix, OneFile oneFile) {
if (!sk_isdir(fileOrPath)) {
if (!this->parseFromFile(fileOrPath)) {
SkDebugf("failed to parse %s\n", fileOrPath);
return false;
}
} else if (OneFile::kNo == oneFile) {
SkOSFile::Iter it(fileOrPath, suffix);
for (SkString file; it.next(&file); ) {
// FIXME: skip difficult file for now
if (string::npos != string(file.c_str()).find("SkFontArguments")) {
continue;
}
if (string::npos != string(file.c_str()).find("SkFontStyle")) {
continue;
}
SkString p = SkOSPath::Join(fileOrPath, file.c_str());
const char* hunk = p.c_str();
if (!SkStrEndsWith(hunk, suffix)) {
continue;
}
if (!this->parseFromFile(hunk)) {
SkDebugf("failed to parse %s\n", hunk);
return false;
}
}
}
return true;
}
bool ParserCommon::parseStatus(const char* statusFile, const char* suffix, StatusFilter filter) {
StatusIter iter(statusFile, suffix, filter);
if (iter.empty()) {
return false;
}
for (string file; iter.next(&file); ) {
SkString p = SkOSPath::Join(iter.baseDir().c_str(), file.c_str());
const char* hunk = p.c_str();
if (!this->parseFromFile(hunk)) {
SkDebugf("failed to parse %s\n", hunk);
return false;
}
}
return true;
}
bool ParserCommon::parseSetup(const char* path) {
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;
}
bool ParserCommon::writeBlockIndent(int size, const char* data, bool ignoreIdent) {
bool wroteSomething = false;
while (size && ' ' >= data[size - 1]) {
--size;
}
bool newLine = false;
char firstCh = 0;
while (size) {
while (size && (' ' > data[0] || (' ' == data[0] && ignoreIdent))) {
++data;
--size;
}
if (!size) {
return wroteSomething;
}
if (fReturnOnWrite) {
return true;
}
if (newLine) {
this->lf(1);
}
int indent = fIndent;
if (!firstCh) {
firstCh = data[0];
} else if ('(' == firstCh) {
indent += 1;
}
TextParser parser(fFileName, data, data + size, fLineCount);
const char* lineEnd = parser.strnchr('\n', data + size);
int len = lineEnd ? (int) (lineEnd - data) : size;
this->writePending();
this->indentToColumn(indent);
if (fDebugOut) {
debug_out(len, data);
}
fprintf(fOut, "%.*s", len, data);
checkLineLength(len, data);
size -= len;
data += len;
newLine = true;
wroteSomething = true;
}
return wroteSomething;
}
bool ParserCommon::writeBlockTrim(int size, const char* data) {
SkASSERT(size >= 0);
if (!fReturnOnWrite && fOutdentNext) {
fIndent -= 4;
fOutdentNext = false;
}
while (size && ' ' >= data[0]) {
++data;
--size;
}
while (size && ' ' >= data[size - 1]) {
--size;
}
if (size <= 0) {
if (!fReturnOnWrite) {
fLastChar = '\0';
}
return false;
}
if (fReturnOnWrite) {
return true;
}
SkASSERT(size < 20000);
if (size > 3 && !strncmp("#end", data, 4)) {
fMaxLF = 1;
}
if (this->leadingPunctuation(data, (size_t) size)) {
fPendingSpace = 0;
}
this->writePending();
if (fDebugOut) {
debug_out(size, data);
}
fprintf(fOut, "%.*s", size, data);
checkLineLength(size, data);
fWroteSomething = true;
int added = 0;
fLastChar = data[size - 1];
while (size > 0 && '\n' != data[--size]) {
++added;
}
fColumn = size ? added : fColumn + added;
fSpaces = 0;
fLinefeeds = 0;
fMaxLF = added > 2 && !strncmp("#if", &data[size + (size > 0)], 3) ? 1 : 2;
if (fOutdentNext) {
fIndent -= 4;
fOutdentNext = false;
}
return true;
}
void ParserCommon::writePending() {
SkASSERT(!fReturnOnWrite);
fPendingLF = SkTMin(fPendingLF, fMaxLF);
bool wroteLF = false;
while (fLinefeeds < fPendingLF) {
if (fDebugOut) {
SkDebugf("\n");
}
fprintf(fOut, "\n");
checkLineLength(1, "\n");
++fLinefeeds;
wroteLF = true;
}
fPendingLF = 0;
if (wroteLF) {
SkASSERT(0 == fColumn);
SkASSERT(fIndent >= fSpaces);
SkASSERT(fIndent - fSpaces < 80);
if (fDebugOut) {
SkDebugf("%*s", fIndent - fSpaces, "");
}
fprintf(fOut, "%*s", fIndent - fSpaces, "");
fColumn = fIndent;
fSpaces = fIndent;
}
SkASSERT(!fWritingIncludes || fColumn + fPendingSpace < 100);
for (int index = 0; index < fPendingSpace; ++index) {
if (fDebugOut) {
SkDebugf(" ");
}
fprintf(fOut, " ");
++fColumn;
}
fPendingSpace = 0;
}
void ParserCommon::writeString(const char* str) {
SkASSERT(!fReturnOnWrite);
const size_t len = strlen(str);
SkASSERT(len > 0);
SkASSERT(' ' < str[0]);
fLastChar = str[len - 1];
SkASSERT(' ' < fLastChar);
SkASSERT(!strchr(str, '\n'));
if (this->leadingPunctuation(str, strlen(str))) {
fPendingSpace = 0;
}
this->writePending();
if (fDebugOut) {
debug_out((int) strlen(str), str);
}
fprintf(fOut, "%s", str);
checkLineLength(strlen(str), str);
fColumn += len;
fSpaces = 0;
fLinefeeds = 0;
fMaxLF = 2;
}
char* ParserCommon::ReadToBuffer(string filename, int* size) {
FILE* file = fopen(filename.c_str(), "rb");
if (!file) {
return nullptr;
}
fseek(file, 0L, SEEK_END);
*size = (int) ftell(file);
rewind(file);
char* buffer = new char[*size];
memset(buffer, ' ', *size);
SkAssertResult(*size == (int)fread(buffer, 1, *size, file));
fclose(file);
fflush(file);
return buffer;
}
char* ParserCommon::FindDateTime(char* buffer, int size) {
int index = -1;
int lineCount = 8;
while (++index < size && ('\n' != buffer[index] || --lineCount))
;
if (lineCount) {
return nullptr;
}
if (strncmp("\n on 20", &buffer[index], 9)) {
return nullptr;
}
return &buffer[index];
}
bool ParserCommon::WrittenFileDiffers(string filename, string readname) {
int writtenSize, readSize;
char* written = ParserCommon::ReadToBuffer(filename, &writtenSize);
if (!written) {
return true;
}
char* read = ParserCommon::ReadToBuffer(readname, &readSize);
if (!read) {
delete[] written;
return true;
}
#if 0 // enable for debugging this
int smaller = SkTMin(writtenSize, readSize);
for (int index = 0; index < smaller; ++index) {
if (written[index] != read[index]) {
SkDebugf("%.*s\n", 40, &written[index]);
SkDebugf("%.*s\n", 40, &read[index]);
break;
}
}
#endif
if (readSize != writtenSize) {
return true;
}
// force the date/time to be the same, if present in both
const char* newDateTime = FindDateTime(written, writtenSize);
char* oldDateTime = FindDateTime(read, readSize);
if (newDateTime && oldDateTime) {
memcpy(oldDateTime, newDateTime, 26);
}
bool result = !!memcmp(written, read, writtenSize);
delete[] written;
delete[] read;
return result;
}
StatusIter::StatusIter(const char* statusFile, const char* suffix, StatusFilter filter)
: fSuffix(suffix)
, fFilter(filter) {
if (!this->parseFromFile(statusFile)) {
return;
}
}
static const char* block_names[] = {
"Completed",
"InProgress",
};
string StatusIter::baseDir() {
SkASSERT(fStack.back().fObject.isArray());
SkASSERT(fStack.size() > 2);
string dir;
for (unsigned index = 2; index < fStack.size(); ++index) {
dir += fStack[index].fName;
if (index < fStack.size() - 1) {
dir += SkOSPath::SEPARATOR;
}
}
return dir;
}
// FIXME: need to compare fBlockName against fFilter
// need to compare fSuffix against next value returned
bool StatusIter::next(string* str) {
JsonStatus* status;
do {
do {
if (fStack.empty()) {
return false;
}
status = &fStack.back();
if (status->fIter != status->fObject.end()) {
break;
}
fStack.pop_back();
} while (true);
if (1 == fStack.size()) {
do {
StatusFilter 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;
break;
}
}
if (blockType <= fFilter) {
break;
}
status->fIter++;
} while (status->fIter != status->fObject.end());
if (status->fIter == status->fObject.end()) {
continue;
}
}
if (!status->fObject.isArray()) {
SkASSERT(status->fIter != status->fObject.end());
JsonStatus block = {
*status->fIter,
status->fIter->begin(),
status->fIter.key().asString()
};
fStack.emplace_back(block);
status = &(&fStack.back())[-1];
status->fIter++;
status = &fStack.back();
continue;
}
*str = status->fIter->asString();
status->fIter++;
if (str->length() - strlen(fSuffix) == str->find(fSuffix)) {
return true;
}
} while (true);
return true;
}
bool JsonCommon::parseFromFile(const char* path) {
sk_sp<SkData> json(SkData::MakeFromFileName(path));
if (!json) {
SkDebugf("file %s:\n", path);
return this->reportError<bool>("file not readable");
}
Json::Reader reader;
const char* data = (const char*)json->data();
if (!reader.parse(data, data + json->size(), fRoot)) {
SkDebugf("file %s:\n", path);
return this->reportError<bool>("file not parsable");
}
JsonStatus block = { fRoot, fRoot.begin(), "" };
fStack.emplace_back(block);
return true;
}