diff --git a/source/niknaks/containers.d b/source/niknaks/containers.d index 9c1a01c..7a82d79 100644 --- a/source/niknaks/containers.d +++ b/source/niknaks/containers.d @@ -597,4 +597,441 @@ unittest // Destroy the map (such that it ends the sweeper destroy(map); +} + +private struct Sector(T) +{ + private T[] data; + + this(T[] data) + { + this.data = data; + } + + public T opIndex(size_t idx) + { + return this.data[idx]; + } + + public void opIndexAssign(T value, size_t index) + { + this.data[index] = value; + } + + // Contract: Obtaining the length must be present + public size_t opDollar() + { + return this.data.length; + } + + // Contract: Obtaining the length must be present + @property + public size_t length() + { + return opDollar(); + } + + public T[] opSlice(size_t start, size_t end) + { + return this.data[start..end]; + } + + public T[] opSlice() + { + return opSlice(0, opDollar); + } + + // Contract: Rezising must be implemented + // TODO: This would then be the very reason for + // using ref actually, as resizing may only + // change a local copy when extding on + // the tail-end "extent" (SectorType) + + // Actually should resizing even be done here? + +} + +// TODO: Make a bit better +private bool isSector(S)() +{ + return __traits(hasMember, S, "opIndex"); +} + +import core.exception : ArrayIndexError; +import core.exception : RangeError; + +public struct View(T, SectorType = Sector!(T)) +if(isSector!(SectorType)()) +{ + private SectorType[] sectors; + // private + + // Maybe current size should be here as we + // are a view, we should allow modofication + // but not make any NEW arrays + private size_t curSize; + + private size_t computeTotalLen() + { + size_t l; + foreach(SectorType sector; this.sectors) + { + l += sector.opDollar(); + } + return l; + } + + public size_t opDollar() + { + return this.length; + } + + public T opIndex(size_t idx) + { + // Within range of "fake" size + if(!(idx < this.length)) + { + throw new ArrayIndexError(idx, this.length); + } + + size_t thunk; + foreach(SectorType sector; this.sectors) + { + if(idx-thunk < sector.opDollar()) + { + return sector[idx-thunk]; + } + else + { + thunk += sector.opDollar(); + } + } + + throw new ArrayIndexError(idx, this.length); + } + + public void opIndexAssign(T value, size_t idx) + { + // Within range of "fake" size + if(!(idx < this.length)) + { + throw new ArrayIndexError(idx, this.length); + } + + size_t thunk; + // TODO: Should be ref, else it is just a local struct copy + // could cheat if sector is never replaced, hence why it works + foreach(SectorType sector; this.sectors) + { + writeln(sector); + writeln("idx: ", idx); + writeln("thunk: ", thunk); + if(idx-thunk < sector.opDollar()) + { + sector[idx-thunk] = value; + return; + } + else + { + thunk += sector.opDollar(); + } + } + + throw new ArrayIndexError(idx, this.length); + } + + public T[] opSlice() + { + return this[0..this.length]; + } + + public T[] opSlice(size_t start, size_t end) + { + // Invariant of start < end + if(!(start <= end)) + { + // TODO: Check + throw new RangeError("Starting index must be smaller than or equal to ending index"); + } + // Within range of "fake" size + else if(!((start < this.length) && (end <= this.length))) + { + throw new RangeError("start index or end index not under range"); + // throw new ArrayIndexError(idx, this.length); + } + + T[] collected; + + size_t thunk; + foreach(SectorType sector; this.sectors) + { + // If the current sector contains + // both the starting AND ending + // indices + if(start-thunk < sector.opDollar() && end-thunk <= sector.opDollar()) + { + return sector[start-thunk..end-thunk]; + } + // If the current sector's starting + // index (only) is included + else if(start-thunk < sector.opDollar() && !(end-thunk <= sector.opDollar())) + { + collected ~= sector[start-thunk..$]; + } + // If the current sector's ending + // index (only) is included + else if(!(start-thunk < sector.opDollar()) && end-thunk <= sector.opDollar()) + { + collected ~= sector[0..end-thunk]; + } + // If the current sector's entirety + // is to be included + else + { + collected ~= sector[]; + } + + thunk += sector.opDollar(); + } + + + + // FIXME: This is lazy, do a check for up to where + // and actually make THIS the real implementation + + writeln(collected); + + // TODO: Also if the range matches the bounds + // of a given range exactly then extract directly + return collected; + } + + private static bool isArrayAppend(P)() + { + return __traits(isSame, P, T[]); + } + + private static bool isElementAppend(P)() + { + return __traits(isSame, P, T); + } + + // Append + public void opOpAssign(string op, E)(E value) + if(op == "~" && (isArrayAppend!(E) || isElementAppend!(E))) + { + static if(isArrayAppend!(E)) + { + add(value); + } + else + { + add([value]); + } + } + + // Takes the data, constructs a kind-of SectorType + // and adds it + private void add(T[] data) + { + // Create a new sector + SectorType sec = SectorType(data); + + // Update the tracking size + this.curSize += sec.length; + + // Concatenate it to the view + this.sectors ~= SectorType(data); + } + + @property + public size_t length() + { + return this.curSize; + } + + @property + public void length(size_t size) + { + // TODO: Add support for sizing down + // TODO: Add support for sizing up + + // TODO: Need we continuously compute this? + // ... we should have a tracking field for + // ... this + size_t actualSize = computeTotalLen(); + + // On successful exit, update the "fake" size + scope(success) + { + this.curSize = size; + } + + + // Don't allow sizing up (doesn't make sense for a view) + if(size > actualSize) + { + auto r = new RangeError(); + r.msg = "Cannot extend the size of a view past its total size (of all attached sectors)"; + throw r; + } + // If nothing changes + else if(size == actualSize) + { + // Nothing + } + // If shrinking to zero + else if(size == 0) + { + // Just drop everything + this.sectors.length = 0; + } + // If shrinking (arbitrary) + else + { + // Sectors from left-to-right to keep + size_t sectorCnt; + + // Accumulator + size_t accumulator; + + foreach(SectorType sector; this.sectors) + { + accumulator += sector.length; + sectorCnt++; + if(size <= accumulator) + { + break; + } + } + + this.sectors.length = sectorCnt; + } + + } +} + +unittest +{ + View!(int) view; + assert(view.opDollar() == 0); + + try + { + view[1]; + assert(false); + } + catch(ArrayIndexError e) + { + assert(e.index == 1); + assert(e.length == 0); + } + + view ~= [1,3,45]; + assert(view.opDollar() == 3); + assert(view.length == 3); + + view ~= 2; + assert(view.opDollar() == 4); + assert(view.length == 4); + + assert(view[0] == 1); + assert(view[1] == 3); + assert(view[2] == 45); + assert(view[3] == 2); + assert(view[0..2] == [1,3]); + assert(view[0..4] == [1,3,45,2]); + + // Update elements + view[0] = 71; + view[3] = 50; + + // Set size to same size + view.length = view.length; + + // Check that update is present + // and size unchanged + int[] all = view[]; + assert(all == [71,3,45,50]); + + // Truncate by 1 element + view.length = view.length-1; + all = view[]; + assert(all == [71,3,45]); + + // This should fail + try + { + view[3] = 3; + assert(false); + } + catch(RangeError e) + { + } + + // This should fail + try + { + int j = view[3]; + assert(false); + } + catch(RangeError e) + { + } + + // Up-sizing past real size should not be allowed + try + { + view.length = view.length+1; + assert(false); + } + catch(RangeError e) + { + } + + // Size to zero + view.length = 0; + assert(view.length == 0); + assert(view[] == []); +} + +unittest +{ + View!(int) view; + view ~= 1; + view ~= [2,3,4]; + view ~= 5; + + assert(view[0..5] == [1,2,3,4,5]); + + // test: start <= end invariant broken + try + { + auto j = view[1..0]; + assert(false); + } + catch(RangeError e) + { + + } + + // test: end out of bounds + try + { + auto j = view[1..view.length+1]; + assert(false); + } + catch(RangeError e) + { + + } + + int[] d = [1,2,3]; + writeln("according to dlang: ", d[1..2]); + + writeln("test lekker: ", view[1..2]); + assert(view[1..2] == [2]); + + writeln("test lekker: ", view[1..1]); + assert(view[1..1] == []); } \ No newline at end of file