shithub: pplay

ref: 928e9db52eaf922181c5b5d7e618e66cfd9afb61
dir: /chunk.c/

View raw version
#include <u.h>
#include <libc.h>
#include <thread.h>
#include "dat.h"
#include "fns.h"

/* chunk ≡ chain of chunks */
struct Buf{
	uchar *buf;
	usize bufsz;
	Ref;
};
static Chunk *norris;

// FIXME: crazy idea, multisnarf with addressable elements; $n registers; fork pplay to display them → ?

typedef struct Op Op;
struct Op{
	Chunk *p1;
	Chunk *p2;
	Chunk *l;
	Chunk *r;
};
static Op *opbuf, *ophead, *opend;
static usize opbufsz;

static struct{
	Chunk *from;
	usize foff;
	Chunk *to;
	usize toff;
} hold;

int
Δfmt(Fmt *fmt)
{
	Dot *d;

	d = va_arg(fmt->args, Dot*);
	if(d == nil)
		return fmtstrcpy(fmt, "[??:??:??:??]");
	return fmtprint(fmt, "[from=%08zux cur=%08zux to=%08zux]",
		d->from, d->pos, d->to);
}

int
χfmt(Fmt *fmt)
{
	Chunk *c;

	c = va_arg(fmt->args, Chunk*);
	if(c == nil)
		return fmtstrcpy(fmt, "[]");
	return fmtprint(fmt, "0x%08p:%08zux::0x%08p:0x%08p", c, c->len, c->left, c->right);
}

static void
printchunks(Chunk *r)
{
	usize len;
	Chunk *c;

	c = r;
	len = 0;
	do{
		fprint(2, "\t%χ\toff=%08zux\n", c, len);
		assert(c->right->left == c);
		len += c->len;
		c = c->right;
	}while(c != r);
	fprint(2, "\n");
}

void
dprint(Chunk *c, char *fmt, ...)
{
	char s[256];
	va_list arg;

	if(!debug)
		return;
	va_start(arg, fmt);
	vseprint(s, s+sizeof s, fmt, arg);
	va_end(arg);
	fprint(2, "%s", s);
	printchunks(c == nil ? norris : c);
}

static Chunk *
newchunk(Buf *b)
{
	Chunk *c;

	c = emalloc(sizeof *c);
	c->left = c;
	c->right = c;
	c->b = b;
	c->boff = 0;
	c->len = b->bufsz;
	incref(&b->Ref);
	return c;
}
static Chunk *
newbuf(usize n)
{
	Buf *b;

	assert((n & 3) == 0);
	b = emalloc(sizeof *b);
	b->bufsz = n;
	b->buf = emalloc(b->bufsz);
	return newchunk(b);
}

static void
linkchunk(Chunk *left, Chunk *c)
{
	c->left->right = left->right;
	left->right->left = c->left;
	c->left = left;
	left->right = c;
}
static void
unlink(Chunk *left, Chunk *right)
{
	left->left->right = right->right;
	right->right->left = left->left;
	left->left = right;
	right->right = left;
}

static Chunk *
clonechunk(Chunk *c)
{
	Chunk *nc;

	assert(c != nil && c->b != nil);
	nc = newchunk(c->b);
	nc->boff = c->boff;
	nc->len = c->len;
	incref(c->b);
	return nc;
}
static Chunk *
clone(Chunk *left, Chunk *right)
{
	Chunk *cl, *c, *nc;

	cl = clonechunk(left);
	c = cl;
	for(; left!=right;){
		left = left->right;
		nc = clonechunk(left);
		linkchunk(c, nc);
		c = nc;
	}
	return cl;
}
static Chunk *
splitchunk(Chunk *p, usize from, usize to)
{
	Chunk *c;

	/* 0-size chunks are permitted */
	assert(to <= p->len && to - from <= p->len);
	c = clonechunk(p);
	c->boff += from;
	c->len = to - from;
	c->left = c->right = c;
	return c;
}

static void
freebuf(Buf *b)
{
	if(b == nil)
		return;
	free(b->buf);
	free(b);
}
static void
freechunk(Chunk *c)
{
	if(c == nil)
		return;
	if(c->b != nil && decref(c->b) == 0)
		freebuf(c->b);
	free(c);
}
void
freechain(Chunk *c)
{
	Chunk *cl, *cp;

	if(c == nil)
		return;
	for(cl=c->right; cl!=c; cl=cp){
		cp = cl->right;
		unlink(cl, cl);
		freechunk(cl);
	}
	freechunk(c);
}

usize
chainlen(Chunk *c)
{
	usize n;
	Chunk *cp;

	for(cp=c, n=cp->len, cp=cp->right; cp!=c; cp=cp->right)
		n += cp->len;
	return n;
}

void
checksz(void)
{
	usize n;

	n = chainlen(norris);
	fprint(2, "totalsz %zd %Δ :: chainlen %zd\n", totalsz, &dot, n);
	assert(n == totalsz);
	assert(dot.from < dot.to);
	assert(dot.from <= totalsz);
	assert(dot.pos >= dot.from && dot.pos < dot.to);
}

Chunk *
p2c(usize p, usize *off)
{
	Chunk *c;

	for(c=norris; p>=c->len; c=c->right){
		if(c == norris->left){
			assert(p == c->len);
			break;
		}
		p -= c->len;
	}
	if(off != nil)
		*off = p;
	return c;
}

static void
forgetop(Op *op)
{
	freechain(op->l);
}

int
unpop(char *)
{
	Op *op;

	if(opend == opbuf || ophead == opend)
		return 0;
	op = ophead++;
	dprint(op->p1, "cmd/unpop dot=%Δ P [%χ][%χ] LR [%χ][%χ]\n",
		&dot, op->p1, op->p2, op->l, op->r);
	totalsz += chainlen(op->l);
	linkchunk(op->p1->left, op->l);
	unlink(op->p1, op->p2);
	totalsz -= chainlen(op->p1);
	if(norris == op->p1)
		norris = op->l;
	dot.from = dot.pos = 0;
	dot.to = totalsz;
	latchedpos = -1;
	return 1;
}

int
popop(char *)
{
	Op *op;

	if(ophead == opbuf)
		return 0;
	op = --ophead;
	dprint(op->l, "cmd/pop dot=%Δ P [%χ][%χ] LR [%χ][%χ]\n",
		&dot, op->p1, op->p2, op->l, op->r);
	totalsz += chainlen(op->p1);
	linkchunk(op->l->left, op->p1);
	unlink(op->l, op->r);
	totalsz -= chainlen(op->l);
	if(norris == op->l)
		norris = op->p1;
	dot.from = dot.pos = 0;
	dot.to = totalsz;
	latchedpos = -1;
	return 1;
}

static void
pushop(Chunk *p1, Chunk *p2, Chunk *l, Chunk *r)
{
	Op *op;

	if(ophead == opbuf + opbufsz){
		opbuf = erealloc(opbuf,
			(opbufsz + 1024) * sizeof *opbuf,
			opbufsz * sizeof *opbuf);
		ophead = opbuf + opbufsz;
		opend = ophead;
		opbufsz += 1024;
	}
	if(opend > ophead){
		for(op=ophead; op<opend; op++)
			forgetop(op);
		memset(ophead, 0, (opend - ophead) * sizeof *ophead);
	}
	*ophead++ = (Op){p1, p2, l, r};
	opend = ophead;
}

void
ccrop(usize from, usize to)
{
	usize n, off;
	Chunk *p1, *p2, *l, *r;

	assert(from < to && to <= totalsz);

	p1 = p2c(from, &off);
	if(p1->len - off >= to - from){
		l = splitchunk(p1, off, off);
		p2 = p1;
		r = splitchunk(p2, off, off + to - from);
		if(p1 != norris)
			p1 = p1->left;
		else
			p2 = p2->right;
	}else{
		p1 = p2c(from, &off);
		l = splitchunk(p1, off, p1->len);
		p2 = p2c(to, &off);
		r = splitchunk(p2, 0, off);
	}
	linkchunk(p1, l);
	linkchunk(p2->left, r);
	unlink(p2, p1);
	n = chainlen(l);
	totalsz = n;
	pushop(p1, p2, l, r);
	norris = l;
	dot.from = dot.pos = 0;
	dot.to = n;
	latchedpos = -1;
}

static int
creplace(usize from, usize to, Chunk *c)
{
	usize n, off;
	Chunk *p1, *p2, *l, *r;

	assert(from < to && to <= totalsz);
	p1 = p2c(from, &off);
	l = splitchunk(p1, 0, off);
	p2 = p2c(to, &off);
	r = splitchunk(p2, off, p2->len);
	linkchunk(c, r);
	linkchunk(l, c);
	n = chainlen(l);
	totalsz += n;
	linkchunk(p1->left, l);
	unlink(p1, p2);
	totalsz -= chainlen(p1);
	pushop(p1, p2, l, r);
	if(p1 == norris)
		norris = l;
	dot.from = dot.pos = from;
	dot.to = from + n;
	latchedpos = -1;
	return 0;
}

// FIXME: use a specific Dot (prep for multibuf); generalize
static int
cinsert(usize pos, Chunk *c)
{
	usize n, off;
	Chunk *p, *l, *r;

	assert(pos <= totalsz);
	p = p2c(pos, &off);
	l = splitchunk(p, 0, off);
	r = splitchunk(p, off, p->len);
	linkchunk(c, r);
	linkchunk(l, c);
	n = chainlen(l);
	totalsz += n;
	linkchunk(p->left, l);
	unlink(p, p);
	totalsz -= chainlen(p);
	pushop(p, p, l, r);
	if(p == norris)
		norris = l;
	dot.from = dot.pos = pos;
	dot.to = pos + n;
	latchedpos = -1;
	return 0;
}

int
cpaste(usize from, usize to)
{
	Chunk *p1, *p2, *l, *r;

	if(hold.from == nil || hold.to == nil){
		werrstr("cpaste: nothing to paste");
		return -1;
	}
	p1 = hold.from;
	p2 = hold.to;
	if(p1 == p2)
		l = splitchunk(p1, hold.foff, hold.toff);
	else{
		l = splitchunk(p1, hold.foff, p1->len);
		r = splitchunk(p2, 0, hold.toff);
		if(p1->right != p2)
			linkchunk(l, clone(p1->right, p2->left));
		linkchunk(l->left, r);
	}
	return from == to ? cinsert(from, l) : creplace(from, to, l);
}

void
ccopy(usize from, usize to)
{
	hold.from = p2c(from, &hold.foff);
	hold.to = p2c(to, &hold.toff);
}

void
chold(Chunk *c)
{
	hold.from = hold.to = c;
	hold.foff = hold.toff = 0;
}

void
ccut(usize from, usize to)
{
	usize n;
	Chunk *p1, *p2, *l, *r;

	if(from - to == totalsz){
		fprint(2, "ccut: not cutting entire buffer\n");
		return;
	}
	assert(from < to && to <= totalsz);
	ccopy(from, to);
	p1 = hold.from;
	p2 = hold.to;
	l = splitchunk(p1, 0, hold.foff);
	r = splitchunk(p2, hold.toff, p2->len);
	linkchunk(l, r);
	n = chainlen(l);
	totalsz += n;
	linkchunk(p1->left, l);
	unlink(p1, p2);
	totalsz -= chainlen(p1);
	pushop(p1, p2, l, r);
	if(p1 == norris)
		norris = l;
	dot.from = dot.pos = from;
	dot.to = totalsz;
	latchedpos = -1;
}

uchar *
getslice(Dot *d, usize want, usize *have)
{
	usize n, off;
	Chunk *c;

	if(d->pos >= totalsz){
		werrstr("out of bounds");
		*have = 0;
		return nil;
	}
	c = p2c(d->pos, &off);
	n = c->len - off;
	*have = want > n ? n : want;
	return c->b->buf + c->boff + off;
}

Chunk *
chunkfile(int fd)
{
	int n;
	Chunk *c;
	Buf *b;

	c = newbuf(Chunksz);
	b = c->b;
	for(;;){
		if(b->bufsz - c->len < Chunksz){
			b->buf = erealloc(c->b->buf, 2 * c->b->bufsz, c->b->bufsz);
			b->bufsz *= 2;
		}
		if((n = readn(fd, b->buf + c->len, Chunksz)) < Chunksz)
			break;
		c->len += n;
		yield();
	}
	if(n < 0){
		fprint(2, "chunkfile: %r\n");
		freechunk(c);
		return nil;
	}else if(c->len == 0){
		fprint(2, "chunkfile: nothing read\n");
		freechunk(c);
		return nil;
	}else if(c->len < b->bufsz){
		b->buf = erealloc(b->buf, c->len, b->bufsz);
		b->bufsz = c->len;
	}
	return c;
}

void
initbuf(Chunk *c)
{
	norris = c;
	totalsz = chainlen(c);
	dot.pos = dot.from = 0;
	dot.to = totalsz;
}