ref: babf901b4a508c3ec5d1f89655f10377bbdf9637
dir: /appl/acme/buff.b/
implement Bufferm;
include "common.m";
sys : Sys;
dat : Dat;
utils : Utils;
diskm : Diskm;
ecmd: Editcmd;
FALSE, TRUE, XXX, Maxblock, Astring : import Dat;
Block : import Dat;
disk : import dat;
Disk : import diskm;
File: import Filem;
error, warning, min : import utils;
init(mods : ref Dat->Mods)
{
sys = mods.sys;
dat = mods.dat;
utils = mods.utils;
diskm = mods.diskm;
ecmd = mods.editcmd;
}
nullbuffer : Buffer;
newbuffer() : ref Buffer
{
b := ref nullbuffer;
return b;
}
Slop : con 100; # room to grow with reallocation
Buffer.sizecache(b : self ref Buffer, n : int)
{
if(n <= b.cmax)
return;
b.cmax = n+Slop;
os := b.c;
b.c = utils->stralloc(b.cmax);
if (os != nil) {
loss := len os.s;
c := b.c;
oss := os.s;
for (i := 0; i < loss && i < b.cmax; i++)
c.s[i] = oss[i];
utils->strfree(os);
}
}
#
# Move cache so b.cq <= q0 < b.cq+b.cnc.
# If at very end, q0 will fall on end of cache block.
#
Buffer.flush(b : self ref Buffer)
{
if(b.cdirty || b.cnc==0){
if(b.cnc == 0)
b.delblock(b.cbi);
else
b.bl[b.cbi] = disk.write(b.bl[b.cbi], b.c.s, b.cnc);
b.cdirty = FALSE;
}
}
Buffer.setcache(b : self ref Buffer, q0 : int)
{
blp, bl : ref Block;
i, q : int;
if (q0 > b.nc)
error("bad assert in setcache");
# flush and reload if q0 is not in cache.
if(b.nc == 0 || (b.cq<=q0 && q0<b.cq+b.cnc))
return;
# if q0 is at end of file and end of cache, continue to grow this block
if(q0==b.nc && q0==b.cq+b.cnc && b.cnc<Maxblock)
return;
b.flush();
# find block
if(q0 < b.cq){
q = 0;
i = 0;
}else{
q = b.cq;
i = b.cbi;
}
blp = b.bl[i];
while(q+blp.n <= q0 && q+blp.n < b.nc){
q += blp.n;
i++;
blp = b.bl[i];
if(i >= b.nbl)
error("block not found");
}
bl = blp;
# remember position
b.cbi = i;
b.cq = q;
b.sizecache(bl.n);
b.cnc = bl.n;
#read block
disk.read(bl, b.c, b.cnc);
}
Buffer.addblock(b : self ref Buffer, i : int, n : int)
{
if (i > b.nbl)
error("bad assert in addblock");
obl := b.bl;
b.bl = array[b.nbl+1] of ref Block;
b.bl[0:] = obl[0:i];
if(i < b.nbl)
b.bl[i+1:] = obl[i:b.nbl];
b.bl[i] = disk.new(n);
b.nbl++;
obl = nil;
}
Buffer.delblock(b : self ref Buffer, i : int)
{
if (i >= b.nbl)
error("bad assert in delblock");
disk.release(b.bl[i]);
obl := b.bl;
b.bl = array[b.nbl-1] of ref Block;
b.bl[0:] = obl[0:i];
if(i < b.nbl-1)
b.bl[i:] = obl[i+1:b.nbl];
b.nbl--;
obl = nil;
}
Buffer.insert(b : self ref Buffer, q0 : int, s : string, n : int)
{
i, j, m, t, off, p : int;
if (q0>b.nc)
error("bad assert in insert");
p = 0;
while(n > 0){
b.setcache(q0);
off = q0-b.cq;
if(b.cnc+n <= Maxblock){
# Everything fits in one block.
t = b.cnc+n;
m = n;
if(b.bl == nil){ # allocate
if (b.cnc != 0)
error("bad assert in insert");
b.addblock(0, t);
b.cbi = 0;
}
b.sizecache(t);
c := b.c;
# cs := c.s;
for (j = b.cnc-1; j >= off; j--)
c.s[j+m] = c.s[j];
for (j = 0; j < m; j++)
c.s[off+j] = s[p+j];
b.cnc = t;
}
#
# We must make a new block. If q0 is at
# the very beginning or end of this block,
# just make a new block and fill it.
#
else if(q0==b.cq || q0==b.cq+b.cnc){
if(b.cdirty)
b.flush();
m = min(n, Maxblock);
if(b.bl == nil){ # allocate
if (b.cnc != 0)
error("bad assert in insert");
i = 0;
}else{
i = b.cbi;
if(q0 > b.cq)
i++;
}
b.addblock(i, m);
b.sizecache(m);
c := b.c;
for (j = 0; j < m; j++)
c.s[j] = s[p+j];
b.cq = q0;
b.cbi = i;
b.cnc = m;
}
else {
#
# Split the block; cut off the right side and
# let go of it.
#
m = b.cnc-off;
if(m > 0){
i = b.cbi+1;
b.addblock(i, m);
b.bl[i] = disk.write(b.bl[i], b.c.s[off:], m);
b.cnc -= m;
}
#
# Now at end of block. Take as much input
# as possible and tack it on end of block.
#
m = min(n, Maxblock-b.cnc);
b.sizecache(b.cnc+m);
c := b.c;
for (j = 0; j < m; j++)
c.s[j+b.cnc] = s[p+j];
b.cnc += m;
}
b.nc += m;
q0 += m;
p += m;
n -= m;
b.cdirty = TRUE;
}
}
Buffer.delete(b : self ref Buffer, q0 : int, q1 : int)
{
m, n, off : int;
if (q0>q1 || q0>b.nc || q1>b.nc)
error("bad assert in delete");
while(q1 > q0){
b.setcache(q0);
off = q0-b.cq;
if(q1 > b.cq+b.cnc)
n = b.cnc - off;
else
n = q1-q0;
m = b.cnc - (off+n);
if(m > 0) {
c := b.c;
# cs := c.s;
p := m+off;
for (j := off; j < p; j++)
c.s[j] = c.s[j+n];
}
b.cnc -= n;
b.cdirty = TRUE;
q1 -= n;
b.nc -= n;
}
}
# Buffer.replace(b: self ref Buffer, q0: int, q1: int, s: string, n: int)
# {
# if(q0>q1 || q0>b.nc || q1>b.nc || n != q1-q0)
# error("bad assert in replace");
# p := 0;
# while(q1 > q0){
# b.setcache(q0);
# off := q0-b.cq;
# if(q1 > b.cq+b.cnc)
# n = b.cnc-off;
# else
# n = q1-q0;
# c := b.c;
# for(i := 0; i < n; i++)
# c.s[i+off] = s[i+p];
# b.cdirty = TRUE;
# q0 += n;
# p += n;
# }
# }
pbuf : array of byte;
bufloader(b: ref Buffer, q0: int, r: string, nr: int): int
{
b.insert(q0, r, nr);
return nr;
}
loadfile(fd: ref Sys->FD, q0: int, fun: int, b: ref Buffer, f: ref File): int
{
p : array of byte;
r : string;
m, n, nb, nr : int;
q1 : int;
if (pbuf == nil)
pbuf = array[Maxblock+Sys->UTFmax] of byte;
p = pbuf;
m = 0;
n = 1;
q1 = q0;
#
# At top of loop, may have m bytes left over from
# last pass, possibly representing a partial rune.
#
while(n > 0){
n = sys->read(fd, p[m:], Maxblock);
if(n < 0){
warning(nil, "read error in Buffer.load");
break;
}
m += n;
nb = sys->utfbytes(p, m);
r = string p[0:nb];
p[0:] = p[nb:m];
m -= nb;
nr = len r;
if(fun == Dat->BUFL)
q1 += bufloader(b, q1, r, nr);
else
q1 += ecmd->readloader(f, q1, r, nr);
}
p = nil;
r = nil;
return q1-q0;
}
Buffer.loadx(b : self ref Buffer, q0 : int, fd : ref Sys->FD) : int
{
if (q0>b.nc)
error("bad assert in load");
return loadfile(fd, q0, Dat->BUFL, b, nil);
}
Buffer.read(b : self ref Buffer, q0 : int, s : ref Astring, p : int, n : int)
{
m : int;
if (q0>b.nc || q0+n>b.nc)
error("bad assert in read");
while(n > 0){
b.setcache(q0);
m = min(n, b.cnc-(q0-b.cq));
c := b.c;
cs := c.s;
for (j := 0; j < m; j++)
s.s[p+j] = cs[j+q0-b.cq];
q0 += m;
p += m;
n -= m;
}
}
Buffer.reset(b : self ref Buffer)
{
i : int;
b.nc = 0;
b.cnc = 0;
b.cq = 0;
b.cdirty = 0;
b.cbi = 0;
# delete backwards to avoid n² behavior
for(i=b.nbl-1; --i>=0; )
b.delblock(i);
}
Buffer.close(b : self ref Buffer)
{
b.reset();
if (b.c != nil) {
utils->strfree(b.c);
b.c = nil;
}
b.cnc = 0;
b.bl = nil;
b.nbl = 0;
}