|
@@ -134,13 +134,7 @@ for (var i = 0; i < 128; i++) {
|
|
|
i >= 97 && i <= 122; // a-z
|
|
i >= 97 && i <= 122; // a-z
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-var identifierPartTable = [];
|
|
|
|
|
-
|
|
|
|
|
-for (var i2 = 0; i2 < 128; i2++) {
|
|
|
|
|
- identifierPartTable[i2] =
|
|
|
|
|
- identifierStartTable[i2] || // $, _, A-Z, a-z
|
|
|
|
|
- i2 >= 48 && i2 <= 57; // 0-9
|
|
|
|
|
-}
|
|
|
|
|
|
|
+var identifierPartTable = identifierStartTable;
|
|
|
|
|
|
|
|
export function Lexer(expression) {
|
|
export function Lexer(expression) {
|
|
|
this.input = expression;
|
|
this.input = expression;
|
|
@@ -423,117 +417,148 @@ Lexer.prototype = {
|
|
|
if (char === '-') {
|
|
if (char === '-') {
|
|
|
value += char;
|
|
value += char;
|
|
|
index += 1;
|
|
index += 1;
|
|
|
- char = this.peek(index);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Numbers must start either with a decimal digit or a point.
|
|
|
|
|
- if (char !== "." && !isDecimalDigit(char)) {
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (char !== ".") {
|
|
|
|
|
- value += this.peek(index);
|
|
|
|
|
- index += 1;
|
|
|
|
|
- char = this.peek(index);
|
|
|
|
|
|
|
+ char = this.peek(index);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if (value === "0") {
|
|
|
|
|
- // Base-16 numbers.
|
|
|
|
|
- if (char === "x" || char === "X") {
|
|
|
|
|
- index += 1;
|
|
|
|
|
- value += char;
|
|
|
|
|
|
|
+ // Numbers must start either with a decimal digit or a point.
|
|
|
|
|
+ if (char !== "." && !isDecimalDigit(char)) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- while (index < length) {
|
|
|
|
|
- char = this.peek(index);
|
|
|
|
|
- if (!isHexDigit(char)) {
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
- value += char;
|
|
|
|
|
- index += 1;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (char !== ".") {
|
|
|
|
|
+ value += this.peek(index);
|
|
|
|
|
+ index += 1;
|
|
|
|
|
+ char = this.peek(index);
|
|
|
|
|
|
|
|
- if (value.length <= 2) { // 0x
|
|
|
|
|
- return {
|
|
|
|
|
- type: 'number',
|
|
|
|
|
- value: value,
|
|
|
|
|
- isMalformed: true,
|
|
|
|
|
- pos: this.char
|
|
|
|
|
- };
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (value === "0") {
|
|
|
|
|
+ // Base-16 numbers.
|
|
|
|
|
+ if (char === "x" || char === "X") {
|
|
|
|
|
+ index += 1;
|
|
|
|
|
+ value += char;
|
|
|
|
|
|
|
|
- if (index < length) {
|
|
|
|
|
- char = this.peek(index);
|
|
|
|
|
- if (isIdentifierStart(char)) {
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ while (index < length) {
|
|
|
|
|
+ char = this.peek(index);
|
|
|
|
|
+ if (!isHexDigit(char)) {
|
|
|
|
|
+ break;
|
|
|
}
|
|
}
|
|
|
|
|
+ value += char;
|
|
|
|
|
+ index += 1;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
|
|
+ if (value.length <= 2) { // 0x
|
|
|
return {
|
|
return {
|
|
|
type: 'number',
|
|
type: 'number',
|
|
|
value: value,
|
|
value: value,
|
|
|
- base: 16,
|
|
|
|
|
- isMalformed: false,
|
|
|
|
|
|
|
+ isMalformed: true,
|
|
|
pos: this.char
|
|
pos: this.char
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Base-8 numbers.
|
|
|
|
|
- if (isOctalDigit(char)) {
|
|
|
|
|
- index += 1;
|
|
|
|
|
- value += char;
|
|
|
|
|
- bad = false;
|
|
|
|
|
|
|
+ if (index < length) {
|
|
|
|
|
+ char = this.peek(index);
|
|
|
|
|
+ if (isIdentifierStart(char)) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- while (index < length) {
|
|
|
|
|
- char = this.peek(index);
|
|
|
|
|
|
|
+ return {
|
|
|
|
|
+ type: 'number',
|
|
|
|
|
+ value: value,
|
|
|
|
|
+ base: 16,
|
|
|
|
|
+ isMalformed: false,
|
|
|
|
|
+ pos: this.char
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // Numbers like '019' (note the 9) are not valid octals
|
|
|
|
|
- // but we still parse them and mark as malformed.
|
|
|
|
|
|
|
+ // Base-8 numbers.
|
|
|
|
|
+ if (isOctalDigit(char)) {
|
|
|
|
|
+ index += 1;
|
|
|
|
|
+ value += char;
|
|
|
|
|
+ bad = false;
|
|
|
|
|
|
|
|
- if (isDecimalDigit(char)) {
|
|
|
|
|
- bad = true;
|
|
|
|
|
- } else if (!isOctalDigit(char)) {
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
- value += char;
|
|
|
|
|
- index += 1;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ while (index < length) {
|
|
|
|
|
+ char = this.peek(index);
|
|
|
|
|
+
|
|
|
|
|
+ // Numbers like '019' (note the 9) are not valid octals
|
|
|
|
|
+ // but we still parse them and mark as malformed.
|
|
|
|
|
|
|
|
- if (index < length) {
|
|
|
|
|
- char = this.peek(index);
|
|
|
|
|
- if (isIdentifierStart(char)) {
|
|
|
|
|
|
|
+ if (isDecimalDigit(char)) {
|
|
|
|
|
+ bad = true;
|
|
|
|
|
+ } if (!isOctalDigit(char)) {
|
|
|
|
|
+ // if the char is a non punctuator then its not a valid number
|
|
|
|
|
+ if (!this.isPunctuator(char)) {
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
|
|
+ break;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- return {
|
|
|
|
|
- type: 'number',
|
|
|
|
|
- value: value,
|
|
|
|
|
- base: 8,
|
|
|
|
|
- isMalformed: false
|
|
|
|
|
- };
|
|
|
|
|
|
|
+ value += char;
|
|
|
|
|
+ index += 1;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Decimal numbers that start with '0' such as '09' are illegal
|
|
|
|
|
- // but we still parse them and return as malformed.
|
|
|
|
|
-
|
|
|
|
|
- if (isDecimalDigit(char)) {
|
|
|
|
|
- index += 1;
|
|
|
|
|
- value += char;
|
|
|
|
|
|
|
+ if (index < length) {
|
|
|
|
|
+ char = this.peek(index);
|
|
|
|
|
+ if (isIdentifierStart(char)) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ type: 'number',
|
|
|
|
|
+ value: value,
|
|
|
|
|
+ base: 8,
|
|
|
|
|
+ isMalformed: bad
|
|
|
|
|
+ };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- while (index < length) {
|
|
|
|
|
- char = this.peek(index);
|
|
|
|
|
- if (!isDecimalDigit(char)) {
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
- value += char;
|
|
|
|
|
|
|
+ // Decimal numbers that start with '0' such as '09' are illegal
|
|
|
|
|
+ // but we still parse them and return as malformed.
|
|
|
|
|
+
|
|
|
|
|
+ if (isDecimalDigit(char)) {
|
|
|
index += 1;
|
|
index += 1;
|
|
|
|
|
+ value += char;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Decimal digits.
|
|
|
|
|
|
|
+ while (index < length) {
|
|
|
|
|
+ char = this.peek(index);
|
|
|
|
|
+ if (!isDecimalDigit(char)) {
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ value += char;
|
|
|
|
|
+ index += 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Decimal digits.
|
|
|
|
|
+
|
|
|
|
|
+ if (char === ".") {
|
|
|
|
|
+ value += char;
|
|
|
|
|
+ index += 1;
|
|
|
|
|
|
|
|
- if (char === ".") {
|
|
|
|
|
|
|
+ while (index < length) {
|
|
|
|
|
+ char = this.peek(index);
|
|
|
|
|
+ if (!isDecimalDigit(char)) {
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ value += char;
|
|
|
|
|
+ index += 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Exponent part.
|
|
|
|
|
+
|
|
|
|
|
+ if (char === "e" || char === "E") {
|
|
|
|
|
+ value += char;
|
|
|
|
|
+ index += 1;
|
|
|
|
|
+ char = this.peek(index);
|
|
|
|
|
+
|
|
|
|
|
+ if (char === "+" || char === "-") {
|
|
|
|
|
+ value += this.peek(index);
|
|
|
|
|
+ index += 1;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ char = this.peek(index);
|
|
|
|
|
+ if (isDecimalDigit(char)) {
|
|
|
value += char;
|
|
value += char;
|
|
|
index += 1;
|
|
index += 1;
|
|
|
|
|
|
|
@@ -545,134 +570,107 @@ Lexer.prototype = {
|
|
|
value += char;
|
|
value += char;
|
|
|
index += 1;
|
|
index += 1;
|
|
|
}
|
|
}
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return null;
|
|
|
}
|
|
}
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // Exponent part.
|
|
|
|
|
-
|
|
|
|
|
- if (char === "e" || char === "E") {
|
|
|
|
|
- value += char;
|
|
|
|
|
- index += 1;
|
|
|
|
|
- char = this.peek(index);
|
|
|
|
|
|
|
+ if (index < length) {
|
|
|
|
|
+ char = this.peek(index);
|
|
|
|
|
+ if (!this.isPunctuator(char)) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if (char === "+" || char === "-") {
|
|
|
|
|
- value += this.peek(index);
|
|
|
|
|
- index += 1;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ return {
|
|
|
|
|
+ type: 'number',
|
|
|
|
|
+ value: value,
|
|
|
|
|
+ base: 10,
|
|
|
|
|
+ pos: this.char,
|
|
|
|
|
+ isMalformed: !isFinite(+value)
|
|
|
|
|
+ };
|
|
|
|
|
+ },
|
|
|
|
|
|
|
|
- char = this.peek(index);
|
|
|
|
|
- if (isDecimalDigit(char)) {
|
|
|
|
|
- value += char;
|
|
|
|
|
- index += 1;
|
|
|
|
|
|
|
+ isPunctuator: function (ch1) {
|
|
|
|
|
+ switch (ch1) {
|
|
|
|
|
+ case ".":
|
|
|
|
|
+ case "(":
|
|
|
|
|
+ case ")":
|
|
|
|
|
+ case ",":
|
|
|
|
|
+ case "{":
|
|
|
|
|
+ case "}":
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- while (index < length) {
|
|
|
|
|
- char = this.peek(index);
|
|
|
|
|
- if (!isDecimalDigit(char)) {
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
- value += char;
|
|
|
|
|
- index += 1;
|
|
|
|
|
- }
|
|
|
|
|
- } else {
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ return false;
|
|
|
|
|
+ },
|
|
|
|
|
|
|
|
- if (index < length) {
|
|
|
|
|
- char = this.peek(index);
|
|
|
|
|
- if (!this.isPunctuator(char)) {
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ scanPunctuator: function () {
|
|
|
|
|
+ var ch1 = this.peek();
|
|
|
|
|
|
|
|
|
|
+ if (this.isPunctuator(ch1)) {
|
|
|
return {
|
|
return {
|
|
|
- type: 'number',
|
|
|
|
|
- value: value,
|
|
|
|
|
- base: 10,
|
|
|
|
|
- pos: this.char,
|
|
|
|
|
- isMalformed: !isFinite(+value)
|
|
|
|
|
|
|
+ type: ch1,
|
|
|
|
|
+ value: ch1,
|
|
|
|
|
+ pos: this.char
|
|
|
};
|
|
};
|
|
|
- },
|
|
|
|
|
-
|
|
|
|
|
- isPunctuator: function (ch1) {
|
|
|
|
|
- switch (ch1) {
|
|
|
|
|
- case ".":
|
|
|
|
|
- case "(":
|
|
|
|
|
- case ")":
|
|
|
|
|
- case ",":
|
|
|
|
|
- case "{":
|
|
|
|
|
- case "}":
|
|
|
|
|
- return true;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return false;
|
|
|
|
|
- },
|
|
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- scanPunctuator: function () {
|
|
|
|
|
- var ch1 = this.peek();
|
|
|
|
|
|
|
+ return null;
|
|
|
|
|
+ },
|
|
|
|
|
|
|
|
- if (this.isPunctuator(ch1)) {
|
|
|
|
|
- return {
|
|
|
|
|
- type: ch1,
|
|
|
|
|
- value: ch1,
|
|
|
|
|
- pos: this.char
|
|
|
|
|
- };
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Extract a string out of the next sequence of characters and/or
|
|
|
|
|
+ * lines or return 'null' if its not possible. Since strings can
|
|
|
|
|
+ * span across multiple lines this method has to move the char
|
|
|
|
|
+ * pointer.
|
|
|
|
|
+ *
|
|
|
|
|
+ * This method recognizes pseudo-multiline JavaScript strings:
|
|
|
|
|
+ *
|
|
|
|
|
+ * var str = "hello\
|
|
|
|
|
+ * world";
|
|
|
|
|
+ */
|
|
|
|
|
+ scanStringLiteral: function () {
|
|
|
|
|
+ /*jshint loopfunc:true */
|
|
|
|
|
+ var quote = this.peek();
|
|
|
|
|
|
|
|
|
|
+ // String must start with a quote.
|
|
|
|
|
+ if (quote !== "\"" && quote !== "'") {
|
|
|
return null;
|
|
return null;
|
|
|
- },
|
|
|
|
|
-
|
|
|
|
|
- /*
|
|
|
|
|
- * Extract a string out of the next sequence of characters and/or
|
|
|
|
|
- * lines or return 'null' if its not possible. Since strings can
|
|
|
|
|
- * span across multiple lines this method has to move the char
|
|
|
|
|
- * pointer.
|
|
|
|
|
- *
|
|
|
|
|
- * This method recognizes pseudo-multiline JavaScript strings:
|
|
|
|
|
- *
|
|
|
|
|
- * var str = "hello\
|
|
|
|
|
- * world";
|
|
|
|
|
- */
|
|
|
|
|
- scanStringLiteral: function () {
|
|
|
|
|
- /*jshint loopfunc:true */
|
|
|
|
|
- var quote = this.peek();
|
|
|
|
|
-
|
|
|
|
|
- // String must start with a quote.
|
|
|
|
|
- if (quote !== "\"" && quote !== "'") {
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- var value = "";
|
|
|
|
|
|
|
+ var value = "";
|
|
|
|
|
|
|
|
- this.skip();
|
|
|
|
|
|
|
+ this.skip();
|
|
|
|
|
|
|
|
- while (this.peek() !== quote) {
|
|
|
|
|
- if (this.peek() === "") { // End Of Line
|
|
|
|
|
- return {
|
|
|
|
|
- type: 'string',
|
|
|
|
|
- value: value,
|
|
|
|
|
- isUnclosed: true,
|
|
|
|
|
- quote: quote,
|
|
|
|
|
- pos: this.char
|
|
|
|
|
- };
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ while (this.peek() !== quote) {
|
|
|
|
|
+ if (this.peek() === "") { // End Of Line
|
|
|
|
|
+ return {
|
|
|
|
|
+ type: 'string',
|
|
|
|
|
+ value: value,
|
|
|
|
|
+ isUnclosed: true,
|
|
|
|
|
+ quote: quote,
|
|
|
|
|
+ pos: this.char
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- var char = this.peek();
|
|
|
|
|
- var jump = 1; // A length of a jump, after we're done
|
|
|
|
|
- // parsing this character.
|
|
|
|
|
|
|
+ var char = this.peek();
|
|
|
|
|
+ var jump = 1; // A length of a jump, after we're done
|
|
|
|
|
+ // parsing this character.
|
|
|
|
|
|
|
|
- value += char;
|
|
|
|
|
- this.skip(jump);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ value += char;
|
|
|
|
|
+ this.skip(jump);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- this.skip();
|
|
|
|
|
- return {
|
|
|
|
|
- type: 'string',
|
|
|
|
|
- value: value,
|
|
|
|
|
- isUnclosed: false,
|
|
|
|
|
- quote: quote,
|
|
|
|
|
- pos: this.char
|
|
|
|
|
- };
|
|
|
|
|
- },
|
|
|
|
|
|
|
+ this.skip();
|
|
|
|
|
+ return {
|
|
|
|
|
+ type: 'string',
|
|
|
|
|
+ value: value,
|
|
|
|
|
+ isUnclosed: false,
|
|
|
|
|
+ quote: quote,
|
|
|
|
|
+ pos: this.char
|
|
|
|
|
+ };
|
|
|
|
|
+ },
|
|
|
|
|
|
|
|
- };
|
|
|
|
|
|
|
+};
|
|
|
|
|
|