blob: 045460459777f59a83138d1a57aa6dfc86016aa3 [file] [log] [blame]
'use strict';
var constants = require('./const');
var PUNCTUATION = constants.PUNCTUATION;
var STOP_URL_RAW = constants.STOP_URL_RAW;
var TYPE = constants.TYPE;
var FULLSTOP = TYPE.FullStop;
var PLUSSIGN = TYPE.PlusSign;
var HYPHENMINUS = TYPE.HyphenMinus;
var PUNCTUATOR = TYPE.Punctuator;
var TAB = 9;
var N = 10;
var F = 12;
var R = 13;
var SPACE = 32;
var BACK_SLASH = 92;
var E = 101; // 'e'.charCodeAt(0)
function firstCharOffset(source) {
// detect BOM (https://en.wikipedia.org/wiki/Byte_order_mark)
if (source.charCodeAt(0) === 0xFEFF || // UTF-16BE
source.charCodeAt(0) === 0xFFFE) { // UTF-16LE
return 1;
}
return 0;
}
function isHex(code) {
return (code >= 48 && code <= 57) || // 0 .. 9
(code >= 65 && code <= 70) || // A .. F
(code >= 97 && code <= 102); // a .. f
}
function isNumber(code) {
return code >= 48 && code <= 57;
}
function isWhiteSpace(code) {
return code === SPACE || code === TAB || isNewline(code);
}
function isNewline(code) {
return code === R || code === N || code === F;
}
function getNewlineLength(source, offset, code) {
if (isNewline(code)) {
if (code === R && offset + 1 < source.length && source.charCodeAt(offset + 1) === N) {
return 2;
}
return 1;
}
return 0;
}
function cmpChar(testStr, offset, referenceCode) {
var code = testStr.charCodeAt(offset);
// code.toLowerCase() for A..Z
if (code >= 65 && code <= 90) {
code = code | 32;
}
return code === referenceCode;
}
function cmpStr(testStr, start, end, referenceStr) {
if (end - start !== referenceStr.length) {
return false;
}
if (start < 0 || end > testStr.length) {
return false;
}
for (var i = start; i < end; i++) {
var testCode = testStr.charCodeAt(i);
var refCode = referenceStr.charCodeAt(i - start);
// testCode.toLowerCase() for A..Z
if (testCode >= 65 && testCode <= 90) {
testCode = testCode | 32;
}
if (testCode !== refCode) {
return false;
}
}
return true;
}
function findWhiteSpaceStart(source, offset) {
while (offset >= 0 && isWhiteSpace(source.charCodeAt(offset))) {
offset--;
}
return offset + 1;
}
function findWhiteSpaceEnd(source, offset) {
while (offset < source.length && isWhiteSpace(source.charCodeAt(offset))) {
offset++;
}
return offset;
}
function findCommentEnd(source, offset) {
var commentEnd = source.indexOf('*/', offset);
if (commentEnd === -1) {
return source.length;
}
return commentEnd + 2;
}
function findStringEnd(source, offset, quote) {
for (; offset < source.length; offset++) {
var code = source.charCodeAt(offset);
// TODO: bad string
if (code === BACK_SLASH) {
offset++;
} else if (code === quote) {
offset++;
break;
}
}
return offset;
}
function findDecimalNumberEnd(source, offset) {
while (offset < source.length && isNumber(source.charCodeAt(offset))) {
offset++;
}
return offset;
}
function findNumberEnd(source, offset, allowFraction) {
var code;
offset = findDecimalNumberEnd(source, offset);
// fraction: .\d+
if (allowFraction && offset + 1 < source.length && source.charCodeAt(offset) === FULLSTOP) {
code = source.charCodeAt(offset + 1);
if (isNumber(code)) {
offset = findDecimalNumberEnd(source, offset + 1);
}
}
// exponent: e[+-]\d+
if (offset + 1 < source.length) {
if ((source.charCodeAt(offset) | 32) === E) { // case insensitive check for `e`
code = source.charCodeAt(offset + 1);
if (code === PLUSSIGN || code === HYPHENMINUS) {
if (offset + 2 < source.length) {
code = source.charCodeAt(offset + 2);
}
}
if (isNumber(code)) {
offset = findDecimalNumberEnd(source, offset + 2);
}
}
}
return offset;
}
// skip escaped unicode sequence that can ends with space
// [0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?
function findEscapeEnd(source, offset) {
for (var i = 0; i < 7 && offset + i < source.length; i++) {
var code = source.charCodeAt(offset + i);
if (i !== 6 && isHex(code)) {
continue;
}
if (i > 0) {
offset += i - 1 + getNewlineLength(source, offset + i, code);
if (code === SPACE || code === TAB) {
offset++;
}
}
break;
}
return offset;
}
function findIdentifierEnd(source, offset) {
for (; offset < source.length; offset++) {
var code = source.charCodeAt(offset);
if (code === BACK_SLASH) {
offset = findEscapeEnd(source, offset + 1);
} else if (code < 0x80 && PUNCTUATION[code] === PUNCTUATOR) {
break;
}
}
return offset;
}
function findUrlRawEnd(source, offset) {
for (; offset < source.length; offset++) {
var code = source.charCodeAt(offset);
if (code === BACK_SLASH) {
offset = findEscapeEnd(source, offset + 1);
} else if (code < 0x80 && STOP_URL_RAW[code] === 1) {
break;
}
}
return offset;
}
module.exports = {
firstCharOffset: firstCharOffset,
isHex: isHex,
isNumber: isNumber,
isWhiteSpace: isWhiteSpace,
isNewline: isNewline,
getNewlineLength: getNewlineLength,
cmpChar: cmpChar,
cmpStr: cmpStr,
findWhiteSpaceStart: findWhiteSpaceStart,
findWhiteSpaceEnd: findWhiteSpaceEnd,
findCommentEnd: findCommentEnd,
findStringEnd: findStringEnd,
findDecimalNumberEnd: findDecimalNumberEnd,
findNumberEnd: findNumberEnd,
findEscapeEnd: findEscapeEnd,
findIdentifierEnd: findIdentifierEnd,
findUrlRawEnd: findUrlRawEnd
};