blob: 5a8867a3896bada3dfc660f5596f7947be023774 [file] [log] [blame]
/*
* 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 "SkOSFile.h"
#include "SkOSPath.h"
#include "parserCommon.h"
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++;
}
}
}
// change Xxx_Xxx to xxx xxx
string ParserCommon::ConvertRef(const string str, bool first) {
string substitute;
for (char c : str) {
if ('_' == c) {
c = ' ';
} else if (isupper(c) && !first) {
c = tolower(c);
}
substitute += c;
first = false;
}
return substitute;
}
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());
}
string ParserCommon::HtmlFileName(string bmhFileName) {
SkASSERT("docs" == bmhFileName.substr(0, 4));
SkASSERT('\\' == bmhFileName[4] || '/' == bmhFileName[4]);
SkASSERT(".bmh" == bmhFileName.substr(bmhFileName.length() - 4));
string result = bmhFileName.substr(5, bmhFileName.length() - 4 - 5);
return result;
}
bool ParserCommon::parseFile(const char* fileOrPath, const char* suffix, OneFile oneFile) {
fRawFilePathDir = string(fileOrPath);
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) {
fRawFilePathDir = string(statusFile);
StatusIter iter(statusFile, suffix, filter);
if (iter.empty()) {
return false;
}
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)) {
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;
}
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]) {
--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);
FPRINTF("%.*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();
FPRINTF("%.*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();
FPRINTF("%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().fArray);
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* strPtr, StatusFilter *filter) {
string str;
JsonStatus* status;
StatusFilter blockType = StatusFilter::kCompleted;
do {
do {
if (fStack.empty()) {
return false;
}
status = &fStack.back();
if (!status->atEnd()) {
break;
}
fStack.pop_back();
} while (true);
if (1 == fStack.size()) {
do {
blockType = StatusFilter::kUnknown;
SkASSERT(status->fObject);
for (unsigned index = 0; index < SK_ARRAY_COUNT(block_names); ++index) {
if (!strcmp(status->fObjectIter->fKey.begin(), block_names[index])) {
blockType = (StatusFilter) index;
break;
}
}
if (blockType <= fFilter) {
break;
}
status->advance();
} while (!status->atEnd());
if (status->atEnd()) {
continue;
}
}
if (!status->fArray) {
SkASSERT(status->fObjectIter != status->fObject->end());
JsonStatus block = JsonStatus::Make(status->current(),
status->fObjectIter->fKey.begin(),
blockType);
fStack.emplace_back(block);
status = &(&fStack.back())[-1];
status->advance();
status = &fStack.back();
continue;
}
if (const skjson::StringValue* sv = status->current()) {
str = sv->begin();
} else {
str = status->current().toString().c_str();
}
if (strPtr) {
*strPtr = str;
}
if (filter) {
*filter = status->fStatusFilter;
}
status->advance();
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");
}
fDom.reset(new skjson::DOM((const char*)json->data(), json->size()));
if (!fDom->root().is<skjson::ObjectValue>()) {
SkDebugf("file %s:\n", path);
return this->reportError<bool>("file not parsable");
}
JsonStatus block = JsonStatus::Make(fDom->root(), "", StatusFilter::kUnknown);
fStack.emplace_back(block);
return true;
}