!7 cmodule FluidTextFileSpike > DynPrintLog { srecord Entry(S header, S text, SS params) { long targetIndex; S actualText() { ret unquote(text); } } switchable int maxRefLength = 8; long maxFileSize() { ret rangeOfNHexNumberWithNDigits(maxRefLength); } transient S demoContent = autoUnindent_mls([=[ refs="at *", name="index" [[ entry 1 at abc entry 2 at def ]] i=abc [[hello]] i=def [[world]] ]=]); start-thread { print(demoContent); Producer p = javaTok_onReader(stringReader(demoContent)); new StringBuilder buf; new L entries; S t; p.next(); // skip initial white space while ((t = p.next()) != null) { if (isMLS(t)) { S header = getAndClearStringBuilder(buf); entries.add(new Entry(header, t, parseEqualsCommaProperties(header))); p.next(); // skip white space } else buf.append(t); // part of entry header } pnlStruct(entries); // make a map and pessimistically assign byte indices to all entries new Map relocationMap; for (Entry e : entries) mapPut(relocationMap, e.params.get("i"), e); makeIndices(entries); print(+relocationMap); // rewrite references for (Entry e : entries) if (hasReferences(e)) e.text = replaceRefs(e, r -> lookupInRelocationMap(relocationMap, r)); // write out long idx = 0; // index into file for (Entry e : entries) { assertEquals(idx, e.targetIndex); e.params.put("i", renderReference(e.targetIndex)); S line = entryToString(e, idx); print_noNewLine(line); idx += lUtf8(line); } } S lookupInRelocationMap(Map relocationMap, S ref) { Entry e = relocationMap.get(ref); //print("lookupInRelocationMap: " + ref + " => " + e); ret e == null ? null : renderReference(e.targetIndex); } S renderReference(long idx) { ret takeLast(maxRefLength, longToHex(idx)); } void makeIndices(L entries) { long idx = 0; // index into file for (Entry e : entries) { e.targetIndex = idx; idx += pessimisticEntryLength(e); } } S entryToString(Entry e, long idx) { ret entryToString(e, renderReference(idx)); } S entryToString(Entry e, S idx) { SS params = litorderedmap( i := idx, l := intToHex_flexLength(l(e.text))); mapPutAll_noOverwrite(params, e.params); ret renderEqualsCommaProperties(params) + " " + e.text + "\n"; } S dummyRef() { ret rep('0', maxRefLength); } bool hasReferences(Entry e) { ret eq(e.params.get("refs"), quote("at *")); } int pessimisticEntryLength(Entry e) { Entry e2 = e; if (hasReferences(e)) { e2 = cloneObject(e); e2.text = replaceRefs(e, r -> dummyRef()); } ret l(entryToString(e2, dummyRef())); } S replaceRefs(Entry e, IF1 f) { LS tok = javaTok(e.actualText()); for (int i : jfindAll(tok, "at *")) { S x = f.get(tok.get(i+2)); if (nempty(x)) tok.set(i+2, x); } ret multiLineQuote(join(tok)); } }