142 lines
3.7 KiB
TypeScript

"use client";
import { zipArrays } from "../zip";
import { TokenIdentifiers } from "./TokenIdentifiers";
export const createElements = (body: string): [TokenMarker[], number] => {
const tabOptions = [
/^\s{2}(?!\s|\t)/m,
/^\s{4}(?!\s|\t)/m,
/^\t(?!\s|\t)]/m,
];
let tabSpacing = 0;
for (const [i, tabOption] of tabOptions.entries()) {
if (body.match(tabOption)) {
tabSpacing = i;
break;
}
}
const tokens = tokenize(body);
return [buildAbstractSyntaxTree(tokens), tabSpacing];
};
const tokenize = (body: string) => {
const tokenizedBody: TokenMarker[] = [];
const addToken = (thing: TokenMarker) => {
tokenizedBody.push(thing);
};
for (const [type, token] of TokenIdentifiers.entries()) {
const rx = new RegExp(token.rx);
let match;
while ((match = rx.exec(body)) !== null) {
const start = match.index;
const end = rx.lastIndex;
addToken({
start,
end,
type,
token: token.parse(match[0]),
});
}
}
return tokenizedBody;
};
function buildAbstractSyntaxTree(markers: TokenMarker[]) {
markers.sort((a, b) => a.start - b.start);
markers = filterOverlappingPBlocks(markers);
establishClosestParent(markers);
for (const marker of markers) {
if (marker.parent) {
marker.parent.token.children = marker.parent.token.children || [];
marker.parent.token.children.push(marker.token);
}
}
// By starting at the end, we can always assure that we are not filtering out children that haven't been processed yet
for (const marker of [...markers].reverse()) {
contentToChildren(marker.token);
}
return markers.filter((m) => !m.parent);
// return markers;
}
function establishClosestParent(blocks: TokenMarker[]): void {
blocks.sort((a, b) => a.start - b.start); // Sort blocks by start position
for (let i = 0; i < blocks.length; i++) {
const block = blocks[i];
if (block.parent) continue; // Skip blocks that already have a parent
let closestParent: TokenMarker | undefined = undefined;
let minDistance = Number.MAX_SAFE_INTEGER;
// Find the closest parent block for each block
for (let j = 0; j < i; j++) {
const otherBlock = blocks[j];
if (otherBlock.end >= block.start && otherBlock.start <= block.start) {
const distance = block.start - otherBlock.start;
if (distance < minDistance) {
minDistance = distance;
closestParent = otherBlock;
}
}
}
if (closestParent) {
block.parent = closestParent; // Assign the closest parent block
}
}
}
function filterOverlappingPBlocks(blocks: TokenMarker[]): TokenMarker[] {
return blocks.filter((block) => {
if (block.type !== "p") {
return true; // Keep blocks that are not 'p' type
}
// Filter out 'p' blocks that overlap with any other block
for (const otherBlock of blocks) {
if (
otherBlock !== block && (
otherBlock.start === block.start ||
otherBlock.end === block.end
)
) {
return false; // Overlapping 'p' block found, filter it out
}
}
return true; // Keep 'p' block if it doesn't overlap with any other block
});
}
const contentToChildren = (token: Token) => {
let content = token.content;
const splitMarker = "{{^^}}";
for (const child of token.children || []) {
content = content.replace(child.raw, splitMarker);
}
token.children = zipArrays(
content.split(splitMarker).map((c): Token => ({
content: c.replaceAll("\n", ""),
metadata: {},
raw: c,
type: "text",
uuid: crypto.randomUUID(),
rendersContentOnly: true,
})),
token.children || [],
).filter((c) => c.children?.length || (c.rendersContentOnly && c.content));
};