mirror of
https://github.com/tenrok/BBob.git
synced 2026-05-15 11:59:37 +03:00
8797f7f363
* feat: initial typescript support * feat: typescript support * feat(plugin-helper): move files to typescript * chore: update lock files * feat: preset types * fix: build * fix: benchmark * fix: remove pnpm cache * fix: bench action * fix: pnpm recursive install * fix: nx cache * fix: lock file * fix: workflows * fix: lerna support in pnpm * fix: pnpm workspace * fix: remove unused files * fix: pnpm lock file * fix: update lerna for support pnpm * fix: lerna bootstrap * fix: rollup build * fix: update nx * fix: build * fix: add nx dep target * fix: remove nx cache * fix: workflow run on push only for master * fix: test workflow run on push only for master * fix: remove parallel for gen types * fix: benchmark * fix: benchmark imports * fix: pnpm * fix: types errors and pnpm * fix: types * fix: types * refactor: parser * fix(parser): tests * fix: preset tests * fix: react types * fix: react type declarations * fix: pnpm lock file * fix: react preset types * fix: lock file * fix: vue2 types * feat: dev container support * fix: types * fix: types * refactor: rewrite pkg-task, add nx gen-types deps, fix react/render.ts * refactor: types * fix: types * fix: rename gen-types to types * fix: nx build order * fix: nx reset * fix: define nx deps explicit * fix: build * fix: nx * fix: nx order build * fix: nx deps * fix: bbob cli tests * fix: tests * fix: cli tests and import * fix: test cover * fix: cli cover
242 lines
6.8 KiB
JavaScript
Vendored
242 lines
6.8 KiB
JavaScript
Vendored
/* eslint-disable no-plusplus,no-param-reassign */
|
|
const {
|
|
OPEN_BRAKET,
|
|
CLOSE_BRAKET,
|
|
QUOTEMARK,
|
|
BACKSLASH,
|
|
SLASH,
|
|
SPACE,
|
|
TAB,
|
|
EQ,
|
|
N,
|
|
} = require('@bbob/plugin-helper');
|
|
|
|
const {
|
|
Token, TYPE_ATTR_NAME, TYPE_ATTR_VALUE, TYPE_NEW_LINE, TYPE_SPACE, TYPE_TAG, TYPE_WORD,
|
|
} = require('@bbob/parser/Token');
|
|
const { createCharGrabber, trimChar, unquote } = require('@bbob/parser/utils');
|
|
|
|
// for cases <!-- -->
|
|
const EM = '!';
|
|
|
|
/**
|
|
* Creates a Token entity class
|
|
* @param {String} type
|
|
* @param {String} value
|
|
* @param {Number} r line number
|
|
* @param {Number} cl char number in line
|
|
*/
|
|
const createToken = (type, value, r = 0, cl = 0) => new Token(type, value, r, cl);
|
|
|
|
/**
|
|
* @typedef {Object} Lexer
|
|
* @property {Function} tokenize
|
|
* @property {Function} isTokenNested
|
|
*/
|
|
|
|
/**
|
|
* @param {String} buffer
|
|
* @param {Object} options
|
|
* @param {Function} options.onToken
|
|
* @param {String} options.openTag
|
|
* @param {String} options.closeTag
|
|
* @param {Boolean} options.enableEscapeTags
|
|
* @return {Lexer}
|
|
*/
|
|
function createLexer(buffer, options = {}) {
|
|
let row = 0;
|
|
let col = 0;
|
|
|
|
let tokenIndex = -1;
|
|
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 RESERVED_CHARS = [closeTag, openTag, QUOTEMARK, BACKSLASH, SPACE, TAB, EQ, N, EM];
|
|
const NOT_CHAR_TOKENS = [
|
|
// ...(options.enableEscapeTags ? [BACKSLASH] : []),
|
|
openTag, SPACE, TAB, N,
|
|
];
|
|
const WHITESPACES = [SPACE, TAB];
|
|
const SPECIAL_CHARS = [EQ, 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 isSpecialChar = (char) => (SPECIAL_CHARS.indexOf(char) >= 0);
|
|
const isEscapableChar = (char) => (char === openTag || char === closeTag || char === BACKSLASH);
|
|
const isEscapeChar = (char) => char === BACKSLASH;
|
|
|
|
/**
|
|
* Emits newly created token to subscriber
|
|
* @param token
|
|
*/
|
|
const emitToken = (token) => {
|
|
if (options.onToken) {
|
|
options.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;
|
|
|
|
const attrTokens = [];
|
|
const attrCharGrabber = createCharGrabber(str);
|
|
|
|
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 (isTagNameEmpty) {
|
|
return (isEQ || isWS || attrCharGrabber.isLast()) === false;
|
|
}
|
|
|
|
if (skipSpecialChars && isSpecialChar(char)) {
|
|
return true;
|
|
}
|
|
|
|
if (char === QUOTEMARK && !isPrevSLASH) {
|
|
skipSpecialChars = !skipSpecialChars;
|
|
|
|
if (!skipSpecialChars && !(nextChar === EQ || isWhiteSpace(nextChar))) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return (isEQ || isWS) === false;
|
|
};
|
|
|
|
const nextAttr = () => {
|
|
const attrStr = attrCharGrabber.grabWhile(validAttr);
|
|
const currChar = attrCharGrabber.getCurr();
|
|
|
|
// 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));
|
|
}
|
|
|
|
attrCharGrabber.skip();
|
|
};
|
|
|
|
while (attrCharGrabber.hasNext()) {
|
|
nextAttr();
|
|
}
|
|
|
|
return { tag: tagName, attrs: attrTokens };
|
|
};
|
|
|
|
const bufferGrabber = createCharGrabber(buffer, {
|
|
onSkip: () => {
|
|
col++;
|
|
},
|
|
});
|
|
|
|
const next = () => {
|
|
const currChar = bufferGrabber.getCurr();
|
|
const nextChar = bufferGrabber.getNext();
|
|
|
|
if (currChar === N) {
|
|
bufferGrabber.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
|
|
|
|
// 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 (isCharReserved(nextChar) || hasInvalidChars || bufferGrabber.isLast()) {
|
|
emitToken(createToken(TYPE_WORD, currChar, row, col));
|
|
} else {
|
|
const str = bufferGrabber.grabWhile((val) => val !== closeTag);
|
|
|
|
bufferGrabber.skip(); // skip closeTag
|
|
// [myTag ]
|
|
const isNoAttrsInTag = str.indexOf(EQ) === -1;
|
|
// [/myTag]
|
|
const isClosingTag = str[0] === SLASH;
|
|
|
|
if (isNoAttrsInTag || isClosingTag) {
|
|
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 (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);
|
|
});
|
|
|
|
emitToken(createToken(TYPE_WORD, str, row, col));
|
|
}
|
|
}
|
|
};
|
|
|
|
const tokenize = () => {
|
|
while (bufferGrabber.hasNext()) {
|
|
next();
|
|
}
|
|
|
|
tokens.length = tokenIndex + 1;
|
|
|
|
return tokens;
|
|
};
|
|
|
|
const isTokenNested = (token) => {
|
|
const value = openTag + SLASH + token.getValue();
|
|
// potential bottleneck
|
|
return buffer.indexOf(value) > -1;
|
|
};
|
|
|
|
return {
|
|
tokenize,
|
|
isTokenNested,
|
|
};
|
|
}
|
|
|
|
module.exports.createLexer = createLexer;
|