import { Parser, Result, Output, List, Node } from '../combinator/parser';
import { tester } from '../combinator/delimiter';
import { recur } from '../combinator';
import { Input, Recursion, Command } from './context';
import { min } from 'spica/alias';
export function repeat
>(
opener: string, after: string | RegExp, closer: string, recursion: Recursion, parser: P,
cons: (nodes: List>>, input: Parser.Input, output: Output>, lead: number, follow: number) =>
List>>,
termination?: (nodes: List>>, input: Input, output: Output>, prefix: number, postfix: number, state: boolean) =>
Result>,
): P;
export function repeat(
opener: string, after: string | RegExp, closer: string, recursion: Recursion, parser: Parser,
cons: (nodes: List>, input: Input, output: Output, lead: number, follow: number) =>
List>,
termination: (nodes: List>, input: Input, output: Output, prefix: number, postfix: number, state: boolean) =>
Result =
(nodes, input, output, prefix, postfix) => {
if (prefix > 0) {
nodes.unshift(new Node(opener[0].repeat(prefix)));
input.range += prefix;
}
if (postfix > 0) {
const { source, position } = input;
nodes.push(new Node(source.slice(position, position + postfix)));
input.position += postfix;
input.range += postfix;
}
return output.import(nodes);
},
): Parser {
const test = tester(after, false);
interface Memory {
readonly position: number;
linebreak: number;
i: number;
lead: number;
follow: number;
state: boolean;
depth: number;
}
const cont: Result> = [
(input, output) => {
const { source, position, linebreak, resources: { recursions } } = input;
if (!source.startsWith(opener, input.position)) return Result.skip;
let i = opener.length;
for (; source[input.position + i] === source[input.position];) ++i;
input.position += i;
if (!test(input, output)) {
input.position = position;
return Result.skip;
}
const depth = i / opener.length + 1 | 0;
recur(output, recursions, recursion, depth, true);
input.memory = {
position,
linebreak,
i,
lead: 0,
follow: 0,
state: false,
depth,
};
input.linebreak = 0;
output.push();
return loop;
},
(input, output) => {
const { source, memory: m, resources: { recursions } } = input;
recur(output, recursions, recursion, -m.depth);
m.depth = 0;
input.linebreak ||= m.linebreak;
const prefix = m.i;
m.i = 0;
for (let len = min(prefix, source.length - input.position); m.i < len && source[input.position + m.i] === closer[0];) {
++m.i;
}
const postfix = m.i;
input.range = input.position - m.position - prefix;
return termination(output.pop(), input, output, prefix, postfix, m.state);
},
];
const loop: Result> = [
(input, output) => {
const { source, memory: m, resources: { recursions } } = input;
for (; m.i >= opener.length; m.i -= opener.length, m.follow -= closer.length) {
recur(output, recursions, recursion, -1);
m.depth -= 1;
const lead = m.lead = m.i - opener.length;
if (source.startsWith(closer, input.position)) {
input.position += closer.length;
const pos = input.position;
m.follow = m.follow > 0 ? m.follow : countFollows(source, pos, closer, lead / opener.length | 0);
input.range = input.position - m.position - m.i + opener.length;
output.push(cons(output.pop(), input, output, lead, m.follow));
if (input.position > pos) {
const advance = input.position - pos;
m.i -= advance;
m.follow -= advance;
recur(output, recursions, recursion, -(advance / closer.length | 0));
m.depth -= advance / closer.length | 0;
}
continue;
}
return output.context;
}
return Result.skip;
},
parser,
(input, output) => {
const { source, memory: m, resources: { recursions } } = input;
const { lead } = m;
input.range = input.position - m.position - m.i + opener.length;
if (!output.state) return;
const pos = input.position;
const nodes = output.peek();
switch (nodes.last?.value) {
case Command.Cancel:
assert(!source.startsWith(closer, input.position));
nodes.pop();
m.state = false;
return;
case Command.Separator:
assert(!source.startsWith(closer, input.position));
m.follow = m.follow > 0 ? m.follow : countFollows(source, pos, closer, lead / opener.length | 0);
nodes.pop();
m.state = true;
m.i -= opener.length, m.follow -= closer.length;
return loop;
default:
m.follow = m.follow > 0 ? m.follow : countFollows(source, pos, closer, lead / opener.length | 0);
output.push(cons(output.pop(), input, output, lead, m.follow));
m.state = true;
if (input.position > pos) {
const advance = input.position - pos;
m.i -= advance;
m.follow -= advance;
recur(output, recursions, recursion, -(advance / closer.length | 0));
m.depth -= advance / closer.length | 0;
}
m.i -= opener.length, m.follow -= closer.length;
return loop;
}
},
];
return () => cont;
}
function countFollows(source: string, position: number, closer: string, limit: number): number {
if (closer.length === 0) return 0;
let pos = position;
for (let i = 0; i < limit && pos < source.length; ++i, pos += closer.length) {
if (!source.startsWith(closer, pos)) break;
}
return pos - position;
}