mirror of
https://github.com/overte-org/overte.git
synced 2025-07-24 13:04:37 +02:00
248 lines
7.9 KiB
C++
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 == '`');
|
|
}
|