import { Parser, Result, Node } from '../parser'; import { some } from '../control/some'; import { always } from '../control/state'; import { block } from './block'; import { line } from './line'; import { match } from './match'; import { open } from './surround'; import { memoize } from 'spica/memoize'; export function indent

(parser: P, separation?: boolean): P; export function indent

(opener: RegExp, parser: P, separation?: boolean): P; export function indent(opener: RegExp | Parser, parser: Parser | boolean = false, separation = false): Parser { if (typeof opener === 'function') { separation = parser as boolean; parser = opener; opener = / {1,4}|\t{1,2}/y; } assert(!opener.flags.match(/[gm]/) && opener.sticky && !opener.source.startsWith('^')); assert(parser = parser as Parser); return always([ (input, output) => { if (input.position === input.source.length) return Result.skip; output.push(); return output.context; }, block(match(opener, memoize( ([indent]) => some(open(indent, line(({ source }, output) => output.append(new Node(source))))), ([indent]) => indent.length * 2 + -(indent[0] === ' '), [], 2 ** 4 - 1)), separation), (input, output) => { const source = trimBlockEnd(output.pop().foldl((acc, node) => acc + node.value, '')); if (source === '') return Result.skip; input.scope.focus(source); return output.context; }, parser, (input, output) => { input.scope.unfocus(); return output.context; }, ]); } function trimBlockEnd(block: string): string { return block === '' || block.at(-1) !== '\n' ? block : block.slice(0, block.at(-2) === '\r' ? -2 : -1); }