shithub: purgatorio

ref: 3efb5bbb4061056e523858b134c555949591efe2
dir: /appl/lib/bufio.b/

View raw version
implement Bufio;

include "sys.m";
	sys: Sys;

include "bufio.m";

UTFself:	con 16r80;	# ascii and UTF sequences are the same (<)
Maxrune:	con 8;	# could probably be Sys->UTFmax
Bufsize:	con Sys->ATOMICIO;

Filler: adt
{
	iobuf:	ref Iobuf;
	fill:	BufioFill;
	next:	cyclic ref Filler;
};

fillers:	ref Filler;

create(filename: string, mode, perm: int): ref Iobuf
{
	if (sys == nil)
		sys = load Sys Sys->PATH;
	if ((fd := sys->create(filename, mode, perm)) == nil)
		return nil;
	return ref Iobuf(fd, array[Bufsize+Maxrune] of byte, 0, 0, 0, big 0, big 0, mode, mode);
}

open(filename: string, mode: int): ref Iobuf
{
	if (sys == nil)
		sys = load Sys Sys->PATH;
	if ((fd := sys->open(filename, mode)) == nil)
		return nil;
	return ref Iobuf(fd, array[Bufsize+Maxrune] of byte, 0, 0, 0, big 0, big 0, mode, mode);
}

fopen(fd: ref Sys->FD, mode: int): ref Iobuf
{
	if (sys == nil)
		sys = load Sys Sys->PATH;
	if ((filpos := sys->seek(fd, big 0, 1)) < big 0)
		filpos = big 0;
	return ref Iobuf(fd, array[Bufsize+Maxrune] of byte, 0, 0, 0, filpos, filpos, mode, mode);
}

sopen(input: string): ref Iobuf
{
	return aopen(array of byte input);
}

aopen(b: array of byte): ref Iobuf
{
	if (sys == nil)
		sys = load Sys Sys->PATH;
	return ref Iobuf(nil, b, 0, len b, 0, big 0, big 0, OREAD, OREAD);
}

readchunk(b: ref Iobuf): int
{
	if (b.fd == nil){
		if ((f := filler(b)) != nil){
			if ((n := f.fill->fill(b)) == EOF)
				nofill(b);
			return n;
		}
		return EOF;
	}
	if (b.filpos != b.bufpos + big b.size) {
		s := b.bufpos + big b.size;
		if (sys->seek(b.fd, s, 0) != s)
			return ERROR;
		b.filpos = s;
	}
	i := len b.buffer - b.size - 1;
	if(i > Bufsize)
		i = Bufsize;
	if ((i = sys->read(b.fd, b.buffer[b.size:], i)) <= 0) {
		if(i < 0)
			return ERROR;
		return EOF;
	}
	b.size += i;
	b.filpos += big i;
	return i;
}

writechunk(b: ref Iobuf): int
{
	err := (b.fd == nil);
	if (b.filpos != b.bufpos) {
		if (sys->seek(b.fd, b.bufpos, 0) != b.bufpos)
			err = 1;
		b.filpos = b.bufpos;
	}
	if ((size := b.size) > Bufsize)
		size = Bufsize;
	if (sys->write(b.fd, b.buffer, size) != size)
		err = 1;
	b.filpos += big size;
	b.size -= size;
	if (b.size) {
		b.dirty = 1;
		b.buffer[0:] = b.buffer[Bufsize:Bufsize+b.size];
	} else
		b.dirty = 0;
	b.bufpos += big size;
	b.index -= size;
	if(err)
		return ERROR;
	return size;
}

Iobuf.close(b: self ref Iobuf)
{
	if (b.fd == nil) {
		nofill(b);
		return;
	}
	if (b.dirty)
		b.flush();
	b.fd = nil;
	b.buffer = nil;
}

Iobuf.flush(b: self ref Iobuf): int
{
	if (b.fd == nil)
		return ERROR;
	if (b.lastop == OREAD){
		b.bufpos = b.filpos;
		b.size = 0;
		return 0;
	}
	while (b.dirty) {
		if (writechunk(b) < 0)
			return ERROR;
		if (b.index < 0) {
			b.bufpos += big b.index;
			b.index = 0;
		}
	}
	return 0;
}

Iobuf.seek(b: self ref Iobuf, off: big, start: int): big
{
	npos: big;

	if (b.fd == nil){
		if(filler(b) != nil)
			return big ERROR;
	}
	case (start) {
	0 =>	# absolute address
		npos = off;
	1 =>	# offset from current location
		npos = b.bufpos + big b.index + off;
		off = npos;
		start = Sys->SEEKSTART;
	2 =>	# offset from EOF
		npos = big -1;
	* =>	return big ERROR;
	}
	if (b.bufpos <= npos && npos < b.bufpos + big b.size) {
		b.index = int(npos - b.bufpos);
		return npos;
	}
	if (b.fd == nil || b.dirty && b.flush() < 0)
		return big ERROR;
	b.size = 0;
	b.index = 0;
	if ((s := sys->seek(b.fd, off, start)) < big 0) {
		b.bufpos = b.filpos;
		return big ERROR;
	}
	b.bufpos = b.filpos = s;
	return s;
}

Iobuf.offset(b: self ref Iobuf): big
{
	return b.bufpos + big b.index;
}

write2read(b: ref Iobuf): int
{
	while (b.dirty) 
		if (b.flush() < 0)
			return ERROR;
	b.bufpos = b.filpos;
	b.size = 0;
	b.lastop = OREAD;
	if ((r := readchunk(b)) < 0)
		return r;
	if (b.index > b.size)
		return EOF;
	return 0;
}

Iobuf.read(b: self ref Iobuf, buf: array of byte, n: int): int
{
	if (b.mode == OWRITE)
		return ERROR;
	if (b.lastop != OREAD){
		if ((r := write2read(b)) < 0)
			return r;
	}
	k := n;
	while (b.size - b.index < k) {
		buf[0:] = b.buffer[b.index:b.size];
		buf = buf[b.size - b.index:];
		k -= b.size - b.index;

		b.bufpos += big b.size;
		b.index = 0;
		b.size = 0;
		if ((r := readchunk(b)) < 0) {
			if(r == EOF || n != k)
				return n-k;
			return ERROR;
		}
	}
	buf[0:] = b.buffer[b.index:b.index+k];
	b.index += k;
	return n;
}

Iobuf.getb(b: self ref Iobuf): int
{
	if(b.lastop != OREAD){
		if(b.mode == OWRITE)
			return ERROR;
		if((r := write2read(b)) < 0)
			return r;
	}
	if (b.index == b.size) {
		b.bufpos += big b.index;
		b.index = 0;
		b.size = 0;
		if ((r := readchunk(b)) < 0)
			return r;
	}
	return int b.buffer[b.index++];
}

Iobuf.ungetb(b: self ref Iobuf): int
{
	if(b.mode == OWRITE || b.lastop != OREAD)
		return ERROR;
	b.index--;
	return 1;
}

Iobuf.getc(b: self ref Iobuf): int
{
	r, i, s:	int;

	if(b.lastop != OREAD){
		if(b.mode == OWRITE)
			return ERROR;
		if((r = write2read(b)) < 0)
			return r;
	}
	for(;;) {
		if(b.index < b.size) {
			r = int b.buffer[b.index];
			if(r < UTFself){
				b.index++;
				return r;
			}
			(r, i, s) = sys->byte2char(b.buffer[0:b.size], b.index);
			if (i != 0) {
				b.index += i;
				return r;
			}
			b.buffer[0:] = b.buffer[b.index:b.size];
		}
		b.bufpos += big b.index;
		b.size -= b.index;
		b.index = 0;
		if ((r = readchunk(b)) < 0)
			return r;
	}
	# Not reached:
	return -1;
}

Iobuf.ungetc(b: self ref Iobuf): int
{
	if(b.index == 0 || b.mode == OWRITE || b.lastop != OREAD)
		return ERROR;
	stop := b.index - Sys->UTFmax;
	if(stop < 0)
		stop = 0;
	buf := b.buffer[0:b.size];
	for(i := b.index-1; i >= stop; i--){
		(nil, n, s) := sys->byte2char(buf, i);
		if(s && i + n == b.index){
			b.index = i;
			return 1;
		}
	}
	b.index--;

	return 1;
}

# optimised when term < UTFself (common case)
tgets(b: ref Iobuf, t: int): string
{
	str: string;
	term := byte t;
	for(;;){
		start := b.index;
		end := start + sys->utfbytes(b.buffer[start:], b.size-start);
		buf := b.buffer;
		# XXX could speed up by adding extra byte to end of buffer and
		# placing a sentinel there (eliminate one test, perhaps 35% speedup).
		# (but not when we've been given the buffer externally)
		for(i := start; i < end; i++){
			if(buf[i] == term){
				i++;
				str += string buf[start:i];
				b.index = i;
				return str;
			}
		}
		str += string buf[start:i];
		if(i < b.size)
			b.buffer[0:] = buf[i:b.size];
		b.size -= i;
		b.bufpos += big i;
		b.index = 0;
		if(readchunk(b) < 0)
			break;
	}
	return str;
}
		
Iobuf.gets(b: self ref Iobuf, term: int): string
{
	i: int;

	if(b.mode == OWRITE)
		return nil;
	if(b.lastop != OREAD && write2read(b) < 0)
		return nil;
#	if(term < UTFself)
#		return tgets(b, term);
	str: string;
	ch := -1;
	for (;;) {
		start := b.index;
		n := 0;
		while(b.index < b.size){
			(ch, i, nil) = sys->byte2char(b.buffer[0:b.size], b.index);
			if(i == 0)	# too few bytes for full Rune
				break;
			n += i;
			b.index += i;
			if(ch == term)
				break;
		}
		if(n > 0)
			str += string b.buffer[start:start+n];
		if(ch == term)
			return str;
		b.buffer[0:] = b.buffer[b.index:b.size];
		b.bufpos += big b.index;
		b.size -= b.index;
		b.index = 0;
		if (readchunk(b) < 0)
			break;
	}
	return str;	# nil at EOF
}

Iobuf.gett(b: self ref Iobuf, s: string): string
{
	r := "";
	if (b.mode == OWRITE || (ch := b.getc()) < 0)
		return nil;
	do {
		r[len r] = ch;
		for (i:=0; i<len(s); i++)
			if (ch == s[i])
				return r;
	} while ((ch = b.getc()) >= 0);
	return r;
}

read2write(b: ref Iobuf)
{
	# last operation was a read
	b.bufpos += big b.index;
	b.size = 0;
	b.index = 0;
	b.lastop = OWRITE;
}

Iobuf.write(b: self ref Iobuf, buf: array of byte, n: int): int
{
	if(b.lastop != OWRITE) {
		if(b.mode == OREAD)
			return ERROR;
		read2write(b);
	}
	start := 0;
	k := n;
	while(k > 0){
		nw := Bufsize - b.index;
		if(nw > k)
			nw = k;
		end := start + nw;
		b.buffer[b.index:] = buf[start:end];
		start = end;
		b.index += nw;
		k -= nw;
		if(b.index > b.size)
			b.size = b.index;
		b.dirty = 1;
		if(b.size == Bufsize && writechunk(b) < 0)
			return ERROR;
	}
	return n;
}

Iobuf.putb(b: self ref Iobuf, c: byte): int
{
	if(b.lastop != OWRITE) {
		if(b.mode == OREAD)
			return ERROR;
		read2write(b);
	}
	b.buffer[b.index++] = c;
	if(b.index > b.size)
		b.size = b.index;
	b.dirty = 1;
	if(b.size >= Bufsize) {
		if (b.fd == nil)
			return ERROR;
		if (writechunk(b) < 0)
			return ERROR;
	}
	return 0;
}

Iobuf.putc(b: self ref Iobuf, c: int): int
{
	if(b.lastop != OWRITE) {
		if (b.mode == OREAD)
			return ERROR;
		read2write(b);
	}
	if(c < UTFself)
		b.buffer[b.index++] = byte c;
	else
		b.index += sys->char2byte(c, b.buffer, b.index);
	if (b.index > b.size)
		b.size = b.index;
	b.dirty = 1;
	if (b.size >= Bufsize) {
		if (writechunk(b) < 0)
			return ERROR;
	}
	return 0;
}

Iobuf.puts(b: self ref Iobuf, s: string): int
{
	if(b.lastop != OWRITE) {
		if (b.mode == OREAD)
			return ERROR;
		read2write(b);
	}
	n := len s;
	if (n == 0)
		return 0;
	ind := b.index;
	buf := b.buffer;
	for(i := 0; i < n; i++){
		c := s[i];
		if(c < UTFself)
			buf[ind++] = byte c;
		else
			ind += sys->char2byte(c, buf, ind);
		if(ind >= Bufsize){
			b.index = ind;
			if(ind > b.size)
				b.size = ind;
			b.dirty = 1;
			if(writechunk(b) < 0)
				return ERROR;
			ind = b.index;
		}
	}
	b.dirty = b.index != ind;
	b.index = ind;
	if (ind > b.size)
		b.size = ind;
	return n;
}

filler(b: ref Iobuf): ref Filler
{
	for (f := fillers; f != nil; f = f.next)
		if(f.iobuf == b)
			return f;
	return nil;
}

Iobuf.setfill(b: self ref Iobuf, fill: BufioFill)
{
	if ((f := filler(b)) != nil)
		f.fill = fill;
	else
		fillers = ref Filler(b, fill, fillers);
}

nofill(b: ref Iobuf)
{
	prev: ref Filler;
	for(f := fillers; f != nil; f = f.next) {
		if(f.iobuf == b) {
			if (prev == nil)
				fillers = f.next;
			else
				prev.next = f.next;
		}
		prev = f;
	}
}