shithub: sl

ref: a70379d7e4b822f532fb0a8ccdd1624a90b64a68
dir: /src/io.c/

View raw version
#include "sl.h"
#include "cvalues.h"
#include "types.h"
#include "print.h"
#include "read.h"
#include "io.h"

static sl_v sl_linesym, sl_blocksym, sl_memorysym, sl_nonesym;
static sl_v sl_ioinsym, sl_whitespace;
sl_v sl_iooutsym;
sl_type *sl_iotype;

static void
print_io(sl_v v, sl_ios *f)
{
	sl_ios *s = value2c(sl_ios*, v);
	sl_print_str(f, "#<io");
	if(*s->loc.filename){
		sl_print_chr(f, ' ');
		sl_print_str(f, s->loc.filename);
	}
	sl_print_chr(f, '>');
}

static void
free_io(sl_v self)
{
	sl_ios *s = value2c(sl_ios*, self);
	ios_close(s);
	ios_free(s);
}

static void
relocate_io(sl_v oldv, sl_v newv)
{
	sl_ios *olds = value2c(sl_ios*, oldv);
	sl_ios *news = value2c(sl_ios*, newv);
	if(news->buf == &olds->local[0])
		news->buf = &news->local[0];
}

static sl_cvtable io_vtable = {
	print_io,
	relocate_io,
	free_io,
	nil
};

bool
isio(sl_v v)
{
	return iscvalue(v) && cv_class(ptr(v)) == sl_iotype;
}

sl_purefn
BUILTIN("io?", iop)
{
	argcount(nargs, 1);
	return isio(args[0]) ? sl_t : sl_nil;
}

sl_purefn
BUILTIN("eof-object?", eof_objectp)
{
	argcount(nargs, 1);
	return args[0] == sl_eof ? sl_t : sl_nil;
}

sl_purefn
sl_ios *
toio(sl_v v)
{
	if(sl_unlikely(!isio(v)))
		type_error("io", v);
	return value2c(sl_ios*, v);
}

BUILTIN("file", file)
{
	if(nargs < 1)
		argcount(nargs, 1);
	bool r = false, w = false, c = false, t = false, a = false;
	for(int i = 1; i < nargs; i++){
		if(args[i] == sl_rdsym)
			r = 1;
		else if(args[i] == sl_wrsym)
			w = 1;
		else if(args[i] == sl_apsym)
			a = w = 1;
		else if(args[i] == sl_crsym)
			c = w = 1;
		else if(args[i] == sl_truncsym)
			t = w = 1;
	}
	if(!r && !w && !c && !t && !a)
		r = true;  // default to reading
	sl_v f = cvalue(sl_iotype, sizeof(sl_ios));
	char *fname = tostr(args[0]);
	sl_ios *s = value2c(sl_ios*, f);
	if(ios_file(s, fname, r, w, c, t) == nil)
		lerrorf(sl_errio, "could not open \"%s\"", fname);
	if(a)
		ios_seek_end(s);
	return f;
}

BUILTIN("io-buffer-mode", io_buffer_mode)
{
	if(nargs < 1 || nargs > 2)
		argcount(nargs, 1);
	sl_ios *s = toio(args[0]);
	if(nargs == 1){
		switch(s->bm){
		case bm_none: return sl_nonesym;
		case bm_line: return sl_linesym;
		case bm_block: return sl_blocksym;
		case bm_mem: return sl_memorysym;
		}
		assert("impossible" == nil);
	}
	sl_v a = args[1];
	int bm = -1;
	if(a == sl_nonesym)
		bm = bm_none;
	else if(a == sl_linesym)
		bm = bm_line;
	else if(a == sl_blocksym)
		bm = bm_block;
	else if(a == sl_memorysym)
		bm = bm_mem;
	if(bm < 0 || ios_bufmode(s, bm) != 0)
		lerrorf(sl_errarg, "invalid buffer mode");
	return sl_void;
}

BUILTIN("buffer", buffer)
{
	argcount(nargs, 0);
	USED(args);
	sl_v f = cvalue(sl_iotype, sizeof(sl_ios));
	sl_ios *s = value2c(sl_ios*, f);
	if(ios_mem(s, 0) == nil)
		lerrorf(sl_errmem, "could not allocate in-memory io");
	return f;
}

BUILTIN("read", read)
{
	bool ws = false;
	if(nargs >= 2 && nargs <= 3){
		int i;
		if(args[i = 0] == sl_whitespace || (nargs > 2 && args[i = 1] == sl_whitespace)){
			ws = args[++i] != sl_nil;
			if(i < 2) // (read :whitespace T io)
				args[0] = args[2];
			nargs -= 2;
		}
	}
	argcount(nargs, 1);
	sl_v a = nargs == 0 ? sym_value(sl_ioinsym) : args[0];
	sl_gc_handle(&a);
	sl_v v = sl_read_sexpr(a, ws);
	sl_free_gc_handles(1);
	return ios_eof(toio(a)) ? sl_eof : v;
}

BUILTIN("io-peekrune", io_peekrune)
{
	argcount(nargs, 1);
	sl_ios *s = toio(args[0]);
	Rune r;
	int res;
	if((res = ios_peekrune(s, &r)) == IOS_EOF)
		//lerrorf(sl_errio, "end of file reached");
		return sl_eof;
	if(res == 0)
		lerrorf(sl_errio, "invalid UTF-8 sequence");
	return mk_rune(r);
}

BUILTIN("io-getrune", io_getrune)
{
	argcount(nargs, 1);
	sl_ios *s = toio(args[0]);
	Rune r;
	int res;
	if((res = ios_getrune(s, &r)) == IOS_EOF)
		//lerrorf(sl_errio, "end of file reached");
		return sl_eof;
	if(res == 0)
		lerrorf(sl_errio, "invalid UTF-8 sequence");
	return mk_rune(r);
}

BUILTIN("io-putrune", io_putrune)
{
	argcount(nargs, 2);
	sl_ios *s = toio(args[0]);
	sl_cprim *cp = ptr(args[1]);
	if(!iscprim(args[1]) || cp_class(cp) != sl_runetype)
		type_error("rune", args[1]);
	Rune r = *(Rune*)cp_data(cp);
	return fixnum(ios_putrune(s, r));
}

BUILTIN("io-skip", io_skip)
{
	argcount(nargs, 2);
	sl_ios *s = toio(args[0]);
	soffset off = tooffset(args[1]);
	soffset res = ios_skip(s, off);
	if(res < 0)
		return sl_nil;
	return sizeof(res) == sizeof(s64int) ? mk_s64(res) : mk_s32(res);
}

BUILTIN("io-flush", io_flush)
{
	argcount(nargs, 1);
	return ios_flush(toio(args[0])) == 0 ? sl_t : sl_nil;
}

BUILTIN("io-close", io_close)
{
	argcount(nargs, 1);
	ios_close(toio(args[0]));
	return sl_void;
}

BUILTIN("io-truncate", io_truncate)
{
	argcount(nargs, 2);
	sl_ios *s = toio(args[0]);
	if(ios_trunc(s, tooffset(args[1])) < 0)
		lerrorf(sl_errio, "truncation failed");
	return sl_void;
}

BUILTIN("io-discardbuffer", io_discardbuffer)
{
	argcount(nargs, 1);
	ios_purge(toio(args[0]));
	return sl_void;
}

sl_purefn
BUILTIN("io-eof?", io_eofp)
{
	argcount(nargs, 1);
	return ios_eof(toio(args[0])) ? sl_t : sl_nil;
}

BUILTIN("io-seek", io_seek)
{
	argcount(nargs, 2);
	sl_ios *s = toio(args[0]);
	usize pos = tosize(args[1]);
	soffset res = ios_seek(s, (soffset)pos);
	if(res < 0)
		return sl_nil;
	return sl_t;
}

BUILTIN("io-pos", io_pos)
{
	argcount(nargs, 1);
	sl_ios *s = toio(args[0]);
	soffset res = ios_pos(s);
	if(res < 0)
		return sl_nil;
	return size_wrap((usize)res);
}

BUILTIN("write", write)
{
	if(nargs < 1 || nargs > 2)
		argcount(nargs, 1);
	sl_ios *s;
	s = nargs == 2 ? toio(args[1]) : toio(sym_value(sl_iooutsym));
	sl_print(s, args[0]);
	return args[0];
}

BUILTIN("io-read", io_read)
{
	if(nargs != 3)
		argcount(nargs, 2);
	sl_ios *s = toio(args[0]);
	usize n;
	sl_type *ft;
	if(nargs == 3){
		// form (io.read s type count)
		ft = get_arr_type(args[1]);
		n = tosize(args[2]) * ft->elsz;
	}else{
		ft = get_type(args[1]);
		if(ft->eltype != nil && !iscons(cdr_(cdr_(args[1]))))
			lerrorf(sl_errarg, "incomplete type");
		n = ft->size;
	}
	sl_v cv = cvalue(ft, n);
	u8int *data = cptr(cv);
	usize got = ios_read(s, data, n);
	if(got < n)
		//lerrorf(sl_errio, "end of input reached");
		return sl_eof;
	return cv;
}

// args must contain data[, offset[, count]]
static void
get_start_count_args(sl_v *args, u32int nargs, usize sz, usize *offs, usize *nb)
{
	if(nargs > 1){
		*offs = tosize(args[1]);
		*nb = nargs > 2 ? tosize(args[2]) : sz - *offs;
		if(*offs >= sz || *offs + *nb > sz)
			bounds_error(args[0], args[1]);
	}
}

BUILTIN("io-write", io_write)
{
	if(nargs < 2 || nargs > 4)
		argcount(nargs, 2);
	sl_ios *s = toio(args[0]);
	sl_v v = args[1];
	sl_cprim *cp = ptr(v);
	if(iscprim(args[1]) && cp_class(cp) == sl_runetype){
		if(nargs > 2)
			lerrorf(sl_errarg, "offset argument not supported for characters");
		Rune r = *(Rune*)cp_data(ptr(args[1]));
		return fixnum(ios_putrune(s, r));
	}
	u8int *data;
	usize sz, offs = 0;
	to_sized_ptr(v, &data, &sz);
	usize nb = sz;
	if(nargs > 2){
		get_start_count_args(args+1, nargs-1, sz, &offs, &nb);
		data += offs;
	}
	return size_wrap(ios_write(s, data, nb));
}

static u8int
get_delim_arg(sl_v arg)
{
	usize uldelim = tosize(arg);
	if(uldelim > 0x7f){
		// runes > 0x7f, or anything else > 0xff, are out of range
		if((iscprim(arg) && cp_class(ptr(arg)) == sl_runetype) || uldelim > 0xff)
			lerrorf(sl_errarg, "delimiter out of range");
	}
	return (u8int)uldelim;
}

BUILTIN("io-readuntil", io_readuntil)
{
	argcount(nargs, 2);
	sl_v str = cvalue_str(80);
	sl_cv *cv = ptr(str);
	u8int *data = cv_data(cv);
	sl_ios dest;
	ios_mem(&dest, 0);
	ios_setbuf(&dest, data, 80, 0);
	u8int delim = get_delim_arg(args[1]);
	sl_ios *src = toio(args[0]);
	usize n = ios_copyuntil(&dest, src, delim);
	cv->len = n;
	if(dest.buf != data){
		// outgrew initial space
		usize sz;
		cv->data = ios_takebuf(&dest, &sz);
		cv_autorelease(cv);
	}else{
		((u8int*)cv->data)[n] = 0;
	}
	ios_free(&dest);
	if(n == 0 && ios_eof(src))
		return sl_eof;
	return str;
}

BUILTIN("io-copyuntil", io_copyuntil)
{
	argcount(nargs, 3);
	sl_ios *dest = toio(args[0]);
	sl_ios *src = toio(args[1]);
	u8int delim = get_delim_arg(args[2]);
	return size_wrap(ios_copyuntil(dest, src, delim));
}

BUILTIN("io-copy", io_copy)
{
	if(nargs < 2 || nargs > 3)
		argcount(nargs, 2);
	sl_ios *dest = toio(args[0]);
	sl_ios *src = toio(args[1]);
	if(nargs == 3)
		return size_wrap(ios_copy(dest, src, tosize(args[2])));
	return size_wrap(ios_copyall(dest, src));
}

BUILTIN("io-filename", io_filename)
{
	argcount(nargs, 1);
	return str_from_cstr(toio(args[0])->loc.filename);
}

BUILTIN("io-set-filename!", io_set_filename)
{
	argcount(nargs, 2);
	sl_ios *s = toio(args[0]);
	char *f = tostr(args[1]);
	MEM_FREE(s->loc.filename);
	s->loc.filename = MEM_STRDUP(f);
	return args[1];
}

BUILTIN("io-line", io_line)
{
	argcount(nargs, 1);
	return size_wrap(toio(args[0])->loc.lineno);
}

BUILTIN("io-set-line!", io_set_line)
{
	argcount(nargs, 2);
	toio(args[0])->loc.lineno = tosize(args[1]);
	return args[1];
}

BUILTIN("io-column", io_column)
{
	argcount(nargs, 1);
	return size_wrap(toio(args[0])->loc.colno);
}

BUILTIN("io-set-column!", io_set_column)
{
	argcount(nargs, 2);
	toio(args[0])->loc.colno = tosize(args[1]);
	return args[1];
}

sl_v
io_to_str(sl_v *ps)
{
	sl_v str;
	usize n;
	sl_ios *st = value2c(sl_ios*, *ps);
	if(st->buf == &st->local[0]){
		n = st->size;
		str = cvalue_str(n);
		memcpy(cvalue_data(str), st->buf, n);
		ios_trunc(st, 0);
	}else{
		u8int *b = ios_takebuf(st, &n); n--;
		if(n == 0)
			return sl_emptystr;
		b[n] = '\0';
		str = cvalue_from_ref(sl_strtype, b, n);
		cv_autorelease(ptr(str));
	}
	return str;
}

BUILTIN("io->str", io_tostr)
{
	argcount(nargs, 1);
	sl_ios *src = toio(args[0]);
	if(src->bm != bm_mem)
		lerrorf(sl_errarg, "requires in-memory io");
	bool eof = ios_eof(src);
	sl_v v = io_to_str(&args[0]);
	if(eof && v == sl_emptystr)
		v = sl_eof;
	return v;
}

void
io_init(void)
{
	sl_iosym = mk_csym("io");
	sl_rdsym = mk_csym(":read");
	sl_wrsym = mk_csym(":write");
	sl_apsym = mk_csym(":append");
	sl_crsym = mk_csym(":create");
	sl_truncsym = mk_csym(":truncate");
	sl_nonesym = mk_csym(":none");
	sl_linesym = mk_csym(":line");
	sl_blocksym = mk_csym(":block");
	sl_memorysym = mk_csym(":memory");
	sl_whitespace = mk_csym(":whitespace");
	sl_ioinsym = mk_csym("*io-in*");
	sl_iooutsym = mk_csym("*io-out*");
	sl_iotype = define_opaque_type(sl_iosym, sizeof(sl_ios), &io_vtable, nil);
	set(mk_csym("*stdout*"), cvalue_from_ref(sl_iotype, ios_stdout, sizeof(sl_ios)));
	set(mk_csym("*stderr*"), cvalue_from_ref(sl_iotype, ios_stderr, sizeof(sl_ios)));
	set(mk_csym("*stdin*"), cvalue_from_ref(sl_iotype, ios_stdin, sizeof(sl_ios)));
}