mirror of
https://github.com/tenrok/BBob.git
synced 2026-06-08 17:22:26 +03:00
refactor(*): convert to babel and generation to lib, es, dist folders (#2)
* refactor(*): convert to babel and generation to lib, es, dist * chore(*): remove generated files * fix(*): lint run command
This commit is contained in:
committed by
GitHub
parent
d22a2895a4
commit
32a7fb51da
@@ -0,0 +1,122 @@
|
||||
import {
|
||||
OPEN_BRAKET,
|
||||
CLOSE_BRAKET,
|
||||
SLASH,
|
||||
} from '@bbob/plugin-helper/lib/char';
|
||||
|
||||
// type, value, line, row,
|
||||
const TOKEN_TYPE_ID = 'type'; // 0;
|
||||
const TOKEN_VALUE_ID = 'value'; // 1;
|
||||
const TOKEN_COLUMN_ID = 'row'; // 2;
|
||||
const TOKEN_LINE_ID = 'line'; // 3;
|
||||
|
||||
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 getTokenValue = token => token[TOKEN_VALUE_ID];
|
||||
const getTokenLine = token => token[TOKEN_LINE_ID];
|
||||
const getTokenColumn = token => token[TOKEN_COLUMN_ID];
|
||||
|
||||
const isTextToken = token =>
|
||||
token[TOKEN_TYPE_ID] === TOKEN_TYPE_SPACE ||
|
||||
token[TOKEN_TYPE_ID] === TOKEN_TYPE_NEW_LINE ||
|
||||
token[TOKEN_TYPE_ID] === TOKEN_TYPE_WORD;
|
||||
|
||||
const isTagToken = token => token[TOKEN_TYPE_ID] === TOKEN_TYPE_TAG;
|
||||
const isTagEnd = token => getTokenValue(token).charCodeAt(0) === SLASH.charCodeAt(0);
|
||||
const isTagStart = token => !isTagEnd(token);
|
||||
const isAttrNameToken = token => token[TOKEN_TYPE_ID] === TOKEN_TYPE_ATTR_NAME;
|
||||
const isAttrValueToken = token => token[TOKEN_TYPE_ID] === TOKEN_TYPE_ATTR_VALUE;
|
||||
|
||||
const getTagName = (token) => {
|
||||
const value = getTokenValue(token);
|
||||
|
||||
return isTagEnd(token) ? value.slice(1) : value;
|
||||
};
|
||||
|
||||
const convertTagToText = (token) => {
|
||||
let text = OPEN_BRAKET;
|
||||
|
||||
if (isTagEnd(token)) {
|
||||
text += SLASH;
|
||||
}
|
||||
|
||||
text += getTokenValue(token);
|
||||
text += CLOSE_BRAKET;
|
||||
|
||||
return text;
|
||||
};
|
||||
|
||||
class Token {
|
||||
constructor(type, value, line, row) {
|
||||
this.type = String(type);
|
||||
this.value = String(value);
|
||||
this.line = Number(line);
|
||||
this.row = Number(row);
|
||||
}
|
||||
|
||||
isEmpty() {
|
||||
return !!this.type;
|
||||
}
|
||||
|
||||
isText() {
|
||||
return isTextToken(this);
|
||||
}
|
||||
|
||||
isTag() {
|
||||
return isTagToken(this);
|
||||
}
|
||||
|
||||
isAttrName() {
|
||||
return isAttrNameToken(this);
|
||||
}
|
||||
|
||||
isAttrValue() {
|
||||
return isAttrValueToken(this);
|
||||
}
|
||||
|
||||
isStart() {
|
||||
return isTagStart(this);
|
||||
}
|
||||
|
||||
isEnd() {
|
||||
return isTagEnd(this);
|
||||
}
|
||||
|
||||
getName() {
|
||||
return getTagName(this);
|
||||
}
|
||||
|
||||
getValue() {
|
||||
return getTokenValue(this);
|
||||
}
|
||||
|
||||
getLine() {
|
||||
return getTokenLine(this);
|
||||
}
|
||||
|
||||
getColumn() {
|
||||
return getTokenColumn(this);
|
||||
}
|
||||
|
||||
toString() {
|
||||
return convertTagToText(this);
|
||||
}
|
||||
}
|
||||
|
||||
export const TYPE_ID = TOKEN_TYPE_ID;
|
||||
export const VALUE_ID = TOKEN_VALUE_ID;
|
||||
export const LINE_ID = TOKEN_LINE_ID;
|
||||
export const COLUMN_ID = TOKEN_COLUMN_ID;
|
||||
export const TYPE_WORD = TOKEN_TYPE_WORD;
|
||||
export const TYPE_TAG = TOKEN_TYPE_TAG;
|
||||
export const TYPE_ATTR_NAME = TOKEN_TYPE_ATTR_NAME;
|
||||
export const TYPE_ATTR_VALUE = TOKEN_TYPE_ATTR_VALUE;
|
||||
export const TYPE_SPACE = TOKEN_TYPE_SPACE;
|
||||
export const TYPE_NEW_LINE = TOKEN_TYPE_NEW_LINE;
|
||||
export { Token };
|
||||
export default Token;
|
||||
@@ -0,0 +1 @@
|
||||
export { parse, createTagNode } from './parse';
|
||||
@@ -0,0 +1,196 @@
|
||||
/* eslint-disable no-plusplus,no-param-reassign */
|
||||
import {
|
||||
OPEN_BRAKET,
|
||||
CLOSE_BRAKET,
|
||||
QUOTEMARK,
|
||||
BACKSLASH,
|
||||
SLASH,
|
||||
SPACE,
|
||||
TAB,
|
||||
EQ,
|
||||
N,
|
||||
} from '@bbob/plugin-helper/lib/char';
|
||||
|
||||
import { Token, TYPE_ATTR_NAME, TYPE_ATTR_VALUE, TYPE_NEW_LINE, TYPE_SPACE, TYPE_TAG, TYPE_WORD } from './Token';
|
||||
|
||||
const RESERVED_CHARS = [CLOSE_BRAKET, OPEN_BRAKET, QUOTEMARK, BACKSLASH, SPACE, TAB, EQ, N];
|
||||
const NOT_CHAR_TOKENS = [OPEN_BRAKET, SPACE, TAB, N];
|
||||
const WHITESPACES = [SPACE, TAB];
|
||||
|
||||
const isCharReserved = char => (RESERVED_CHARS.indexOf(char) >= 0);
|
||||
const isWhiteSpace = char => (WHITESPACES.indexOf(char) >= 0);
|
||||
const isCharToken = char => (NOT_CHAR_TOKENS.indexOf(char) === -1);
|
||||
|
||||
const createCharGrabber = (source) => {
|
||||
let idx = 0;
|
||||
|
||||
const skip = () => {
|
||||
idx += 1;
|
||||
};
|
||||
const hasNext = () => source.length > idx;
|
||||
|
||||
return {
|
||||
skip,
|
||||
hasNext,
|
||||
isLast: () => (idx === source.length),
|
||||
grabWhile: (cond) => {
|
||||
const start = idx;
|
||||
|
||||
while (hasNext() && cond(source[idx])) {
|
||||
skip();
|
||||
}
|
||||
|
||||
return source.substr(start, idx - start);
|
||||
},
|
||||
getNext: () => source[idx + 1],
|
||||
getPrev: () => source[idx - 1],
|
||||
getCurr: () => source[idx],
|
||||
};
|
||||
};
|
||||
|
||||
const trimChar = (str, charToRemove) => {
|
||||
while (str.charAt(0) === charToRemove) {
|
||||
str = str.substring(1);
|
||||
}
|
||||
|
||||
while (str.charAt(str.length - 1) === charToRemove) {
|
||||
str = str.substring(0, str.length - 1);
|
||||
}
|
||||
|
||||
return str;
|
||||
};
|
||||
|
||||
const unquote = str => str.replace(BACKSLASH + QUOTEMARK, QUOTEMARK);
|
||||
const createToken = (type, value, r = 0, cl = 0) => new Token(type, value, r, cl);
|
||||
|
||||
function createLexer(buffer, options = {}) {
|
||||
let row = 0;
|
||||
let col = 0;
|
||||
|
||||
let tokenIndex = -1;
|
||||
const tokens = new Array(Math.floor(buffer.length));
|
||||
const emitToken = (token) => {
|
||||
if (options.onToken) {
|
||||
options.onToken(token);
|
||||
}
|
||||
|
||||
tokenIndex += 1;
|
||||
tokens[tokenIndex] = token;
|
||||
};
|
||||
|
||||
const parseAttrs = (str) => {
|
||||
let tagName = null;
|
||||
let skipSpaces = false;
|
||||
|
||||
const attrTokens = [];
|
||||
const attrCharGrabber = createCharGrabber(str);
|
||||
const validAttr = (val) => {
|
||||
const isEQ = val === EQ;
|
||||
const isWS = isWhiteSpace(val);
|
||||
const isPrevSLASH = attrCharGrabber.getPrev() === SLASH;
|
||||
|
||||
if (tagName === null) {
|
||||
return !(isEQ || isWS || attrCharGrabber.isLast());
|
||||
}
|
||||
|
||||
if (skipSpaces && isWS) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (val === QUOTEMARK && !isPrevSLASH) {
|
||||
skipSpaces = !skipSpaces;
|
||||
}
|
||||
|
||||
return !(isEQ || isWS);
|
||||
};
|
||||
|
||||
const nextAttr = () => {
|
||||
const attrStr = attrCharGrabber.grabWhile(validAttr);
|
||||
|
||||
// first string before space is a tag name
|
||||
if (tagName === null) {
|
||||
tagName = attrStr;
|
||||
} else if (isWhiteSpace(attrCharGrabber.getCurr()) || !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));
|
||||
}
|
||||
|
||||
attrCharGrabber.skip();
|
||||
};
|
||||
|
||||
while (attrCharGrabber.hasNext()) {
|
||||
nextAttr();
|
||||
}
|
||||
|
||||
return { tag: tagName, attrs: attrTokens };
|
||||
};
|
||||
|
||||
const grabber = createCharGrabber(buffer);
|
||||
|
||||
const next = () => {
|
||||
const char = grabber.getCurr();
|
||||
|
||||
if (char === N) {
|
||||
grabber.skip();
|
||||
col = 0;
|
||||
row++;
|
||||
|
||||
emitToken(createToken(TYPE_NEW_LINE, char, row, col));
|
||||
} else if (isWhiteSpace(char)) {
|
||||
const str = grabber.grabWhile(isWhiteSpace);
|
||||
emitToken(createToken(TYPE_SPACE, str, row, col));
|
||||
} else if (char === OPEN_BRAKET) {
|
||||
const nextChar = grabber.getNext();
|
||||
grabber.skip(); // skip [
|
||||
|
||||
if (isCharReserved(nextChar)) {
|
||||
emitToken(createToken(TYPE_WORD, char, row, col));
|
||||
} else {
|
||||
const str = grabber.grabWhile(val => val !== CLOSE_BRAKET);
|
||||
grabber.skip(); // skip ]
|
||||
|
||||
if (!(str.indexOf(EQ) > 0) || str[0] === SLASH) {
|
||||
emitToken(createToken(TYPE_TAG, str, row, col));
|
||||
} else {
|
||||
const parsed = parseAttrs(str);
|
||||
|
||||
emitToken(createToken(TYPE_TAG, parsed.tag, row, col));
|
||||
parsed.attrs.map(emitToken);
|
||||
}
|
||||
}
|
||||
} else if (char === CLOSE_BRAKET) {
|
||||
grabber.skip();
|
||||
|
||||
emitToken(createToken(TYPE_WORD, char, row, col));
|
||||
} else if (isCharToken(char)) {
|
||||
const str = grabber.grabWhile(isCharToken);
|
||||
|
||||
emitToken(createToken(TYPE_WORD, str, row, col));
|
||||
}
|
||||
};
|
||||
|
||||
const tokenize = () => {
|
||||
while (grabber.hasNext()) {
|
||||
next();
|
||||
}
|
||||
|
||||
tokens.length = tokenIndex + 1;
|
||||
|
||||
return tokens;
|
||||
};
|
||||
|
||||
const isTokenNested = (token) => {
|
||||
const value = OPEN_BRAKET + SLASH + token.getValue();
|
||||
return buffer.indexOf(value) > -1;
|
||||
};
|
||||
|
||||
return {
|
||||
tokenize,
|
||||
isTokenNested,
|
||||
};
|
||||
}
|
||||
|
||||
export const createTokenOfType = createToken;
|
||||
export { createLexer };
|
||||
@@ -0,0 +1,231 @@
|
||||
import TagNode from '@bbob/plugin-helper/lib/TagNode';
|
||||
import { createLexer } from './lexer';
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {Array}
|
||||
*/
|
||||
let nodes;
|
||||
/**
|
||||
* @private
|
||||
* @type {Array}
|
||||
*/
|
||||
let nestedNodes;
|
||||
/**
|
||||
* @private
|
||||
* @type {Array}
|
||||
*/
|
||||
let tagNodes;
|
||||
/**
|
||||
* @private
|
||||
* @type {Array}
|
||||
*/
|
||||
let tagNodesAttrName;
|
||||
|
||||
let options = {};
|
||||
let tokenizer = null;
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let tokens = null;
|
||||
|
||||
const createTokenizer = (input, onToken) => createLexer(input, { onToken });
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param token
|
||||
* @return {*}
|
||||
*/
|
||||
const isTagNested = token => tokenizer.isTokenNested(token);
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @return {TagNode}
|
||||
*/
|
||||
const getTagNode = () => (tagNodes.length ? tagNodes[tagNodes.length - 1] : null);
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Token} token
|
||||
* @return {Array}
|
||||
*/
|
||||
const createTagNode = token => tagNodes.push(TagNode.create(token.getValue()));
|
||||
/**
|
||||
* @private
|
||||
* @param {Token} token
|
||||
* @return {Array}
|
||||
*/
|
||||
const createTagNodeAttrName = token => tagNodesAttrName.push(token.getValue());
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @return {Array}
|
||||
*/
|
||||
const getTagNodeAttrName = () =>
|
||||
(tagNodesAttrName.length ? tagNodesAttrName[tagNodesAttrName.length - 1] : getTagNode().tag);
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @return {Array}
|
||||
*/
|
||||
const clearTagNodeAttrName = () => {
|
||||
if (tagNodesAttrName.length) {
|
||||
tagNodesAttrName.pop();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @return {Array}
|
||||
*/
|
||||
const clearTagNode = () => {
|
||||
if (tagNodes.length) {
|
||||
tagNodes.pop();
|
||||
|
||||
clearTagNodeAttrName();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @return {Array}
|
||||
*/
|
||||
const getNodes = () => {
|
||||
if (nestedNodes.length) {
|
||||
const nestedNode = nestedNodes[nestedNodes.length - 1];
|
||||
return nestedNode.content;
|
||||
}
|
||||
|
||||
return nodes;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param tag
|
||||
*/
|
||||
const appendNode = (tag) => {
|
||||
getNodes().push(tag);
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param value
|
||||
* @return {boolean}
|
||||
*/
|
||||
const isAllowedTag = (value) => {
|
||||
if (options.onlyAllowTags && options.onlyAllowTags.length) {
|
||||
return options.onlyAllowTags.indexOf(value) >= 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
* @param {Token} token
|
||||
*/
|
||||
const handleTagStart = (token) => {
|
||||
if (token.isStart()) {
|
||||
createTagNode(token);
|
||||
|
||||
if (isTagNested(token)) {
|
||||
nestedNodes.push(getTagNode());
|
||||
} else {
|
||||
appendNode(getTagNode());
|
||||
clearTagNode();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Token} token
|
||||
*/
|
||||
const handleTagEnd = (token) => {
|
||||
if (token.isEnd()) {
|
||||
clearTagNode();
|
||||
|
||||
const lastNestedNode = nestedNodes.pop();
|
||||
|
||||
if (lastNestedNode) {
|
||||
appendNode(lastNestedNode);
|
||||
} else if (options.onError) {
|
||||
const tag = token.getValue();
|
||||
const line = token.getLine();
|
||||
const column = token.getColumn();
|
||||
options.onError({
|
||||
message: `Inconsistent tag '${tag}' on line ${line} and column ${column}`,
|
||||
lineNumber: line,
|
||||
columnNumber: column,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Token} token
|
||||
*/
|
||||
const handleTagToken = (token) => {
|
||||
if (token.isTag()) {
|
||||
if (isAllowedTag(token.getName())) {
|
||||
// [tag]
|
||||
handleTagStart(token);
|
||||
|
||||
// [/tag]
|
||||
handleTagEnd(token);
|
||||
} else {
|
||||
appendNode(token.toString());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Token} token
|
||||
*/
|
||||
const handleTagNode = (token) => {
|
||||
const tagNode = getTagNode();
|
||||
|
||||
if (tagNode) {
|
||||
if (token.isAttrName()) {
|
||||
createTagNodeAttrName(token);
|
||||
tagNode.attr(getTagNodeAttrName(), null);
|
||||
} else if (token.isAttrValue()) {
|
||||
tagNode.attr(getTagNodeAttrName(), token.getValue());
|
||||
clearTagNodeAttrName();
|
||||
} else if (token.isText()) {
|
||||
tagNode.append(token.getValue());
|
||||
}
|
||||
} else if (token.isText()) {
|
||||
appendNode(token.getValue());
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param token
|
||||
*/
|
||||
const parseToken = (token) => {
|
||||
handleTagToken(token);
|
||||
handleTagNode(token);
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @return {Array}
|
||||
*/
|
||||
const parse = (input, opts = {}) => {
|
||||
options = opts;
|
||||
tokenizer = (opts.createTokenizer ? opts.createTokenizer : createTokenizer)(input, parseToken);
|
||||
|
||||
nodes = [];
|
||||
nestedNodes = [];
|
||||
tagNodes = [];
|
||||
tagNodesAttrName = [];
|
||||
|
||||
tokens = tokenizer.tokenize();
|
||||
|
||||
return nodes;
|
||||
};
|
||||
|
||||
export { createTagNode, parse };
|
||||
export default parse;
|
||||
Reference in New Issue
Block a user