ui5-tooling-modules
Version:
UI5 tooling extensions to load and convert node modules as UI5 AMD-like modules
353 lines (327 loc) • 7.37 kB
JavaScript
/*
* The following code has been taken from OpenUI5 to support parsing the core:require
* parts of the XMLView to derive the necessary modules
*
* Git URL: https://github.com/SAP/openui5/blob/7e59f5702a1209128ecf5e8a53b8f89255e26eb9/src/sap.ui.core/src/sap/base/util/JSTokenizer.js
*/
/**
* @class Tokenizer for JS values.
*
* Contains functions to consume tokens on an input string.
*
* @example
* sap.ui.require(["sap/base/util/JSTokenizer"], function(JSTokenizer){
* JSTokenizer().parseJS("{test:'123'}"); // {test:'123'}
* });
*
* @alias module:sap/base/util/JSTokenizer
* @since 1.58
* @private
* @ui5-restricted sap.ui.core
*/
var JSTokenizer = function () {
this.at; // The index of the current character
this.ch; // The current character
this.escapee = {
'"': '"',
"'": "'",
"\\": "\\",
"/": "/",
b: "\b",
f: "\f",
n: "\n",
r: "\r",
t: "\t",
};
this.text;
};
JSTokenizer.prototype.error = function (m) {
// Call error when something is wrong.
throw {
name: "SyntaxError",
message: m,
at: this.at,
text: this.text,
};
};
JSTokenizer.prototype.next = function (c) {
// If a c parameter is provided, verify that it matches the current character.
if (c && c !== this.ch) {
this.error("Expected '" + c + "' instead of '" + this.ch + "'");
}
// Get the next character. When there are no more characters,
// return the empty string.
this.ch = this.text.charAt(this.at);
this.at += 1;
return this.ch;
};
JSTokenizer.prototype.number = function () {
// Parse a number value.
var number,
string = "";
if (this.ch === "-") {
string = "-";
this.next("-");
}
while (this.ch >= "0" && this.ch <= "9") {
string += this.ch;
this.next();
}
if (this.ch === ".") {
string += ".";
while (this.next() && this.ch >= "0" && this.ch <= "9") {
string += this.ch;
}
}
if (this.ch === "e" || this.ch === "E") {
string += this.ch;
this.next();
if (this.ch === "-" || this.ch === "+") {
string += this.ch;
this.next();
}
while (this.ch >= "0" && this.ch <= "9") {
string += this.ch;
this.next();
}
}
number = +string;
if (!isFinite(number)) {
this.error("Bad number");
} else {
return number;
}
};
JSTokenizer.prototype.string = function () {
// Parse a string value.
var hex,
i,
string = "",
quote,
uffff;
// When parsing for string values, we must look for " and \ characters.
if (this.ch === '"' || this.ch === "'") {
quote = this.ch;
while (this.next()) {
if (this.ch === quote) {
this.next();
return string;
}
if (this.ch === "\\") {
this.next();
if (this.ch === "u") {
uffff = 0;
for (i = 0; i < 4; i += 1) {
hex = parseInt(this.next(), 16);
if (!isFinite(hex)) {
break;
}
uffff = uffff * 16 + hex;
}
string += String.fromCharCode(uffff);
} else if (typeof this.escapee[this.ch] === "string") {
string += this.escapee[this.ch];
} else {
break;
}
} else {
string += this.ch;
}
}
}
this.error("Bad string");
};
JSTokenizer.prototype.name = function () {
// Parse a name value.
var name = "",
allowed = function (ch) {
return ch === "_" || ch === "$" || (ch >= "0" && ch <= "9") || (ch >= "a" && ch <= "z") || (ch >= "A" && ch <= "Z");
};
if (allowed(this.ch)) {
name += this.ch;
} else {
this.error("Bad name");
}
while (this.next()) {
if (this.ch === " ") {
this.next();
return name;
}
if (this.ch === ":") {
return name;
}
if (allowed(this.ch)) {
name += this.ch;
} else {
this.error("Bad name");
}
}
this.error("Bad name");
};
JSTokenizer.prototype.white = function () {
// Skip whitespace.
while (this.ch && this.ch <= " ") {
this.next();
}
};
JSTokenizer.prototype.word = function () {
// true, false, or null.
switch (this.ch) {
case "t":
this.next("t");
this.next("r");
this.next("u");
this.next("e");
return true;
case "f":
this.next("f");
this.next("a");
this.next("l");
this.next("s");
this.next("e");
return false;
case "n":
this.next("n");
this.next("u");
this.next("l");
this.next("l");
return null;
}
this.error("Unexpected '" + this.ch + "'");
};
//value, // Place holder for the value function.
JSTokenizer.prototype.array = function () {
// Parse an array value.
var array = [];
if (this.ch === "[") {
this.next("[");
this.white();
if (this.ch === "]") {
this.next("]");
return array; // empty array
}
while (this.ch) {
array.push(this.value());
this.white();
if (this.ch === "]") {
this.next("]");
return array;
}
this.next(",");
this.white();
}
}
this.error("Bad array");
};
var object = function () {
// Parse an object value.
var key,
object = {};
if (this.ch === "{") {
this.next("{");
this.white();
if (this.ch === "}") {
this.next("}");
return object; // empty object
}
while (this.ch) {
if (this.ch >= "0" && this.ch <= "9") {
key = this.number();
} else if (this.ch === '"' || this.ch === "'") {
key = this.string();
} else {
key = this.name();
}
this.white();
this.next(":");
if (Object.hasOwn(object, key)) {
this.error('Duplicate key "' + key + '"');
}
object[key] = this.value();
this.white();
if (this.ch === "}") {
this.next("}");
return object;
}
this.next(",");
this.white();
}
}
this.error("Bad object");
};
JSTokenizer.prototype.value = function () {
// Parse a JS value. It could be an object, an array, a string, a number,
// or a word.
this.white();
switch (this.ch) {
case "{":
return object.call(this);
case "[":
return this.array();
case '"':
case "'":
return this.string();
case "-":
return this.number();
default:
return this.ch >= "0" && this.ch <= "9" ? this.number() : this.word();
}
};
/**
* Returns the index of the current character.
*
* @private
* @returns {int} The current character's index.
*/
JSTokenizer.prototype.getIndex = function () {
return this.at - 1;
};
JSTokenizer.prototype.getCh = function () {
return this.ch;
};
JSTokenizer.prototype.init = function (sSource, iIndex) {
this.text = sSource;
this.at = iIndex || 0;
this.ch = " ";
};
/**
* Advances the index in the text to <code>iIndex</code>. Fails if the new index
* is smaller than the previous index.
*
* @private
* @param {int} iIndex - the new index
*/
JSTokenizer.prototype.setIndex = function (iIndex) {
if (iIndex < this.at - 1) {
throw new Error("Must not set index " + iIndex + " before previous index " + (this.at - 1));
}
this.at = iIndex;
this.next();
};
/**
* Return the parse function. It will have access to all of the above
* functions and variables.
*
* @private
* @ui5-restricted sap.ui.core
* @static
* @param {string} sSource The js source
* @param {int} iStart The start position
* @returns {object} the JavaScript object
*/
JSTokenizer.parseJS = function parseJS(sSource, iStart) {
var oJSTokenizer = new JSTokenizer();
var result;
oJSTokenizer.init(sSource, iStart);
result = oJSTokenizer.value();
if (isNaN(iStart)) {
oJSTokenizer.white();
if (oJSTokenizer.getCh()) {
oJSTokenizer.error("Syntax error");
}
return result;
} else {
return { result: result, at: oJSTokenizer.getIndex() };
}
};
module.exports = JSTokenizer.parseJS;