@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
91 lines • 3.25 kB
JavaScript
import { named } from "../../shell/errors.js";
import { unescapeUnquoted } from "../../shell/escapes.js";
import { getText } from "../../shell/helpers.js";
function span(node) {
if (node.startIndex === undefined || node.endIndex === undefined) {
throw new Error('substring operand has no source span');
}
return [node.startIndex, node.endIndex];
}
function atoms(node) {
if (node.type === 'concatenation')
return node.children.flatMap(atoms);
return node.isNamed === true && !['word', 'number'].includes(node.type) ? [node] : [];
}
function separator(text, start, end, nodes, base) {
const opaque = new Map(nodes.map((node) => {
const [start, end] = span(node);
return [start - base, end - base];
}));
let depth = 0;
let ternary = 0;
let index = start;
while (index < end) {
const next = opaque.get(index);
if (next !== undefined) {
index = next;
continue;
}
const char = text[index] ?? '';
if (char === '\\') {
index += 2;
continue;
}
if (char === '(' || char === '[')
depth += 1;
else if (char === ')' || char === ']')
depth -= 1;
else if (depth === 0) {
if (char === '?')
ternary += 1;
else if (char === ':') {
if (ternary === 0)
return index;
ternary -= 1;
}
}
index += 1;
}
return end;
}
/**
* Split offset and length before expanding nested words. Substituted colons
* are data; quotes, substitutions, subscripts, parentheses and ternaries own
* their colons. Scalar and array slicing share this path, and arithmetic
* evaluation applies the offset before requesting the next operand.
*/
export async function* substringOperands(node, expandChild) {
const operator = node.children.find((child) => getText(child) === ':');
if (operator === undefined)
throw new Error('substring operator missing');
const [base] = span(node);
const [, operandStart] = span(operator);
const nodes = node.children
.filter((child) => span(child)[0] >= operandStart && child.type !== '}')
.flatMap(atoms);
const text = getText(node);
const start = operandStart - base;
const end = text.length - 1;
const split = separator(text, start, end, nodes, base);
const spans = [[start, split]];
if (split < end)
spans.push([split + 1, end]);
for (const [begin, stop] of spans) {
const pieces = [];
let cursor = begin;
const word = text.slice(begin, stop);
for (const atom of nodes) {
const [atomStart, atomEnd] = span(atom);
const left = atomStart - base;
const right = atomEnd - base;
if (begin <= left && right <= stop) {
pieces.push(unescapeUnquoted(text.slice(cursor, left)));
pieces.push(await named(word, expandChild(atom)));
cursor = right;
}
}
pieces.push(unescapeUnquoted(text.slice(cursor, stop)));
yield pieces.join('');
}
}
//# sourceMappingURL=substring.js.map