UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

203 lines • 6.97 kB
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= /** Build a dense array from consecutive values, starting at index 0. */ export function makeArray(values) { return [...values]; } /** * One past the highest assigned index, which is what bash resolves a * negative subscript against. */ export function arrayExtent(arr) { return arr.length; } /** The assigned values in index order, skipping holes. */ export function arrayValues(arr) { return arr.filter((v) => v !== null); } /** The assigned indices in order, skipping holes. */ export function arrayIndices(arr) { const out = []; arr.forEach((v, i) => { if (v !== null) out.push(i); }); return out; } /** The number of assigned elements, which is `${#arr[@]}`. */ export function arrayCount(arr) { return arrayValues(arr).length; } /** Whether `idx` holds an assigned element. */ export function arrayHas(arr, idx) { return idx >= 0 && idx < arr.length && arr[idx] !== null && arr[idx] !== undefined; } /** * The element at `idx`, or the empty string for a hole or an * out-of-range index. */ export function arrayGet(arr, idx) { if (idx < 0 || idx >= arr.length) return ''; return arr[idx] ?? ''; } /** * Assign `value` at `idx`, extending with holes as needed. * * The subscript comes from script text, so the write goes through * `splice` rather than `arr[idx] = value`: an element assignment on a * caller-supplied key is a prototype-pollution shape, and `splice` * cannot name a property at all. */ export function arraySet(arr, idx, value) { while (arr.length <= idx) arr.push(null); arr.splice(idx, 1, value); } /** * A copy of `arr` with `value` assigned at `idx`. * * What a writer hands the session view: the session view speaks in whole * variables, so an element write states itself as the array the write * produces. Building it on a copy is what keeps a refusal from leaving * the element applied. */ export function arrayWith(arr, idx, value) { const updated = [...arr]; arraySet(updated, idx, value); return updated; } /** * Take the assigned elements from index `offset` on, in index order. * * bash slices an indexed array by *subscript*, not by position among the * assigned values: for `a=([1]=b [3]=d [9]=j)`, `${a[@]:2}` is `d j` * because it keeps every index >= 2. `length` then caps how many of those * are taken. A negative offset counts back from the extent and yields * nothing if it is still negative. */ export function arraySlice(arr, offset, length) { let start = offset; if (start < 0) { start += arrayExtent(arr); if (start < 0) return []; } const picked = []; arr.forEach((v, i) => { if (v !== null && i >= start) picked.push(v); }); if (length === null) return picked; if (length < 0) return picked.slice(0, Math.max(0, picked.length + length)); return picked.slice(0, length); } /** * Clear one element, keeping the indices of the elements after it. * * Trailing holes are dropped so the extent stays at one past the highest * assigned index, matching how bash resolves `arr[-1]`. */ export function arrayUnset(arr, idx) { if (idx < 0 || idx >= arr.length) return; arr.splice(idx, 1, null); while (arr.length > 0 && arr[arr.length - 1] === null) arr.pop(); } /** * Split one `[key]=value` literal element, null for a plain word. * * The split lands on the first `]=`, which is where bash finds it after * quote removal; a key that itself holds `]=` needed quoting in bash too * and is the one spelling this cannot recover. */ export function keyedWord(word) { if (!word.startsWith('[')) return null; const pos = word.indexOf(']=', 1); if (pos <= 1) return null; return [word.slice(1, pos), word.slice(pos + 2)]; } /** * The indexed array a compound literal produces. * * A `[i]=v` element places at `i` and moves the cursor past it, a plain * word continues from the cursor, and a repeated index keeps the last * value, which is GNU's `([3]=x y [1]=z)` giving * `([1]="z" [3]="x" [4]="y")`. `+=` starts the cursor at the extent * instead of replacing. `indexOf` is async because a subscript may * assign, and the assignment lands through the session view. */ export async function buildIndexedLiteral(base, words, append, indexOf) { const arr = append && base !== null ? [...base] : []; let cursor = append ? arrayExtent(arr) : 0; for (const word of words) { const keyed = keyedWord(word); if (keyed !== null) { let idx = await indexOf(keyed[0]); if (idx < 0) idx += arrayExtent(arr); if (idx < 0) continue; arraySet(arr, idx, keyed[1]); cursor = idx + 1; } else { arraySet(arr, cursor, word); cursor++; } } return arr; } /** * The associative array a compound literal produces. * * The first word picks the grammar, as GNU does: a `[key]=value` first * word makes every plain word an error (reported back for the caller to * render in bash's must-use-subscript voice), while a plain first word * reads the whole list as alternating keys and values, `[a]=1` * spellings included, literally. An odd pair list stores the last key * with an empty value. A repeated key keeps the last value; `+=` merges * over the existing map instead of replacing. */ export function buildAssocLiteral(base, words, append) { const map = append && base !== null ? { ...base } : {}; const first = words[0]; if (first === undefined) return { map, badWords: [] }; if (keyedWord(first) === null) { for (let i = 0; i < words.length; i += 2) { const key = words[i]; if (key === undefined) break; map[key] = words[i + 1] ?? ''; } return { map, badWords: [] }; } const badWords = []; for (const word of words) { const keyed = keyedWord(word); if (keyed === null) { badWords.push(word); continue; } map[keyed[0]] = keyed[1]; } return { map, badWords }; } //# sourceMappingURL=array.js.map