UNPKG

fruitsconfits

Version:

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

457 lines 15.6 kB
// Copyright (c) 2019 Shellyl_N and Authors // license: ISC // https://github.com/shellyln import { ParseError, parserInput } from './types'; function getLineAndCol(src, pos) { 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(result) { let msg = ''; let src = ''; if (typeof result.src === 'string') { src = result.src.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.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.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.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(helper) { 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(message) { 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(helper) { 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(helper) { 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(min, max) { min = min || 0; return (parser => { return (input => { let next = input; const matched = []; 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) { break; } else { return ({ succeeded: false, error: false, src: next.src, pos: next.start, message: 'operator "quantify"', }); } } } if (matched.length > 0) { const 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(...parsers) { return (input => { let matched = null; let last = 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(...parsers) { return (input => { const matched = []; let last = 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(trans, ctxTrans) { return ((...parsers) => { return (input => { let next = input; const tokens = []; 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(...parsers) { return transform()(...parsers); } export function lookAhead(...parsers) { 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(n, helper) { 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 = { 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()] : [], }); }); }); } export function applyProductionRules(args) { return (lexer => { return (lexerInput => { const lexResult = lexer(lexerInput); if (!lexResult.succeeded) { return lexResult; } const input = parserInput(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(parser) { 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; } } }); } //# sourceMappingURL=parser.js.map