@bbob/parser
Advanced tools
Comparing version 2.5.8 to 2.6.0
@@ -6,2 +6,13 @@ # Change Log | ||
# [2.6.0](https://github.com/JiLiZART/bbob/compare/v2.5.8...v2.6.0) (2020-12-10) | ||
### Features | ||
* **parser:** rewrite lexer to make it faster ([#50](https://github.com/JiLiZART/bbob/issues/50)) ([772d422](https://github.com/JiLiZART/bbob/commit/772d422d770b0f7716a86ac82c10eb3baaf77828)) | ||
## [2.5.8](https://github.com/JiLiZART/bbob/compare/v2.5.7...v2.5.8) (2020-07-08) | ||
@@ -8,0 +19,0 @@ |
@@ -88,2 +88,6 @@ (function (global, factory) { | ||
var keysReduce = function keysReduce(obj, reduce, def) { | ||
return Object.keys(obj).reduce(reduce, def); | ||
}; | ||
var getNodeLength = function getNodeLength(node) { | ||
@@ -164,7 +168,7 @@ if (isTagNode(node)) { | ||
// To avoid some malformed attributes | ||
if (typeof values === 'undefined') { | ||
if (values == null) { | ||
return ''; | ||
} | ||
return Object.keys(values).reduce(function (arr, key) { | ||
return keysReduce(values, function (arr, key) { | ||
return [].concat(arr, [attrValue(key, values[key])]); | ||
@@ -185,3 +189,3 @@ }, ['']).join(' '); | ||
var getUniqAttr = function getUniqAttr(attrs) { | ||
return Object.keys(attrs).reduce(function (res, key) { | ||
return keysReduce(attrs, function (res, key) { | ||
return attrs[key] === key ? attrs[key] : null; | ||
@@ -270,3 +274,3 @@ }, null); | ||
this.attrs = attrs; | ||
this.content = [].concat(content); | ||
this.content = Array.isArray(content) ? content : [content]; | ||
} | ||
@@ -343,204 +347,23 @@ | ||
var char_1$1 = createCommonjsModule(function (module, exports) { | ||
var TOKEN_TYPE_ID = 'type'; // 0; | ||
exports.__esModule = true; | ||
exports.BACKSLASH = exports.PLACEHOLDER_SPACE = exports.PLACEHOLDER_SPACE_TAB = exports.SLASH = exports.CLOSE_BRAKET = exports.OPEN_BRAKET = exports.SPACE = exports.QUOTEMARK = exports.EQ = exports.TAB = exports.R = exports.F = exports.N = void 0; | ||
var N = '\n'; | ||
exports.N = N; | ||
var TAB = '\t'; | ||
exports.TAB = TAB; | ||
var F = '\f'; | ||
exports.F = F; | ||
var R = '\r'; | ||
exports.R = R; | ||
var EQ = '='; | ||
exports.EQ = EQ; | ||
var QUOTEMARK = '"'; | ||
exports.QUOTEMARK = QUOTEMARK; | ||
var SPACE = ' '; | ||
exports.SPACE = SPACE; | ||
var OPEN_BRAKET = '['; | ||
exports.OPEN_BRAKET = OPEN_BRAKET; | ||
var CLOSE_BRAKET = ']'; | ||
exports.CLOSE_BRAKET = CLOSE_BRAKET; | ||
var SLASH = '/'; | ||
exports.SLASH = SLASH; | ||
var BACKSLASH = '\\'; | ||
exports.BACKSLASH = BACKSLASH; | ||
var PLACEHOLDER_SPACE_TAB = ' '; | ||
exports.PLACEHOLDER_SPACE_TAB = PLACEHOLDER_SPACE_TAB; | ||
var PLACEHOLDER_SPACE = ' '; // const getChar = String.fromCharCode; | ||
var TOKEN_VALUE_ID = 'value'; // 1; | ||
exports.PLACEHOLDER_SPACE = PLACEHOLDER_SPACE; | ||
}); | ||
var TOKEN_COLUMN_ID = 'row'; // 2; | ||
unwrapExports(char_1$1); | ||
var char_2$1 = char_1$1.BACKSLASH; | ||
var char_3$1 = char_1$1.PLACEHOLDER_SPACE; | ||
var char_4$1 = char_1$1.PLACEHOLDER_SPACE_TAB; | ||
var char_5$1 = char_1$1.SLASH; | ||
var char_6$1 = char_1$1.CLOSE_BRAKET; | ||
var char_7$1 = char_1$1.OPEN_BRAKET; | ||
var char_8$1 = char_1$1.SPACE; | ||
var char_9$1 = char_1$1.QUOTEMARK; | ||
var char_10$1 = char_1$1.EQ; | ||
var char_11$1 = char_1$1.TAB; | ||
var char_12$1 = char_1$1.R; | ||
var char_13$1 = char_1$1.F; | ||
var char_14$1 = char_1$1.N; | ||
var TOKEN_LINE_ID = 'line'; // 3; | ||
var es = createCommonjsModule(function (module, exports) { | ||
var TOKEN_TYPE_WORD = 1; // 'word'; | ||
exports.__esModule = true; | ||
exports.isEOL = exports.isStringNode = exports.isTagNode = exports.getUniqAttr = exports.getNodeLength = exports.escapeHTML = exports.appendToNode = exports.attrValue = exports.attrsToString = void 0; | ||
var TOKEN_TYPE_TAG = 2; // 'tag'; | ||
var TOKEN_TYPE_ATTR_NAME = 3; // 'attr-name'; | ||
var TOKEN_TYPE_ATTR_VALUE = 4; // 'attr-value'; | ||
var isTagNode = function isTagNode(el) { | ||
return typeof el === 'object' && !!el.tag; | ||
}; | ||
var TOKEN_TYPE_SPACE = 5; // 'space'; | ||
exports.isTagNode = isTagNode; | ||
var TOKEN_TYPE_NEW_LINE = 6; // 'new-line'; | ||
var isStringNode = function isStringNode(el) { | ||
return typeof el === 'string'; | ||
}; | ||
exports.isStringNode = isStringNode; | ||
var isEOL = function isEOL(el) { | ||
return el === char_1$1.N; | ||
}; | ||
exports.isEOL = isEOL; | ||
var getNodeLength = function getNodeLength(node) { | ||
if (isTagNode(node)) { | ||
return node.content.reduce(function (count, contentNode) { | ||
return count + getNodeLength(contentNode); | ||
}, 0); | ||
} | ||
if (isStringNode(node)) { | ||
return node.length; | ||
} | ||
return 0; | ||
}; | ||
/** | ||
* Appends value to Tag Node | ||
* @param {TagNode} node | ||
* @param value | ||
*/ | ||
exports.getNodeLength = getNodeLength; | ||
var appendToNode = function appendToNode(node, value) { | ||
node.content.push(value); | ||
}; | ||
/** | ||
* Replaces " to &qquot; | ||
* @param {String} value | ||
*/ | ||
exports.appendToNode = appendToNode; | ||
var escapeHTML = function escapeHTML(value) { | ||
return value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''') // eslint-disable-next-line no-script-url | ||
.replace(/(javascript):/gi, '$1%3A'); | ||
}; | ||
/** | ||
* Acept name and value and return valid html5 attribute string | ||
* @param {String} name | ||
* @param {String} value | ||
* @return {string} | ||
*/ | ||
exports.escapeHTML = escapeHTML; | ||
var attrValue = function attrValue(name, value) { | ||
var type = typeof value; | ||
var types = { | ||
boolean: function boolean() { | ||
return value ? "" + name : ''; | ||
}, | ||
number: function number() { | ||
return name + "=\"" + value + "\""; | ||
}, | ||
string: function string() { | ||
return name + "=\"" + escapeHTML(value) + "\""; | ||
}, | ||
object: function object() { | ||
return name + "=\"" + escapeHTML(JSON.stringify(value)) + "\""; | ||
} | ||
}; | ||
return types[type] ? types[type]() : ''; | ||
}; | ||
/** | ||
* Transforms attrs to html params string | ||
* @param values | ||
*/ | ||
exports.attrValue = attrValue; | ||
var attrsToString = function attrsToString(values) { | ||
// To avoid some malformed attributes | ||
if (typeof values === 'undefined') { | ||
return ''; | ||
} | ||
return Object.keys(values).reduce(function (arr, key) { | ||
return [].concat(arr, [attrValue(key, values[key])]); | ||
}, ['']).join(' '); | ||
}; | ||
/** | ||
* Gets value from | ||
* @example | ||
* getUniqAttr({ 'foo': true, 'bar': bar' }) => 'bar' | ||
* @param attrs | ||
* @returns {string} | ||
*/ | ||
exports.attrsToString = attrsToString; | ||
var getUniqAttr = function getUniqAttr(attrs) { | ||
return Object.keys(attrs).reduce(function (res, key) { | ||
return attrs[key] === key ? attrs[key] : null; | ||
}, null); | ||
}; | ||
exports.getUniqAttr = getUniqAttr; | ||
}); | ||
unwrapExports(es); | ||
var es_1 = es.isEOL; | ||
var es_2 = es.isStringNode; | ||
var es_3 = es.isTagNode; | ||
var es_4 = es.getUniqAttr; | ||
var es_5 = es.getNodeLength; | ||
var es_6 = es.escapeHTML; | ||
var es_7 = es.appendToNode; | ||
var es_8 = es.attrValue; | ||
var es_9 = es.attrsToString; | ||
var TOKEN_TYPE_ID = 'type'; // 0; | ||
var TOKEN_VALUE_ID = 'value'; // 1; | ||
var TOKEN_COLUMN_ID = 'row'; // 2; | ||
var TOKEN_LINE_ID = 'line'; // 3; | ||
var TOKEN_TYPE_WORD = 'word'; | ||
var TOKEN_TYPE_TAG = 'tag'; | ||
var TOKEN_TYPE_ATTR_NAME = 'attr-name'; | ||
var TOKEN_TYPE_ATTR_VALUE = 'attr-value'; | ||
var TOKEN_TYPE_SPACE = 'space'; | ||
var TOKEN_TYPE_NEW_LINE = 'new-line'; | ||
/** | ||
* @param {Token} token | ||
@@ -648,3 +471,3 @@ * @returns {string} | ||
function Token(type, value, line, row) { | ||
this[TOKEN_TYPE_ID] = String(type); | ||
this[TOKEN_TYPE_ID] = Number(type); | ||
this[TOKEN_VALUE_ID] = String(value); | ||
@@ -658,3 +481,4 @@ this[TOKEN_LINE_ID] = Number(line); | ||
_proto.isEmpty = function isEmpty() { | ||
return !!this[TOKEN_TYPE_ID]; | ||
// eslint-disable-next-line no-restricted-globals | ||
return isNaN(this[TOKEN_TYPE_ID]); | ||
}; | ||
@@ -715,106 +539,136 @@ | ||
/** | ||
* @typedef {Object} CharGrabber | ||
* @property {Function} skip | ||
* @property {Function} hasNext | ||
* @property {Function} isLast | ||
* @property {Function} grabWhile | ||
*/ | ||
/** | ||
* Creates a grabber wrapper for source string, that helps to iterate over string char by char | ||
* @param {String} source | ||
* @param {Object} options | ||
* @param {Function} options.onSkip | ||
* @returns | ||
*/ | ||
var createCharGrabber = function createCharGrabber(source, options) { | ||
// let idx = 0; | ||
function CharGrabber(source, options) { | ||
var cursor = { | ||
pos: 0, | ||
length: source.length | ||
len: source.length | ||
}; | ||
var skip = function skip() { | ||
cursor.pos += 1; | ||
var substrUntilChar = function substrUntilChar(char) { | ||
var pos = cursor.pos; | ||
var idx = source.indexOf(char, pos); | ||
return idx >= 0 ? source.substr(pos, idx - pos) : ''; | ||
}; | ||
if (options && options.onSkip) { | ||
options.onSkip(); | ||
} | ||
var includes = function includes(val) { | ||
return source.indexOf(val, cursor.pos) >= 0; | ||
}; | ||
var hasNext = function hasNext() { | ||
return cursor.length > cursor.pos; | ||
return cursor.len > cursor.pos; | ||
}; | ||
var getRest = function getRest() { | ||
var isLast = function isLast() { | ||
return cursor.pos === cursor.len; | ||
}; | ||
var skip = function skip(num, silent) { | ||
if (num === void 0) { | ||
num = 1; | ||
} | ||
cursor.pos += num; | ||
if (options && options.onSkip && !silent) { | ||
options.onSkip(); | ||
} | ||
}; | ||
var rest = function rest() { | ||
return source.substr(cursor.pos); | ||
}; | ||
var getCurr = function getCurr() { | ||
var curr = function curr() { | ||
return source[cursor.pos]; | ||
}; | ||
return { | ||
skip: skip, | ||
hasNext: hasNext, | ||
isLast: function isLast() { | ||
return cursor.pos === cursor.length; | ||
}, | ||
var prev = function prev() { | ||
var prevPos = cursor.pos - 1; | ||
return typeof source[prevPos] !== 'undefined' ? source[prevPos] : null; | ||
}; | ||
/** | ||
* @param {Function} cond | ||
* @returns {string} | ||
*/ | ||
grabWhile: function grabWhile(cond) { | ||
var start = 0; | ||
var next = function next() { | ||
var nextPos = cursor.pos + 1; | ||
return nextPos <= source.length - 1 ? source[nextPos] : null; | ||
}; | ||
if (hasNext()) { | ||
start = cursor.pos; | ||
var grabWhile = function grabWhile(cond, silent) { | ||
var start = 0; | ||
while (hasNext() && cond(getCurr())) { | ||
skip(); | ||
} | ||
if (hasNext()) { | ||
start = cursor.pos; | ||
while (hasNext() && cond(curr())) { | ||
skip(1, silent); | ||
} | ||
} | ||
return source.substr(start, cursor.pos - start); | ||
}, | ||
getNext: function getNext() { | ||
var nextPos = cursor.pos + 1; | ||
return source.substr(start, cursor.pos - start); | ||
}; | ||
/** | ||
* @type {skip} | ||
*/ | ||
if (nextPos <= source.length - 1) { | ||
return source[nextPos]; | ||
} | ||
return null; | ||
}, | ||
getPrev: function getPrev() { | ||
var prevPos = cursor.pos - 1; | ||
this.skip = skip; | ||
/** | ||
* @returns {Boolean} | ||
*/ | ||
if (typeof source[prevPos] !== 'undefined') { | ||
return source[prevPos]; | ||
} | ||
this.hasNext = hasNext; | ||
/** | ||
* @returns {String} | ||
*/ | ||
return null; | ||
}, | ||
getCurr: getCurr, | ||
getRest: getRest, | ||
this.getCurr = curr; | ||
/** | ||
* @returns {String} | ||
*/ | ||
/** | ||
* Grabs rest of string until it find a char | ||
* @param {String} char | ||
* @return {String} | ||
*/ | ||
substrUntilChar: function substrUntilChar(char) { | ||
var restStr = getRest(); | ||
var indexOfChar = restStr.indexOf(char); | ||
this.getRest = rest; | ||
/** | ||
* @returns {String} | ||
*/ | ||
if (indexOfChar >= 0) { | ||
return restStr.substr(0, indexOfChar); | ||
} | ||
this.getNext = next; | ||
/** | ||
* @returns {String} | ||
*/ | ||
return ''; | ||
} | ||
}; | ||
this.getPrev = prev; | ||
/** | ||
* @returns {Boolean} | ||
*/ | ||
this.isLast = isLast; | ||
/** | ||
* @returns {Boolean} | ||
*/ | ||
this.includes = includes; | ||
/** | ||
* @param {Function} cond | ||
* @param {Boolean} silent | ||
* @return {String} | ||
*/ | ||
this.grabWhile = grabWhile; | ||
/** | ||
* Grabs rest of string until it find a char | ||
* @param {String} char | ||
* @return {String} | ||
*/ | ||
this.substrUntilChar = substrUntilChar; | ||
} | ||
/** | ||
* Creates a grabber wrapper for source string, that helps to iterate over string char by char | ||
* @param {String} source | ||
* @param {Object} options | ||
* @param {Function} options.onSkip | ||
* @return CharGrabber | ||
*/ | ||
var createCharGrabber = function createCharGrabber(source, options) { | ||
return new CharGrabber(source, options); | ||
}; | ||
@@ -852,18 +706,4 @@ /** | ||
}; | ||
/** | ||
* @typedef {Object} ItemList | ||
* @type {Object} | ||
* @property {getLastCb} getLast | ||
* @property {flushLastCb} flushLast | ||
* @property {pushCb} push | ||
* @property {toArrayCb} toArray | ||
*/ | ||
/** | ||
* | ||
* @param values | ||
* @return {ItemList} | ||
*/ | ||
var createList = function createList(values) { | ||
function NodeList(values) { | ||
if (values === void 0) { | ||
@@ -874,49 +714,37 @@ values = []; | ||
var nodes = values; | ||
/** | ||
* @callback getLastCb | ||
*/ | ||
var getLast = function getLast() { | ||
if (Array.isArray(nodes) && nodes.length > 0 && typeof nodes[nodes.length - 1] !== 'undefined') { | ||
return nodes[nodes.length - 1]; | ||
} | ||
return null; | ||
return Array.isArray(nodes) && nodes.length > 0 && typeof nodes[nodes.length - 1] !== 'undefined' ? nodes[nodes.length - 1] : null; | ||
}; | ||
/** | ||
* @callback flushLastCb | ||
* @return {*} | ||
*/ | ||
var flushLast = function flushLast() { | ||
if (nodes.length) { | ||
return nodes.pop(); | ||
} | ||
return false; | ||
return nodes.length ? nodes.pop() : false; | ||
}; | ||
/** | ||
* @callback pushCb | ||
* @param value | ||
*/ | ||
var push = function push(value) { | ||
return nodes.push(value); | ||
}; | ||
/** | ||
* @callback toArrayCb | ||
* @return {Array} | ||
*/ | ||
var toArray = function toArray() { | ||
return nodes; | ||
}; | ||
return { | ||
getLast: getLast, | ||
flushLast: flushLast, | ||
push: push, | ||
toArray: function toArray() { | ||
return nodes; | ||
} | ||
}; | ||
this.push = push; | ||
this.toArray = toArray; | ||
this.getLast = getLast; | ||
this.flushLast = flushLast; | ||
} | ||
/** | ||
* | ||
* @param values | ||
* @return {NodeList} | ||
*/ | ||
var createList = function createList(values) { | ||
if (values === void 0) { | ||
values = []; | ||
} | ||
return new NodeList(values); | ||
}; | ||
@@ -929,3 +757,3 @@ | ||
* Creates a Token entity class | ||
* @param {String} type | ||
* @param {Number} type | ||
* @param {String} value | ||
@@ -969,9 +797,20 @@ * @param {Number} r line number | ||
var STATE_WORD = 0; | ||
var STATE_TAG = 1; | ||
var STATE_TAG_ATTRS = 2; | ||
var TAG_STATE_NAME = 0; | ||
var TAG_STATE_ATTR = 1; | ||
var TAG_STATE_VALUE = 2; | ||
var row = 0; | ||
var col = 0; | ||
var tokenIndex = -1; | ||
var stateMode = STATE_WORD; | ||
var tagMode = TAG_STATE_NAME; | ||
var tokens = new Array(Math.floor(buffer.length)); | ||
var openTag = options.openTag || char_7; | ||
var closeTag = options.closeTag || char_6; | ||
var escapeTags = options.enableEscapeTags; | ||
var escapeTags = !!options.enableEscapeTags; | ||
var onToken = options.onToken || function () {}; | ||
var RESERVED_CHARS = [closeTag, openTag, char_9, char_2, char_8, char_11, char_10, char_14, EM]; | ||
@@ -987,2 +826,6 @@ var NOT_CHAR_TOKENS = [// ...(options.enableEscapeTags ? [BACKSLASH] : []), | ||
var isNewLine = function isNewLine(char) { | ||
return char === char_14; | ||
}; | ||
var isWhiteSpace = function isWhiteSpace(char) { | ||
@@ -1007,160 +850,244 @@ return WHITESPACES.indexOf(char) >= 0; | ||
}; | ||
var onSkip = function onSkip() { | ||
col++; | ||
}; | ||
var unq = function unq(val) { | ||
return unquote(trimChar(val, char_9)); | ||
}; | ||
var chars = createCharGrabber(buffer, { | ||
onSkip: onSkip | ||
}); | ||
/** | ||
* Emits newly created token to subscriber | ||
* @param token | ||
* @param {Number} type | ||
* @param {String} value | ||
*/ | ||
var emitToken = function emitToken(token) { | ||
if (options.onToken) { | ||
options.onToken(token); | ||
} | ||
function emitToken(type, value) { | ||
var token = createToken(type, value, row, col); | ||
onToken(token); | ||
tokenIndex += 1; | ||
tokens[tokenIndex] = token; | ||
}; | ||
/** | ||
* Parses params inside [myTag---params goes here---]content[/myTag] | ||
* @param str | ||
* @returns {{tag: *, attrs: Array}} | ||
*/ | ||
} | ||
function nextTagState(tagChars, isSingleValueTag) { | ||
if (tagMode === TAG_STATE_ATTR) { | ||
var validAttrName = function validAttrName(char) { | ||
return !(char === char_10 || isWhiteSpace(char)); | ||
}; | ||
var parseAttrs = function parseAttrs(str) { | ||
var tagName = null; | ||
var skipSpecialChars = false; | ||
var attrTokens = []; | ||
var attrCharGrabber = createCharGrabber(str); | ||
var _name = tagChars.grabWhile(validAttrName); | ||
var validAttr = function validAttr(char) { | ||
var isEQ = char === char_10; | ||
var isWS = isWhiteSpace(char); | ||
var prevChar = attrCharGrabber.getPrev(); | ||
var nextChar = attrCharGrabber.getNext(); | ||
var isPrevSLASH = prevChar === char_2; | ||
var isTagNameEmpty = tagName === null; | ||
var isEnd = tagChars.isLast(); | ||
var isValue = tagChars.getCurr() !== char_10; | ||
tagChars.skip(); | ||
if (isTagNameEmpty) { | ||
return (isEQ || isWS || attrCharGrabber.isLast()) === false; | ||
if (isEnd || isValue) { | ||
emitToken(TYPE_ATTR_VALUE, unq(_name)); | ||
} else { | ||
emitToken(TYPE_ATTR_NAME, _name); | ||
} | ||
if (skipSpecialChars && isSpecialChar(char)) { | ||
return true; | ||
if (isEnd) { | ||
return TAG_STATE_NAME; | ||
} | ||
if (char === char_9 && !isPrevSLASH) { | ||
skipSpecialChars = !skipSpecialChars; | ||
if (isValue) { | ||
return TAG_STATE_ATTR; | ||
} | ||
if (!skipSpecialChars && !(nextChar === char_10 || isWhiteSpace(nextChar))) { | ||
return false; | ||
return TAG_STATE_VALUE; | ||
} | ||
if (tagMode === TAG_STATE_VALUE) { | ||
var stateSpecial = false; | ||
var validAttrValue = function validAttrValue(char) { | ||
// const isEQ = char === EQ; | ||
var isQM = char === char_9; | ||
var prevChar = tagChars.getPrev(); | ||
var nextChar = tagChars.getNext(); | ||
var isPrevSLASH = prevChar === char_2; | ||
var isNextEQ = nextChar === char_10; | ||
var isWS = isWhiteSpace(char); // const isPrevWS = isWhiteSpace(prevChar); | ||
var isNextWS = isWhiteSpace(nextChar); | ||
if (stateSpecial && isSpecialChar(char)) { | ||
return true; | ||
} | ||
} | ||
return (isEQ || isWS) === false; | ||
}; | ||
if (isQM && !isPrevSLASH) { | ||
stateSpecial = !stateSpecial; | ||
var nextAttr = function nextAttr() { | ||
var attrStr = attrCharGrabber.grabWhile(validAttr); | ||
var currChar = attrCharGrabber.getCurr(); // first string before space is a tag name [tagName params...] | ||
if (!stateSpecial && !(isNextEQ || isNextWS)) { | ||
return false; | ||
} | ||
} | ||
if (tagName === null) { | ||
tagName = attrStr; | ||
} else if (isWhiteSpace(currChar) || currChar === char_9 || !attrCharGrabber.hasNext()) { | ||
var escaped = unquote(trimChar(attrStr, char_9)); | ||
attrTokens.push(createToken(TYPE_ATTR_VALUE, escaped, row, col)); | ||
} else { | ||
attrTokens.push(createToken(TYPE_ATTR_NAME, attrStr, row, col)); | ||
if (!isSingleValueTag) { | ||
return isWS === false; // return (isEQ || isWS) === false; | ||
} | ||
return true; | ||
}; | ||
var _name2 = tagChars.grabWhile(validAttrValue); | ||
tagChars.skip(); | ||
emitToken(TYPE_ATTR_VALUE, unq(_name2)); | ||
if (tagChars.isLast()) { | ||
return TAG_STATE_NAME; | ||
} | ||
attrCharGrabber.skip(); | ||
}; | ||
while (attrCharGrabber.hasNext()) { | ||
nextAttr(); | ||
return TAG_STATE_ATTR; | ||
} | ||
return { | ||
tag: tagName, | ||
attrs: attrTokens | ||
var validName = function validName(char) { | ||
return !(char === char_10 || isWhiteSpace(char) || tagChars.isLast()); | ||
}; | ||
}; | ||
var bufferGrabber = createCharGrabber(buffer, { | ||
onSkip: function onSkip() { | ||
col++; | ||
var name = tagChars.grabWhile(validName); | ||
emitToken(TYPE_TAG, name); | ||
tagChars.skip(); // in cases when we has [url=someval]GET[/url] and we dont need to parse all | ||
if (isSingleValueTag) { | ||
return TAG_STATE_VALUE; | ||
} | ||
}); | ||
var next = function next() { | ||
var currChar = bufferGrabber.getCurr(); | ||
var nextChar = bufferGrabber.getNext(); | ||
var hasEQ = tagChars.includes(char_10); | ||
return hasEQ ? TAG_STATE_ATTR : TAG_STATE_VALUE; | ||
} | ||
if (currChar === char_14) { | ||
bufferGrabber.skip(); | ||
col = 0; | ||
row++; | ||
emitToken(createToken(TYPE_NEW_LINE, currChar, row, col)); | ||
} else if (isWhiteSpace(currChar)) { | ||
var str = bufferGrabber.grabWhile(isWhiteSpace); | ||
emitToken(createToken(TYPE_SPACE, str, row, col)); | ||
} else if (escapeTags && isEscapeChar(currChar) && isEscapableChar(nextChar)) { | ||
bufferGrabber.skip(); // skip the \ without emitting anything | ||
function stateTag() { | ||
var currChar = chars.getCurr(); | ||
bufferGrabber.skip(); // skip past the [, ] or \ as well | ||
if (currChar === openTag) { | ||
var nextChar = chars.getNext(); | ||
chars.skip(); // detect case where we have '[My word [tag][/tag]' or we have '[My last line word' | ||
emitToken(createToken(TYPE_WORD, nextChar, row, col)); | ||
} else if (currChar === openTag) { | ||
bufferGrabber.skip(); // skip openTag | ||
// detect case where we have '[My word [tag][/tag]' or we have '[My last line word' | ||
var substr = bufferGrabber.substrUntilChar(closeTag); | ||
var substr = chars.substrUntilChar(closeTag); | ||
var hasInvalidChars = substr.length === 0 || substr.indexOf(openTag) >= 0; | ||
if (isCharReserved(nextChar) || hasInvalidChars || bufferGrabber.isLast()) { | ||
emitToken(createToken(TYPE_WORD, currChar, row, col)); | ||
} else { | ||
var _str = bufferGrabber.grabWhile(function (val) { | ||
return val !== closeTag; | ||
if (isCharReserved(nextChar) || hasInvalidChars || chars.isLast()) { | ||
emitToken(TYPE_WORD, currChar); | ||
return STATE_WORD; | ||
} // [myTag ] | ||
var isNoAttrsInTag = substr.indexOf(char_10) === -1; // [/myTag] | ||
var isClosingTag = substr[0] === char_5; | ||
if (isNoAttrsInTag || isClosingTag) { | ||
var name = chars.grabWhile(function (char) { | ||
return char !== closeTag; | ||
}); | ||
chars.skip(); // skip closeTag | ||
bufferGrabber.skip(); // skip closeTag | ||
// [myTag ] | ||
emitToken(TYPE_TAG, name); | ||
return STATE_WORD; | ||
} | ||
var isNoAttrsInTag = _str.indexOf(char_10) === -1; // [/myTag] | ||
return STATE_TAG_ATTRS; | ||
} | ||
var isClosingTag = _str[0] === char_5; | ||
return STATE_WORD; | ||
} | ||
if (isNoAttrsInTag || isClosingTag) { | ||
emitToken(createToken(TYPE_TAG, _str, row, col)); | ||
} else { | ||
var parsed = parseAttrs(_str); | ||
emitToken(createToken(TYPE_TAG, parsed.tag, row, col)); | ||
parsed.attrs.map(emitToken); | ||
} | ||
function stateAttrs() { | ||
var silent = true; | ||
var tagStr = chars.grabWhile(function (char) { | ||
return char !== closeTag; | ||
}, silent); | ||
var tagGrabber = createCharGrabber(tagStr, { | ||
onSkip: onSkip | ||
}); | ||
var hasSpace = tagGrabber.includes(char_8); | ||
while (tagGrabber.hasNext()) { | ||
tagMode = nextTagState(tagGrabber, !hasSpace); | ||
} | ||
chars.skip(); // skip closeTag | ||
return STATE_WORD; | ||
} | ||
function stateWord() { | ||
if (isNewLine(chars.getCurr())) { | ||
emitToken(TYPE_NEW_LINE, chars.getCurr()); | ||
chars.skip(); | ||
col = 0; | ||
row++; | ||
return STATE_WORD; | ||
} | ||
if (isWhiteSpace(chars.getCurr())) { | ||
emitToken(TYPE_SPACE, chars.grabWhile(isWhiteSpace)); | ||
return STATE_WORD; | ||
} | ||
if (chars.getCurr() === openTag) { | ||
if (chars.includes(closeTag)) { | ||
return STATE_TAG; | ||
} | ||
} else if (currChar === closeTag) { | ||
bufferGrabber.skip(); // skip closeTag | ||
emitToken(createToken(TYPE_WORD, currChar, row, col)); | ||
} else if (isCharToken(currChar)) { | ||
if (escapeTags && isEscapeChar(currChar) && !isEscapableChar(nextChar)) { | ||
bufferGrabber.skip(); | ||
emitToken(createToken(TYPE_WORD, currChar, row, col)); | ||
} else { | ||
var _str2 = bufferGrabber.grabWhile(function (char) { | ||
if (escapeTags) { | ||
return isCharToken(char) && !isEscapeChar(char); | ||
} | ||
emitToken(TYPE_WORD, chars.getCurr()); | ||
chars.skip(); | ||
return STATE_WORD; | ||
} | ||
return isCharToken(char); | ||
}); | ||
if (escapeTags) { | ||
if (isEscapeChar(chars.getCurr())) { | ||
var currChar = chars.getCurr(); | ||
var nextChar = chars.getNext(); | ||
chars.skip(); // skip the \ without emitting anything | ||
emitToken(createToken(TYPE_WORD, _str2, row, col)); | ||
if (isEscapableChar(nextChar)) { | ||
chars.skip(); // skip past the [, ] or \ as well | ||
emitToken(TYPE_WORD, nextChar); | ||
return STATE_WORD; | ||
} | ||
emitToken(TYPE_WORD, currChar); | ||
return STATE_WORD; | ||
} | ||
var isChar = function isChar(char) { | ||
return isCharToken(char) && !isEscapeChar(char); | ||
}; | ||
emitToken(TYPE_WORD, chars.grabWhile(isChar)); | ||
return STATE_WORD; | ||
} | ||
}; | ||
var tokenize = function tokenize() { | ||
while (bufferGrabber.hasNext()) { | ||
next(); | ||
emitToken(TYPE_WORD, chars.grabWhile(isCharToken)); | ||
return STATE_WORD; | ||
} | ||
function tokenize() { | ||
while (chars.hasNext()) { | ||
switch (stateMode) { | ||
case STATE_TAG: | ||
stateMode = stateTag(); | ||
break; | ||
case STATE_TAG_ATTRS: | ||
stateMode = stateAttrs(); | ||
break; | ||
case STATE_WORD: | ||
stateMode = stateWord(); | ||
break; | ||
default: | ||
stateMode = STATE_WORD; | ||
break; | ||
} | ||
} | ||
@@ -1170,9 +1097,9 @@ | ||
return tokens; | ||
}; | ||
} | ||
var isTokenNested = function isTokenNested(token) { | ||
function isTokenNested(token) { | ||
var value = openTag + char_5 + token.getValue(); // potential bottleneck | ||
return buffer.indexOf(value) > -1; | ||
}; | ||
} | ||
@@ -1207,3 +1134,3 @@ return { | ||
* @private | ||
* @type {ItemList} | ||
* @type {NodeList} | ||
*/ | ||
@@ -1215,3 +1142,3 @@ | ||
* @private | ||
* @type {ItemList} | ||
* @type {NodeList} | ||
*/ | ||
@@ -1223,3 +1150,3 @@ | ||
* @private | ||
* @type {ItemList} | ||
* @type {NodeList} | ||
*/ | ||
@@ -1231,3 +1158,3 @@ | ||
* @private | ||
* @type {ItemList} | ||
* @type {NodeList} | ||
*/ | ||
@@ -1310,3 +1237,3 @@ | ||
if (Array.isArray(items)) { | ||
if (es_3(node)) { | ||
if (lib_3(node)) { | ||
if (isAllowedTag(node.tag)) { | ||
@@ -1313,0 +1240,0 @@ items.push(node.toTagNode()); |
@@ -1,1 +0,1 @@ | ||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t=t||self).BbobParser={})}(this,(function(t){"use strict";function e(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}function n(t,e){return t(e={exports:{}},e.exports),e.exports}var r=n((function(t,e){e.__esModule=!0,e.BACKSLASH=e.PLACEHOLDER_SPACE=e.PLACEHOLDER_SPACE_TAB=e.SLASH=e.CLOSE_BRAKET=e.OPEN_BRAKET=e.SPACE=e.QUOTEMARK=e.EQ=e.TAB=e.R=e.F=e.N=void 0;e.N="\n";e.TAB="\t";e.F="\f";e.R="\r";e.EQ="=";e.QUOTEMARK='"';e.SPACE=" ";e.OPEN_BRAKET="[";e.CLOSE_BRAKET="]";e.SLASH="/";e.BACKSLASH="\\";e.PLACEHOLDER_SPACE_TAB=" ";e.PLACEHOLDER_SPACE=" "}));e(r);var o=r.BACKSLASH,i=r.SLASH,u=r.CLOSE_BRAKET,a=r.OPEN_BRAKET,s=r.SPACE,c=r.QUOTEMARK,f=r.EQ,l=r.TAB,g=r.N,p=n((function(t,e){e.__esModule=!0,e.isEOL=e.isStringNode=e.isTagNode=e.getUniqAttr=e.getNodeLength=e.escapeHTML=e.appendToNode=e.attrValue=e.attrsToString=void 0;var n=function(t){return"object"==typeof t&&!!t.tag};e.isTagNode=n;var o=function(t){return"string"==typeof t};e.isStringNode=o;e.isEOL=function(t){return t===r.N};e.getNodeLength=function t(e){return n(e)?e.content.reduce((function(e,n){return e+t(n)}),0):o(e)?e.length:0};e.appendToNode=function(t,e){t.content.push(e)};var i=function(t){return t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/(javascript):/gi,"$1%3A")};e.escapeHTML=i;var u=function(t,e){var n=typeof e,r={boolean:function(){return e?""+t:""},number:function(){return t+'="'+e+'"'},string:function(){return t+'="'+i(e)+'"'},object:function(){return t+'="'+i(JSON.stringify(e))+'"'}};return r[n]?r[n]():""};e.attrValue=u;e.attrsToString=function(t){return void 0===t?"":Object.keys(t).reduce((function(e,n){return[].concat(e,[u(n,t[n])])}),[""]).join(" ")};e.getUniqAttr=function(t){return Object.keys(t).reduce((function(e,n){return t[n]===n?t[n]:null}),null)}}));e(p);var d=n((function(t,e){function n(t,e){for(var n=0;e.length>n;n++){var r=e[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(t,r.key,r)}}function o(){return(o=Object.assign||function(t){for(var e=1;arguments.length>e;e++){var n=arguments[e];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(t[r]=n[r])}return t}).apply(this,arguments)}e.__esModule=!0,e.default=e.TagNode=void 0;var i=function(){function t(t,e,n){this.tag=t,this.attrs=e,this.content=[].concat(n)}var e,i,u,a=t.prototype;return a.attr=function(t,e){return void 0!==e&&(this.attrs[t]=e),this.attrs[t]},a.append=function(t){return(0,p.appendToNode)(this,t)},a.toTagNode=function(){return new t(this.tag.toLowerCase(),this.attrs,this.content)},a.toString=function(){var t=r.OPEN_BRAKET,e=r.CLOSE_BRAKET,n=0===this.content.length,i=this.content.reduce((function(t,e){return t+""+e}),""),u=function(t,e){var n=(0,p.getUniqAttr)(e);if(n){var r=(0,p.attrValue)(t,n),i=o({},e);return delete i[n],""+r+(0,p.attrsToString)(i)}return""+t+(0,p.attrsToString)(e)}(this.tag,this.attrs);return n?""+t+u+e:""+t+u+e+i+t+r.SLASH+this.tag+e},e=t,(i=[{key:"length",get:function(){return(0,p.getNodeLength)(this)}}])&&n(e.prototype,i),u&&n(e,u),t}();e.TagNode=i,i.create=function(t,e,n){return void 0===e&&(e={}),void 0===n&&(n=[]),new i(t,e,n)},i.isOf=function(t,e){return t.tag===e},e.default=i})),h=e(d),v=d.TagNode,A=n((function(t,e){e.__esModule=!0,e.BACKSLASH=e.PLACEHOLDER_SPACE=e.PLACEHOLDER_SPACE_TAB=e.SLASH=e.CLOSE_BRAKET=e.OPEN_BRAKET=e.SPACE=e.QUOTEMARK=e.EQ=e.TAB=e.R=e.F=e.N=void 0;e.N="\n";e.TAB="\t";e.F="\f";e.R="\r";e.EQ="=";e.QUOTEMARK='"';e.SPACE=" ";e.OPEN_BRAKET="[";e.CLOSE_BRAKET="]";e.SLASH="/";e.BACKSLASH="\\";e.PLACEHOLDER_SPACE_TAB=" ";e.PLACEHOLDER_SPACE=" "}));e(A);var T=n((function(t,e){e.__esModule=!0,e.isEOL=e.isStringNode=e.isTagNode=e.getUniqAttr=e.getNodeLength=e.escapeHTML=e.appendToNode=e.attrValue=e.attrsToString=void 0;var n=function(t){return"object"==typeof t&&!!t.tag};e.isTagNode=n;var r=function(t){return"string"==typeof t};e.isStringNode=r;e.isEOL=function(t){return t===A.N};e.getNodeLength=function t(e){return n(e)?e.content.reduce((function(e,n){return e+t(n)}),0):r(e)?e.length:0};e.appendToNode=function(t,e){t.content.push(e)};var o=function(t){return t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/(javascript):/gi,"$1%3A")};e.escapeHTML=o;var i=function(t,e){var n=typeof e,r={boolean:function(){return e?""+t:""},number:function(){return t+'="'+e+'"'},string:function(){return t+'="'+o(e)+'"'},object:function(){return t+'="'+o(JSON.stringify(e))+'"'}};return r[n]?r[n]():""};e.attrValue=i;e.attrsToString=function(t){return void 0===t?"":Object.keys(t).reduce((function(e,n){return[].concat(e,[i(n,t[n])])}),[""]).join(" ")};e.getUniqAttr=function(t){return Object.keys(t).reduce((function(e,n){return t[n]===n?t[n]:null}),null)}}));e(T);var E=T.isTagNode,L=function(t){return t&&void 0!==t.value?t.value:""},S=function(t){return L(t).charCodeAt(0)===i.charCodeAt(0)},N=function(){function t(t,e,n,r){this.type=t+"",this.value=e+"",this.line=Number(n),this.row=Number(r)}var e=t.prototype;return e.isEmpty=function(){return!!this.type},e.isText=function(){return!(!(t=this)||void 0===t.type||"space"!==t.type&&"new-line"!==t.type&&"word"!==t.type);var t},e.isTag=function(){return!(!(t=this)||void 0===t.type)&&"tag"===t.type;var t},e.isAttrName=function(){return!(!(t=this)||void 0===t.type)&&"attr-name"===t.type;var t},e.isAttrValue=function(){return!(!(t=this)||void 0===t.type)&&"attr-value"===t.type;var t},e.isStart=function(){return!S(this)},e.isEnd=function(){return S(this)},e.getName=function(){return e=L(t=this),S(t)?e.slice(1):e;var t,e},e.getValue=function(){return L(this)},e.getLine=function(){return(t=this)&&t.line||0;var t},e.getColumn=function(){return(t=this)&&t.row||0;var t},e.toString=function(){return t=a,t+=L(this),t+=u;var t},t}(),y=function(t,e){var n={pos:0,length:t.length},r=function(){n.pos+=1,e&&e.onSkip&&e.onSkip()},o=function(){return n.length>n.pos},i=function(){return t.substr(n.pos)},u=function(){return t[n.pos]};return{skip:r,hasNext:o,isLast:function(){return n.pos===n.length},grabWhile:function(e){var i=0;if(o())for(i=n.pos;o()&&e(u());)r();return t.substr(i,n.pos-i)},getNext:function(){var e=n.pos+1;return e>t.length-1?null:t[e]},getPrev:function(){var e=n.pos-1;return void 0!==t[e]?t[e]:null},getCurr:u,getRest:i,substrUntilChar:function(t){var e=i(),n=e.indexOf(t);return 0>n?"":e.substr(0,n)}}},O=function(t){void 0===t&&(t=[]);var e=t;return{getLast:function(){return Array.isArray(e)&&e.length>0&&void 0!==e[e.length-1]?e[e.length-1]:null},flushLast:function(){return!!e.length&&e.pop()},push:function(t){return e.push(t)},toArray:function(){return e}}},b=function(t,e,n,r){return void 0===n&&(n=0),void 0===r&&(r=0),new N(t,e,n,r)};function C(t,e){void 0===e&&(e={});var n=0,r=0,p=-1,d=Array(Math.floor(t.length)),h=e.openTag||a,v=e.closeTag||u,A=e.enableEscapeTags,T=[v,h,c,o,s,l,f,g,"!"],E=[h,s,l,g],L=[s,l],S=[f,s,l],N=function(t){return L.indexOf(t)>=0},O=function(t){return-1===E.indexOf(t)},C=function(t){return t===h||t===v||t===o},_=function(t){return t===o},P=function(t){e.onToken&&e.onToken(t),d[p+=1]=t},R=function(t){for(var e=null,i=!1,u=[],a=y(t),s=function(t){var n=t===f,r=N(t),u=a.getPrev(),s=a.getNext(),l=u===o;return null===e?!1===(n||r||a.isLast()):!(!i||!function(t){return S.indexOf(t)>=0}(t))||!!(t!==c||l||(i=!i)||s===f||N(s))&&!1===(n||r)},l=function(){var t=a.grabWhile(s),i=a.getCurr();if(null===e)e=t;else if(N(i)||i===c||!a.hasNext()){var f=function(t){return t.replace(o+c,c)}(function(t,e){for(;t.charAt(0)===e;)t=t.substring(1);for(;t.charAt(t.length-1)===e;)t=t.substring(0,t.length-1);return t}(t,c));u.push(b("attr-value",f,n,r))}else u.push(b("attr-name",t,n,r));a.skip()};a.hasNext();)l();return{tag:e,attrs:u}},k=y(t,{onSkip:function(){r++}}),B=function(){var t=k.getCurr(),e=k.getNext();if(t===g)k.skip(),n++,P(b("new-line",t,n,r=0));else if(N(t)){var o=k.grabWhile(N);P(b("space",o,n,r))}else if(A&&_(t)&&C(e))k.skip(),k.skip(),P(b("word",e,n,r));else if(t===h){k.skip();var u=k.substrUntilChar(v),a=0===u.length||u.indexOf(h)>=0;if(T.indexOf(e)>=0||a||k.isLast())P(b("word",t,n,r));else{var s=k.grabWhile((function(t){return t!==v}));if(k.skip(),-1===s.indexOf(f)||s[0]===i)P(b("tag",s,n,r));else{var c=R(s);P(b("tag",c.tag,n,r)),c.attrs.map(P)}}}else if(t===v)k.skip(),P(b("word",t,n,r));else if(O(t))if(A&&_(t)&&!C(e))k.skip(),P(b("word",t,n,r));else{var l=k.grabWhile((function(t){return A?O(t)&&!_(t):O(t)}));P(b("word",l,n,r))}};return{tokenize:function(){for(;k.hasNext();)B();return d.length=p+1,d},isTokenNested:function(e){var n=h+i+e.getValue();return t.indexOf(n)>-1}}}var _=function(t,e){void 0===e&&(e={});var n=e,r=null,o=O(),i=O(),u=O(),a=O(),s={},c=function(){u.flushLast()&&a.flushLast()},f=function(t){var e,r,u=(e=i.getLast())&&Array.isArray(e.content)?e.content:o.toArray();Array.isArray(u)&&(E(t)?(r=t.tag,n.onlyAllowTags&&n.onlyAllowTags.length&&0>n.onlyAllowTags.indexOf(r)?u.push(""+t):u.push(t.toTagNode())):u.push(t))},l=function(t){c();var e=h.create(t.getValue()),n=function(t){return void 0===s[t.getValue()]&&(s[t.getValue()]=r.isTokenNested(t)),s[t.getValue()]}(t);u.push(e),n?i.push(e):f(e)},g=function(t){t.isStart()&&l(t),t.isEnd()&&function(t){c();var e=i.flushLast();if(e)f(e);else if("function"==typeof n.onError){var r=t.getValue(),o=t.getLine(),u=t.getColumn();n.onError({message:"Inconsistent tag '"+r+"' on line "+o+" and column "+u,tagName:r,lineNumber:o,columnNumber:u})}}(t)},p=function(t){var e=u.getLast(),n=t.getValue(),r=!!s[t];if(e)if(t.isAttrName())a.push(n),e.attr(a.getLast(),"");else if(t.isAttrValue()){var o=a.getLast();o?(e.attr(o,n),a.flushLast()):e.attr(n,n)}else t.isText()?r?e.append(n):f(n):t.isTag()&&f(""+t);else t.isText()?f(n):t.isTag()&&f(""+t)};(r=(e.createTokenizer?e.createTokenizer:C)(t,{onToken:function(t){t.isTag()?g(t):p(t)},onlyAllowTags:n.onlyAllowTags,openTag:n.openTag,closeTag:n.closeTag,enableEscapeTags:n.enableEscapeTags})).tokenize();return o.toArray()};t.TagNode=v,t.default=_,t.parse=_,Object.defineProperty(t,"__esModule",{value:!0})})); | ||
!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((t=t||self).BbobParser={})}(this,(function(t){"use strict";function n(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}function e(t,n){return t(n={exports:{}},n.exports),n.exports}var r=e((function(t,n){n.__esModule=!0,n.BACKSLASH=n.PLACEHOLDER_SPACE=n.PLACEHOLDER_SPACE_TAB=n.SLASH=n.CLOSE_BRAKET=n.OPEN_BRAKET=n.SPACE=n.QUOTEMARK=n.EQ=n.TAB=n.R=n.F=n.N=void 0;n.N="\n";n.TAB="\t";n.F="\f";n.R="\r";n.EQ="=";n.QUOTEMARK='"';n.SPACE=" ";n.OPEN_BRAKET="[";n.CLOSE_BRAKET="]";n.SLASH="/";n.BACKSLASH="\\";n.PLACEHOLDER_SPACE_TAB=" ";n.PLACEHOLDER_SPACE=" "}));n(r);var i=r.BACKSLASH,u=r.SLASH,o=r.CLOSE_BRAKET,a=r.OPEN_BRAKET,s=r.SPACE,f=r.QUOTEMARK,c=r.EQ,l=r.TAB,g=r.N,p=e((function(t,n){n.__esModule=!0,n.isEOL=n.isStringNode=n.isTagNode=n.getUniqAttr=n.getNodeLength=n.escapeHTML=n.appendToNode=n.attrValue=n.attrsToString=void 0;var e=function(t){return"object"==typeof t&&!!t.tag};n.isTagNode=e;var i=function(t){return"string"==typeof t};n.isStringNode=i;n.isEOL=function(t){return t===r.N};var u=function(t,n,e){return Object.keys(t).reduce(n,e)};n.getNodeLength=function t(n){return e(n)?n.content.reduce((function(n,e){return n+t(e)}),0):i(n)?n.length:0};n.appendToNode=function(t,n){t.content.push(n)};var o=function(t){return t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/(javascript):/gi,"$1%3A")};n.escapeHTML=o;var a=function(t,n){var e=typeof n,r={boolean:function(){return n?""+t:""},number:function(){return t+'="'+n+'"'},string:function(){return t+'="'+o(n)+'"'},object:function(){return t+'="'+o(JSON.stringify(n))+'"'}};return r[e]?r[e]():""};n.attrValue=a;n.attrsToString=function(t){return null==t?"":u(t,(function(n,e){return[].concat(n,[a(e,t[e])])}),[""]).join(" ")};n.getUniqAttr=function(t){return u(t,(function(n,e){return t[e]===e?t[e]:null}),null)}}));n(p);var h=p.isTagNode,d=e((function(t,n){function e(t,n){for(var e=0;n.length>e;e++){var r=n[e];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(t,r.key,r)}}function i(){return(i=Object.assign||function(t){for(var n=1;arguments.length>n;n++){var e=arguments[n];for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])}return t}).apply(this,arguments)}n.__esModule=!0,n.default=n.TagNode=void 0;var u=function(){function t(t,n,e){this.tag=t,this.attrs=n,this.content=Array.isArray(e)?e:[e]}var n,u,o,a=t.prototype;return a.attr=function(t,n){return void 0!==n&&(this.attrs[t]=n),this.attrs[t]},a.append=function(t){return(0,p.appendToNode)(this,t)},a.toTagNode=function(){return new t(this.tag.toLowerCase(),this.attrs,this.content)},a.toString=function(){var t=r.OPEN_BRAKET,n=r.CLOSE_BRAKET,e=0===this.content.length,u=this.content.reduce((function(t,n){return t+""+n}),""),o=function(t,n){var e=(0,p.getUniqAttr)(n);if(e){var r=(0,p.attrValue)(t,e),u=i({},n);return delete u[e],""+r+(0,p.attrsToString)(u)}return""+t+(0,p.attrsToString)(n)}(this.tag,this.attrs);return e?""+t+o+n:""+t+o+n+u+t+r.SLASH+this.tag+n},n=t,(u=[{key:"length",get:function(){return(0,p.getNodeLength)(this)}}])&&e(n.prototype,u),o&&e(n,o),t}();n.TagNode=u,u.create=function(t,n,e){return void 0===n&&(n={}),void 0===e&&(e=[]),new u(t,n,e)},u.isOf=function(t,n){return t.tag===n},n.default=u})),v=n(d),A=d.TagNode,T=function(t){return t&&void 0!==t.value?t.value:""},y=function(t){return T(t).charCodeAt(0)===u.charCodeAt(0)},E=function(){function t(t,n,e,r){this.type=Number(t),this.value=n+"",this.line=Number(e),this.row=Number(r)}var n=t.prototype;return n.isEmpty=function(){return isNaN(this.type)},n.isText=function(){return!(!(t=this)||void 0===t.type||5!==t.type&&6!==t.type&&1!==t.type);var t},n.isTag=function(){return!(!(t=this)||void 0===t.type)&&2===t.type;var t},n.isAttrName=function(){return!(!(t=this)||void 0===t.type)&&3===t.type;var t},n.isAttrValue=function(){return!(!(t=this)||void 0===t.type)&&4===t.type;var t},n.isStart=function(){return!y(this)},n.isEnd=function(){return y(this)},n.getName=function(){return n=T(t=this),y(t)?n.slice(1):n;var t,n},n.getValue=function(){return T(this)},n.getLine=function(){return(t=this)&&t.line||0;var t},n.getColumn=function(){return(t=this)&&t.row||0;var t},n.toString=function(){return t=a,t+=T(this),t+=o;var t},t}();function N(t,n){var e={pos:0,len:t.length},r=function(){return e.len>e.pos},i=function(t,r){void 0===t&&(t=1),e.pos+=t,n&&n.onSkip&&!r&&n.onSkip()},u=function(){return t[e.pos]};this.skip=i,this.hasNext=r,this.getCurr=u,this.getRest=function(){return t.substr(e.pos)},this.getNext=function(){var n=e.pos+1;return n>t.length-1?null:t[n]},this.getPrev=function(){var n=e.pos-1;return void 0!==t[n]?t[n]:null},this.isLast=function(){return e.pos===e.len},this.includes=function(n){return t.indexOf(n,e.pos)>=0},this.grabWhile=function(n,o){var a=0;if(r())for(a=e.pos;r()&&n(u());)i(1,o);return t.substr(a,e.pos-a)},this.substrUntilChar=function(n){var r=e.pos,i=t.indexOf(n,r);return 0>i?"":t.substr(r,i-r)}}var b=function(t,n){return new N(t,n)};function L(t){void 0===t&&(t=[]);var n=t;this.push=function(t){return n.push(t)},this.toArray=function(){return n},this.getLast=function(){return Array.isArray(n)&&n.length>0&&void 0!==n[n.length-1]?n[n.length-1]:null},this.flushLast=function(){return!!n.length&&n.pop()}}var S=function(t){return void 0===t&&(t=[]),new L(t)};function O(t,n){void 0===n&&(n={});var e=0,r=0,p=-1,h=0,d=0,v=Array(Math.floor(t.length)),A=n.openTag||a,T=n.closeTag||o,y=!!n.enableEscapeTags,N=n.onToken||function(){},L=[T,A,f,i,s,l,c,g,"!"],S=[A,s,l,g],O=[s,l],C=[c,s,l],k=function(t){return O.indexOf(t)>=0},x=function(t){return-1===S.indexOf(t)},_=function(t){return t===i},P=function(){r++},m=function(t){return function(t){return t.replace(i+f,f)}(function(t,n){for(;t.charAt(0)===n;)t=t.substring(1);for(;t.charAt(t.length-1)===n;)t=t.substring(0,t.length-1);return t}(t,f))},R=b(t,{onSkip:P});function w(t,n){var i=function(t,n,e,r){return void 0===e&&(e=0),void 0===r&&(r=0),new E(t,n,e,r)}(t,n,e,r);N(i),v[p+=1]=i}function B(t,n){if(1===d){var e=t.grabWhile((function(t){return!(t===c||k(t))})),r=t.isLast(),u=t.getCurr()!==c;return t.skip(),r||u?w(4,m(e)):w(3,e),r?0:u?1:2}if(2===d){var o=!1,a=t.grabWhile((function(e){var r=e===f,u=t.getPrev(),a=t.getNext(),s=u===i,l=a===c,g=k(e),p=k(a);return!(!o||!function(t){return C.indexOf(t)>=0}(e))||!!(!r||s||(o=!o)||l||p)&&(!!n||!1===g)}));return t.skip(),w(4,m(a)),t.isLast()?0:1}return w(2,t.grabWhile((function(n){return!(n===c||k(n)||t.isLast())}))),t.skip(),n?2:t.includes(c)?1:2}function K(){var t=R.getCurr();if(t===A){var n=R.getNext();R.skip();var e=R.substrUntilChar(T),r=0===e.length||e.indexOf(A)>=0;if(L.indexOf(n)>=0||r||R.isLast())return w(1,t),0;if(-1===e.indexOf(c)||e[0]===u){var i=R.grabWhile((function(t){return t!==T}));return R.skip(),w(2,i),0}return 2}return 0}function H(){for(var t=R.grabWhile((function(t){return t!==T}),!0),n=b(t,{onSkip:P}),e=n.includes(s);n.hasNext();)d=B(n,!e);return R.skip(),0}function V(){if(R.getCurr()===g)return w(6,R.getCurr()),R.skip(),r=0,e++,0;if(k(R.getCurr()))return w(5,R.grabWhile(k)),0;if(R.getCurr()===A)return R.includes(T)?1:(w(1,R.getCurr()),R.skip(),0);if(y){if(_(R.getCurr())){var t=R.getCurr(),n=R.getNext();return R.skip(),function(t){return t===A||t===T||t===i}(n)?(R.skip(),w(1,n),0):(w(1,t),0)}return w(1,R.grabWhile((function(t){return x(t)&&!_(t)}))),0}return w(1,R.grabWhile(x)),0}return{tokenize:function(){for(;R.hasNext();)switch(h){case 1:h=K();break;case 2:h=H();break;case 0:h=V();break;default:h=0}return v.length=p+1,v},isTokenNested:function(n){var e=A+u+n.getValue();return t.indexOf(e)>-1}}}var C=function(t,n){void 0===n&&(n={});var e=n,r=null,i=S(),u=S(),o=S(),a=S(),s={},f=function(){o.flushLast()&&a.flushLast()},c=function(t){var n,r,o=(n=u.getLast())&&Array.isArray(n.content)?n.content:i.toArray();Array.isArray(o)&&(h(t)?(r=t.tag,e.onlyAllowTags&&e.onlyAllowTags.length&&0>e.onlyAllowTags.indexOf(r)?o.push(""+t):o.push(t.toTagNode())):o.push(t))},l=function(t){f();var n=v.create(t.getValue()),e=function(t){return void 0===s[t.getValue()]&&(s[t.getValue()]=r.isTokenNested(t)),s[t.getValue()]}(t);o.push(n),e?u.push(n):c(n)},g=function(t){t.isStart()&&l(t),t.isEnd()&&function(t){f();var n=u.flushLast();if(n)c(n);else if("function"==typeof e.onError){var r=t.getValue(),i=t.getLine(),o=t.getColumn();e.onError({message:"Inconsistent tag '"+r+"' on line "+i+" and column "+o,tagName:r,lineNumber:i,columnNumber:o})}}(t)},p=function(t){var n=o.getLast(),e=t.getValue(),r=!!s[t];if(n)if(t.isAttrName())a.push(e),n.attr(a.getLast(),"");else if(t.isAttrValue()){var i=a.getLast();i?(n.attr(i,e),a.flushLast()):n.attr(e,e)}else t.isText()?r?n.append(e):c(e):t.isTag()&&c(""+t);else t.isText()?c(e):t.isTag()&&c(""+t)};(r=(n.createTokenizer?n.createTokenizer:O)(t,{onToken:function(t){t.isTag()?g(t):p(t)},onlyAllowTags:e.onlyAllowTags,openTag:e.openTag,closeTag:e.closeTag,enableEscapeTags:e.enableEscapeTags})).tokenize();return i.toArray()};t.TagNode=A,t.default=C,t.parse=C,Object.defineProperty(t,"__esModule",{value:!0})})); |
327
es/lexer.js
@@ -18,3 +18,3 @@ "use strict"; | ||
* Creates a Token entity class | ||
* @param {String} type | ||
* @param {Number} type | ||
* @param {String} value | ||
@@ -58,9 +58,20 @@ * @param {Number} r line number | ||
var STATE_WORD = 0; | ||
var STATE_TAG = 1; | ||
var STATE_TAG_ATTRS = 2; | ||
var TAG_STATE_NAME = 0; | ||
var TAG_STATE_ATTR = 1; | ||
var TAG_STATE_VALUE = 2; | ||
var row = 0; | ||
var col = 0; | ||
var tokenIndex = -1; | ||
var stateMode = STATE_WORD; | ||
var tagMode = TAG_STATE_NAME; | ||
var tokens = new Array(Math.floor(buffer.length)); | ||
var openTag = options.openTag || _char.OPEN_BRAKET; | ||
var closeTag = options.closeTag || _char.CLOSE_BRAKET; | ||
var escapeTags = options.enableEscapeTags; | ||
var escapeTags = !!options.enableEscapeTags; | ||
var onToken = options.onToken || (() => {}); | ||
var RESERVED_CHARS = [closeTag, openTag, _char.QUOTEMARK, _char.BACKSLASH, _char.SPACE, _char.TAB, _char.EQ, _char.N, EM]; | ||
@@ -74,2 +85,4 @@ var NOT_CHAR_TOKENS = [// ...(options.enableEscapeTags ? [BACKSLASH] : []), | ||
var isNewLine = char => char === _char.N; | ||
var isWhiteSpace = char => WHITESPACES.indexOf(char) >= 0; | ||
@@ -84,169 +97,245 @@ | ||
var isEscapeChar = char => char === _char.BACKSLASH; | ||
var onSkip = () => { | ||
col++; | ||
}; | ||
var unq = val => (0, _utils.unquote)((0, _utils.trimChar)(val, _char.QUOTEMARK)); | ||
var chars = (0, _utils.createCharGrabber)(buffer, { | ||
onSkip | ||
}); | ||
/** | ||
* Emits newly created token to subscriber | ||
* @param token | ||
* @param {Number} type | ||
* @param {String} value | ||
*/ | ||
var emitToken = token => { | ||
if (options.onToken) { | ||
options.onToken(token); | ||
} | ||
function emitToken(type, value) { | ||
var token = createToken(type, value, row, col); | ||
onToken(token); | ||
tokenIndex += 1; | ||
tokens[tokenIndex] = token; | ||
}; | ||
/** | ||
* Parses params inside [myTag---params goes here---]content[/myTag] | ||
* @param str | ||
* @returns {{tag: *, attrs: Array}} | ||
*/ | ||
} | ||
function nextTagState(tagChars, isSingleValueTag) { | ||
if (tagMode === TAG_STATE_ATTR) { | ||
var validAttrName = char => !(char === _char.EQ || isWhiteSpace(char)); | ||
var parseAttrs = str => { | ||
var tagName = null; | ||
var skipSpecialChars = false; | ||
var attrTokens = []; | ||
var attrCharGrabber = (0, _utils.createCharGrabber)(str); | ||
var _name = tagChars.grabWhile(validAttrName); | ||
var validAttr = char => { | ||
var isEQ = char === _char.EQ; | ||
var isWS = isWhiteSpace(char); | ||
var prevChar = attrCharGrabber.getPrev(); | ||
var nextChar = attrCharGrabber.getNext(); | ||
var isPrevSLASH = prevChar === _char.BACKSLASH; | ||
var isTagNameEmpty = tagName === null; | ||
var isEnd = tagChars.isLast(); | ||
if (isTagNameEmpty) { | ||
return (isEQ || isWS || attrCharGrabber.isLast()) === false; | ||
var isValue = tagChars.getCurr() !== _char.EQ; | ||
tagChars.skip(); | ||
if (isEnd || isValue) { | ||
emitToken(_Token.TYPE_ATTR_VALUE, unq(_name)); | ||
} else { | ||
emitToken(_Token.TYPE_ATTR_NAME, _name); | ||
} | ||
if (skipSpecialChars && isSpecialChar(char)) { | ||
return true; | ||
if (isEnd) { | ||
return TAG_STATE_NAME; | ||
} | ||
if (char === _char.QUOTEMARK && !isPrevSLASH) { | ||
skipSpecialChars = !skipSpecialChars; | ||
if (isValue) { | ||
return TAG_STATE_ATTR; | ||
} | ||
if (!skipSpecialChars && !(nextChar === _char.EQ || isWhiteSpace(nextChar))) { | ||
return false; | ||
return TAG_STATE_VALUE; | ||
} | ||
if (tagMode === TAG_STATE_VALUE) { | ||
var stateSpecial = false; | ||
var validAttrValue = char => { | ||
// const isEQ = char === EQ; | ||
var isQM = char === _char.QUOTEMARK; | ||
var prevChar = tagChars.getPrev(); | ||
var nextChar = tagChars.getNext(); | ||
var isPrevSLASH = prevChar === _char.BACKSLASH; | ||
var isNextEQ = nextChar === _char.EQ; | ||
var isWS = isWhiteSpace(char); // const isPrevWS = isWhiteSpace(prevChar); | ||
var isNextWS = isWhiteSpace(nextChar); | ||
if (stateSpecial && isSpecialChar(char)) { | ||
return true; | ||
} | ||
} | ||
return (isEQ || isWS) === false; | ||
}; | ||
if (isQM && !isPrevSLASH) { | ||
stateSpecial = !stateSpecial; | ||
var nextAttr = () => { | ||
var attrStr = attrCharGrabber.grabWhile(validAttr); | ||
var currChar = attrCharGrabber.getCurr(); // first string before space is a tag name [tagName params...] | ||
if (!stateSpecial && !(isNextEQ || isNextWS)) { | ||
return false; | ||
} | ||
} | ||
if (tagName === null) { | ||
tagName = attrStr; | ||
} else if (isWhiteSpace(currChar) || currChar === _char.QUOTEMARK || !attrCharGrabber.hasNext()) { | ||
var escaped = (0, _utils.unquote)((0, _utils.trimChar)(attrStr, _char.QUOTEMARK)); | ||
attrTokens.push(createToken(_Token.TYPE_ATTR_VALUE, escaped, row, col)); | ||
} else { | ||
attrTokens.push(createToken(_Token.TYPE_ATTR_NAME, attrStr, row, col)); | ||
if (!isSingleValueTag) { | ||
return isWS === false; // return (isEQ || isWS) === false; | ||
} | ||
return true; | ||
}; | ||
var _name2 = tagChars.grabWhile(validAttrValue); | ||
tagChars.skip(); | ||
emitToken(_Token.TYPE_ATTR_VALUE, unq(_name2)); | ||
if (tagChars.isLast()) { | ||
return TAG_STATE_NAME; | ||
} | ||
attrCharGrabber.skip(); | ||
}; | ||
return TAG_STATE_ATTR; | ||
} | ||
while (attrCharGrabber.hasNext()) { | ||
nextAttr(); | ||
var validName = char => !(char === _char.EQ || isWhiteSpace(char) || tagChars.isLast()); | ||
var name = tagChars.grabWhile(validName); | ||
emitToken(_Token.TYPE_TAG, name); | ||
tagChars.skip(); // in cases when we has [url=someval]GET[/url] and we dont need to parse all | ||
if (isSingleValueTag) { | ||
return TAG_STATE_VALUE; | ||
} | ||
return { | ||
tag: tagName, | ||
attrs: attrTokens | ||
}; | ||
}; | ||
var hasEQ = tagChars.includes(_char.EQ); | ||
return hasEQ ? TAG_STATE_ATTR : TAG_STATE_VALUE; | ||
} | ||
var bufferGrabber = (0, _utils.createCharGrabber)(buffer, { | ||
onSkip: () => { | ||
col++; | ||
function stateTag() { | ||
var currChar = chars.getCurr(); | ||
if (currChar === openTag) { | ||
var nextChar = chars.getNext(); | ||
chars.skip(); // detect case where we have '[My word [tag][/tag]' or we have '[My last line word' | ||
var substr = chars.substrUntilChar(closeTag); | ||
var hasInvalidChars = substr.length === 0 || substr.indexOf(openTag) >= 0; | ||
if (isCharReserved(nextChar) || hasInvalidChars || chars.isLast()) { | ||
emitToken(_Token.TYPE_WORD, currChar); | ||
return STATE_WORD; | ||
} // [myTag ] | ||
var isNoAttrsInTag = substr.indexOf(_char.EQ) === -1; // [/myTag] | ||
var isClosingTag = substr[0] === _char.SLASH; | ||
if (isNoAttrsInTag || isClosingTag) { | ||
var name = chars.grabWhile(char => char !== closeTag); | ||
chars.skip(); // skip closeTag | ||
emitToken(_Token.TYPE_TAG, name); | ||
return STATE_WORD; | ||
} | ||
return STATE_TAG_ATTRS; | ||
} | ||
}); | ||
var next = () => { | ||
var currChar = bufferGrabber.getCurr(); | ||
var nextChar = bufferGrabber.getNext(); | ||
return STATE_WORD; | ||
} | ||
if (currChar === _char.N) { | ||
bufferGrabber.skip(); | ||
function stateAttrs() { | ||
var silent = true; | ||
var tagStr = chars.grabWhile(char => char !== closeTag, silent); | ||
var tagGrabber = (0, _utils.createCharGrabber)(tagStr, { | ||
onSkip | ||
}); | ||
var hasSpace = tagGrabber.includes(_char.SPACE); | ||
while (tagGrabber.hasNext()) { | ||
tagMode = nextTagState(tagGrabber, !hasSpace); | ||
} | ||
chars.skip(); // skip closeTag | ||
return STATE_WORD; | ||
} | ||
function stateWord() { | ||
if (isNewLine(chars.getCurr())) { | ||
emitToken(_Token.TYPE_NEW_LINE, chars.getCurr()); | ||
chars.skip(); | ||
col = 0; | ||
row++; | ||
emitToken(createToken(_Token.TYPE_NEW_LINE, currChar, row, col)); | ||
} else if (isWhiteSpace(currChar)) { | ||
var str = bufferGrabber.grabWhile(isWhiteSpace); | ||
emitToken(createToken(_Token.TYPE_SPACE, str, row, col)); | ||
} else if (escapeTags && isEscapeChar(currChar) && isEscapableChar(nextChar)) { | ||
bufferGrabber.skip(); // skip the \ without emitting anything | ||
return STATE_WORD; | ||
} | ||
bufferGrabber.skip(); // skip past the [, ] or \ as well | ||
if (isWhiteSpace(chars.getCurr())) { | ||
emitToken(_Token.TYPE_SPACE, chars.grabWhile(isWhiteSpace)); | ||
return STATE_WORD; | ||
} | ||
emitToken(createToken(_Token.TYPE_WORD, nextChar, row, col)); | ||
} else if (currChar === openTag) { | ||
bufferGrabber.skip(); // skip openTag | ||
// detect case where we have '[My word [tag][/tag]' or we have '[My last line word' | ||
if (chars.getCurr() === openTag) { | ||
if (chars.includes(closeTag)) { | ||
return STATE_TAG; | ||
} | ||
var substr = bufferGrabber.substrUntilChar(closeTag); | ||
var hasInvalidChars = substr.length === 0 || substr.indexOf(openTag) >= 0; | ||
emitToken(_Token.TYPE_WORD, chars.getCurr()); | ||
chars.skip(); | ||
return STATE_WORD; | ||
} | ||
if (isCharReserved(nextChar) || hasInvalidChars || bufferGrabber.isLast()) { | ||
emitToken(createToken(_Token.TYPE_WORD, currChar, row, col)); | ||
} else { | ||
var _str = bufferGrabber.grabWhile(val => val !== closeTag); | ||
if (escapeTags) { | ||
if (isEscapeChar(chars.getCurr())) { | ||
var currChar = chars.getCurr(); | ||
var nextChar = chars.getNext(); | ||
chars.skip(); // skip the \ without emitting anything | ||
bufferGrabber.skip(); // skip closeTag | ||
// [myTag ] | ||
if (isEscapableChar(nextChar)) { | ||
chars.skip(); // skip past the [, ] or \ as well | ||
var isNoAttrsInTag = _str.indexOf(_char.EQ) === -1; // [/myTag] | ||
emitToken(_Token.TYPE_WORD, nextChar); | ||
return STATE_WORD; | ||
} | ||
var isClosingTag = _str[0] === _char.SLASH; | ||
if (isNoAttrsInTag || isClosingTag) { | ||
emitToken(createToken(_Token.TYPE_TAG, _str, row, col)); | ||
} else { | ||
var parsed = parseAttrs(_str); | ||
emitToken(createToken(_Token.TYPE_TAG, parsed.tag, row, col)); | ||
parsed.attrs.map(emitToken); | ||
} | ||
emitToken(_Token.TYPE_WORD, currChar); | ||
return STATE_WORD; | ||
} | ||
} else if (currChar === closeTag) { | ||
bufferGrabber.skip(); // skip closeTag | ||
emitToken(createToken(_Token.TYPE_WORD, currChar, row, col)); | ||
} else if (isCharToken(currChar)) { | ||
if (escapeTags && isEscapeChar(currChar) && !isEscapableChar(nextChar)) { | ||
bufferGrabber.skip(); | ||
emitToken(createToken(_Token.TYPE_WORD, currChar, row, col)); | ||
} else { | ||
var _str2 = bufferGrabber.grabWhile(char => { | ||
if (escapeTags) { | ||
return isCharToken(char) && !isEscapeChar(char); | ||
} | ||
var isChar = char => isCharToken(char) && !isEscapeChar(char); | ||
return isCharToken(char); | ||
}); | ||
emitToken(_Token.TYPE_WORD, chars.grabWhile(isChar)); | ||
return STATE_WORD; | ||
} | ||
emitToken(createToken(_Token.TYPE_WORD, _str2, row, col)); | ||
emitToken(_Token.TYPE_WORD, chars.grabWhile(isCharToken)); | ||
return STATE_WORD; | ||
} | ||
function tokenize() { | ||
while (chars.hasNext()) { | ||
switch (stateMode) { | ||
case STATE_TAG: | ||
stateMode = stateTag(); | ||
break; | ||
case STATE_TAG_ATTRS: | ||
stateMode = stateAttrs(); | ||
break; | ||
case STATE_WORD: | ||
stateMode = stateWord(); | ||
break; | ||
default: | ||
stateMode = STATE_WORD; | ||
break; | ||
} | ||
} | ||
}; | ||
var tokenize = () => { | ||
while (bufferGrabber.hasNext()) { | ||
next(); | ||
} | ||
tokens.length = tokenIndex + 1; | ||
return tokens; | ||
}; | ||
} | ||
var isTokenNested = token => { | ||
function isTokenNested(token) { | ||
var value = openTag + _char.SLASH + token.getValue(); // potential bottleneck | ||
return buffer.indexOf(value) > -1; | ||
}; | ||
} | ||
@@ -253,0 +342,0 @@ return { |
310
es/lexer2.js
@@ -18,3 +18,3 @@ "use strict"; | ||
* Creates a Token entity class | ||
* @param {String} type | ||
* @param {Number} type | ||
* @param {String} value | ||
@@ -41,10 +41,3 @@ * @param {Number} r line number | ||
*/ | ||
// [tag attr-name="attr-value"]content[/tag] other content | ||
var STATE_WORD = 0; | ||
var STATE_TAG = 1; | ||
var STATE_ATTR = 2; | ||
var STATE_ATTR_NAME = 3; | ||
var STATE_ATTR_VALUE = 4; | ||
/** | ||
@@ -60,2 +53,3 @@ * @param {String} buffer | ||
function createLexer(buffer, options) { | ||
@@ -66,10 +60,20 @@ if (options === void 0) { | ||
var STATE_WORD = 0; | ||
var STATE_TAG = 1; | ||
var STATE_TAG_ATTRS = 2; | ||
var TAG_STATE_NAME = 0; | ||
var TAG_STATE_ATTR = 1; | ||
var TAG_STATE_VALUE = 2; | ||
var row = 0; | ||
var col = 0; | ||
var tokenIndex = -1; | ||
var mode = 0; | ||
var stateMode = STATE_WORD; | ||
var tagMode = TAG_STATE_NAME; | ||
var tokens = new Array(Math.floor(buffer.length)); | ||
var openTag = options.openTag || _char.OPEN_BRAKET; | ||
var closeTag = options.closeTag || _char.CLOSE_BRAKET; | ||
var escapeTags = options.enableEscapeTags; | ||
var escapeTags = !!options.enableEscapeTags; | ||
var onToken = options.onToken || (() => {}); | ||
var RESERVED_CHARS = [closeTag, openTag, _char.QUOTEMARK, _char.BACKSLASH, _char.SPACE, _char.TAB, _char.EQ, _char.N, EM]; | ||
@@ -95,121 +99,263 @@ var NOT_CHAR_TOKENS = [// ...(options.enableEscapeTags ? [BACKSLASH] : []), | ||
var bufferGrabber = (0, _utils.createCharGrabber)(buffer, { | ||
onSkip: () => { | ||
col++; | ||
} | ||
var onSkip = () => { | ||
col++; | ||
}; | ||
var unq = val => (0, _utils.unquote)((0, _utils.trimChar)(val, _char.QUOTEMARK)); | ||
var chars = (0, _utils.createCharGrabber)(buffer, { | ||
onSkip | ||
}); | ||
/** | ||
* Emits newly created token to subscriber | ||
* @param token | ||
* @param {Number} type | ||
* @param {String} value | ||
*/ | ||
var emitToken = token => { | ||
if (options.onToken) { | ||
options.onToken(token); | ||
} | ||
function emitToken(type, value) { | ||
var token = createToken(type, value, row, col); | ||
onToken(token); | ||
tokenIndex += 1; | ||
tokens[tokenIndex] = token; | ||
}; | ||
} | ||
var switchMode = newMode => { | ||
mode = newMode; | ||
}; | ||
function nextTagState(tagChars, isSingleValueTag) { | ||
if (tagMode === TAG_STATE_NAME) { | ||
var currChar = tagChars.getCurr(); | ||
var hasNext = tagChars.hasNext(); | ||
var isWS = isWhiteSpace(currChar); | ||
var isQM = currChar === _char.QUOTEMARK; | ||
var processWord = () => { | ||
var currChar = bufferGrabber.getCurr(); | ||
var nextChar = bufferGrabber.getNext(); | ||
if (isWS || isQM || !hasNext) { | ||
return TAG_STATE_VALUE; | ||
} | ||
if (currChar === openTag && bufferGrabber.includes(closeTag)) { | ||
return switchMode(STATE_TAG); | ||
} | ||
var validName = char => !(char === _char.EQ || isWhiteSpace(char) || tagChars.isLast()); | ||
if (isNewLine(currChar)) { | ||
bufferGrabber.skip(); | ||
col = 0; | ||
row++; | ||
return emitToken(createToken(_Token.TYPE_NEW_LINE, currChar, row, col)); | ||
} | ||
var name = tagChars.grabWhile(validName); | ||
emitToken(_Token.TYPE_TAG, name); | ||
tagChars.skip(); // in cases when we has [url=someval]GET[/url] and we dont need to parse all | ||
if (isWhiteSpace(currChar)) { | ||
return emitToken(createToken(_Token.TYPE_SPACE, bufferGrabber.grabWhile(isWhiteSpace), row, col)); | ||
if (isSingleValueTag) { | ||
return TAG_STATE_VALUE; | ||
} | ||
var hasEQ = tagChars.includes(_char.EQ); | ||
return hasEQ ? TAG_STATE_ATTR : TAG_STATE_VALUE; | ||
} | ||
if (escapeTags) { | ||
if (isEscapeChar(currChar) && !isEscapableChar(nextChar)) { | ||
bufferGrabber.skip(); | ||
return emitToken(createToken(_Token.TYPE_WORD, currChar, row, col)); | ||
if (tagMode === TAG_STATE_ATTR) { | ||
var validAttrName = char => !(char === _char.EQ || isWhiteSpace(char)); | ||
var _name = tagChars.grabWhile(validAttrName); | ||
var isEnd = tagChars.isLast(); | ||
var isValue = tagChars.getCurr() !== _char.EQ; | ||
tagChars.skip(); | ||
if (isEnd || isValue) { | ||
emitToken(_Token.TYPE_ATTR_VALUE, unq(_name)); | ||
} else { | ||
emitToken(_Token.TYPE_ATTR_NAME, _name); | ||
} | ||
var _str = bufferGrabber.grabWhile(char => isCharToken(char) && !isEscapeChar(char)); | ||
if (isEnd) { | ||
return TAG_STATE_NAME; | ||
} | ||
return emitToken(createToken(_Token.TYPE_WORD, _str, row, col)); | ||
if (isValue) { | ||
return TAG_STATE_ATTR; | ||
} | ||
return TAG_STATE_VALUE; | ||
} | ||
var str = bufferGrabber.grabWhile(char => isCharToken(char)); | ||
return emitToken(createToken(_Token.TYPE_WORD, str, row, col)); | ||
}; | ||
if (tagMode === TAG_STATE_VALUE) { | ||
var stateSpecial = false; | ||
var processTag = () => { | ||
var currChar = bufferGrabber.getCurr(); | ||
var nextChar = bufferGrabber.getNext(); | ||
var validAttrValue = char => { | ||
// const isEQ = char === EQ; | ||
var isQM = char === _char.QUOTEMARK; | ||
var prevChar = tagChars.getPrev(); | ||
var nextChar = tagChars.getNext(); | ||
var isPrevSLASH = prevChar === _char.BACKSLASH; | ||
var isNextEQ = nextChar === _char.EQ; | ||
var isWS = isWhiteSpace(char); // const isPrevWS = isWhiteSpace(prevChar); | ||
if (currChar === closeTag) { | ||
bufferGrabber.skip(); // skip closeTag | ||
var isNextWS = isWhiteSpace(nextChar); | ||
switchMode(STATE_WORD); | ||
return emitToken(createToken(_Token.TYPE_WORD, currChar, row, col)); | ||
if (stateSpecial && isSpecialChar(char)) { | ||
return true; | ||
} | ||
if (isQM && !isPrevSLASH) { | ||
stateSpecial = !stateSpecial; | ||
if (!stateSpecial && !(isNextEQ || isNextWS)) { | ||
return false; | ||
} | ||
} | ||
if (!isSingleValueTag) { | ||
return isWS === false; // return (isEQ || isWS) === false; | ||
} | ||
return true; | ||
}; | ||
var _name2 = tagChars.grabWhile(validAttrValue); | ||
tagChars.skip(); | ||
emitToken(_Token.TYPE_ATTR_VALUE, unq(_name2)); | ||
if (tagChars.isLast()) { | ||
return TAG_STATE_NAME; | ||
} | ||
return TAG_STATE_ATTR; | ||
} | ||
return TAG_STATE_NAME; | ||
} | ||
function stateTag() { | ||
var currChar = chars.getCurr(); | ||
if (currChar === openTag) { | ||
bufferGrabber.skip(); // skip openTag | ||
// detect case where we have '[My word [tag][/tag]' or we have '[My last line word' | ||
var nextChar = chars.getNext(); | ||
chars.skip(); // detect case where we have '[My word [tag][/tag]' or we have '[My last line word' | ||
var str = bufferGrabber.grabWhile(val => val !== closeTag); | ||
var hasInvalidChars = str.length === 0 || str.indexOf(openTag) >= 0; | ||
var substr = chars.substrUntilChar(closeTag); | ||
var hasInvalidChars = substr.length === 0 || substr.indexOf(openTag) >= 0; | ||
if (isCharReserved(nextChar) || hasInvalidChars || bufferGrabber.isLast()) { | ||
return emitToken(createToken(_Token.TYPE_WORD, currChar, row, col)); | ||
} | ||
if (isCharReserved(nextChar) || hasInvalidChars || chars.isLast()) { | ||
emitToken(_Token.TYPE_WORD, currChar); | ||
return STATE_WORD; | ||
} // [myTag ] | ||
bufferGrabber.skip(); // skip closeTag | ||
// [myTag ] | ||
var isNoAttrsInTag = str.indexOf(_char.EQ) === -1; // [/myTag] | ||
var isNoAttrsInTag = substr.indexOf(_char.EQ) === -1; // [/myTag] | ||
var isClosingTag = str[0] === _char.SLASH; | ||
var isClosingTag = substr[0] === _char.SLASH; | ||
if (isNoAttrsInTag || isClosingTag) { | ||
return emitToken(createToken(_Token.TYPE_TAG, str, row, col)); | ||
var name = chars.grabWhile(char => char !== closeTag); | ||
chars.skip(); // skip closeTag | ||
emitToken(_Token.TYPE_TAG, name); | ||
return STATE_WORD; | ||
} | ||
return STATE_TAG_ATTRS; | ||
} | ||
return switchMode(STATE_WORD); | ||
}; | ||
if (currChar === closeTag) { | ||
chars.skip(); | ||
emitToken(_Token.TYPE_WORD, currChar); | ||
return STATE_WORD; | ||
} | ||
var processAttrName = () => {}; | ||
return STATE_WORD; | ||
} | ||
var processAttrValue = () => {}; | ||
function stateAttrs() { | ||
var silent = true; | ||
var tagStr = chars.grabWhile(char => char !== closeTag, silent); | ||
var tagGrabber = (0, _utils.createCharGrabber)(tagStr, { | ||
onSkip | ||
}); | ||
var hasSpace = tagGrabber.includes(_char.SPACE); | ||
var modeMap = { | ||
[STATE_WORD]: processWord, | ||
[STATE_TAG]: processTag, | ||
[STATE_ATTR_NAME]: processAttrName, | ||
[STATE_ATTR_VALUE]: processAttrValue | ||
}; | ||
while (tagGrabber.hasNext()) { | ||
tagMode = nextTagState(tagGrabber, !hasSpace); | ||
} | ||
var tokenize = () => { | ||
while (bufferGrabber.hasNext()) { | ||
modeMap[mode](bufferGrabber); | ||
chars.skip(); // skip closeTag | ||
return STATE_WORD; | ||
} | ||
function stateWord() { | ||
if (isNewLine(chars.getCurr())) { | ||
emitToken(_Token.TYPE_NEW_LINE, chars.getCurr()); | ||
chars.skip(); | ||
col = 0; | ||
row++; | ||
return STATE_WORD; | ||
} | ||
if (isWhiteSpace(chars.getCurr())) { | ||
emitToken(_Token.TYPE_SPACE, chars.grabWhile(isWhiteSpace)); | ||
return STATE_WORD; | ||
} | ||
if (chars.getCurr() === openTag) { | ||
if (chars.includes(closeTag)) { | ||
return STATE_TAG; | ||
} | ||
emitToken(_Token.TYPE_WORD, chars.getCurr()); | ||
chars.skip(); | ||
return STATE_WORD; | ||
} | ||
if (escapeTags) { | ||
if (isEscapeChar(chars.getCurr())) { | ||
var currChar = chars.getCurr(); | ||
var nextChar = chars.getNext(); | ||
chars.skip(); // skip the \ without emitting anything | ||
if (isEscapableChar(nextChar)) { | ||
chars.skip(); // skip past the [, ] or \ as well | ||
emitToken(_Token.TYPE_WORD, nextChar); | ||
return STATE_WORD; | ||
} | ||
emitToken(_Token.TYPE_WORD, currChar); | ||
return STATE_WORD; | ||
} | ||
var isChar = char => isCharToken(char) && !isEscapeChar(char); | ||
emitToken(_Token.TYPE_WORD, chars.grabWhile(isChar)); | ||
return STATE_WORD; | ||
} | ||
emitToken(_Token.TYPE_WORD, chars.grabWhile(isCharToken)); | ||
return STATE_WORD; | ||
} | ||
function tokenize() { | ||
while (chars.hasNext()) { | ||
switch (stateMode) { | ||
case STATE_TAG: | ||
stateMode = stateTag(); | ||
break; | ||
case STATE_TAG_ATTRS: | ||
stateMode = stateAttrs(); | ||
break; | ||
case STATE_WORD: | ||
stateMode = stateWord(); | ||
break; | ||
default: | ||
stateMode = STATE_WORD; | ||
break; | ||
} | ||
} | ||
tokens.length = tokenIndex + 1; | ||
return tokens; | ||
}; | ||
} | ||
var isTokenNested = token => { | ||
function isTokenNested(token) { | ||
var value = openTag + _char.SLASH + token.getValue(); // potential bottleneck | ||
return buffer.indexOf(value) > -1; | ||
}; | ||
} | ||
@@ -216,0 +362,0 @@ return { |
@@ -8,3 +8,3 @@ "use strict"; | ||
var _pluginHelper = require("@bbob/plugin-helper"); | ||
var _index = require("@bbob/plugin-helper/lib/index"); | ||
@@ -38,3 +38,3 @@ var _lexer = require("./lexer"); | ||
* @private | ||
* @type {ItemList} | ||
* @type {NodeList} | ||
*/ | ||
@@ -46,3 +46,3 @@ | ||
* @private | ||
* @type {ItemList} | ||
* @type {NodeList} | ||
*/ | ||
@@ -54,3 +54,3 @@ | ||
* @private | ||
* @type {ItemList} | ||
* @type {NodeList} | ||
*/ | ||
@@ -62,3 +62,3 @@ | ||
* @private | ||
* @type {ItemList} | ||
* @type {NodeList} | ||
*/ | ||
@@ -139,3 +139,3 @@ | ||
if (Array.isArray(items)) { | ||
if ((0, _pluginHelper.isTagNode)(node)) { | ||
if ((0, _index.isTagNode)(node)) { | ||
if (isAllowedTag(node.tag)) { | ||
@@ -142,0 +142,0 @@ items.push(node.toTagNode()); |
@@ -17,8 +17,14 @@ "use strict"; | ||
var TOKEN_TYPE_WORD = 'word'; | ||
var TOKEN_TYPE_TAG = 'tag'; | ||
var TOKEN_TYPE_ATTR_NAME = 'attr-name'; | ||
var TOKEN_TYPE_ATTR_VALUE = 'attr-value'; | ||
var TOKEN_TYPE_SPACE = 'space'; | ||
var TOKEN_TYPE_NEW_LINE = 'new-line'; | ||
var TOKEN_TYPE_WORD = 1; // 'word'; | ||
var TOKEN_TYPE_TAG = 2; // 'tag'; | ||
var TOKEN_TYPE_ATTR_NAME = 3; // 'attr-name'; | ||
var TOKEN_TYPE_ATTR_VALUE = 4; // 'attr-value'; | ||
var TOKEN_TYPE_SPACE = 5; // 'space'; | ||
var TOKEN_TYPE_NEW_LINE = 6; // 'new-line'; | ||
/** | ||
@@ -117,3 +123,3 @@ * @param {Token} token | ||
constructor(type, value, line, row) { | ||
this[TOKEN_TYPE_ID] = String(type); | ||
this[TOKEN_TYPE_ID] = Number(type); | ||
this[TOKEN_VALUE_ID] = String(value); | ||
@@ -125,3 +131,4 @@ this[TOKEN_LINE_ID] = Number(line); | ||
isEmpty() { | ||
return !!this[TOKEN_TYPE_ID]; | ||
// eslint-disable-next-line no-restricted-globals | ||
return isNaN(this[TOKEN_TYPE_ID]); | ||
} | ||
@@ -128,0 +135,0 @@ |
244
es/utils.js
@@ -8,28 +8,30 @@ "use strict"; | ||
/** | ||
* @typedef {Object} CharGrabber | ||
* @property {Function} skip | ||
* @property {Function} hasNext | ||
* @property {Function} isLast | ||
* @property {Function} grabWhile | ||
*/ | ||
/** | ||
* Creates a grabber wrapper for source string, that helps to iterate over string char by char | ||
* @param {String} source | ||
* @param {Object} options | ||
* @param {Function} options.onSkip | ||
* @returns | ||
*/ | ||
var createCharGrabber = (source, options) => { | ||
// let idx = 0; | ||
function CharGrabber(source, options) { | ||
var cursor = { | ||
pos: 0, | ||
length: source.length | ||
len: source.length | ||
}; | ||
var skip = () => { | ||
cursor.pos += 1; | ||
var substrUntilChar = char => { | ||
var { | ||
pos | ||
} = cursor; | ||
var idx = source.indexOf(char, pos); | ||
return idx >= 0 ? source.substr(pos, idx - pos) : ''; | ||
}; | ||
if (options && options.onSkip) { | ||
var includes = val => source.indexOf(val, cursor.pos) >= 0; | ||
var hasNext = () => cursor.len > cursor.pos; | ||
var isLast = () => cursor.pos === cursor.len; | ||
var skip = function skip(num, silent) { | ||
if (num === void 0) { | ||
num = 1; | ||
} | ||
cursor.pos += num; | ||
if (options && options.onSkip && !silent) { | ||
options.onSkip(); | ||
@@ -39,69 +41,96 @@ } | ||
var hasNext = () => cursor.length > cursor.pos; | ||
var rest = () => source.substr(cursor.pos); | ||
var getRest = () => source.substr(cursor.pos); | ||
var curr = () => source[cursor.pos]; | ||
var getCurr = () => source[cursor.pos]; | ||
var prev = () => { | ||
var prevPos = cursor.pos - 1; | ||
return typeof source[prevPos] !== 'undefined' ? source[prevPos] : null; | ||
}; | ||
return { | ||
skip, | ||
hasNext, | ||
isLast: () => cursor.pos === cursor.length, | ||
var next = () => { | ||
var nextPos = cursor.pos + 1; | ||
return nextPos <= source.length - 1 ? source[nextPos] : null; | ||
}; | ||
/** | ||
* @param {Function} cond | ||
* @returns {string} | ||
*/ | ||
grabWhile: cond => { | ||
var start = 0; | ||
var grabWhile = (cond, silent) => { | ||
var start = 0; | ||
if (hasNext()) { | ||
start = cursor.pos; | ||
if (hasNext()) { | ||
start = cursor.pos; | ||
while (hasNext() && cond(getCurr())) { | ||
skip(); | ||
} | ||
while (hasNext() && cond(curr())) { | ||
skip(1, silent); | ||
} | ||
} | ||
return source.substr(start, cursor.pos - start); | ||
}, | ||
getNext: () => { | ||
var nextPos = cursor.pos + 1; | ||
return source.substr(start, cursor.pos - start); | ||
}; | ||
/** | ||
* @type {skip} | ||
*/ | ||
if (nextPos <= source.length - 1) { | ||
return source[nextPos]; | ||
} | ||
return null; | ||
}, | ||
getPrev: () => { | ||
var prevPos = cursor.pos - 1; | ||
this.skip = skip; | ||
/** | ||
* @returns {Boolean} | ||
*/ | ||
if (typeof source[prevPos] !== 'undefined') { | ||
return source[prevPos]; | ||
} | ||
this.hasNext = hasNext; | ||
/** | ||
* @returns {String} | ||
*/ | ||
return null; | ||
}, | ||
getCurr, | ||
getRest, | ||
this.getCurr = curr; | ||
/** | ||
* @returns {String} | ||
*/ | ||
/** | ||
* Grabs rest of string until it find a char | ||
* @param {String} char | ||
* @return {String} | ||
*/ | ||
substrUntilChar: char => { | ||
var restStr = getRest(); | ||
var indexOfChar = restStr.indexOf(char); | ||
this.getRest = rest; | ||
/** | ||
* @returns {String} | ||
*/ | ||
if (indexOfChar >= 0) { | ||
return restStr.substr(0, indexOfChar); | ||
} | ||
this.getNext = next; | ||
/** | ||
* @returns {String} | ||
*/ | ||
return ''; | ||
} | ||
}; | ||
}; | ||
this.getPrev = prev; | ||
/** | ||
* @returns {Boolean} | ||
*/ | ||
this.isLast = isLast; | ||
/** | ||
* @returns {Boolean} | ||
*/ | ||
this.includes = includes; | ||
/** | ||
* @param {Function} cond | ||
* @param {Boolean} silent | ||
* @return {String} | ||
*/ | ||
this.grabWhile = grabWhile; | ||
/** | ||
* Grabs rest of string until it find a char | ||
* @param {String} char | ||
* @return {String} | ||
*/ | ||
this.substrUntilChar = substrUntilChar; | ||
} | ||
/** | ||
* Creates a grabber wrapper for source string, that helps to iterate over string char by char | ||
* @param {String} source | ||
* @param {Object} options | ||
* @param {Function} options.onSkip | ||
* @return CharGrabber | ||
*/ | ||
var createCharGrabber = (source, options) => new CharGrabber(source, options); | ||
/** | ||
* Trims string from start and end by char | ||
@@ -141,21 +170,6 @@ * @example | ||
var unquote = str => str.replace(_char.BACKSLASH + _char.QUOTEMARK, _char.QUOTEMARK); | ||
/** | ||
* @typedef {Object} ItemList | ||
* @type {Object} | ||
* @property {getLastCb} getLast | ||
* @property {flushLastCb} flushLast | ||
* @property {pushCb} push | ||
* @property {toArrayCb} toArray | ||
*/ | ||
/** | ||
* | ||
* @param values | ||
* @return {ItemList} | ||
*/ | ||
exports.unquote = unquote; | ||
var createList = function createList(values) { | ||
function NodeList(values) { | ||
if (values === void 0) { | ||
@@ -166,47 +180,31 @@ values = []; | ||
var nodes = values; | ||
/** | ||
* @callback getLastCb | ||
*/ | ||
var getLast = () => { | ||
if (Array.isArray(nodes) && nodes.length > 0 && typeof nodes[nodes.length - 1] !== 'undefined') { | ||
return nodes[nodes.length - 1]; | ||
} | ||
var getLast = () => Array.isArray(nodes) && nodes.length > 0 && typeof nodes[nodes.length - 1] !== 'undefined' ? nodes[nodes.length - 1] : null; | ||
return null; | ||
}; | ||
/** | ||
* @callback flushLastCb | ||
* @return {*} | ||
*/ | ||
var flushLast = () => nodes.length ? nodes.pop() : false; | ||
var push = value => nodes.push(value); | ||
var flushLast = () => { | ||
if (nodes.length) { | ||
return nodes.pop(); | ||
} | ||
var toArray = () => nodes; | ||
return false; | ||
}; | ||
/** | ||
* @callback pushCb | ||
* @param value | ||
*/ | ||
this.push = push; | ||
this.toArray = toArray; | ||
this.getLast = getLast; | ||
this.flushLast = flushLast; | ||
} | ||
/** | ||
* | ||
* @param values | ||
* @return {NodeList} | ||
*/ | ||
var push = value => nodes.push(value); | ||
/** | ||
* @callback toArrayCb | ||
* @return {Array} | ||
*/ | ||
var createList = function createList(values) { | ||
if (values === void 0) { | ||
values = []; | ||
} | ||
return { | ||
getLast, | ||
flushLast, | ||
push, | ||
toArray: () => nodes | ||
}; | ||
return new NodeList(values); | ||
}; | ||
exports.createList = createList; |
375
lib/lexer.js
@@ -7,3 +7,3 @@ "use strict"; | ||
var _char9 = require("@bbob/plugin-helper/lib/char"); | ||
var _char14 = require("@bbob/plugin-helper/lib/char"); | ||
@@ -19,3 +19,3 @@ var _Token = require("./Token"); | ||
* Creates a Token entity class | ||
* @param {String} type | ||
* @param {Number} type | ||
* @param {String} value | ||
@@ -59,14 +59,25 @@ * @param {Number} r line number | ||
var STATE_WORD = 0; | ||
var STATE_TAG = 1; | ||
var STATE_TAG_ATTRS = 2; | ||
var TAG_STATE_NAME = 0; | ||
var TAG_STATE_ATTR = 1; | ||
var TAG_STATE_VALUE = 2; | ||
var row = 0; | ||
var col = 0; | ||
var tokenIndex = -1; | ||
var stateMode = STATE_WORD; | ||
var tagMode = TAG_STATE_NAME; | ||
var tokens = new Array(Math.floor(buffer.length)); | ||
var openTag = options.openTag || _char9.OPEN_BRAKET; | ||
var closeTag = options.closeTag || _char9.CLOSE_BRAKET; | ||
var escapeTags = options.enableEscapeTags; | ||
var RESERVED_CHARS = [closeTag, openTag, _char9.QUOTEMARK, _char9.BACKSLASH, _char9.SPACE, _char9.TAB, _char9.EQ, _char9.N, EM]; | ||
var openTag = options.openTag || _char14.OPEN_BRAKET; | ||
var closeTag = options.closeTag || _char14.CLOSE_BRAKET; | ||
var escapeTags = !!options.enableEscapeTags; | ||
var onToken = options.onToken || function () {}; | ||
var RESERVED_CHARS = [closeTag, openTag, _char14.QUOTEMARK, _char14.BACKSLASH, _char14.SPACE, _char14.TAB, _char14.EQ, _char14.N, EM]; | ||
var NOT_CHAR_TOKENS = [// ...(options.enableEscapeTags ? [BACKSLASH] : []), | ||
openTag, _char9.SPACE, _char9.TAB, _char9.N]; | ||
var WHITESPACES = [_char9.SPACE, _char9.TAB]; | ||
var SPECIAL_CHARS = [_char9.EQ, _char9.SPACE, _char9.TAB]; | ||
openTag, _char14.SPACE, _char14.TAB, _char14.N]; | ||
var WHITESPACES = [_char14.SPACE, _char14.TAB]; | ||
var SPECIAL_CHARS = [_char14.EQ, _char14.SPACE, _char14.TAB]; | ||
@@ -77,179 +88,269 @@ var isCharReserved = function isCharReserved(_char) { | ||
var isWhiteSpace = function isWhiteSpace(_char2) { | ||
return WHITESPACES.indexOf(_char2) >= 0; | ||
var isNewLine = function isNewLine(_char2) { | ||
return _char2 === _char14.N; | ||
}; | ||
var isCharToken = function isCharToken(_char3) { | ||
return NOT_CHAR_TOKENS.indexOf(_char3) === -1; | ||
var isWhiteSpace = function isWhiteSpace(_char3) { | ||
return WHITESPACES.indexOf(_char3) >= 0; | ||
}; | ||
var isSpecialChar = function isSpecialChar(_char4) { | ||
return SPECIAL_CHARS.indexOf(_char4) >= 0; | ||
var isCharToken = function isCharToken(_char4) { | ||
return NOT_CHAR_TOKENS.indexOf(_char4) === -1; | ||
}; | ||
var isEscapableChar = function isEscapableChar(_char5) { | ||
return _char5 === openTag || _char5 === closeTag || _char5 === _char9.BACKSLASH; | ||
var isSpecialChar = function isSpecialChar(_char5) { | ||
return SPECIAL_CHARS.indexOf(_char5) >= 0; | ||
}; | ||
var isEscapeChar = function isEscapeChar(_char6) { | ||
return _char6 === _char9.BACKSLASH; | ||
var isEscapableChar = function isEscapableChar(_char6) { | ||
return _char6 === openTag || _char6 === closeTag || _char6 === _char14.BACKSLASH; | ||
}; | ||
/** | ||
* Emits newly created token to subscriber | ||
* @param token | ||
*/ | ||
var isEscapeChar = function isEscapeChar(_char7) { | ||
return _char7 === _char14.BACKSLASH; | ||
}; | ||
var emitToken = function emitToken(token) { | ||
if (options.onToken) { | ||
options.onToken(token); | ||
} | ||
var onSkip = function onSkip() { | ||
col++; | ||
}; | ||
tokenIndex += 1; | ||
tokens[tokenIndex] = token; | ||
var unq = function unq(val) { | ||
return (0, _utils.unquote)((0, _utils.trimChar)(val, _char14.QUOTEMARK)); | ||
}; | ||
var chars = (0, _utils.createCharGrabber)(buffer, { | ||
onSkip: onSkip | ||
}); | ||
/** | ||
* Parses params inside [myTag---params goes here---]content[/myTag] | ||
* @param str | ||
* @returns {{tag: *, attrs: Array}} | ||
* Emits newly created token to subscriber | ||
* @param {Number} type | ||
* @param {String} value | ||
*/ | ||
function emitToken(type, value) { | ||
var token = createToken(type, value, row, col); | ||
onToken(token); | ||
tokenIndex += 1; | ||
tokens[tokenIndex] = token; | ||
} | ||
var parseAttrs = function parseAttrs(str) { | ||
var tagName = null; | ||
var skipSpecialChars = false; | ||
var attrTokens = []; | ||
var attrCharGrabber = (0, _utils.createCharGrabber)(str); | ||
function nextTagState(tagChars, isSingleValueTag) { | ||
if (tagMode === TAG_STATE_ATTR) { | ||
var validAttrName = function validAttrName(_char8) { | ||
return !(_char8 === _char14.EQ || isWhiteSpace(_char8)); | ||
}; | ||
var validAttr = function validAttr(_char7) { | ||
var isEQ = _char7 === _char9.EQ; | ||
var isWS = isWhiteSpace(_char7); | ||
var prevChar = attrCharGrabber.getPrev(); | ||
var nextChar = attrCharGrabber.getNext(); | ||
var isPrevSLASH = prevChar === _char9.BACKSLASH; | ||
var isTagNameEmpty = tagName === null; | ||
var _name = tagChars.grabWhile(validAttrName); | ||
if (isTagNameEmpty) { | ||
return (isEQ || isWS || attrCharGrabber.isLast()) === false; | ||
var isEnd = tagChars.isLast(); | ||
var isValue = tagChars.getCurr() !== _char14.EQ; | ||
tagChars.skip(); | ||
if (isEnd || isValue) { | ||
emitToken(_Token.TYPE_ATTR_VALUE, unq(_name)); | ||
} else { | ||
emitToken(_Token.TYPE_ATTR_NAME, _name); | ||
} | ||
if (skipSpecialChars && isSpecialChar(_char7)) { | ||
return true; | ||
if (isEnd) { | ||
return TAG_STATE_NAME; | ||
} | ||
if (_char7 === _char9.QUOTEMARK && !isPrevSLASH) { | ||
skipSpecialChars = !skipSpecialChars; | ||
if (isValue) { | ||
return TAG_STATE_ATTR; | ||
} | ||
if (!skipSpecialChars && !(nextChar === _char9.EQ || isWhiteSpace(nextChar))) { | ||
return false; | ||
return TAG_STATE_VALUE; | ||
} | ||
if (tagMode === TAG_STATE_VALUE) { | ||
var stateSpecial = false; | ||
var validAttrValue = function validAttrValue(_char9) { | ||
// const isEQ = char === EQ; | ||
var isQM = _char9 === _char14.QUOTEMARK; | ||
var prevChar = tagChars.getPrev(); | ||
var nextChar = tagChars.getNext(); | ||
var isPrevSLASH = prevChar === _char14.BACKSLASH; | ||
var isNextEQ = nextChar === _char14.EQ; | ||
var isWS = isWhiteSpace(_char9); // const isPrevWS = isWhiteSpace(prevChar); | ||
var isNextWS = isWhiteSpace(nextChar); | ||
if (stateSpecial && isSpecialChar(_char9)) { | ||
return true; | ||
} | ||
} | ||
return (isEQ || isWS) === false; | ||
}; | ||
if (isQM && !isPrevSLASH) { | ||
stateSpecial = !stateSpecial; | ||
var nextAttr = function nextAttr() { | ||
var attrStr = attrCharGrabber.grabWhile(validAttr); | ||
var currChar = attrCharGrabber.getCurr(); // first string before space is a tag name [tagName params...] | ||
if (!stateSpecial && !(isNextEQ || isNextWS)) { | ||
return false; | ||
} | ||
} | ||
if (tagName === null) { | ||
tagName = attrStr; | ||
} else if (isWhiteSpace(currChar) || currChar === _char9.QUOTEMARK || !attrCharGrabber.hasNext()) { | ||
var escaped = (0, _utils.unquote)((0, _utils.trimChar)(attrStr, _char9.QUOTEMARK)); | ||
attrTokens.push(createToken(_Token.TYPE_ATTR_VALUE, escaped, row, col)); | ||
} else { | ||
attrTokens.push(createToken(_Token.TYPE_ATTR_NAME, attrStr, row, col)); | ||
if (!isSingleValueTag) { | ||
return isWS === false; // return (isEQ || isWS) === false; | ||
} | ||
return true; | ||
}; | ||
var _name2 = tagChars.grabWhile(validAttrValue); | ||
tagChars.skip(); | ||
emitToken(_Token.TYPE_ATTR_VALUE, unq(_name2)); | ||
if (tagChars.isLast()) { | ||
return TAG_STATE_NAME; | ||
} | ||
attrCharGrabber.skip(); | ||
}; | ||
while (attrCharGrabber.hasNext()) { | ||
nextAttr(); | ||
return TAG_STATE_ATTR; | ||
} | ||
return { | ||
tag: tagName, | ||
attrs: attrTokens | ||
var validName = function validName(_char10) { | ||
return !(_char10 === _char14.EQ || isWhiteSpace(_char10) || tagChars.isLast()); | ||
}; | ||
}; | ||
var bufferGrabber = (0, _utils.createCharGrabber)(buffer, { | ||
onSkip: function onSkip() { | ||
col++; | ||
var name = tagChars.grabWhile(validName); | ||
emitToken(_Token.TYPE_TAG, name); | ||
tagChars.skip(); // in cases when we has [url=someval]GET[/url] and we dont need to parse all | ||
if (isSingleValueTag) { | ||
return TAG_STATE_VALUE; | ||
} | ||
}); | ||
var next = function next() { | ||
var currChar = bufferGrabber.getCurr(); | ||
var nextChar = bufferGrabber.getNext(); | ||
var hasEQ = tagChars.includes(_char14.EQ); | ||
return hasEQ ? TAG_STATE_ATTR : TAG_STATE_VALUE; | ||
} | ||
if (currChar === _char9.N) { | ||
bufferGrabber.skip(); | ||
col = 0; | ||
row++; | ||
emitToken(createToken(_Token.TYPE_NEW_LINE, currChar, row, col)); | ||
} else if (isWhiteSpace(currChar)) { | ||
var str = bufferGrabber.grabWhile(isWhiteSpace); | ||
emitToken(createToken(_Token.TYPE_SPACE, str, row, col)); | ||
} else if (escapeTags && isEscapeChar(currChar) && isEscapableChar(nextChar)) { | ||
bufferGrabber.skip(); // skip the \ without emitting anything | ||
function stateTag() { | ||
var currChar = chars.getCurr(); | ||
bufferGrabber.skip(); // skip past the [, ] or \ as well | ||
if (currChar === openTag) { | ||
var nextChar = chars.getNext(); | ||
chars.skip(); // detect case where we have '[My word [tag][/tag]' or we have '[My last line word' | ||
emitToken(createToken(_Token.TYPE_WORD, nextChar, row, col)); | ||
} else if (currChar === openTag) { | ||
bufferGrabber.skip(); // skip openTag | ||
// detect case where we have '[My word [tag][/tag]' or we have '[My last line word' | ||
var substr = bufferGrabber.substrUntilChar(closeTag); | ||
var substr = chars.substrUntilChar(closeTag); | ||
var hasInvalidChars = substr.length === 0 || substr.indexOf(openTag) >= 0; | ||
if (isCharReserved(nextChar) || hasInvalidChars || bufferGrabber.isLast()) { | ||
emitToken(createToken(_Token.TYPE_WORD, currChar, row, col)); | ||
} else { | ||
var _str = bufferGrabber.grabWhile(function (val) { | ||
return val !== closeTag; | ||
if (isCharReserved(nextChar) || hasInvalidChars || chars.isLast()) { | ||
emitToken(_Token.TYPE_WORD, currChar); | ||
return STATE_WORD; | ||
} // [myTag ] | ||
var isNoAttrsInTag = substr.indexOf(_char14.EQ) === -1; // [/myTag] | ||
var isClosingTag = substr[0] === _char14.SLASH; | ||
if (isNoAttrsInTag || isClosingTag) { | ||
var name = chars.grabWhile(function (_char11) { | ||
return _char11 !== closeTag; | ||
}); | ||
chars.skip(); // skip closeTag | ||
bufferGrabber.skip(); // skip closeTag | ||
// [myTag ] | ||
emitToken(_Token.TYPE_TAG, name); | ||
return STATE_WORD; | ||
} | ||
var isNoAttrsInTag = _str.indexOf(_char9.EQ) === -1; // [/myTag] | ||
return STATE_TAG_ATTRS; | ||
} | ||
var isClosingTag = _str[0] === _char9.SLASH; | ||
return STATE_WORD; | ||
} | ||
if (isNoAttrsInTag || isClosingTag) { | ||
emitToken(createToken(_Token.TYPE_TAG, _str, row, col)); | ||
} else { | ||
var parsed = parseAttrs(_str); | ||
emitToken(createToken(_Token.TYPE_TAG, parsed.tag, row, col)); | ||
parsed.attrs.map(emitToken); | ||
} | ||
function stateAttrs() { | ||
var silent = true; | ||
var tagStr = chars.grabWhile(function (_char12) { | ||
return _char12 !== closeTag; | ||
}, silent); | ||
var tagGrabber = (0, _utils.createCharGrabber)(tagStr, { | ||
onSkip: onSkip | ||
}); | ||
var hasSpace = tagGrabber.includes(_char14.SPACE); | ||
while (tagGrabber.hasNext()) { | ||
tagMode = nextTagState(tagGrabber, !hasSpace); | ||
} | ||
chars.skip(); // skip closeTag | ||
return STATE_WORD; | ||
} | ||
function stateWord() { | ||
if (isNewLine(chars.getCurr())) { | ||
emitToken(_Token.TYPE_NEW_LINE, chars.getCurr()); | ||
chars.skip(); | ||
col = 0; | ||
row++; | ||
return STATE_WORD; | ||
} | ||
if (isWhiteSpace(chars.getCurr())) { | ||
emitToken(_Token.TYPE_SPACE, chars.grabWhile(isWhiteSpace)); | ||
return STATE_WORD; | ||
} | ||
if (chars.getCurr() === openTag) { | ||
if (chars.includes(closeTag)) { | ||
return STATE_TAG; | ||
} | ||
} else if (currChar === closeTag) { | ||
bufferGrabber.skip(); // skip closeTag | ||
emitToken(createToken(_Token.TYPE_WORD, currChar, row, col)); | ||
} else if (isCharToken(currChar)) { | ||
if (escapeTags && isEscapeChar(currChar) && !isEscapableChar(nextChar)) { | ||
bufferGrabber.skip(); | ||
emitToken(createToken(_Token.TYPE_WORD, currChar, row, col)); | ||
} else { | ||
var _str2 = bufferGrabber.grabWhile(function (_char8) { | ||
if (escapeTags) { | ||
return isCharToken(_char8) && !isEscapeChar(_char8); | ||
} | ||
emitToken(_Token.TYPE_WORD, chars.getCurr()); | ||
chars.skip(); | ||
return STATE_WORD; | ||
} | ||
return isCharToken(_char8); | ||
}); | ||
if (escapeTags) { | ||
if (isEscapeChar(chars.getCurr())) { | ||
var currChar = chars.getCurr(); | ||
var nextChar = chars.getNext(); | ||
chars.skip(); // skip the \ without emitting anything | ||
emitToken(createToken(_Token.TYPE_WORD, _str2, row, col)); | ||
if (isEscapableChar(nextChar)) { | ||
chars.skip(); // skip past the [, ] or \ as well | ||
emitToken(_Token.TYPE_WORD, nextChar); | ||
return STATE_WORD; | ||
} | ||
emitToken(_Token.TYPE_WORD, currChar); | ||
return STATE_WORD; | ||
} | ||
var isChar = function isChar(_char13) { | ||
return isCharToken(_char13) && !isEscapeChar(_char13); | ||
}; | ||
emitToken(_Token.TYPE_WORD, chars.grabWhile(isChar)); | ||
return STATE_WORD; | ||
} | ||
}; | ||
var tokenize = function tokenize() { | ||
while (bufferGrabber.hasNext()) { | ||
next(); | ||
emitToken(_Token.TYPE_WORD, chars.grabWhile(isCharToken)); | ||
return STATE_WORD; | ||
} | ||
function tokenize() { | ||
while (chars.hasNext()) { | ||
switch (stateMode) { | ||
case STATE_TAG: | ||
stateMode = stateTag(); | ||
break; | ||
case STATE_TAG_ATTRS: | ||
stateMode = stateAttrs(); | ||
break; | ||
case STATE_WORD: | ||
stateMode = stateWord(); | ||
break; | ||
default: | ||
stateMode = STATE_WORD; | ||
break; | ||
} | ||
} | ||
@@ -259,9 +360,9 @@ | ||
return tokens; | ||
}; | ||
} | ||
var isTokenNested = function isTokenNested(token) { | ||
var value = openTag + _char9.SLASH + token.getValue(); // potential bottleneck | ||
function isTokenNested(token) { | ||
var value = openTag + _char14.SLASH + token.getValue(); // potential bottleneck | ||
return buffer.indexOf(value) > -1; | ||
}; | ||
} | ||
@@ -268,0 +369,0 @@ return { |
@@ -7,3 +7,3 @@ "use strict"; | ||
var _char10 = require("@bbob/plugin-helper/lib/char"); | ||
var _char14 = require("@bbob/plugin-helper/lib/char"); | ||
@@ -19,3 +19,3 @@ var _Token = require("./Token"); | ||
* Creates a Token entity class | ||
* @param {String} type | ||
* @param {Number} type | ||
* @param {String} value | ||
@@ -42,10 +42,3 @@ * @param {Number} r line number | ||
*/ | ||
// [tag attr-name="attr-value"]content[/tag] other content | ||
var STATE_WORD = 0; | ||
var STATE_TAG = 1; | ||
var STATE_ATTR = 2; | ||
var STATE_ATTR_NAME = 3; | ||
var STATE_ATTR_VALUE = 4; | ||
/** | ||
@@ -61,5 +54,4 @@ * @param {String} buffer | ||
function createLexer(buffer, options) { | ||
var _modeMap; | ||
if (options === void 0) { | ||
@@ -69,15 +61,25 @@ options = {}; | ||
var STATE_WORD = 0; | ||
var STATE_TAG = 1; | ||
var STATE_TAG_ATTRS = 2; | ||
var TAG_STATE_NAME = 0; | ||
var TAG_STATE_ATTR = 1; | ||
var TAG_STATE_VALUE = 2; | ||
var row = 0; | ||
var col = 0; | ||
var tokenIndex = -1; | ||
var mode = 0; | ||
var stateMode = STATE_WORD; | ||
var tagMode = TAG_STATE_NAME; | ||
var tokens = new Array(Math.floor(buffer.length)); | ||
var openTag = options.openTag || _char10.OPEN_BRAKET; | ||
var closeTag = options.closeTag || _char10.CLOSE_BRAKET; | ||
var escapeTags = options.enableEscapeTags; | ||
var RESERVED_CHARS = [closeTag, openTag, _char10.QUOTEMARK, _char10.BACKSLASH, _char10.SPACE, _char10.TAB, _char10.EQ, _char10.N, EM]; | ||
var openTag = options.openTag || _char14.OPEN_BRAKET; | ||
var closeTag = options.closeTag || _char14.CLOSE_BRAKET; | ||
var escapeTags = !!options.enableEscapeTags; | ||
var onToken = options.onToken || function () {}; | ||
var RESERVED_CHARS = [closeTag, openTag, _char14.QUOTEMARK, _char14.BACKSLASH, _char14.SPACE, _char14.TAB, _char14.EQ, _char14.N, EM]; | ||
var NOT_CHAR_TOKENS = [// ...(options.enableEscapeTags ? [BACKSLASH] : []), | ||
openTag, _char10.SPACE, _char10.TAB, _char10.N]; | ||
var WHITESPACES = [_char10.SPACE, _char10.TAB]; | ||
var SPECIAL_CHARS = [_char10.EQ, _char10.SPACE, _char10.TAB]; | ||
openTag, _char14.SPACE, _char14.TAB, _char14.N]; | ||
var WHITESPACES = [_char14.SPACE, _char14.TAB]; | ||
var SPECIAL_CHARS = [_char14.EQ, _char14.SPACE, _char14.TAB]; | ||
@@ -89,3 +91,3 @@ var isCharReserved = function isCharReserved(_char) { | ||
var isNewLine = function isNewLine(_char2) { | ||
return _char2 === _char10.N; | ||
return _char2 === _char14.N; | ||
}; | ||
@@ -106,118 +108,271 @@ | ||
var isEscapableChar = function isEscapableChar(_char6) { | ||
return _char6 === openTag || _char6 === closeTag || _char6 === _char10.BACKSLASH; | ||
return _char6 === openTag || _char6 === closeTag || _char6 === _char14.BACKSLASH; | ||
}; | ||
var isEscapeChar = function isEscapeChar(_char7) { | ||
return _char7 === _char10.BACKSLASH; | ||
return _char7 === _char14.BACKSLASH; | ||
}; | ||
var bufferGrabber = (0, _utils.createCharGrabber)(buffer, { | ||
onSkip: function onSkip() { | ||
col++; | ||
} | ||
var onSkip = function onSkip() { | ||
col++; | ||
}; | ||
var unq = function unq(val) { | ||
return (0, _utils.unquote)((0, _utils.trimChar)(val, _char14.QUOTEMARK)); | ||
}; | ||
var chars = (0, _utils.createCharGrabber)(buffer, { | ||
onSkip: onSkip | ||
}); | ||
/** | ||
* Emits newly created token to subscriber | ||
* @param token | ||
* @param {Number} type | ||
* @param {String} value | ||
*/ | ||
var emitToken = function emitToken(token) { | ||
if (options.onToken) { | ||
options.onToken(token); | ||
} | ||
function emitToken(type, value) { | ||
var token = createToken(type, value, row, col); | ||
onToken(token); | ||
tokenIndex += 1; | ||
tokens[tokenIndex] = token; | ||
}; | ||
} | ||
var switchMode = function switchMode(newMode) { | ||
mode = newMode; | ||
}; | ||
function nextTagState(tagChars, isSingleValueTag) { | ||
if (tagMode === TAG_STATE_NAME) { | ||
var currChar = tagChars.getCurr(); | ||
var hasNext = tagChars.hasNext(); | ||
var isWS = isWhiteSpace(currChar); | ||
var isQM = currChar === _char14.QUOTEMARK; | ||
var processWord = function processWord() { | ||
var currChar = bufferGrabber.getCurr(); | ||
var nextChar = bufferGrabber.getNext(); | ||
if (isWS || isQM || !hasNext) { | ||
return TAG_STATE_VALUE; | ||
} | ||
if (currChar === openTag && bufferGrabber.includes(closeTag)) { | ||
return switchMode(STATE_TAG); | ||
var validName = function validName(_char8) { | ||
return !(_char8 === _char14.EQ || isWhiteSpace(_char8) || tagChars.isLast()); | ||
}; | ||
var name = tagChars.grabWhile(validName); | ||
emitToken(_Token.TYPE_TAG, name); | ||
tagChars.skip(); // in cases when we has [url=someval]GET[/url] and we dont need to parse all | ||
if (isSingleValueTag) { | ||
return TAG_STATE_VALUE; | ||
} | ||
var hasEQ = tagChars.includes(_char14.EQ); | ||
return hasEQ ? TAG_STATE_ATTR : TAG_STATE_VALUE; | ||
} | ||
if (isNewLine(currChar)) { | ||
bufferGrabber.skip(); | ||
col = 0; | ||
row++; | ||
return emitToken(createToken(_Token.TYPE_NEW_LINE, currChar, row, col)); | ||
if (tagMode === TAG_STATE_ATTR) { | ||
var validAttrName = function validAttrName(_char9) { | ||
return !(_char9 === _char14.EQ || isWhiteSpace(_char9)); | ||
}; | ||
var _name = tagChars.grabWhile(validAttrName); | ||
var isEnd = tagChars.isLast(); | ||
var isValue = tagChars.getCurr() !== _char14.EQ; | ||
tagChars.skip(); | ||
if (isEnd || isValue) { | ||
emitToken(_Token.TYPE_ATTR_VALUE, unq(_name)); | ||
} else { | ||
emitToken(_Token.TYPE_ATTR_NAME, _name); | ||
} | ||
if (isEnd) { | ||
return TAG_STATE_NAME; | ||
} | ||
if (isValue) { | ||
return TAG_STATE_ATTR; | ||
} | ||
return TAG_STATE_VALUE; | ||
} | ||
if (isWhiteSpace(currChar)) { | ||
return emitToken(createToken(_Token.TYPE_SPACE, bufferGrabber.grabWhile(isWhiteSpace), row, col)); | ||
if (tagMode === TAG_STATE_VALUE) { | ||
var stateSpecial = false; | ||
var validAttrValue = function validAttrValue(_char10) { | ||
// const isEQ = char === EQ; | ||
var isQM = _char10 === _char14.QUOTEMARK; | ||
var prevChar = tagChars.getPrev(); | ||
var nextChar = tagChars.getNext(); | ||
var isPrevSLASH = prevChar === _char14.BACKSLASH; | ||
var isNextEQ = nextChar === _char14.EQ; | ||
var isWS = isWhiteSpace(_char10); // const isPrevWS = isWhiteSpace(prevChar); | ||
var isNextWS = isWhiteSpace(nextChar); | ||
if (stateSpecial && isSpecialChar(_char10)) { | ||
return true; | ||
} | ||
if (isQM && !isPrevSLASH) { | ||
stateSpecial = !stateSpecial; | ||
if (!stateSpecial && !(isNextEQ || isNextWS)) { | ||
return false; | ||
} | ||
} | ||
if (!isSingleValueTag) { | ||
return isWS === false; // return (isEQ || isWS) === false; | ||
} | ||
return true; | ||
}; | ||
var _name2 = tagChars.grabWhile(validAttrValue); | ||
tagChars.skip(); | ||
emitToken(_Token.TYPE_ATTR_VALUE, unq(_name2)); | ||
if (tagChars.isLast()) { | ||
return TAG_STATE_NAME; | ||
} | ||
return TAG_STATE_ATTR; | ||
} | ||
if (escapeTags) { | ||
if (isEscapeChar(currChar) && !isEscapableChar(nextChar)) { | ||
bufferGrabber.skip(); | ||
return emitToken(createToken(_Token.TYPE_WORD, currChar, row, col)); | ||
return TAG_STATE_NAME; | ||
} | ||
function stateTag() { | ||
var currChar = chars.getCurr(); | ||
if (currChar === openTag) { | ||
var nextChar = chars.getNext(); | ||
chars.skip(); // detect case where we have '[My word [tag][/tag]' or we have '[My last line word' | ||
var substr = chars.substrUntilChar(closeTag); | ||
var hasInvalidChars = substr.length === 0 || substr.indexOf(openTag) >= 0; | ||
if (isCharReserved(nextChar) || hasInvalidChars || chars.isLast()) { | ||
emitToken(_Token.TYPE_WORD, currChar); | ||
return STATE_WORD; | ||
} // [myTag ] | ||
var isNoAttrsInTag = substr.indexOf(_char14.EQ) === -1; // [/myTag] | ||
var isClosingTag = substr[0] === _char14.SLASH; | ||
if (isNoAttrsInTag || isClosingTag) { | ||
var name = chars.grabWhile(function (_char11) { | ||
return _char11 !== closeTag; | ||
}); | ||
chars.skip(); // skip closeTag | ||
emitToken(_Token.TYPE_TAG, name); | ||
return STATE_WORD; | ||
} | ||
var _str = bufferGrabber.grabWhile(function (_char8) { | ||
return isCharToken(_char8) && !isEscapeChar(_char8); | ||
}); | ||
return STATE_TAG_ATTRS; | ||
} | ||
return emitToken(createToken(_Token.TYPE_WORD, _str, row, col)); | ||
if (currChar === closeTag) { | ||
chars.skip(); | ||
emitToken(_Token.TYPE_WORD, currChar); | ||
return STATE_WORD; | ||
} | ||
var str = bufferGrabber.grabWhile(function (_char9) { | ||
return isCharToken(_char9); | ||
return STATE_WORD; | ||
} | ||
function stateAttrs() { | ||
var silent = true; | ||
var tagStr = chars.grabWhile(function (_char12) { | ||
return _char12 !== closeTag; | ||
}, silent); | ||
var tagGrabber = (0, _utils.createCharGrabber)(tagStr, { | ||
onSkip: onSkip | ||
}); | ||
return emitToken(createToken(_Token.TYPE_WORD, str, row, col)); | ||
}; | ||
var hasSpace = tagGrabber.includes(_char14.SPACE); | ||
var processTag = function processTag() { | ||
var currChar = bufferGrabber.getCurr(); | ||
var nextChar = bufferGrabber.getNext(); | ||
while (tagGrabber.hasNext()) { | ||
tagMode = nextTagState(tagGrabber, !hasSpace); | ||
} | ||
if (currChar === closeTag) { | ||
bufferGrabber.skip(); // skip closeTag | ||
chars.skip(); // skip closeTag | ||
switchMode(STATE_WORD); | ||
return emitToken(createToken(_Token.TYPE_WORD, currChar, row, col)); | ||
return STATE_WORD; | ||
} | ||
function stateWord() { | ||
if (isNewLine(chars.getCurr())) { | ||
emitToken(_Token.TYPE_NEW_LINE, chars.getCurr()); | ||
chars.skip(); | ||
col = 0; | ||
row++; | ||
return STATE_WORD; | ||
} | ||
if (currChar === openTag) { | ||
bufferGrabber.skip(); // skip openTag | ||
// detect case where we have '[My word [tag][/tag]' or we have '[My last line word' | ||
if (isWhiteSpace(chars.getCurr())) { | ||
emitToken(_Token.TYPE_SPACE, chars.grabWhile(isWhiteSpace)); | ||
return STATE_WORD; | ||
} | ||
var str = bufferGrabber.grabWhile(function (val) { | ||
return val !== closeTag; | ||
}); | ||
var hasInvalidChars = str.length === 0 || str.indexOf(openTag) >= 0; | ||
if (isCharReserved(nextChar) || hasInvalidChars || bufferGrabber.isLast()) { | ||
return emitToken(createToken(_Token.TYPE_WORD, currChar, row, col)); | ||
if (chars.getCurr() === openTag) { | ||
if (chars.includes(closeTag)) { | ||
return STATE_TAG; | ||
} | ||
bufferGrabber.skip(); // skip closeTag | ||
// [myTag ] | ||
emitToken(_Token.TYPE_WORD, chars.getCurr()); | ||
chars.skip(); | ||
return STATE_WORD; | ||
} | ||
var isNoAttrsInTag = str.indexOf(_char10.EQ) === -1; // [/myTag] | ||
if (escapeTags) { | ||
if (isEscapeChar(chars.getCurr())) { | ||
var currChar = chars.getCurr(); | ||
var nextChar = chars.getNext(); | ||
chars.skip(); // skip the \ without emitting anything | ||
var isClosingTag = str[0] === _char10.SLASH; | ||
if (isEscapableChar(nextChar)) { | ||
chars.skip(); // skip past the [, ] or \ as well | ||
if (isNoAttrsInTag || isClosingTag) { | ||
return emitToken(createToken(_Token.TYPE_TAG, str, row, col)); | ||
emitToken(_Token.TYPE_WORD, nextChar); | ||
return STATE_WORD; | ||
} | ||
emitToken(_Token.TYPE_WORD, currChar); | ||
return STATE_WORD; | ||
} | ||
var isChar = function isChar(_char13) { | ||
return isCharToken(_char13) && !isEscapeChar(_char13); | ||
}; | ||
emitToken(_Token.TYPE_WORD, chars.grabWhile(isChar)); | ||
return STATE_WORD; | ||
} | ||
return switchMode(STATE_WORD); | ||
}; | ||
emitToken(_Token.TYPE_WORD, chars.grabWhile(isCharToken)); | ||
return STATE_WORD; | ||
} | ||
var processAttrName = function processAttrName() {}; | ||
function tokenize() { | ||
while (chars.hasNext()) { | ||
switch (stateMode) { | ||
case STATE_TAG: | ||
stateMode = stateTag(); | ||
break; | ||
var processAttrValue = function processAttrValue() {}; | ||
case STATE_TAG_ATTRS: | ||
stateMode = stateAttrs(); | ||
break; | ||
var modeMap = (_modeMap = {}, _modeMap[STATE_WORD] = processWord, _modeMap[STATE_TAG] = processTag, _modeMap[STATE_ATTR_NAME] = processAttrName, _modeMap[STATE_ATTR_VALUE] = processAttrValue, _modeMap); | ||
case STATE_WORD: | ||
stateMode = stateWord(); | ||
break; | ||
var tokenize = function tokenize() { | ||
while (bufferGrabber.hasNext()) { | ||
modeMap[mode](bufferGrabber); | ||
default: | ||
stateMode = STATE_WORD; | ||
break; | ||
} | ||
} | ||
@@ -227,9 +382,9 @@ | ||
return tokens; | ||
}; | ||
} | ||
var isTokenNested = function isTokenNested(token) { | ||
var value = openTag + _char10.SLASH + token.getValue(); // potential bottleneck | ||
function isTokenNested(token) { | ||
var value = openTag + _char14.SLASH + token.getValue(); // potential bottleneck | ||
return buffer.indexOf(value) > -1; | ||
}; | ||
} | ||
@@ -236,0 +391,0 @@ return { |
@@ -8,3 +8,3 @@ "use strict"; | ||
var _pluginHelper = require("@bbob/plugin-helper"); | ||
var _index = require("@bbob/plugin-helper/lib/index"); | ||
@@ -38,3 +38,3 @@ var _lexer = require("./lexer"); | ||
* @private | ||
* @type {ItemList} | ||
* @type {NodeList} | ||
*/ | ||
@@ -46,3 +46,3 @@ | ||
* @private | ||
* @type {ItemList} | ||
* @type {NodeList} | ||
*/ | ||
@@ -54,3 +54,3 @@ | ||
* @private | ||
* @type {ItemList} | ||
* @type {NodeList} | ||
*/ | ||
@@ -62,3 +62,3 @@ | ||
* @private | ||
* @type {ItemList} | ||
* @type {NodeList} | ||
*/ | ||
@@ -141,3 +141,3 @@ | ||
if (Array.isArray(items)) { | ||
if ((0, _pluginHelper.isTagNode)(node)) { | ||
if ((0, _index.isTagNode)(node)) { | ||
if (isAllowedTag(node.tag)) { | ||
@@ -144,0 +144,0 @@ items.push(node.toTagNode()); |
@@ -17,8 +17,14 @@ "use strict"; | ||
var TOKEN_TYPE_WORD = 'word'; | ||
var TOKEN_TYPE_TAG = 'tag'; | ||
var TOKEN_TYPE_ATTR_NAME = 'attr-name'; | ||
var TOKEN_TYPE_ATTR_VALUE = 'attr-value'; | ||
var TOKEN_TYPE_SPACE = 'space'; | ||
var TOKEN_TYPE_NEW_LINE = 'new-line'; | ||
var TOKEN_TYPE_WORD = 1; // 'word'; | ||
var TOKEN_TYPE_TAG = 2; // 'tag'; | ||
var TOKEN_TYPE_ATTR_NAME = 3; // 'attr-name'; | ||
var TOKEN_TYPE_ATTR_VALUE = 4; // 'attr-value'; | ||
var TOKEN_TYPE_SPACE = 5; // 'space'; | ||
var TOKEN_TYPE_NEW_LINE = 6; // 'new-line'; | ||
/** | ||
@@ -127,3 +133,3 @@ * @param {Token} token | ||
function Token(type, value, line, row) { | ||
this[TOKEN_TYPE_ID] = String(type); | ||
this[TOKEN_TYPE_ID] = Number(type); | ||
this[TOKEN_VALUE_ID] = String(value); | ||
@@ -137,3 +143,4 @@ this[TOKEN_LINE_ID] = Number(line); | ||
_proto.isEmpty = function isEmpty() { | ||
return !!this[TOKEN_TYPE_ID]; | ||
// eslint-disable-next-line no-restricted-globals | ||
return isNaN(this[TOKEN_TYPE_ID]); | ||
}; | ||
@@ -140,0 +147,0 @@ |
256
lib/utils.js
@@ -8,105 +8,136 @@ "use strict"; | ||
/** | ||
* @typedef {Object} CharGrabber | ||
* @property {Function} skip | ||
* @property {Function} hasNext | ||
* @property {Function} isLast | ||
* @property {Function} grabWhile | ||
*/ | ||
/** | ||
* Creates a grabber wrapper for source string, that helps to iterate over string char by char | ||
* @param {String} source | ||
* @param {Object} options | ||
* @param {Function} options.onSkip | ||
* @returns | ||
*/ | ||
var createCharGrabber = function createCharGrabber(source, options) { | ||
// let idx = 0; | ||
function CharGrabber(source, options) { | ||
var cursor = { | ||
pos: 0, | ||
length: source.length | ||
len: source.length | ||
}; | ||
var skip = function skip() { | ||
cursor.pos += 1; | ||
var substrUntilChar = function substrUntilChar(_char) { | ||
var pos = cursor.pos; | ||
var idx = source.indexOf(_char, pos); | ||
return idx >= 0 ? source.substr(pos, idx - pos) : ''; | ||
}; | ||
if (options && options.onSkip) { | ||
options.onSkip(); | ||
} | ||
var includes = function includes(val) { | ||
return source.indexOf(val, cursor.pos) >= 0; | ||
}; | ||
var hasNext = function hasNext() { | ||
return cursor.length > cursor.pos; | ||
return cursor.len > cursor.pos; | ||
}; | ||
var getRest = function getRest() { | ||
var isLast = function isLast() { | ||
return cursor.pos === cursor.len; | ||
}; | ||
var skip = function skip(num, silent) { | ||
if (num === void 0) { | ||
num = 1; | ||
} | ||
cursor.pos += num; | ||
if (options && options.onSkip && !silent) { | ||
options.onSkip(); | ||
} | ||
}; | ||
var rest = function rest() { | ||
return source.substr(cursor.pos); | ||
}; | ||
var getCurr = function getCurr() { | ||
var curr = function curr() { | ||
return source[cursor.pos]; | ||
}; | ||
return { | ||
skip: skip, | ||
hasNext: hasNext, | ||
isLast: function isLast() { | ||
return cursor.pos === cursor.length; | ||
}, | ||
var prev = function prev() { | ||
var prevPos = cursor.pos - 1; | ||
return typeof source[prevPos] !== 'undefined' ? source[prevPos] : null; | ||
}; | ||
/** | ||
* @param {Function} cond | ||
* @returns {string} | ||
*/ | ||
grabWhile: function grabWhile(cond) { | ||
var start = 0; | ||
var next = function next() { | ||
var nextPos = cursor.pos + 1; | ||
return nextPos <= source.length - 1 ? source[nextPos] : null; | ||
}; | ||
if (hasNext()) { | ||
start = cursor.pos; | ||
var grabWhile = function grabWhile(cond, silent) { | ||
var start = 0; | ||
while (hasNext() && cond(getCurr())) { | ||
skip(); | ||
} | ||
if (hasNext()) { | ||
start = cursor.pos; | ||
while (hasNext() && cond(curr())) { | ||
skip(1, silent); | ||
} | ||
} | ||
return source.substr(start, cursor.pos - start); | ||
}, | ||
getNext: function getNext() { | ||
var nextPos = cursor.pos + 1; | ||
return source.substr(start, cursor.pos - start); | ||
}; | ||
/** | ||
* @type {skip} | ||
*/ | ||
if (nextPos <= source.length - 1) { | ||
return source[nextPos]; | ||
} | ||
return null; | ||
}, | ||
getPrev: function getPrev() { | ||
var prevPos = cursor.pos - 1; | ||
this.skip = skip; | ||
/** | ||
* @returns {Boolean} | ||
*/ | ||
if (typeof source[prevPos] !== 'undefined') { | ||
return source[prevPos]; | ||
} | ||
this.hasNext = hasNext; | ||
/** | ||
* @returns {String} | ||
*/ | ||
return null; | ||
}, | ||
getCurr: getCurr, | ||
getRest: getRest, | ||
this.getCurr = curr; | ||
/** | ||
* @returns {String} | ||
*/ | ||
/** | ||
* Grabs rest of string until it find a char | ||
* @param {String} char | ||
* @return {String} | ||
*/ | ||
substrUntilChar: function substrUntilChar(_char) { | ||
var restStr = getRest(); | ||
var indexOfChar = restStr.indexOf(_char); | ||
this.getRest = rest; | ||
/** | ||
* @returns {String} | ||
*/ | ||
if (indexOfChar >= 0) { | ||
return restStr.substr(0, indexOfChar); | ||
} | ||
this.getNext = next; | ||
/** | ||
* @returns {String} | ||
*/ | ||
return ''; | ||
} | ||
}; | ||
this.getPrev = prev; | ||
/** | ||
* @returns {Boolean} | ||
*/ | ||
this.isLast = isLast; | ||
/** | ||
* @returns {Boolean} | ||
*/ | ||
this.includes = includes; | ||
/** | ||
* @param {Function} cond | ||
* @param {Boolean} silent | ||
* @return {String} | ||
*/ | ||
this.grabWhile = grabWhile; | ||
/** | ||
* Grabs rest of string until it find a char | ||
* @param {String} char | ||
* @return {String} | ||
*/ | ||
this.substrUntilChar = substrUntilChar; | ||
} | ||
/** | ||
* Creates a grabber wrapper for source string, that helps to iterate over string char by char | ||
* @param {String} source | ||
* @param {Object} options | ||
* @param {Function} options.onSkip | ||
* @return CharGrabber | ||
*/ | ||
var createCharGrabber = function createCharGrabber(source, options) { | ||
return new CharGrabber(source, options); | ||
}; | ||
@@ -150,21 +181,6 @@ /** | ||
}; | ||
/** | ||
* @typedef {Object} ItemList | ||
* @type {Object} | ||
* @property {getLastCb} getLast | ||
* @property {flushLastCb} flushLast | ||
* @property {pushCb} push | ||
* @property {toArrayCb} toArray | ||
*/ | ||
/** | ||
* | ||
* @param values | ||
* @return {ItemList} | ||
*/ | ||
exports.unquote = unquote; | ||
var createList = function createList(values) { | ||
function NodeList(values) { | ||
if (values === void 0) { | ||
@@ -175,51 +191,39 @@ values = []; | ||
var nodes = values; | ||
/** | ||
* @callback getLastCb | ||
*/ | ||
var getLast = function getLast() { | ||
if (Array.isArray(nodes) && nodes.length > 0 && typeof nodes[nodes.length - 1] !== 'undefined') { | ||
return nodes[nodes.length - 1]; | ||
} | ||
return null; | ||
return Array.isArray(nodes) && nodes.length > 0 && typeof nodes[nodes.length - 1] !== 'undefined' ? nodes[nodes.length - 1] : null; | ||
}; | ||
/** | ||
* @callback flushLastCb | ||
* @return {*} | ||
*/ | ||
var flushLast = function flushLast() { | ||
if (nodes.length) { | ||
return nodes.pop(); | ||
} | ||
return false; | ||
return nodes.length ? nodes.pop() : false; | ||
}; | ||
/** | ||
* @callback pushCb | ||
* @param value | ||
*/ | ||
var push = function push(value) { | ||
return nodes.push(value); | ||
}; | ||
/** | ||
* @callback toArrayCb | ||
* @return {Array} | ||
*/ | ||
var toArray = function toArray() { | ||
return nodes; | ||
}; | ||
return { | ||
getLast: getLast, | ||
flushLast: flushLast, | ||
push: push, | ||
toArray: function toArray() { | ||
return nodes; | ||
} | ||
}; | ||
this.push = push; | ||
this.toArray = toArray; | ||
this.getLast = getLast; | ||
this.flushLast = flushLast; | ||
} | ||
/** | ||
* | ||
* @param values | ||
* @return {NodeList} | ||
*/ | ||
var createList = function createList(values) { | ||
if (values === void 0) { | ||
values = []; | ||
} | ||
return new NodeList(values); | ||
}; | ||
exports.createList = createList; |
{ | ||
"name": "@bbob/parser", | ||
"version": "2.5.8", | ||
"version": "2.6.0", | ||
"description": "Just parses BBcode to AST array. Part of @bbob bbcode parser", | ||
@@ -16,3 +16,3 @@ "keywords": [ | ||
"dependencies": { | ||
"@bbob/plugin-helper": "^2.5.8" | ||
"@bbob/plugin-helper": "^2.6.0" | ||
}, | ||
@@ -39,3 +39,3 @@ "main": "lib/index.js", | ||
"build": "npm run build:commonjs && npm run build:es && npm run build:umd", | ||
"test": "../../node_modules/.bin/jest --", | ||
"test": "../../node_modules/.bin/jest", | ||
"cover": "../../node_modules/.bin/jest --coverage", | ||
@@ -67,3 +67,3 @@ "lint": "../../node_modules/.bin/eslint .", | ||
], | ||
"gitHead": "2eb1fbcccf065a95df2c1a28020119582d4a64d1" | ||
"gitHead": "99f503df0cc35d33c9e57d49b6581631d49ab6c3" | ||
} |
329
src/lexer.js
@@ -24,3 +24,3 @@ /* eslint-disable no-plusplus,no-param-reassign */ | ||
* Creates a Token entity class | ||
* @param {String} type | ||
* @param {Number} type | ||
* @param {String} value | ||
@@ -48,2 +48,10 @@ * @param {Number} r line number | ||
function createLexer(buffer, options = {}) { | ||
const STATE_WORD = 0; | ||
const STATE_TAG = 1; | ||
const STATE_TAG_ATTRS = 2; | ||
const TAG_STATE_NAME = 0; | ||
const TAG_STATE_ATTR = 1; | ||
const TAG_STATE_VALUE = 2; | ||
let row = 0; | ||
@@ -53,6 +61,10 @@ let col = 0; | ||
let tokenIndex = -1; | ||
let stateMode = STATE_WORD; | ||
let tagMode = TAG_STATE_NAME; | ||
const tokens = new Array(Math.floor(buffer.length)); | ||
const openTag = options.openTag || OPEN_BRAKET; | ||
const closeTag = options.closeTag || CLOSE_BRAKET; | ||
const escapeTags = options.enableEscapeTags; | ||
const escapeTags = !!options.enableEscapeTags; | ||
const onToken = options.onToken || (() => { | ||
}); | ||
@@ -68,2 +80,3 @@ const RESERVED_CHARS = [closeTag, openTag, QUOTEMARK, BACKSLASH, SPACE, TAB, EQ, N, EM]; | ||
const isCharReserved = (char) => (RESERVED_CHARS.indexOf(char) >= 0); | ||
const isNewLine = (char) => char === N; | ||
const isWhiteSpace = (char) => (WHITESPACES.indexOf(char) >= 0); | ||
@@ -74,154 +87,244 @@ const isCharToken = (char) => (NOT_CHAR_TOKENS.indexOf(char) === -1); | ||
const isEscapeChar = (char) => char === BACKSLASH; | ||
const onSkip = () => { | ||
col++; | ||
}; | ||
const unq = (val) => unquote(trimChar(val, QUOTEMARK)); | ||
const chars = createCharGrabber(buffer, { onSkip }); | ||
/** | ||
* Emits newly created token to subscriber | ||
* @param token | ||
* @param {Number} type | ||
* @param {String} value | ||
*/ | ||
const emitToken = (token) => { | ||
if (options.onToken) { | ||
options.onToken(token); | ||
} | ||
function emitToken(type, value) { | ||
const token = createToken(type, value, row, col); | ||
onToken(token); | ||
tokenIndex += 1; | ||
tokens[tokenIndex] = token; | ||
}; | ||
} | ||
/** | ||
* Parses params inside [myTag---params goes here---]content[/myTag] | ||
* @param str | ||
* @returns {{tag: *, attrs: Array}} | ||
*/ | ||
const parseAttrs = (str) => { | ||
let tagName = null; | ||
let skipSpecialChars = false; | ||
function nextTagState(tagChars, isSingleValueTag) { | ||
if (tagMode === TAG_STATE_ATTR) { | ||
const validAttrName = (char) => !(char === EQ || isWhiteSpace(char)); | ||
const name = tagChars.grabWhile(validAttrName); | ||
const isEnd = tagChars.isLast(); | ||
const isValue = tagChars.getCurr() !== EQ; | ||
const attrTokens = []; | ||
const attrCharGrabber = createCharGrabber(str); | ||
tagChars.skip(); | ||
const validAttr = (char) => { | ||
const isEQ = char === EQ; | ||
const isWS = isWhiteSpace(char); | ||
const prevChar = attrCharGrabber.getPrev(); | ||
const nextChar = attrCharGrabber.getNext(); | ||
const isPrevSLASH = prevChar === BACKSLASH; | ||
const isTagNameEmpty = tagName === null; | ||
if (isEnd || isValue) { | ||
emitToken(TYPE_ATTR_VALUE, unq(name)); | ||
} else { | ||
emitToken(TYPE_ATTR_NAME, name); | ||
} | ||
if (isTagNameEmpty) { | ||
return (isEQ || isWS || attrCharGrabber.isLast()) === false; | ||
if (isEnd) { | ||
return TAG_STATE_NAME; | ||
} | ||
if (skipSpecialChars && isSpecialChar(char)) { | ||
return true; | ||
if (isValue) { | ||
return TAG_STATE_ATTR; | ||
} | ||
if (char === QUOTEMARK && !isPrevSLASH) { | ||
skipSpecialChars = !skipSpecialChars; | ||
return TAG_STATE_VALUE; | ||
} | ||
if (tagMode === TAG_STATE_VALUE) { | ||
let stateSpecial = false; | ||
if (!skipSpecialChars && !(nextChar === EQ || isWhiteSpace(nextChar))) { | ||
return false; | ||
const validAttrValue = (char) => { | ||
// const isEQ = char === EQ; | ||
const isQM = char === QUOTEMARK; | ||
const prevChar = tagChars.getPrev(); | ||
const nextChar = tagChars.getNext(); | ||
const isPrevSLASH = prevChar === BACKSLASH; | ||
const isNextEQ = nextChar === EQ; | ||
const isWS = isWhiteSpace(char); | ||
// const isPrevWS = isWhiteSpace(prevChar); | ||
const isNextWS = isWhiteSpace(nextChar); | ||
if (stateSpecial && isSpecialChar(char)) { | ||
return true; | ||
} | ||
if (isQM && !isPrevSLASH) { | ||
stateSpecial = !stateSpecial; | ||
if (!stateSpecial && !(isNextEQ || isNextWS)) { | ||
return false; | ||
} | ||
} | ||
if (!isSingleValueTag) { | ||
return isWS === false; | ||
// return (isEQ || isWS) === false; | ||
} | ||
return true; | ||
}; | ||
const name = tagChars.grabWhile(validAttrValue); | ||
tagChars.skip(); | ||
emitToken(TYPE_ATTR_VALUE, unq(name)); | ||
if (tagChars.isLast()) { | ||
return TAG_STATE_NAME; | ||
} | ||
return (isEQ || isWS) === false; | ||
}; | ||
return TAG_STATE_ATTR; | ||
} | ||
const nextAttr = () => { | ||
const attrStr = attrCharGrabber.grabWhile(validAttr); | ||
const currChar = attrCharGrabber.getCurr(); | ||
const validName = (char) => !(char === EQ || isWhiteSpace(char) || tagChars.isLast()); | ||
const name = tagChars.grabWhile(validName); | ||
// first string before space is a tag name [tagName params...] | ||
if (tagName === null) { | ||
tagName = attrStr; | ||
} else if (isWhiteSpace(currChar) || currChar === QUOTEMARK || !attrCharGrabber.hasNext()) { | ||
const escaped = unquote(trimChar(attrStr, QUOTEMARK)); | ||
attrTokens.push(createToken(TYPE_ATTR_VALUE, escaped, row, col)); | ||
} else { | ||
attrTokens.push(createToken(TYPE_ATTR_NAME, attrStr, row, col)); | ||
emitToken(TYPE_TAG, name); | ||
tagChars.skip(); | ||
// in cases when we has [url=someval]GET[/url] and we dont need to parse all | ||
if (isSingleValueTag) { | ||
return TAG_STATE_VALUE; | ||
} | ||
const hasEQ = tagChars.includes(EQ); | ||
return hasEQ ? TAG_STATE_ATTR : TAG_STATE_VALUE; | ||
} | ||
function stateTag() { | ||
const currChar = chars.getCurr(); | ||
if (currChar === openTag) { | ||
const nextChar = chars.getNext(); | ||
chars.skip(); | ||
// detect case where we have '[My word [tag][/tag]' or we have '[My last line word' | ||
const substr = chars.substrUntilChar(closeTag); | ||
const hasInvalidChars = substr.length === 0 || substr.indexOf(openTag) >= 0; | ||
if (isCharReserved(nextChar) || hasInvalidChars || chars.isLast()) { | ||
emitToken(TYPE_WORD, currChar); | ||
return STATE_WORD; | ||
} | ||
attrCharGrabber.skip(); | ||
}; | ||
// [myTag ] | ||
const isNoAttrsInTag = substr.indexOf(EQ) === -1; | ||
// [/myTag] | ||
const isClosingTag = substr[0] === SLASH; | ||
while (attrCharGrabber.hasNext()) { | ||
nextAttr(); | ||
if (isNoAttrsInTag || isClosingTag) { | ||
const name = chars.grabWhile((char) => char !== closeTag); | ||
chars.skip(); // skip closeTag | ||
emitToken(TYPE_TAG, name); | ||
return STATE_WORD; | ||
} | ||
return STATE_TAG_ATTRS; | ||
} | ||
return { tag: tagName, attrs: attrTokens }; | ||
}; | ||
return STATE_WORD; | ||
} | ||
const bufferGrabber = createCharGrabber(buffer, { | ||
onSkip: () => { | ||
col++; | ||
}, | ||
}); | ||
function stateAttrs() { | ||
const silent = true; | ||
const tagStr = chars.grabWhile((char) => char !== closeTag, silent); | ||
const tagGrabber = createCharGrabber(tagStr, { onSkip }); | ||
const hasSpace = tagGrabber.includes(SPACE); | ||
const next = () => { | ||
const currChar = bufferGrabber.getCurr(); | ||
const nextChar = bufferGrabber.getNext(); | ||
while (tagGrabber.hasNext()) { | ||
tagMode = nextTagState(tagGrabber, !hasSpace); | ||
} | ||
if (currChar === N) { | ||
bufferGrabber.skip(); | ||
chars.skip(); // skip closeTag | ||
return STATE_WORD; | ||
} | ||
function stateWord() { | ||
if (isNewLine(chars.getCurr())) { | ||
emitToken(TYPE_NEW_LINE, chars.getCurr()); | ||
chars.skip(); | ||
col = 0; | ||
row++; | ||
emitToken(createToken(TYPE_NEW_LINE, currChar, row, col)); | ||
} else if (isWhiteSpace(currChar)) { | ||
const str = bufferGrabber.grabWhile(isWhiteSpace); | ||
emitToken(createToken(TYPE_SPACE, str, row, col)); | ||
} else if (escapeTags && isEscapeChar(currChar) && isEscapableChar(nextChar)) { | ||
bufferGrabber.skip(); // skip the \ without emitting anything | ||
bufferGrabber.skip(); // skip past the [, ] or \ as well | ||
emitToken(createToken(TYPE_WORD, nextChar, row, col)); | ||
} else if (currChar === openTag) { | ||
bufferGrabber.skip(); // skip openTag | ||
return STATE_WORD; | ||
} | ||
// detect case where we have '[My word [tag][/tag]' or we have '[My last line word' | ||
const substr = bufferGrabber.substrUntilChar(closeTag); | ||
const hasInvalidChars = substr.length === 0 || substr.indexOf(openTag) >= 0; | ||
if (isWhiteSpace(chars.getCurr())) { | ||
emitToken(TYPE_SPACE, chars.grabWhile(isWhiteSpace)); | ||
if (isCharReserved(nextChar) || hasInvalidChars || bufferGrabber.isLast()) { | ||
emitToken(createToken(TYPE_WORD, currChar, row, col)); | ||
} else { | ||
const str = bufferGrabber.grabWhile((val) => val !== closeTag); | ||
return STATE_WORD; | ||
} | ||
bufferGrabber.skip(); // skip closeTag | ||
// [myTag ] | ||
const isNoAttrsInTag = str.indexOf(EQ) === -1; | ||
// [/myTag] | ||
const isClosingTag = str[0] === SLASH; | ||
if (chars.getCurr() === openTag) { | ||
if (chars.includes(closeTag)) { | ||
return STATE_TAG; | ||
} | ||
if (isNoAttrsInTag || isClosingTag) { | ||
emitToken(createToken(TYPE_TAG, str, row, col)); | ||
} else { | ||
const parsed = parseAttrs(str); | ||
emitToken(TYPE_WORD, chars.getCurr()); | ||
emitToken(createToken(TYPE_TAG, parsed.tag, row, col)); | ||
chars.skip(); | ||
parsed.attrs.map(emitToken); | ||
return STATE_WORD; | ||
} | ||
if (escapeTags) { | ||
if (isEscapeChar(chars.getCurr())) { | ||
const currChar = chars.getCurr(); | ||
const nextChar = chars.getNext(); | ||
chars.skip(); // skip the \ without emitting anything | ||
if (isEscapableChar(nextChar)) { | ||
chars.skip(); // skip past the [, ] or \ as well | ||
emitToken(TYPE_WORD, nextChar); | ||
return STATE_WORD; | ||
} | ||
emitToken(TYPE_WORD, currChar); | ||
return STATE_WORD; | ||
} | ||
} else if (currChar === closeTag) { | ||
bufferGrabber.skip(); // skip closeTag | ||
emitToken(createToken(TYPE_WORD, currChar, row, col)); | ||
} else if (isCharToken(currChar)) { | ||
if (escapeTags && isEscapeChar(currChar) && !isEscapableChar(nextChar)) { | ||
bufferGrabber.skip(); | ||
emitToken(createToken(TYPE_WORD, currChar, row, col)); | ||
} else { | ||
const str = bufferGrabber.grabWhile((char) => { | ||
if (escapeTags) { | ||
return isCharToken(char) && !isEscapeChar(char); | ||
} | ||
return isCharToken(char); | ||
}); | ||
const isChar = (char) => isCharToken(char) && !isEscapeChar(char); | ||
emitToken(createToken(TYPE_WORD, str, row, col)); | ||
} | ||
emitToken(TYPE_WORD, chars.grabWhile(isChar)); | ||
return STATE_WORD; | ||
} | ||
}; | ||
const tokenize = () => { | ||
while (bufferGrabber.hasNext()) { | ||
next(); | ||
emitToken(TYPE_WORD, chars.grabWhile(isCharToken)); | ||
return STATE_WORD; | ||
} | ||
function tokenize() { | ||
while (chars.hasNext()) { | ||
switch (stateMode) { | ||
case STATE_TAG: | ||
stateMode = stateTag(); | ||
break; | ||
case STATE_TAG_ATTRS: | ||
stateMode = stateAttrs(); | ||
break; | ||
case STATE_WORD: | ||
stateMode = stateWord(); | ||
break; | ||
default: | ||
stateMode = STATE_WORD; | ||
break; | ||
} | ||
} | ||
@@ -232,9 +335,9 @@ | ||
return tokens; | ||
}; | ||
} | ||
const isTokenNested = (token) => { | ||
function isTokenNested(token) { | ||
const value = openTag + SLASH + token.getValue(); | ||
// potential bottleneck | ||
return buffer.indexOf(value) > -1; | ||
}; | ||
} | ||
@@ -241,0 +344,0 @@ return { |
import TagNode from '@bbob/plugin-helper/lib/TagNode'; | ||
import { isTagNode } from '@bbob/plugin-helper'; | ||
import { isTagNode } from '@bbob/plugin-helper/lib/index'; | ||
import { createLexer } from './lexer'; | ||
@@ -25,3 +25,3 @@ import { createList } from './utils'; | ||
* @private | ||
* @type {ItemList} | ||
* @type {NodeList} | ||
*/ | ||
@@ -32,3 +32,3 @@ const nodes = createList(); | ||
* @private | ||
* @type {ItemList} | ||
* @type {NodeList} | ||
*/ | ||
@@ -39,3 +39,3 @@ const nestedNodes = createList(); | ||
* @private | ||
* @type {ItemList} | ||
* @type {NodeList} | ||
*/ | ||
@@ -46,3 +46,3 @@ const tagNodes = createList(); | ||
* @private | ||
* @type {ItemList} | ||
* @type {NodeList} | ||
*/ | ||
@@ -49,0 +49,0 @@ const tagNodesAttrName = createList(); |
@@ -13,8 +13,8 @@ import { | ||
const TOKEN_TYPE_WORD = 'word'; | ||
const TOKEN_TYPE_TAG = 'tag'; | ||
const TOKEN_TYPE_ATTR_NAME = 'attr-name'; | ||
const TOKEN_TYPE_ATTR_VALUE = 'attr-value'; | ||
const TOKEN_TYPE_SPACE = 'space'; | ||
const TOKEN_TYPE_NEW_LINE = 'new-line'; | ||
const TOKEN_TYPE_WORD = 1; // 'word'; | ||
const TOKEN_TYPE_TAG = 2; // 'tag'; | ||
const TOKEN_TYPE_ATTR_NAME = 3; // 'attr-name'; | ||
const TOKEN_TYPE_ATTR_VALUE = 4; // 'attr-value'; | ||
const TOKEN_TYPE_SPACE = 5; // 'space'; | ||
const TOKEN_TYPE_NEW_LINE = 6; // 'new-line'; | ||
@@ -109,3 +109,3 @@ /** | ||
constructor(type, value, line, row) { | ||
this[TOKEN_TYPE_ID] = String(type); | ||
this[TOKEN_TYPE_ID] = Number(type); | ||
this[TOKEN_VALUE_ID] = String(value); | ||
@@ -117,3 +117,4 @@ this[TOKEN_LINE_ID] = Number(line); | ||
isEmpty() { | ||
return !!this[TOKEN_TYPE_ID]; | ||
// eslint-disable-next-line no-restricted-globals | ||
return isNaN(this[TOKEN_TYPE_ID]); | ||
} | ||
@@ -120,0 +121,0 @@ |
228
src/utils.js
@@ -6,93 +6,105 @@ import { | ||
/** | ||
* @typedef {Object} CharGrabber | ||
* @property {Function} skip | ||
* @property {Function} hasNext | ||
* @property {Function} isLast | ||
* @property {Function} grabWhile | ||
*/ | ||
/** | ||
* Creates a grabber wrapper for source string, that helps to iterate over string char by char | ||
* @param {String} source | ||
* @param {Object} options | ||
* @param {Function} options.onSkip | ||
* @returns | ||
*/ | ||
export const createCharGrabber = (source, options) => { | ||
// let idx = 0; | ||
function CharGrabber(source, options) { | ||
const cursor = { | ||
pos: 0, | ||
length: source.length, | ||
len: source.length, | ||
}; | ||
const skip = () => { | ||
cursor.pos += 1; | ||
const substrUntilChar = (char) => { | ||
const { pos } = cursor; | ||
const idx = source.indexOf(char, pos); | ||
if (options && options.onSkip) { | ||
return idx >= 0 ? source.substr(pos, idx - pos) : ''; | ||
}; | ||
const includes = (val) => source.indexOf(val, cursor.pos) >= 0; | ||
const hasNext = () => cursor.len > cursor.pos; | ||
const isLast = () => cursor.pos === cursor.len; | ||
const skip = (num = 1, silent) => { | ||
cursor.pos += num; | ||
if (options && options.onSkip && !silent) { | ||
options.onSkip(); | ||
} | ||
}; | ||
const hasNext = () => cursor.length > cursor.pos; | ||
const getRest = () => source.substr(cursor.pos); | ||
const getCurr = () => source[cursor.pos]; | ||
const rest = () => source.substr(cursor.pos); | ||
const curr = () => source[cursor.pos]; | ||
const prev = () => { | ||
const prevPos = cursor.pos - 1; | ||
return { | ||
skip, | ||
hasNext, | ||
isLast: () => (cursor.pos === cursor.length), | ||
/** | ||
* @param {Function} cond | ||
* @returns {string} | ||
*/ | ||
grabWhile: (cond) => { | ||
let start = 0; | ||
return typeof source[prevPos] !== 'undefined' ? source[prevPos] : null; | ||
}; | ||
const next = () => { | ||
const nextPos = cursor.pos + 1; | ||
if (hasNext()) { | ||
start = cursor.pos; | ||
return nextPos <= (source.length - 1) ? source[nextPos] : null; | ||
}; | ||
const grabWhile = (cond, silent) => { | ||
let start = 0; | ||
while (hasNext() && cond(getCurr())) { | ||
skip(); | ||
} | ||
} | ||
if (hasNext()) { | ||
start = cursor.pos; | ||
return source.substr(start, cursor.pos - start); | ||
}, | ||
getNext: () => { | ||
const nextPos = cursor.pos + 1; | ||
if (nextPos <= (source.length - 1)) { | ||
return source[nextPos]; | ||
while (hasNext() && cond(curr())) { | ||
skip(1, silent); | ||
} | ||
return null; | ||
}, | ||
getPrev: () => { | ||
const prevPos = cursor.pos - 1; | ||
} | ||
if (typeof source[prevPos] !== 'undefined') { | ||
return source[prevPos]; | ||
} | ||
return null; | ||
}, | ||
getCurr, | ||
getRest, | ||
/** | ||
* Grabs rest of string until it find a char | ||
* @param {String} char | ||
* @return {String} | ||
*/ | ||
substrUntilChar: (char) => { | ||
const restStr = getRest(); | ||
const indexOfChar = restStr.indexOf(char); | ||
if (indexOfChar >= 0) { | ||
return restStr.substr(0, indexOfChar); | ||
} | ||
return ''; | ||
}, | ||
return source.substr(start, cursor.pos - start); | ||
}; | ||
}; | ||
/** | ||
* @type {skip} | ||
*/ | ||
this.skip = skip; | ||
/** | ||
* @returns {Boolean} | ||
*/ | ||
this.hasNext = hasNext; | ||
/** | ||
* @returns {String} | ||
*/ | ||
this.getCurr = curr; | ||
/** | ||
* @returns {String} | ||
*/ | ||
this.getRest = rest; | ||
/** | ||
* @returns {String} | ||
*/ | ||
this.getNext = next; | ||
/** | ||
* @returns {String} | ||
*/ | ||
this.getPrev = prev; | ||
/** | ||
* @returns {Boolean} | ||
*/ | ||
this.isLast = isLast; | ||
/** | ||
* @returns {Boolean} | ||
*/ | ||
this.includes = includes; | ||
/** | ||
* @param {Function} cond | ||
* @param {Boolean} silent | ||
* @return {String} | ||
*/ | ||
this.grabWhile = grabWhile; | ||
/** | ||
* Grabs rest of string until it find a char | ||
* @param {String} char | ||
* @return {String} | ||
*/ | ||
this.substrUntilChar = substrUntilChar; | ||
} | ||
/** | ||
* Creates a grabber wrapper for source string, that helps to iterate over string char by char | ||
* @param {String} source | ||
* @param {Object} options | ||
* @param {Function} options.onSkip | ||
* @return CharGrabber | ||
*/ | ||
export const createCharGrabber = (source, options) => new CharGrabber(source, options); | ||
/** | ||
* Trims string from start and end by char | ||
@@ -126,56 +138,24 @@ * @example | ||
/** | ||
* @typedef {Object} ItemList | ||
* @type {Object} | ||
* @property {getLastCb} getLast | ||
* @property {flushLastCb} flushLast | ||
* @property {pushCb} push | ||
* @property {toArrayCb} toArray | ||
*/ | ||
function NodeList(values = []) { | ||
const nodes = values; | ||
const getLast = () => ( | ||
Array.isArray(nodes) && nodes.length > 0 && typeof nodes[nodes.length - 1] !== 'undefined' | ||
? nodes[nodes.length - 1] | ||
: null); | ||
const flushLast = () => (nodes.length ? nodes.pop() : false); | ||
const push = (value) => nodes.push(value); | ||
const toArray = () => nodes; | ||
this.push = push; | ||
this.toArray = toArray; | ||
this.getLast = getLast; | ||
this.flushLast = flushLast; | ||
} | ||
/** | ||
* | ||
* @param values | ||
* @return {ItemList} | ||
* @return {NodeList} | ||
*/ | ||
export const createList = (values = []) => { | ||
const nodes = values; | ||
/** | ||
* @callback getLastCb | ||
*/ | ||
const getLast = () => { | ||
if (Array.isArray(nodes) && nodes.length > 0 && typeof nodes[nodes.length - 1] !== 'undefined') { | ||
return nodes[nodes.length - 1]; | ||
} | ||
return null; | ||
}; | ||
/** | ||
* @callback flushLastCb | ||
* @return {*} | ||
*/ | ||
const flushLast = () => { | ||
if (nodes.length) { | ||
return nodes.pop(); | ||
} | ||
return false; | ||
}; | ||
/** | ||
* @callback pushCb | ||
* @param value | ||
*/ | ||
const push = (value) => nodes.push(value); | ||
/** | ||
* @callback toArrayCb | ||
* @return {Array} | ||
*/ | ||
return { | ||
getLast, | ||
flushLast, | ||
push, | ||
toArray: () => nodes, | ||
}; | ||
}; | ||
export const createList = (values = []) => new NodeList(values); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
210419
27
4740
Updated@bbob/plugin-helper@^2.6.0