overte/libraries/baking/src/JSBaker.cpp
2018-05-04 16:26:41 -07:00

248 lines
7.9 KiB
C++

//
// JSBaker.cpp
// libraries/baking/src
//
// Created by Utkarsh Gautam on 9/18/17.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "JSBaker.h"
#include <PathUtils.h>
#include "Baker.h"
const int ASCII_CHARACTERS_UPPER_LIMIT = 126;
JSBaker::JSBaker(const QUrl& jsURL, const QString& bakedOutputDir) :
_jsURL(jsURL),
_bakedOutputDir(bakedOutputDir)
{
}
void JSBaker::bake() {
qCDebug(js_baking) << "JS Baker " << _jsURL << "bake starting";
// Import file to start baking
QFile jsFile(_jsURL.toLocalFile());
if (!jsFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
handleError("Error opening " + _jsURL.fileName() + " for reading");
return;
}
// Read file into an array
QByteArray inputJS = jsFile.readAll();
QByteArray outputJS;
// Call baking on inputJS and store result in outputJS
bool success = bakeJS(inputJS, outputJS);
if (!success) {
qCDebug(js_baking) << "Bake Failed";
handleError("Unterminated multi-line comment");
return;
}
// Bake Successful. Export the file
auto fileName = _jsURL.fileName();
auto baseName = fileName.left(fileName.lastIndexOf('.'));
auto bakedFilename = baseName + BAKED_JS_EXTENSION;
_bakedJSFilePath = _bakedOutputDir + "/" + bakedFilename;
QFile bakedFile;
bakedFile.setFileName(_bakedJSFilePath);
if (!bakedFile.open(QIODevice::WriteOnly)) {
handleError("Error opening " + _bakedJSFilePath + " for writing");
return;
}
bakedFile.write(outputJS);
// Export successful
_outputFiles.push_back(_bakedJSFilePath);
qCDebug(js_baking) << "Exported" << _jsURL << "minified to" << _bakedJSFilePath;
// emit signal to indicate the JS baking is finished
emit finished();
}
bool JSBaker::bakeJS(const QByteArray& inputFile, QByteArray& outputFile) {
// Read from inputFile and write to outputFile per character
QTextStream in(inputFile, QIODevice::ReadOnly);
QTextStream out(&outputFile, QIODevice::WriteOnly);
// Algorithm requires the knowledge of previous and next character for each character read
QChar currentCharacter;
QChar nextCharacter;
// Initialize previousCharacter with new line
QChar previousCharacter = '\n';
in >> currentCharacter;
while (!in.atEnd()) {
in >> nextCharacter;
if (currentCharacter == '\r') {
out << '\n';
} else if (currentCharacter == '/') {
// Check if single line comment i.e. //
if (nextCharacter == '/') {
handleSingleLineComments(in);
//Start fresh after handling comments
previousCharacter = '\n';
in >> currentCharacter;
continue;
} else if (nextCharacter == '*') {
// Check if multi line comment i.e. /*
bool success = handleMultiLineComments(in);
if (!success) {
// Errors present return false
return false;
}
//Start fresh after handling comments
previousCharacter = '\n';
in >> currentCharacter;
continue;
} else {
// If '/' is not followed by '/' or '*' print '/'
out << currentCharacter;
}
} else if (isSpaceOrTab(currentCharacter)) {
// Check if white space or tab
// Skip multiple spaces or tabs
while (isSpaceOrTab(nextCharacter)) {
in >> nextCharacter;
if (nextCharacter == '\n') {
break;
}
}
// check if space can be omitted
if (!canOmitSpace(previousCharacter, nextCharacter)) {
out << ' ';
}
} else if (currentCharacter == '\n') {
// Check if new line
//Skip multiple new lines
//Skip new line followed by space or tab
while (nextCharacter == '\n' || isSpaceOrTab(nextCharacter)) {
in >> nextCharacter;
}
// Check if new line can be omitted
if (!canOmitNewLine(previousCharacter, nextCharacter)) {
out << '\n';
}
} else if (isQuote(currentCharacter)) {
// Print the current quote and nextCharacter as is
out << currentCharacter;
out << nextCharacter;
// Store the type of quote we are processing
QChar quote = currentCharacter;
// Don't modify the quoted strings
while (nextCharacter != quote) {
in >> nextCharacter;
out << nextCharacter;
}
//Start fresh after handling quoted strings
previousCharacter = nextCharacter;
in >> currentCharacter;
continue;
} else {
// In all other cases write the currentCharacter to outputFile
out << currentCharacter;
}
previousCharacter = currentCharacter;
currentCharacter = nextCharacter;
}
//write currentCharacter to output file when nextCharacter reaches EOF
if (currentCharacter != '\n') {
out << currentCharacter;
}
// Successful bake. Return true
return true;
}
void JSBaker::handleSingleLineComments(QTextStream& in) {
QChar character;
while (!in.atEnd()) {
in >> character;
if (character == '\n') {
break;
}
}
}
bool JSBaker::handleMultiLineComments(QTextStream& in) {
QChar character;
while (!in.atEnd()) {
in >> character;
if (character == '*') {
if (in.read(1) == "/") {
return true;
}
}
}
return false;
}
bool JSBaker::canOmitSpace(QChar previousCharacter, QChar nextCharacter) {
return(!((isAlphanum(previousCharacter) || isNonAscii(previousCharacter) || isSpecialCharacter(previousCharacter)) &&
(isAlphanum(nextCharacter) || isNonAscii(nextCharacter) || isSpecialCharacter(nextCharacter)))
);
}
bool JSBaker::canOmitNewLine(QChar previousCharacter, QChar nextCharacter) {
return (!((isAlphanum(previousCharacter) || isNonAscii(previousCharacter) || isSpecialCharacterPrevious(previousCharacter)) &&
(isAlphanum(nextCharacter) || isNonAscii(nextCharacter) || isSpecialCharacterNext(nextCharacter)))
);
}
//Check if character is alphabet, number or one of the following: '_', '$', '\\' or a non-ASCII character
bool JSBaker::isAlphanum(QChar c) {
return ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z')
|| c == '_' || c == '$' || c == '\\' || c > ASCII_CHARACTERS_UPPER_LIMIT);
}
bool JSBaker::isNonAscii(QChar c) {
return ((int)c.toLatin1() > ASCII_CHARACTERS_UPPER_LIMIT);
}
// If previous and next characters are special characters, don't omit space
bool JSBaker::isSpecialCharacter(QChar c) {
return (c == '\'' || c == '$' || c == '_' || c == '/' || c== '+' || c == '-');
}
// If previous character is a special character, maybe don't omit new line (depends on next character as well)
bool JSBaker::isSpecialCharacterPrevious(QChar c) {
return (c == '\'' || c == '$' || c == '_' || c == '}' || c == ']' || c == ')' || c == '+' || c == '-'
|| c == '"' || c == '\'');
}
// If next character is a special character, maybe don't omit new line (depends on previous character as well)
bool JSBaker::isSpecialCharacterNext(QChar c) {
return (c == '\'' || c == '$' || c == '_' || c == '{' || c == '[' || c == '(' || c == '+' || c == '-');
}
// Check if white space or tab
bool JSBaker::isSpaceOrTab(QChar c) {
return (c == ' ' || c == '\t');
}
// Check If the currentCharacter is " or ' or `
bool JSBaker::isQuote(QChar c) {
return (c == '"' || c == '\'' || c == '`');
}