/* * 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, 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 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 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); 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* strPtr, StatusFilter *filter) { string str; JsonStatus* status; StatusFilter blockType = StatusFilter::kCompleted; 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 { 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(), blockType }; fStack.emplace_back(block); status = &(&fStack.back())[-1]; status->fIter++; status = &fStack.back(); continue; } str = status->fIter->asString(); if (strPtr) { *strPtr = str; } if (filter) { *filter = status->fStatusFilter; } status->fIter++; if (str.length() - strlen(fSuffix) == str.find(fSuffix)) { return true; } } while (true); return true; } bool JsonCommon::parseFromFile(const char* path) { sk_sp json(SkData::MakeFromFileName(path)); if (!json) { SkDebugf("file %s:\n", path); return this->reportError("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("file not parsable"); } JsonStatus block = { fRoot, fRoot.begin(), "", StatusFilter::kUnknown }; fStack.emplace_back(block); return true; }