@v4fire/core
Version:
V4Fire core library
186 lines (148 loc) • 4.19 kB
text/typescript
/*!
* V4Fire Core
* https://github.com/V4Fire/Core
*
* Released under the MIT license
* https://github.com/V4Fire/Core/blob/master/LICENSE
*/
/**
* [[include:core/json/stream/parser/README.md]]
* @packageDocumentation
*/
import { parserStates } from 'core/json/stream/parser/states';
import { parserStateTypes, parserPatterns, PARSING_COMPLETE } from 'core/json/stream/parser/const';
import type {
ParserState,
ParentParserState,
Token,
TokenProcessor
} from 'core/json/stream/parser/interface';
export * from 'core/json/stream/parser/const';
export * from 'core/json/stream/parser/interface';
export default class Parser {
/**
* Parses the specified iterable object as a JSON stream and yields tokens via a Generator
* @param source
*/
static from(source: Iterable<string> | AsyncIterable<string>): AsyncGenerator<Token>;
/**
* Parses the specified iterable object as a JSON stream and yields tokens or values via a Generator
*
* @param source
* @param [processors] - list of token processors to apply to the output iterable
*/
static from<T extends Array<TokenProcessor<any>>>(
source: Iterable<string> | AsyncIterable<string>,
...processors: T
): T extends [TokenProcessor<infer R>] ?
AsyncGenerator<R> :
T extends [...infer A, TokenProcessor<infer R>] ? AsyncGenerator<R> : unknown;
static async*from(
source: Iterable<string> | AsyncIterable<string>,
...processors: Array<TokenProcessor<unknown>>
): AsyncGenerator {
const
parser = new Parser();
for await (const chunk of source) {
yield* process(parser.processChunk(chunk));
}
yield* process(parser.finishChunkProcessing());
function* process(stream: IterableIterator<unknown>, currentProcessor: number = 0): Generator {
if (currentProcessor >= processors.length) {
for (const el of stream) {
yield el;
}
return;
}
const
processor = processors[currentProcessor];
for (const val of stream) {
yield* process(processor.processToken(Object.cast(val)), currentProcessor + 1);
}
if (processor.finishTokenProcessing != null) {
yield* process(processor.finishTokenProcessing(), currentProcessor + 1);
}
}
}
/**
* The current parent of a parsed structure
*/
protected parent: ParentParserState = parserStateTypes.EMPTY;
/**
* An array of parent objects for the current parsed structure
*/
protected readonly stack: ParentParserState[] = [];
/**
* The current piece of JSON
*/
protected buffer: string = '';
/**
* Accumulator for the current parsed structure
*/
protected accumulator: string = '';
/**
* The current parsed value
*/
protected value?: string = '';
/**
* The current index in a buffer parsing process
*/
protected index: number = 0;
/**
* The current match value after RegExp execution
*/
protected matched?: RegExpExecArray | null;
/**
* The next expected parser state from a stream
*/
protected expected: ParserState = parserStateTypes.VALUE;
/**
* Dictionary with RegExp-s to different types of data
*/
protected patterns: Record<string, RegExp> = parserPatterns;
/**
* Is the parser parsing a number now
*/
protected isOpenNumber: boolean = false;
/**
* Closes all unclosed tokens and returns a Generator of tokens.
* The method must be called after the end of parsing.
*/
*finishChunkProcessing(): Generator<Token> {
if (this.expected !== parserStateTypes.DONE) {
this.expected = parserStateTypes.DONE;
yield* parserStates[this.expected].call(this);
}
}
/**
* Processes the passed JSON chunk and yields tokens via an asynchronous Generator
* @param chunk
*/
*processChunk(chunk: string): Generator<Token> {
this.buffer += chunk;
this.matched = null;
this.value = '';
this.index = 0;
while (true) {
const
handler = parserStates[this.expected],
iter: Generator<Token> = handler.call(this);
let
res;
while (true) {
const
val = iter.next();
if (val.done) {
res = val.value;
break;
} else {
yield val.value;
}
}
if (res === PARSING_COMPLETE) {
break;
}
}
this.buffer = this.buffer.slice(this.index);
}
}