"use strict";
module.exports = tokenize;
var delimRe = /[\s{}=;:[\],'"()<>]/g,
stringDoubleRe = /(?:"([^"\\]*(?:\\.[^"\\]*)*)")/g,
stringSingleRe = /(?:'([^'\\]*(?:\\.[^'\\]*)*)')/g;
var setCommentRe = /^ *[*/]+ */,
setCommentAltRe = /^\s*\*?\/*/,
setCommentSplitRe = /\n/g,
whitespaceRe = /\s/,
unescapeRe = /\\(.?)/g;
var unescapeMap = {
"0": "\0",
"r": "\r",
"n": "\n",
"t": "\t"
};
function unescape(str) {
return str.replace(unescapeRe, function($0, $1) {
switch ($1) {
case "\\":
case "":
return $1;
default:
return unescapeMap[$1] || "";
}
});
}
tokenize.unescape = unescape;
function tokenize(source, alternateCommentMode) {
source = source.toString();
var offset = 0,
length = source.length,
line = 1,
lastCommentLine = 0,
comments = {};
var stack = [];
var stringDelim = null;
function illegal(subject) {
return Error("illegal " + subject + " (line " + line + ")");
}
function readString() {
var re = stringDelim === "'" ? stringSingleRe : stringDoubleRe;
re.lastIndex = offset - 1;
var match = re.exec(source);
if (!match)
throw illegal("string");
offset = re.lastIndex;
push(stringDelim);
stringDelim = null;
return unescape(match[1]);
}
function charAt(pos) {
return source.charAt(pos);
}
function setComment(start, end, isLeading) {
var comment = {
type: source.charAt(start++),
lineEmpty: false,
leading: isLeading,
};
var lookback;
if (alternateCommentMode) {
lookback = 2; } else {
lookback = 3; }
var commentOffset = start - lookback,
c;
do {
if (--commentOffset < 0 ||
(c = source.charAt(commentOffset)) === "\n") {
comment.lineEmpty = true;
break;
}
} while (c === " " || c === "\t");
var lines = source
.substring(start, end)
.split(setCommentSplitRe);
for (var i = 0; i < lines.length; ++i)
lines[i] = lines[i]
.replace(alternateCommentMode ? setCommentAltRe : setCommentRe, "")
.trim();
comment.text = lines
.join("\n")
.trim();
comments[line] = comment;
lastCommentLine = line;
}
function isDoubleSlashCommentLine(startOffset) {
var endOffset = findEndOfLine(startOffset);
var lineText = source.substring(startOffset, endOffset);
var isComment = /^\s*\/\//.test(lineText);
return isComment;
}
function findEndOfLine(cursor) {
var endOffset = cursor;
while (endOffset < length && charAt(endOffset) !== "\n") {
endOffset++;
}
return endOffset;
}
function next() {
if (stack.length > 0)
return stack.shift();
if (stringDelim)
return readString();
var repeat,
prev,
curr,
start,
isDoc,
isLeadingComment = offset === 0;
do {
if (offset === length)
return null;
repeat = false;
while (whitespaceRe.test(curr = charAt(offset))) {
if (curr === "\n") {
isLeadingComment = true;
++line;
}
if (++offset === length)
return null;
}
if (charAt(offset) === "/") {
if (++offset === length) {
throw illegal("comment");
}
if (charAt(offset) === "/") { if (!alternateCommentMode) {
isDoc = charAt(start = offset + 1) === "/";
while (charAt(++offset) !== "\n") {
if (offset === length) {
return null;
}
}
++offset;
if (isDoc) {
setComment(start, offset - 1, isLeadingComment);
isLeadingComment = true;
}
++line;
repeat = true;
} else {
start = offset;
isDoc = false;
if (isDoubleSlashCommentLine(offset - 1)) {
isDoc = true;
do {
offset = findEndOfLine(offset);
if (offset === length) {
break;
}
offset++;
if (!isLeadingComment) {
break;
}
} while (isDoubleSlashCommentLine(offset));
} else {
offset = Math.min(length, findEndOfLine(offset) + 1);
}
if (isDoc) {
setComment(start, offset, isLeadingComment);
isLeadingComment = true;
}
line++;
repeat = true;
}
} else if ((curr = charAt(offset)) === "*") {
start = offset + 1;
isDoc = alternateCommentMode || charAt(start) === "*";
do {
if (curr === "\n") {
++line;
}
if (++offset === length) {
throw illegal("comment");
}
prev = curr;
curr = charAt(offset);
} while (prev !== "*" || curr !== "/");
++offset;
if (isDoc) {
setComment(start, offset - 2, isLeadingComment);
isLeadingComment = true;
}
repeat = true;
} else {
return "/";
}
}
} while (repeat);
var end = offset;
delimRe.lastIndex = 0;
var delim = delimRe.test(charAt(end++));
if (!delim)
while (end < length && !delimRe.test(charAt(end)))
++end;
var token = source.substring(offset, offset = end);
if (token === "\"" || token === "'")
stringDelim = token;
return token;
}
function push(token) {
stack.push(token);
}
function peek() {
if (!stack.length) {
var token = next();
if (token === null)
return null;
push(token);
}
return stack[0];
}
function skip(expected, optional) {
var actual = peek(),
equals = actual === expected;
if (equals) {
next();
return true;
}
if (!optional)
throw illegal("token '" + actual + "', '" + expected + "' expected");
return false;
}
function cmnt(trailingLine) {
var ret = null;
var comment;
if (trailingLine === undefined) {
comment = comments[line - 1];
delete comments[line - 1];
if (comment && (alternateCommentMode || comment.type === "*" || comment.lineEmpty)) {
ret = comment.leading ? comment.text : null;
}
} else {
if (lastCommentLine < trailingLine) {
peek();
}
comment = comments[trailingLine];
delete comments[trailingLine];
if (comment && !comment.lineEmpty && (alternateCommentMode || comment.type === "/")) {
ret = comment.leading ? null : comment.text;
}
}
return ret;
}
return Object.defineProperty({
next: next,
peek: peek,
push: push,
skip: skip,
cmnt: cmnt
}, "line", {
get: function() { return line; }
});
}