UNPKG

fruitsconfits

Version:

FruitsConfits - A well typed and sugared parser combinator framework for TypeScript/JavaScript.

562 lines (488 loc) 18 kB
// Copyright (c) 2019 Shellyl_N and Authors // license: ISC // https://github.com/shellyln import { ParserInputWithCtx, ParseError, parserInput, ParserFnFailedResult, ParserFnWithCtx } from './types'; function getLineAndCol(src: string, pos: number) { let line = 1; let col = 1; for (let i = 0; i <= pos; i++) { switch (src[i]) { case '\r': if (src[i + 1] === '\n') { i++; } // Fall Throught case '\n': line++; col = 1; break; default: col++; break; } } return ({ line, col, }); } export function formatErrorMessage<T extends ArrayLike<T[number]>, C, R>( result: ParserFnFailedResult<T, C, R>): string { let msg = ''; let src = ''; if (typeof result.src === 'string') { src = (result.src as string).slice(Math.max(result.pos - 5, 0), result.pos + 55); // NOTE: (TS>=4.0) TS2339: Property 'slice' does not exist on type 'never'. let ar = src.split(/\r\n|\n|\r/); ar = ar.slice(0, 1) .concat(' ^~~~~~~~') .concat(...ar.slice(1)); src = ar.join('\n') + '\n\n'; const lineAndCol = getLineAndCol(result.src, result.pos); msg = (`parse failed at position:${ result.pos} line:${lineAndCol.line} col:${lineAndCol.col} ${ result.message ? ` ${result.message}` : ''}\n ${src}`); } else { src = ' (object)\n ^~~~~~~~'; try { src = ' ' + // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access JSON.stringify((result.src as any).slice(Math.max(result.pos - 10, 0), result.pos)) + '\n ' + // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access JSON.stringify((result.src as any).slice(result.pos, result.pos + 1)) + '\n ' + // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access JSON.stringify((result.src as any).slice(result.pos + 1, result.pos + 10)); let ar = src.split(/\r\n|\n|\r/); ar = ar.slice(0, 2) .concat(' ^~~~~~~~') .concat(...ar.slice(2)); src = ar.join('\n') + '\n\n'; } catch (e) { // Nothing to do. } msg = (`parse failed at position:${ result.pos} ${ result.message ? ` ${result.message}` : ''}\n ${src}`); } return msg; } export function zeroWidth<T extends ArrayLike<T[number]>, C, R>( helper?: () => R ): ParserFnWithCtx<T, C, R> { return (input => { return ({ succeeded: true, next: { src: input.src, start: input.start, end: input.end, context: input.context, templateArgs: input.templateArgs, templateArgsPos: input.templateArgsPos, }, tokens: helper ? [helper()] : [], }); }); } export function zeroWidthError<T extends ArrayLike<T[number]>, C, R>( message: string ): ParserFnWithCtx<T, C, R> { return (input => { throw new ParseError({ succeeded: false, error: true, src: input.src, pos: input.start, message: message || '', }); // return ({ // succeeded: false, // error: true, // src: input.src, // pos: input.start, // message: message || '', // }); }); } export function beginning<T extends ArrayLike<T[number]>, C, R>( helper?: () => R ): ParserFnWithCtx<T, C, R> { return (input => { return (input.start === 0 ? { succeeded: true, next: { src: input.src, start: input.start, end: input.end, context: input.context, templateArgs: input.templateArgs, templateArgsPos: input.templateArgsPos, }, tokens: helper ? [helper()] : [], } : { succeeded: false, error: false, src: input.src, pos: input.start, message: 'operator "beginning"', }); }); } export function end<T extends ArrayLike<T[number]>, C, R>( helper?: () => R ): ParserFnWithCtx<T, C, R> { return (input => { return (input.start === input.end ? { succeeded: true, next: { src: input.src, start: input.start, end: input.end, context: input.context, templateArgs: input.templateArgs, templateArgsPos: input.templateArgsPos, }, tokens: helper ? [helper()] : [], } : { succeeded: false, error: false, src: input.src, pos: input.start, message: 'operator "end"', }); }); } // TODO: match by callback function parser // TODO: `nesting` parser export function quantify<T extends ArrayLike<T[number]>, C, R>( min?: number, max?: number ): (parser: ParserFnWithCtx<T, C, R>) => ParserFnWithCtx<T, C, R> { min = min || 0; return (parser => { return (input => { let next = input; const matched: Array<{next: ParserInputWithCtx<T, C>, tokens: R[]}> = []; for (;;) { const x = parser(next); if (x.succeeded) { next = x.next; matched.push({next: x.next, tokens: x.tokens}); if (max && max === matched.length) { break; } } else { if (x.error) { return x; } if (matched.length >= (min as number)) { break; } else { return ({ succeeded: false, error: false, src: next.src, pos: next.start, message: 'operator "quantify"', }); } } } if (matched.length > 0) { const r: R[] = []; for (const x of matched) { r.push(...x.tokens); } return ({ succeeded: true, next: (matched[matched.length - 1]).next, tokens: r, }); } else { return ({ succeeded: true, next: { src: input.src, start: input.start, end: input.end, context: input.context, templateArgs: input.templateArgs, templateArgsPos: input.templateArgsPos, }, tokens: [], }); } }); }); } export function first<T extends ArrayLike<T[number]>, C, R>( ...parsers: Array<ParserFnWithCtx<T, C, R>> ): ParserFnWithCtx<T, C, R> { return (input => { let matched: {next: ParserInputWithCtx<T, C>, tokens: R[]} | null = null; let last: ParserFnFailedResult<T, C, R> | null = null; for (const parser of parsers) { const x = parser(input); if (x.succeeded) { matched = {next: x.next, tokens: x.tokens}; break; } if (last) { if (x.error) { if (!last.error || last.pos < x.pos) { last = x; } } else if (last.pos < x.pos) { last = x; } } else { last = x; } } return (matched ? { succeeded: true, next: matched.next, tokens: matched.tokens } : last ? last : { succeeded: false, error: false, src: input.src, pos: input.start, message: 'operator "first"', }); }); } export function or<T extends ArrayLike<T[number]>, C, R>( ...parsers: Array<ParserFnWithCtx<T, C, R>> ): ParserFnWithCtx<T, C, R> { return (input => { const matched: Array<{next: ParserInputWithCtx<T, C>, tokens: R[]}> = []; let last: ParserFnFailedResult<T, C, R> | null = null; for (const parser of parsers) { const x = parser(input); if (x.succeeded) { matched.push({next: x.next, tokens: x.tokens}); } else { if (last) { if (x.error) { if (!last.error || last.pos < x.pos) { last = x; } } else if (last.pos < x.pos) { last = x; } } else { last = x; } } } if (matched.length > 0) { const z = matched.reduce((a, b) => a.next.start >= b.next.start ? a : b); return ({succeeded: true, next: z.next, tokens: z.tokens}); } return (last ? last : { succeeded: false, error: false, src: input.src, pos: input.start, message: 'operator "or"', }); }); } export function transform<T extends ArrayLike<T[number]>, C, R>( trans?: ((tokens: R[], input: ParserInputWithCtx<T, C>) => R[]), ctxTrans?: ((context: C) => C) ): (...parsers: Array<ParserFnWithCtx<T, C, R>>) => ParserFnWithCtx<T, C, R> { return ((...parsers) => { return (input => { let next = input; const tokens: R[] = []; for (const parser of parsers) { const x = parser(next); if (! x.succeeded) { return x; } next = x.next; tokens.push(...x.tokens); } // TODO: report errors while transforming const t2 = trans ? trans(tokens, input) : tokens; return ({ succeeded: true, next: ctxTrans ? { src: next.src, start: next.start, end: next.end, context: ctxTrans(next.context), templateArgs: next.templateArgs, templateArgsPos: next.templateArgsPos, } : next, tokens: t2, }); }); }); } export function combine<T extends ArrayLike<T[number]>, C, R>( ...parsers: Array<ParserFnWithCtx<T, C, R>> ): ParserFnWithCtx<T, C, R> { return transform<T, C, R>()(...parsers); } export function lookAhead<T extends ArrayLike<T[number]>, C, R>( ...parsers: Array<ParserFnWithCtx<T, C, R>> ): ParserFnWithCtx<T, C, R> { return (input => { let next = input; for (const parser of parsers) { const x = parser(next); if (! x.succeeded) { return x; } next = x.next; } return ({ succeeded: true, next: input, tokens: [], }); }); } export function lookBehind<T extends ArrayLike<T[number]>, C, R>( n: number, helper?: () => R): ( ...parsers: Array<ParserFnWithCtx<T, C, R>> ) => ParserFnWithCtx<T, C, R> { return ((...parsers) => { return (input => { if (input.start - n < 0) { return ({ succeeded: false, error: false, src: input.src, pos: input.start, message: 'lookBehind: src is too short', }); } let next: ParserInputWithCtx<T, C> = { src: input.src, start: input.start - n, end: input.end, context: input.context, templateArgs: input.templateArgs, templateArgsPos: input.templateArgsPos, }; for (const parser of parsers) { const x = parser(next); if (! x.succeeded) { return x; } next = x.next; } return ({ succeeded: true, next: input, tokens: helper ? [helper()] : [], }); }); }); } // tslint:disable-next-line: interface-over-type-literal export type ApplyProductionRulesArg<T extends ArrayLike<T[number]>, C, R> = { rules: Array<ParserFnWithCtx<R[], C, R> | {parser: ParserFnWithCtx<R[], C, R>, rtol: boolean}>, maxApply?: number, check: ParserFnWithCtx<R[], C, R>, }; export function applyProductionRules<T extends ArrayLike<T[number]>, C, R>( args: ApplyProductionRulesArg<T, C, R> ): (lexer: ParserFnWithCtx<T, C, R>) => ParserFnWithCtx<T, C, R> { return (lexer => { return (lexerInput => { const lexResult = lexer(lexerInput); if (! lexResult.succeeded) { return lexResult; } const input = parserInput<R[], C>(lexResult.tokens, lexerInput.context); let next = input; let completed = false; if (args.check(next).succeeded) { return ({ succeeded: true, next: lexResult.next, tokens: lexResult.tokens, }); } completed: for (let i = 0; args.maxApply !== void 0 ? i < args.maxApply : true; i++) { let matched = false; rules: for (const rule of args.rules) { const {parser, rtol} = typeof rule === 'function' ? {parser: rule, rtol: false} : rule; const len = next.src.length; for (let s = 0; s <= len; s++) { const x = parser({ src: next.src, start: rtol ? len - s : s, end: next.src.length, context: next.context, templateArgs: next.templateArgs, templateArgsPos: next.templateArgsPos, }); if (x.succeeded) { matched = true; const nextSrc = next.src.slice(0, rtol ? len - s : s); nextSrc.push(...x.tokens); nextSrc.push(...next.src.slice(x.next.start)); next = { src: nextSrc, start: 0, end: nextSrc.length, context: x.next.context, templateArgs: x.next.templateArgs, templateArgsPos: x.next.templateArgsPos, }; if (args.check(next).succeeded) { completed = true; break completed; } break rules; } } } if (! matched) { break; } } if (! completed) { if (! args.check(next).succeeded) { throw new ParseError({ succeeded: false, error: true, src: input.src, pos: input.start, message: 'The application of production rules was not finished', }); } } return ({ succeeded: true, next: lexResult.next, tokens: next.src, }); }); }); } export function makeProgram<T extends ArrayLike<T[number]>, C, R>( parser: ParserFnWithCtx<T, C, R>): ParserFnWithCtx<T, C, R> { return (input => { try { return parser(input); } catch (e) { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (e.result) { // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access return e.result; } else { throw e; } } }); }