mirror of
https://github.com/tenrok/BBob.git
synced 2026-06-14 18:42:24 +03:00
* fix: TagNode.create with null content by default * Create five-meals-sing.md * fix: tests * fix(preset): types inference * fix: preset types * fix: preset types * fix: lock file, parser and utils * refactor: move types to separate package * fix(preset): add @bbob/core to dev deps * fix(preset): lock file * test(preset-vue): create tags * test(preset-vue): tests * chore(nx): fix nx cover deps * chore: changesets
This commit is contained in:
@@ -3,6 +3,7 @@ import {
|
||||
CLOSE_BRAKET,
|
||||
SLASH,
|
||||
} from '@bbob/plugin-helper';
|
||||
import type { Token as TokenInterface } from "@bbob/types";
|
||||
|
||||
// type, value, line, row,
|
||||
|
||||
@@ -87,11 +88,11 @@ const tokenToText = (token: Token) => {
|
||||
* @export
|
||||
* @class Token
|
||||
*/
|
||||
class Token<TokenValue = string> {
|
||||
private t: number // type
|
||||
private v: string // value
|
||||
private l: number // line
|
||||
private r: number // row
|
||||
class Token<TokenValue = string> implements TokenInterface {
|
||||
readonly t: number // type
|
||||
readonly v: string // value
|
||||
readonly l: number // line
|
||||
readonly r: number // row
|
||||
|
||||
constructor(type?: number, value?: TokenValue, row: number = 0, col: number = 0) {
|
||||
this[TOKEN_LINE_ID] = row;
|
||||
|
||||
@@ -2,4 +2,3 @@ export { TagNode } from '@bbob/plugin-helper';
|
||||
export { default } from './parse';
|
||||
export * from './parse';
|
||||
export * from './lexer'
|
||||
export * from './types'
|
||||
|
||||
@@ -10,12 +10,12 @@ import {
|
||||
EQ,
|
||||
N,
|
||||
} from '@bbob/plugin-helper';
|
||||
import type { LexerOptions, LexerTokenizer } from "@bbob/types";
|
||||
|
||||
import {
|
||||
Token, TYPE_ATTR_NAME, TYPE_ATTR_VALUE, TYPE_NEW_LINE, TYPE_SPACE, TYPE_TAG, TYPE_WORD,
|
||||
} from './Token';
|
||||
import { CharGrabber, createCharGrabber, trimChar, unquote } from './utils';
|
||||
import type { LexerOptions, LexerTokenizer } from "./types";
|
||||
|
||||
// for cases <!-- -->
|
||||
const EM = '!';
|
||||
@@ -24,15 +24,24 @@ export function createTokenOfType(type: number, value: string, r = 0, cl = 0) {
|
||||
return new Token(type, value, r, cl)
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
const WHITESPACES = [SPACE, TAB];
|
||||
const SPECIAL_CHARS = [EQ, SPACE, TAB];
|
||||
|
||||
const isWhiteSpace = (char: string) => (WHITESPACES.indexOf(char) >= 0);
|
||||
const isEscapeChar = (char: string) => char === BACKSLASH;
|
||||
const isSpecialChar = (char: string) => (SPECIAL_CHARS.indexOf(char) >= 0);
|
||||
const isNewLine = (char: string) => char === N;
|
||||
const unq = (val: string) => unquote(trimChar(val, QUOTEMARK));
|
||||
|
||||
export function createLexer(buffer: string, options: LexerOptions = {}): LexerTokenizer {
|
||||
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;
|
||||
let col = 0;
|
||||
|
||||
@@ -47,6 +56,7 @@ export function createLexer(buffer: string, options: LexerOptions = {}): LexerTo
|
||||
const contextFreeTags = (options.contextFreeTags || [])
|
||||
.filter(Boolean)
|
||||
.map((tag) => tag.toLowerCase());
|
||||
const nestedMap = new Map<string, boolean>();
|
||||
const onToken = options.onToken || (() => {
|
||||
});
|
||||
|
||||
@@ -54,22 +64,14 @@ export function createLexer(buffer: string, options: LexerOptions = {}): LexerTo
|
||||
const NOT_CHAR_TOKENS = [
|
||||
openTag, SPACE, TAB, N,
|
||||
];
|
||||
const WHITESPACES = [SPACE, TAB];
|
||||
const SPECIAL_CHARS = [EQ, SPACE, TAB];
|
||||
|
||||
const isCharReserved = (char: string) => (RESERVED_CHARS.indexOf(char) >= 0);
|
||||
const isNewLine = (char: string) => char === N;
|
||||
const isWhiteSpace = (char: string) => (WHITESPACES.indexOf(char) >= 0);
|
||||
const isCharToken = (char: string) => (NOT_CHAR_TOKENS.indexOf(char) === -1);
|
||||
const isSpecialChar = (char: string) => (SPECIAL_CHARS.indexOf(char) >= 0);
|
||||
const isEscapableChar = (char: string) => (char === openTag || char === closeTag || char === BACKSLASH);
|
||||
const isEscapeChar = (char: string) => char === BACKSLASH;
|
||||
const onSkip = () => {
|
||||
col++;
|
||||
};
|
||||
|
||||
const unq = (val: string) => unquote(trimChar(val, QUOTEMARK));
|
||||
|
||||
const checkContextFreeMode = (name: string, isClosingTag?: boolean) => {
|
||||
if (contextFreeTag !== '' && isClosingTag) {
|
||||
contextFreeTag = '';
|
||||
@@ -339,8 +341,16 @@ export function createLexer(buffer: string, options: LexerOptions = {}): LexerTo
|
||||
|
||||
function isTokenNested(token: Token) {
|
||||
const value = openTag + SLASH + token.getValue();
|
||||
// potential bottleneck
|
||||
return buffer.indexOf(value) > -1;
|
||||
|
||||
if (nestedMap.has(value)) {
|
||||
return !!nestedMap.get(value);
|
||||
} else {
|
||||
const status = (buffer.indexOf(value) > -1)
|
||||
|
||||
nestedMap.set(value, status);
|
||||
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { NodeContent, TagNodeTree, LexerTokenizer, ParseOptions } from "@bbob/types";
|
||||
|
||||
import {
|
||||
CLOSE_BRAKET,
|
||||
OPEN_BRAKET,
|
||||
@@ -7,26 +9,8 @@ import {
|
||||
|
||||
import { createLexer } from "./lexer";
|
||||
|
||||
import type { NodeContent, TagNodeTree } from "@bbob/plugin-helper";
|
||||
import type { LexerTokenizer, LexerOptions } from "./types";
|
||||
import type { Token } from "./Token";
|
||||
|
||||
type ParseError = {
|
||||
tagName: string;
|
||||
lineNumber: number;
|
||||
columnNumber: number;
|
||||
};
|
||||
|
||||
export interface ParseOptions {
|
||||
createTokenizer?: (input: string, options?: LexerOptions) => LexerTokenizer;
|
||||
openTag?: string;
|
||||
closeTag?: string;
|
||||
onlyAllowTags?: string[];
|
||||
contextFreeTags?: string[];
|
||||
enableEscapeTags?: boolean;
|
||||
onError?: (error: ParseError) => void;
|
||||
}
|
||||
|
||||
class NodeList<Value> {
|
||||
private n: Value[];
|
||||
|
||||
@@ -201,7 +185,7 @@ function parse(input: string, opts: ParseOptions = {}) {
|
||||
function handleTagStart(token: Token) {
|
||||
flushTagNodes();
|
||||
|
||||
const tagNode = TagNode.create(token.getValue());
|
||||
const tagNode = TagNode.create(token.getValue(), {}, []);
|
||||
const isNested = isTokenNested(token);
|
||||
|
||||
tagNodes.push(tagNode);
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import type Token from "./Token";
|
||||
|
||||
export interface LexerTokenizer {
|
||||
tokenize: () => Token<string>[];
|
||||
isTokenNested?: (token: Token<string>) => boolean;
|
||||
}
|
||||
|
||||
export type LexerOptions = {
|
||||
openTag?: string;
|
||||
closeTag?: string;
|
||||
onlyAllowTags?: string[];
|
||||
enableEscapeTags?: boolean;
|
||||
contextFreeTags?: string[];
|
||||
onToken?: (token?: Token<string>) => void;
|
||||
};
|
||||
@@ -35,6 +35,10 @@ export class CharGrabber {
|
||||
}
|
||||
|
||||
getCurr() {
|
||||
if (typeof this.s[this.c.pos] === 'undefined') {
|
||||
return ''
|
||||
}
|
||||
|
||||
return this.s[this.c.pos]
|
||||
}
|
||||
|
||||
@@ -51,7 +55,11 @@ export class CharGrabber {
|
||||
getPrev() {
|
||||
const prevPos = this.c.pos - 1;
|
||||
|
||||
return typeof this.s[prevPos] !== 'undefined' ? this.s[prevPos] : null;
|
||||
if (typeof this.s[prevPos] === 'undefined') {
|
||||
return null
|
||||
}
|
||||
|
||||
return this.s[prevPos];
|
||||
}
|
||||
|
||||
isLast() {
|
||||
|
||||
Reference in New Issue
Block a user