#include "inc/priv.h" #include "inc/cvalues.h" #include "inc/types.h" #include "inc/print.h" #include "inc/read.h" #include "inc/io.h" static sl_v iosym, rdsym, wrsym, apsym, crsym, truncsym; static sl_v linesym, blocksym, memorysym; static sl_v ioinsym, wssym, startsym, endsym, bendiansym, lendiansym; sl_v sl_iooutsym; const sl_type *sl_iotype; static void print_io(sl_v v) { ios *s = value2c(ios*, v); sl_print_str("#filename != 0){ sl_print_str(" \"", true); sl_print_str(s->filename, false); sl_print_str("\" ", true); sl_print(fixnum(s->loc.lineno)); sl_print_chr(' '); sl_print(fixnum(s->loc.colno)); } sl_print_chr('>'); } static void relocate_io(sl_v oldv, sl_v newv) { ios *olds = value2c(ios*, oldv); ios *news = value2c(ios*, newv); if(news->data != sl_nil){ news->data = sl_relocate(news->data); usize sz, esz; news->buf = to_sized_ptr(news->data, &sz, &esz, &news->tmp); news->buf += news->start; }else if(news->buf == &olds->local[0]) news->buf = &news->local[0]; } void free_io(sl_v self) { ios *s = value2c(ios*, self); ios_close(s); ios_free(s); } static sl_cvtable io_vtable = { print_io, relocate_io, free_io, nil, nil }; bool isio(sl_v v) { return iscvalue(v) && cvalue_class(v) == sl_iotype; } static bool isflipped(sl_v v) { bool little; if(v == lendiansym) little = true; else if(v == bendiansym) little = false; else cthrow(error(sl_errarg, "invalid endian specifier"), v); #if BYTE_ORDER == LITTLE_ENDIAN return !little; #else return little; #endif } BUILTIN("io?", iop) { argcount(1); return isio(args[0]) ? sl_t : sl_nil; } BUILTIN("eof?", eofp) { argcount(1); return args[0] == sl_eof ? sl_t : sl_nil; } ios * toio(sl_v v, bool open, bool rd, bool wr) { if(sl_unlikely(!isio(v))) cthrow(type_error(nil, "io", v), v); ios *s = value2c(ios*, v); if(sl_likely(open)){ if(sl_unlikely(s->state == IOS_STATE_CLOSED)) cthrow(error(sl_errio, "closed file"), v); if(wr && sl_unlikely((s->flags & IOS_FL_RDONLY) != 0)) cthrow(error(sl_errio, "read-only file"), v); if(rd && sl_unlikely((s->flags & IOS_FL_WRONLY) != 0)) cthrow(error(sl_errio, "write-only file"), v); } return s; } BUILTIN("file", file) { argcount_least(1); char *fname = tostr(args[0]); ios_flags flags = 0; usize mode = 0644; int wmode = 0, wrd = 0, wwr = 0, wap = 0, wcr = 0, wtrunc = 0; for(int i = 1; i < nargs; i++){ if(isfixnum(args[i]) && !wmode++) mode = tosize(args[i]); else if(args[i] == rdsym && !wrd++) flags |= IOS_READ; else if(args[i] == wrsym && !wwr++) flags |= IOS_WRITE; else if(args[i] == apsym && !wap++) flags |= IOS_WRITE | IOS_APPEND; else if(args[i] == crsym && !wcr++) flags |= IOS_WRITE | IOS_CREATE; else if(args[i] == truncsym && !wtrunc++) flags |= IOS_WRITE | IOS_TRUNC; else bthrow(error(sl_errarg, "invalid arguments")); } if(flags == 0) flags = IOS_READ; sl_v f = cvalue(sl_iotype, sizeof(ios)); ios *s = value2c(ios*, f); if(ios_file(s, fname, flags, mode) == nil) bthrow(error(sl_errio, "could not open \"%s\"", fname)); if(flags & IOS_APPEND) ios_seek_end(s); return f; } BUILTIN("io-buffer-mode", io_buffer_mode) { argcount(1); switch(toio(args[0], true, false, false)->bm){ case IOS_BUF_NONE: return sl_nil; case IOS_BUF_LINE: return linesym; case IOS_BUF_BLOCK: return blocksym; case IOS_BUF_MEM: return memorysym; default: abort(); } } BUILTIN("io-buffer-mode!", io_buffer_mode_q) { argcount(2); sl_v m = args[1]; int bm; if(m == sl_nil) bm = IOS_BUF_NONE; else if(m == linesym) bm = IOS_BUF_LINE; else if(m == blocksym) bm = IOS_BUF_BLOCK; else if(m == memorysym) bm = IOS_BUF_MEM; else bthrow(error(sl_errarg, "invalid buffer mode")); if(ios_bufmode(toio(args[0], true, false, false), bm) != 0) bthrow(error(sl_errarg, "invalid buffer mode for this io")); return args[1]; } BUILTIN("buffer", buffer) { sl_v keys[] = {startsym, endsym}, values[nelem(keys)]; if(sl_unlikely(nargs > 0)){ nargs = sl_process_args(args, 1, nargs, keys, nelem(keys), values); argcount(1); } sl_v f; if(sl_unlikely(nargs > 0)){ usize sz, esz; u64int tmp; u8int *p = to_sized_ptr(args[0], &sz, &esz, &tmp); usize start = nonnil(values[0]) ? tosize(values[0]) : 0; usize end = nonnil(values[1]) ? tosize(values[1]) : sz; if(sl_unlikely(start > sz || start > end || end > sz)) bthrow(error(sl_errarg, "limits out of range")); f = cvalue(sl_iotype, sizeof(ios)); ios *s = value2c(ios*, f); ios_static_buffer(s, p+start, end-start); s->start = start; s->end = end; s->data = args[0]; }else{ f = cvalue(sl_iotype, sizeof(ios)); ios *s = value2c(ios*, f); if(sl_unlikely(ios_mem(s, 0) == nil)) bthrow(error(sl_errmem, "could not allocate in-memory io")); } return f; } BUILTIN("read", read) { sl_v keys[] = {wssym}, values[nelem(keys)]; nargs = sl_process_args(args, 0, nargs, keys, nelem(keys), values); if(sl_unlikely(nargs > 1)) bthrow(error(sl_errarg, "extra arguments")); bool ws = nonnil(values[0]); sl_v a = nargs == 0 ? sym_value(ioinsym) : args[0]; USED(toio(a, true, true, false)); return sl_read_sexpr(a, sl_nil, SL_EXPR_S, ws); } BUILTIN("read-list-until", read_list_until) { argcount_least(1); argcount_most(2); sl_v a = nargs == 1 ? sym_value(ioinsym) : args[1]; USED(toio(a, true, true, false)); return sl_read_sexpr(a, args[0], SL_EXPR_LIST, false); } static sl_v fetch_data(sl_v *args, int nargs, bool peek) { ios *s = toio(args[0], true, true, false); const sl_type *ft; usize n, nem, emsz; bool isrune, isutf8, flip = false; if(nargs >= 3 && iskeysym(args[nargs-1])) flip = isflipped(args[--nargs]); if(nargs == 3){ // form (io-* s type count) ft = get_arr_type(args[1]); nem = tosize(args[2]); emsz = ft->elsz; n = nem * emsz; isrune = ft->eltype == sl_runetype; isutf8 = ft->eltype == sl_utf8type; }else{ ft = get_type(args[1], 0); if(ft->eltype != nil && !iscons(cdr_(cdr_(args[1])))) bthrow(error(sl_errarg, "incomplete type")); nem = 1; emsz = n = ft->size; isrune = ft == sl_runetype; isutf8 = ft == sl_utf8type; } if(isutf8){ ft = sl_strtype; n *= 4; } // give it double the space if we need to peek for runes // it will get cut down later on sl_v cv = cvalue(ft, (isrune && peek) ? n*2 : n); u8int *buf = cvalue_data(cv), *buf0 = buf; usize got; if(!isrune && !isutf8){ got = (peek ? ios_peek : ios_read)(s, buf, n); nem = got/emsz; got = nem*emsz; buf += got; }else if(peek){ char *data = (char*)(isrune ? buf+n : buf); Rune r; if(nem > 0) got = ios_peek(s, (u8int*)data, n); else if((got = ios_peekrune(s, &r)) == 1) got = runetochar(data, &r); usize nr, nb, left = got; for(nr = nb = 0; left > 0 && nr < nem; nr++){ if(!fullrune(data, left)) break; int bc = chartorune(&r, data); if(r == Runeerror) bthrow(error(sl_errio, "invalid rune")); if(isrune){ if(ft == sl_runetype) return mk_rune(r); *(Rune*)buf = r; buf += sizeof(Rune); }else{ memcpy(buf, data, bc); buf += bc; } data += bc; left -= bc; nb += bc; } got = isutf8 ? nb : nr*sizeof(Rune); }else{ usize nr; for(got = nr = 0; nr < nem; nr++){ Rune r; int rc = ios_getrune(s, &r); if(rc == 0) bthrow(error(sl_errio, "invalid rune")); if(rc != 1) break; if(isrune){ if(ft == sl_runetype) return mk_rune(r); *(Rune*)buf = r; buf += sizeof(Rune); got += sizeof(Rune); }else{ int bc = runetochar((char*)buf, &r); buf += bc; got += bc; } } } if((nem > 0 && got < 1) || (nem == 0 && (s->flags & IOS_FL_EOF) != 0)) //bthrow(error(sl_errio, "end of input reached")); return sl_eof; cvalue_cutdown(cv, got); if(flip && emsz > 1){ int half = emsz >> 1; for(; buf0 < buf; buf0 += emsz){ for(int j = 0; j < half; j++){ u8int x = buf0[j]; buf0[j] = buf0[emsz-1-j]; buf0[emsz-1-j] = x; } } } return cv; } BUILTIN("io-peek", io_peek) { argcount_least(2); argcount_most(4); return fetch_data(args, nargs, true); } BUILTIN("io-skip", io_skip) { sl_v keys[] = {endsym}, values[nelem(keys)]; nargs = sl_process_args(args, 2, nargs, keys, nelem(keys), values); bool end = nonnil(values[0]); argcount(2); ios *s = toio(args[0], true, false, false); soffset off = tooffset(args[1]); soffset res = ios_skip(s, off, end); if(res < 0) return sl_nil; return size_wrap((usize)res); } BUILTIN("io-flush", io_flush) { argcount(1); return ios_flush(toio(args[0], true, false, false)) == 0 ? sl_t : sl_nil; } BUILTIN("io-close", io_close) { argcount(1); ios_close(toio(args[0], false, false, false)); return sl_void; } BUILTIN("io-truncate", io_truncate) { argcount(2); ios *s = toio(args[0], true, false, true); if(ios_trunc(s, tooffset(args[1])) < 0) bthrow(error(sl_errio, "truncation failed")); return sl_void; } BUILTIN("io-discard-buffer", io_discard_buffer) { argcount(1); ios_purge(toio(args[0], true, false, false)); return sl_void; } BUILTIN("io-eof?", io_eofp) { argcount(1); return (toio(args[0], true, false, false)->flags & IOS_FL_EOF) != 0 ? sl_t : sl_nil; } BUILTIN("io-pos", io_pos) { argcount(1); ios *s = toio(args[0], true, false, false); soffset res = ios_skip(s, 0, false); return res < 0 ? sl_nil : size_wrap((usize)res); } BUILTIN("io-pos!", io_pos_q) { argcount(2); ios *s = toio(args[0], true, false, false); soffset res = ios_seek(s, tosize(args[1])); return res < 0 ? sl_nil : size_wrap((usize)res); } BUILTIN("write", write) { argcount_least(1); argcount_most(2); ios *s; s = toio( nargs == 2 && args[1] != sl_nil ? args[1] : sym_value(sl_iooutsym), true, false, true ); sl.pr.f = s; sl_print(args[0]); return args[0]; } BUILTIN("io-read", io_read) { argcount_least(2); argcount_most(4); return fetch_data(args, nargs, false); } BUILTIN("io-write", io_write) { argcount_least(2); argcount_most(3); ios *s = toio(args[0], true, false, true); sl_v v = args[1]; bool flip = nargs > 2 && isflipped(args[2]); if(sl_unlikely(sl_isrune(v))) return fixnum(ios_putrune(s, torune(v))); u64int tmp; usize sz, esz, nb = 0; const u8int *data = to_sized_ptr(v, &sz, &esz, &tmp); if(!iscvalue(v) || cvalue_class(v) != sl_runetype->artype){ if(sl_likely(!flip || esz == 1)) nb = ios_write(s, data, sz); else{ u8int f[8]; assert(esz <= sizeof(f)); for(usize i = 0; i < sz; i += esz){ for(usize j = 0; j < esz; j++) f[esz-j-1] = data[i+j]; usize r = ios_write(s, f, esz); if(r != esz) break; nb += r; } } }else{ sz /= sizeof(Rune); for(usize i = 0; i < sz; i++){ int r = ios_putrune(s, ((Rune*)data)[i]); if(r <= 0) break; nb += r; } } return size_wrap(nb); } static Rune get_delim_arg(sl_v arg) { return sl_isrune(arg) ? torune(arg) : tosize(arg); } BUILTIN("io-read-until", io_read_until) { argcount(2); ios *src = toio(args[0], true, true, false); sl_v str = cvalue_str(80); sl_cv *cv = ptr(sl_cv, str); u8int *data = cv_data(cv); ios dest; ios_mem(&dest, 0); ios_setbuf(&dest, data, 80, 0); Rune delim = get_delim_arg(args[1]); 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 && (src->flags & IOS_FL_EOF) != 0) return sl_eof; return str; } BUILTIN("io-copy-until", io_copy_until) { argcount(3); ios *dest = toio(args[0], true, false, true); ios *src = toio(args[1], true, true, false); Rune delim = get_delim_arg(args[2]); return size_wrap(ios_copyuntil(dest, src, delim)); } BUILTIN("io-copy", io_copy) { argcount_least(2); argcount_most(3); ios *dest = toio(args[0], true, false, true); ios *src = toio(args[1], true, true, false); 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(1); return str_from_cstr(toio(args[0], false, false, false)->filename); } BUILTIN("io-filename!", io_filename_q) { argcount(2); ios *s = toio(args[0], false, false, false); char *f = tostr(args[1]); MEM_FREE(s->filename); s->filename = MEM_STRDUP(f); return args[1]; } BUILTIN("io-line", io_line) { argcount(1); return size_wrap(toio(args[0], false, false, false)->loc.lineno); } BUILTIN("io-line!", io_line_q) { argcount(2); toio(args[0], false, false, false)->loc.lineno = tosize(args[1]); return args[1]; } BUILTIN("io-column", io_column) { argcount(1); return size_wrap(toio(args[0], false, false, false)->loc.colno); } BUILTIN("io-column!", io_column_q) { argcount(2); toio(args[0], false, false, false)->loc.colno = tosize(args[1]); return args[1]; } sl_v io_to_str(const sl_v *ps) { sl_v str; usize n; ios *st = value2c(ios*, *ps); if(st->buf == &st->local[0]){ n = st->size; str = cvalue_str(n); st = value2c(ios*, *ps); 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(sl_cv, str)); } return str; } BUILTIN("io->str", io_tostr) { argcount(1); ios *s = toio(args[0], true, true, false); if(sl_unlikely(s->bm != IOS_BUF_MEM)) bthrow(error(sl_errarg, "requires in-memory io")); bool eof = (s->flags & IOS_FL_EOF) != 0; sl_v v = io_to_str(&args[0]); if(sl_unlikely(eof && v == sl_emptystr)) v = sl_eof; return v; } BUILTIN("path-cwd", path_cwd) { argcount_most(1); if(nargs == 0){ char *path; if((path = sys_getcwd()) == nil) bthrow(error(sl_errio, "could not get current dir")); sl_v v = str_from_cstr(path); MEM_FREE(path); return v; } char *path = tostr(args[0]); if(sys_chdir(-1, path) != 0) bthrow(error(sl_errio, "could not cd to %s", path)); return sl_void; } BUILTIN("path-exists?", path_existsp) { argcount(1); const char *path = tostr(args[0]); return access(path, F_OK) == 0 ? sl_t : sl_nil; } BUILTIN("path-delete", path_delete) { argcount(1); const char *path = tostr(args[0]); if(remove(path) != 0) bthrow(error(sl_errio, "could not remove %s", path)); return sl_void; } BUILTIN("path-rename", path_rename) { argcount(2); const char *old = tostr(args[0]); const char *new = tostr(args[1]); if(sys_rename(old, new) != 0) bthrow(error(sl_errio, "could not rename %s to %s", old, new)); return sl_void; } BUILTIN("path-make-dir", path_make_dir) { argcount_least(1); argcount_most(2); const char *path = tostr(args[0]); usize mode = nargs > 1 ? tofixnum(args[1]) : 0755; if(sys_mkdir(path, mode) != 0) bthrow(error(sl_errio, "could not make dir %s", path)); return sl_void; } void io_init(void) { iosym = mk_csym("io"); rdsym = mk_csym(":read"); wrsym = mk_csym(":write"); apsym = mk_csym(":append"); crsym = mk_csym(":create"); truncsym = mk_csym(":truncate"); linesym = mk_csym(":line"); blocksym = mk_csym(":block"); memorysym = mk_csym(":memory"); wssym = mk_csym(":whitespace"); startsym = mk_csym(":start"); endsym = mk_csym(":end"); ioinsym = mk_csym("*io-in*"); bendiansym = mk_csym(":big-endian"); lendiansym = mk_csym(":little-endian"); #if BYTE_ORDER == LITTLE_ENDIAN setc(mk_csym("+endianness+"), lendiansym); #else setc(mk_csym("+endianness+"), bendiansym); #endif sl_iooutsym = mk_csym("*io-out*"); sl_iotype = define_opaque_type(iosym, sizeof(ios), &io_vtable, nil); set(mk_csym("*stdout*"), cvalue_from_ref(sl_iotype, ios_stdout, sizeof(ios))); set(mk_csym("*stderr*"), cvalue_from_ref(sl_iotype, ios_stderr, sizeof(ios))); set(mk_csym("*stdin*"), cvalue_from_ref(sl_iotype, ios_stdin, sizeof(ios))); }