UNPKG

stream-token-parser

Version:

Token parser designed based on stream property. Accept chunk string and parse it into tokens simultaneously. [readme-lang:zh]基于流特性设计的token解析器,接受一个chunk的字符串,同时解析成tokens。

263 lines (223 loc) 6.05 kB
'use strict'; let { isString, funType, listType, isFunction, mapType, isFalsy, isNumber, or } = require('basetype'); let { WAIT, MATCH, QUIT } = require('./const'); let { stateGraphDSL } = require('cl-fsm'); let buildFSM = require('./buildFSM'); let { map } = require('bolzano'); let { getMatch } = require('./match'); let { findToken, filterTypes } = require('./findToken'); let { assembleToken } = require('./util'); /** * * A token spliter used to split stream string. * * When accept a chunk, parsing it at the same time. * * ## options * * tokenTypes = [ * { * name, * priority, * match * } * ] * * - priority * * When meets ambiguity, priority will be helpful. * * Assume we got two types: \w*, \s. When split "today is a good day". If we set \s has a higher priority, we will get ["t", "o", "d", "a", "y", " ", "i", "s", " ", "a", " ", "g", "o", "o", "d", " ", "d", "a", "y"], just one token. If we set \w* has a higher priority, we will get ["today", " ", "is", " ", "a", " ", "good", " ", "day"]. * * - match (letter, prefix) -> WAIT | MATCH | QUIT * * Because we are handling chunks, we need to know finished a chunk or not. * * ## rules * * - priority rule * * - longest matching * * eg: four rules a(def, 1), b(default[s?], 2), c(/\w\w+/, 0), d(_, 2) * * ``` * input isPart match * d (a, b, c) () * de (a, b, c) (c:0) * def (a, b, c) (a:1, c:0) * defa (b, c) (c:0) * defau (b, c) (c:0) * defaul (b, c) (c:0) * default (b, c) (b:2, c:0) * defaults (b, c) (b:2, c:0) * defaults_ () () * ``` * * When empty situation happend, analysis the process. * * ``` * 1. possible situations * de (a, b, c) (c:0) * def (a, b, c) (a:1, c:0) * defa (b, c) (c:0) * defau (b, c) (c:0) * defaul (b, c) (c:0) * default (b, c) (b:2, c:0) * defaults (b, c) (b:2, c:0) * * 2. for any rule (a, b, c) only consider it's biggest matching situation. (longest matching rule) * def (a, b, c) (a:1) longest for a * defaults (b, c) (b:2, c:0) longest for b and c * * 3. choose the highest priority rule. (priority rule) * defaults (b:2) * ``` */ let parser = funType((tokenTypes) => { tokenTypes = map(tokenTypes, (tokenType) => { let { priority, name, independent, match } = tokenType; name = name || (match && match.toString()); match = getMatch(match); if (!isFunction(match)) { throw new Error(`Error match in token type ${strTokenType(tokenType)}`); } return { priority: priority || 0, name: name, match, independent }; }); let stock = ''; return (chunk) => { if (chunk === null) { // means finished let tokens = splitTokensToEnd(stock, tokenTypes); stock = ''; return tokens; } stock += chunk.toString(); let { rest, tokens } = splitTokens(stock, tokenTypes); stock = rest; return tokens; }; }, [ listType(mapType({ priority: or(isFalsy, isNumber), name: or(isFalsy, isString) })) ]); let strTokenType = ({ priority, match, name, independent }) => { return `{ priority: ${priority}, match: ${match}, name: ${name}, independent: ${independent} }`; }; parser.parse = (str, tokenTypes) => { let parse = parser(tokenTypes); return parse(str).concat(parse(null)); }; let splitTokensToEnd = (stock, tokenTypes) => { let { tokens } = splitTokens(stock, tokenTypes, 'end'); return tokens; }; let splitTokens = (stock, tokenTypes, type) => { let ret; let tokens = []; while (stock && (ret = getToken(stock, tokenTypes, type))) { let { token, rest } = ret; stock = rest; tokens.push(token); } return { tokens, rest: stock }; }; /** * type = 'mid' | 'end' * * get toke from stock based on tokenTypes */ let getToken = (stock, tokenTypes, type = 'mid') => { let next = stock; let prefix = ''; // used to store current prefix let retMatrix = []; let restTypes = tokenTypes; while (next) { let nextLetter = next[0]; prefix += nextLetter; // shorten next next = next.substring(1); let [partTypes, matchTypes, independentType] = filterTypes(nextLetter, prefix, restTypes); restTypes = partTypes; // reduce types // see if there is a independent token type // find independent token if (independentType) { return splitTokenRet( assembleToken(independentType, prefix), stock ); } // obey longest match rule // no matchs futher, means look forward more won't get any matchs if (!partTypes.length && !matchTypes.length) { return fetchToken(stock, retMatrix, prefix); } else { retMatrix.push({ partTypes, matchTypes, prefix }); } } // if this is end, fetchToken if (prefix === stock && type === 'end') { // match stop point return fetchToken(stock, retMatrix, prefix); } return null; }; let fetchToken = (stock, retMatrix, prefix) => { // empty let token = findToken(retMatrix); if (!token) { throw new Error(`Can not find token from prefix "${prefix}". And prefix is not any part of token. stock is "${stock}".`); } return splitTokenRet(token, stock); }; let splitTokenRet = (token, stock) => { return { token, rest: stock.substring(token.text.length) }; }; module.exports = { parser, WAIT, QUIT, MATCH, stateGraphDSL, buildFSM };