-- BTree2.mesa
-- last edited by Levin: 29-Jan-82 15:21:01
DIRECTORY
BTree2Defs USING [EntryR, Index, VArray];
-- A Varray consists of:
-- 1) the number of items (n) stored in the array.
-- 2) a mumble, described below.
-- 3) n packed descriptors for the items, each consisting of a pointer to the starting place and a length for the key.
-- 4) some empty space perhaps
-- 5) the items themselves, stored backward from the end of array
-- The length of the value part of the item can be deduced from the start of the preceeding item. The mumble is a start for a phony item so it all works without special end tests.
-- The Copy routines make the code smaller by 10%
-- A Copy is A[i] ←B[i] + x for all i
-- A Slip moves entries startIndex and beyond from this array to newIndex and newData in array other, copying both index and data.
-- A Move appends entries startIndex and beyond from this array to the other array. move updates the entry count, slip doesn't.
-- (I've made a few cleanups, but I haven't the stomach to give this the rewrite it so desparately needs. RL)
BTree2: PROGRAM EXPORTS BTree2Defs =
BEGIN
NN: TYPE = CARDINAL;
RealJob: TYPE = {insert, replace, delete};
w: CARDINAL = 2;
OverheadPerItem: CARDINAL = 1;
nullER: BTree2Defs.EntryR ← [DESCRIPTOR[NIL, 0], DESCRIPTOR[NIL, 0]];
debug: BOOLEAN ← TRUE;
maxKeyLength: CARDINAL = 63;
Form: TYPE = MACHINE DEPENDENT RECORD [k: [0..1024), v: [0..maxKeyLength]];
Foo: TYPE = LONG POINTER TO ARRAY [0..3) OF Form;
IndexOutOfBounds: ERROR = CODE;
IllegalEntry: ERROR = CODE;
GetAnother: PUBLIC SIGNAL RETURNS [BTree2Defs.VArray] = CODE;
Merger: PUBLIC SIGNAL RETURNS [BTree2Defs.VArray, BOOLEAN] = CODE;
DeletePage: PUBLIC SIGNAL = CODE;
Initialize: PUBLIC PROCEDURE [v: BTree2Defs.VArray] =
BEGIN
i: CARDINAL;
FOR i IN [0..LENGTH[v↑]) DO v[i] ← 0; ENDLOOP;
v[1] ← LOOPHOLE[Form[LENGTH[v↑], 0]];
END;
Items: PUBLIC PROCEDURE [v: BTree2Defs.VArray] RETURNS [NN] = BEGIN RETURN[v[0]]; END;
Lookup: PUBLIC PROCEDURE [this: BTree2Defs.VArray, item: BTree2Defs.Index]
RETURNS [BTree2Defs.EntryR] =
BEGIN
foo: Foo;
startK, endK, startV, lengthK: NN;
[foo, , , startK, endK] ← Unpack[this, item];
IF item >= LOOPHOLE[this[0], INTEGER] THEN ERROR IndexOutOfBounds;
startV ← startK + (lengthK ← (foo + item)[0].v);
RETURN[
[
DESCRIPTOR[@this[startK], lengthK], DESCRIPTOR[
@this[startV], endK - startV]]];
END;
Insert: PUBLIC PROCEDURE [
this: BTree2Defs.VArray, item: BTree2Defs.Index, e: BTree2Defs.EntryR] =
BEGIN
IF ((LENGTH[e.k] > maxKeyLength) OR (LENGTH[e.k] + LENGTH[e.v] > 80)) THEN
ERROR IllegalEntry;
ReplaceX[this, item, e, insert];
END;
Delete: PUBLIC PROCEDURE [this: BTree2Defs.VArray, item: BTree2Defs.Index] =
{ReplaceX[this, item, nullER, delete]};
Replace: PUBLIC PROCEDURE [
this: BTree2Defs.VArray, item: BTree2Defs.Index, e: BTree2Defs.EntryR] =
{ReplaceX[this, item, e, replace]};
ReplaceX: PROCEDURE [
this: BTree2Defs.VArray, item: BTree2Defs.Index, e: BTree2Defs.EntryR,
r: RealJob] =
BEGIN
diddle: INTEGER = SELECT r FROM replace => 0, insert => 1, ENDCASE => -1;
foo: Foo;
other: BTree2Defs.VArray;
shrinkage: INTEGER;
nextIndex: BTree2Defs.Index;
startLast, startItem, page, halfPage, sizeKey, sizeVal: NN;
sizeNew, sizeOld, start, used, endItem: NN;
[foo, nextIndex, startLast, startItem, endItem] ← Unpack[this, item];
page ← (foo - 1)[0].k;
halfPage ← page/2;
sizeNew ← (sizeKey ← LENGTH[e.k]) + (sizeVal ← LENGTH[e.v]);
sizeOld ← IF r = insert THEN 0 ELSE endItem - startItem;
shrinkage ← sizeOld - sizeNew;
start ← startItem + shrinkage;
used ← (page - startLast) - shrinkage + (nextIndex - 1) + OverheadPerItem + w;
IF debug THEN Check[this];
IF nextIndex < item + 1 THEN ERROR IndexOutOfBounds;
IF used + 4 > page THEN
BEGIN
withoutItem: NN ← halfPage + w;
i: INTEGER;
withItem: NN ← halfPage + w - shrinkage + diddle;
other ← SIGNAL GetAnother;
FOR i IN [0..nextIndex) DO -- test is split result to be at >= halfPage.
IF foo[i].k <
((IF i >= item THEN withItem ELSE withoutItem) +
CARDINAL[i + 1]*OverheadPerItem) THEN
BEGIN -- and it better be <= Page.
IF foo[i].k <=
((IF i >= item THEN withItem - halfPage ELSE w) +
CARDINAL[i + 1]*OverheadPerItem) THEN
BEGIN IF i = 0 THEN ERROR; i ← i - 1; END;
Move[this, i + 1, other];
IF i >= item THEN
ReplaceX[this, item, e, r ! Merger, GetAnother => ERROR]
ELSE ReplaceX[other, item - i - 1, e, r ! Merger, GetAnother => ERROR];
RETURN;
END;
ENDLOOP;
ERROR; -- oops
END;
IF shrinkage # 0 THEN
Slip[this, this, item + 1, item + 1 + diddle, startLast + shrinkage];
IF r # delete THEN
BEGIN
foo[item + diddle] ← [start, sizeKey];
Copy[@e.k[0], @this[start], sizeKey, 0];
Copy[@e.v[0], @this[start + sizeKey], sizeVal, 0];
END;
this[0] ← nextIndex + diddle;
IF ((used < halfPage) AND (shrinkage > 0)) THEN Balance[this, used];
IF debug THEN Check[this];
END;
Balance: PROCEDURE [this: BTree2Defs.VArray, used: NN] =
BEGIN
oFoo: Foo;
other: BTree2Defs.VArray;
onRight: BOOLEAN;
page, halfPage, nextIndex, thisEnd: NN;
l, here, nextOther, otherEnd, i, carry: NN;
page ← LOOPHOLE[this[1], Form].k;
halfPage ← page/2;
[other, onRight] ← SIGNAL Merger;
IF other = NIL THEN RETURN;
[, nextIndex, thisEnd, , ] ← Unpack[this, 0];
[oFoo, nextOther, otherEnd, , ] ← Unpack[other, 0];
IF used + nextOther < otherEnd THEN
BEGIN
IF onRight THEN Move[other, 0, this] ELSE Move[this, 0, other];
SIGNAL DeletePage;
RETURN;
END;
carry ← halfPage + w + (IF onRight THEN used ELSE 0);
-- magic to leave the page on the left just over half full
i ← 0;
UNTIL carry + i > (here ← (oFoo + i - 1)[0].k) DO
IF i >= nextOther THEN EXIT; i ← i + 1; ENDLOOP;
IF carry + i <= here THEN ERROR;
IF onRight THEN
BEGIN
other[0] ← i;
Move[other, 0, this];
other[0] ← nextOther;
Slip[other, other, i, 0, otherEnd + page - here];
other[0] ← nextOther - i;
END
ELSE
BEGIN
l ← nextOther - i;
Slip[this, this, 0, l, otherEnd + thisEnd - here];
this[0] ← 0;
Move[other, i, this];
this[0] ← l + nextIndex;
END;
END;
--I think this returns the array of pntrs to items, the count of items, and the offsets: of start of first item on the page, of end of requested item, of start of requested item.
Unpack: PROCEDURE [v: BTree2Defs.VArray, item: LONG INTEGER]
RETURNS [Foo, BTree2Defs.Index, NN, NN, NN] =
BEGIN
next: NN = v[0];
foo: Foo = LOOPHOLE[@v[w]];
RETURN[foo, next, (foo + next - 1)[0].k, (foo + item)[0].k, (foo + item - 1)[0].k];
END;
Move: PROCEDURE [
from: BTree2Defs.VArray, at: BTree2Defs.Index, to: BTree2Defs.VArray] =
BEGIN
toNext, fromNext, fromEnd, atEnd, toEnd: NN;
[, fromNext, fromEnd, , atEnd] ← Unpack[from, at];
[, toNext, toEnd, , ] ← Unpack[to, 0];
Slip[from, to, at, toNext, toEnd - atEnd + fromEnd];
to[0] ← toNext + fromNext - at;
from[0] ← at;
END;
Slip: PROCEDURE [
this, other: BTree2Defs.VArray,
startIndex, newIndex, newData: BTree2Defs.Index] =
BEGIN
foo, toFoo: Foo;
endIndex, endData, startData, q: NN;
[foo, endIndex, endData, , startData] ← Unpack[this, startIndex];
toFoo ← LOOPHOLE[@other[w]];
q ← LOOPHOLE[Form[newData - endData, 0]];
Copy[foo + startIndex, toFoo + newIndex, endIndex - startIndex, q];
Copy[@this[endData], @other[newData], startData - endData, 0];
END;
Copy: PROCEDURE [from, to: LONG POINTER, len, a: NN] =
BEGIN
i: NN;
IF len > 256 THEN ERROR;
IF LOOPHOLE[to, LONG CARDINAL] > LOOPHOLE[from, LONG CARDINAL] THEN
FOR i DECREASING IN [0..len) DO (to + i)↑ ← (from + i)↑ + a; ENDLOOP
ELSE FOR i IN [0..len) DO (to + i)↑ ← (from + i)↑ + a; ENDLOOP;
END;
Check: PROCEDURE [v: BTree2Defs.VArray] =
BEGIN
foo: Foo = LOOPHOLE[@v[w]];
i: NN;
IF v[0] >= LENGTH[v↑] THEN ERROR;
IF (foo - 1)[0].k # LENGTH[v↑] THEN ERROR;
FOR i IN [0..v[0]) DO
IF (((foo + i)[0].k >= (foo + i - 1)[0].k) OR
((foo + i)[0].v > (foo + i - 1)[0].k - (foo + i)[0].k)) THEN
ERROR;
ENDLOOP;
END;
END.
Edit Log
RTE: March 28, 1980 2:47 PM: KK: changed format of program so I could read it.
RTE: April 28, 1980 12:18 PM: KK: bug in replacex, could cause page to overflow after split.
RTE: September 9, 1980 3:44 PM: KK: Nori found loop index in Balance that was depended upon but could be undefined.
RTE: 15-Jan-82 11:36:59: Andrew Birrell: allowed keys up to 63 words long.