import { MatchPath, MatchState, Matcher, matchAny, parseSeq } from './match'; import { InitTag, HasContexts, HasMeta, HasInit, TaggedToken, TokenTag, ContextKey, ContextToken, ContextMeta, ParserResult } from './model'; export interface ParseFunc, Meta> { ($: ParserHandle, ...captures: string[]): void; } export interface ParserHandle, Meta> extends MatchState { p: ParserMatchers; // parser matchers a: ParserProcs; c: Ctx; // current context m: Meta; // parser meta t: ContextToken[]; } export interface InitFunc, Meta> { (m: MetaType): void; } export type ParserInits = InitFunc, Meta>[]; export const dropToken: any[] = []; export interface ProcFunc, Meta> { ($: ParserHandle, token: ContextToken): ContextToken[] | void; } export type ParserContextProcs, Meta> = { [Tag in TokenTag>]: ProcFunc, Meta>; } export type ParserProcs = { [Ctx in ContextKey]: ParserContextProcs; } export type ParserRuleSimple, Meta> = [ Ctx[], // contexts number, // weight (order) string, // regexp to match ParseFunc // parser function ]; export type ParserRuleWithInit, Meta> = [ Ctx[], // contexts number, // weight (order) string, // regexp to match ParseFunc, // parsing function InitFunc // init function ]; export type ParserRuleWithPost, Meta> = [ Ctx[], // contexts number, // weight (order) string, // regexp to match ParseFunc, // parsing function TokenTag>[], // token tags ProcFunc // post-processing function ]; export type ParserRuleWithInitAndPost, Meta> = [ Ctx[], // contexts number, // weight (order) string, // regexp to match ParseFunc, // parsing function InitFunc, // init function TokenTag>[], // token tags ProcFunc // post-processing function ]; export type ParserRule, Meta> = ParserRuleSimple | ParserRuleWithInit | ParserRuleWithPost | ParserRuleWithInitAndPost ; export type ParserRules = ParserRule, Meta>[]; export type ParserMatchers = { [Ctx in ContextKey]: Matcher> }; export interface Parser> { p: ParserMatchers>; // matchers m: ParserInits>; // initializers a: ParserProcs>; // postprocessors s: Ctx; // starting context } export function init(...rules: ParserRules>): Parser; export function init>(start: Ctx, ...rules: ParserRules>): Parser; export function init(...rules: ParserRules>): Parser { let start = typeof rules[0] == 'number' ? rules.shift() : 0; // aggregating matchers const ctxs: (ContextKey)[] = []; for (const [ctxl] of rules) { for (const ctx of ctxl) { if (ctxs.indexOf(ctx) < 0) { ctxs.push(ctx); } } } const matchers = {} as ParserMatchers>; for (const context of ctxs) { type MatchPathWithWeight = [ number, // weight (order) string, // regexp to match ParseFunc, ContextMeta> // parser function ]; const paths = [] as MatchPathWithWeight[]; for (const [contexts, ...path] of rules) { if (contexts.indexOf(context as ContextKey) > -1) { paths.push(path as MatchPathWithWeight); } } // sorting rules by weight paths.sort(([weight_a,], [weight_b,]) => weight_a < weight_b ? -1 : weight_b < weight_a ? 1 : 0); // overriding duplicated rules (with same weight) for (let i = 1; i < paths.length;) { if (paths[i][0] == paths[i - 1][0]) { paths.splice(i - 1, 1); } else { i++; } } // set matcher paths matchers[context] = matchAny( paths.map(([, ...path]) => path as MatchPath, ContextMeta>>)); } // aggregating init and post fns const inits = [] as ParserInits>; const procs = {} as ParserProcs>; for (const rule of rules) { if (rule.length > 4 && typeof rule[4] == 'function') { inits.push(rule[4] as InitFunc, ContextMeta>); } if (rule.length > 5) { const [ctxs,] = rule; const [tags, proc] = rule.slice(rule.length > 6 ? 5 : 4) as [ TokenTag>[], ProcFunc> ]; if (tags.length) { for (const ctx of ctxs) { if (!procs[ctx]) procs[ctx] = {} as ParserContextProcs>; for (const tag of tags) { procs[ctx][tag] = proc; } } } } } return { p: matchers, m: inits, a: procs, s: start, }; } export function parse>({ p, a, s, m }: Parser, source: string): ParserResult { const meta = {} as ContextMeta; for (const init of m) { init(meta); } const $ = { p, a, m: meta, c: s, } as ParserHandle>; const tokens = parseNest( $, source .replace(/\r\n|\r/g, '\n') // unify line feeds .replace(/\t/g, ' ') // replace tabs by four spaces .replace(/\u00a0/g, ' ') // replace non-break space by space .replace(/\u2424/g, '\n') // replace unicode NL by newline ); procNest($, tokens); return [$.m, tokens]; } export function parseNest, NestedCtx extends ContextKey>($: ParserHandle>, src: string, ctx: NestedCtx): ContextToken[]; export function parseNest>($: ParserHandle>, src: string): ContextToken[]; export function parseNest, NestedCtx extends ContextKey>($: ParserHandle>, src: string, ctx: Ctx | NestedCtx = $.c): ContextToken[] { const $$: ParserHandle = { ...$, t: [], c: ctx as NestedCtx, s: src.replace(/^ +$/gm, ''), // remove spaces on empty lines which has spaces only r: true }; parseSeq($$.p[$$.c], $$); if ($$.r && $$.s.length) throw new Error(`Unable to parse: ${$$.s}...`); return $$.t; } export function procNest>($: ParserHandle>, tokens: ContextToken[]): void; export function procNest, NestedCtx extends ContextKey>($: ParserHandle>, tokens: ContextToken[], ctx: NestedCtx): void; export function procNest, NestedCtx extends ContextKey>($: ParserHandle>, tokens: ContextToken[], ctx: Ctx | NestedCtx = $.c) { const procs = $.a[$.c]; if (procs) { for (let i = 0; i < tokens.length; i++) { const token = tokens[i]; const proc = (procs as any)[(token as any as TaggedToken).$]; if (proc) { const tokens_ = proc($, token); if (tokens_) tokens.splice(i--, 1, ...tokens_); } } } } export function lastToken, Meta>({ t: tokens }: ParserHandle): ContextToken | void { if (tokens.length) { return tokens[tokens.length - 1]; } } export function pushToken, Meta>({ t: tokens }: ParserHandle, token: ContextToken) { tokens.push(token); } export interface TextToken { $: keyof any; _: string; } export function pushText, Meta, Token extends TextToken & ContextToken>($: ParserHandle, tag: TokenTag, chunk: string) { if (chunk.length) { const token = lastToken($) as TextToken | void; if (token && token.$ == tag) { token._ += chunk; } else { $.t.push({ $: tag, _: chunk } as any as Token); } } }