#include "std/base.toc", base; #include "std/mem.toc", mem; str_to_cstr ::= fn(s: []char) []char { ret := mem.news(char, s.len+1); mem.mem_copy(&ret[0], &s[0], s.len); return ret; } // @TODO(eventually): enum FileError ::= int; FILE_ERR_OK ::= 0; FILE_ERR_EOF ::= 1; FILE_ERR_MISC ::= 2; // @TODO: more of these /* the raw file interface: raw_stdout - standard output raw_stderr - standard error raw_stdin - standard input raw_file_read - read from a raw file - unlike raw_file_write, it's okay if the number of bytes read doesn't match the number of bytes requested, but if 0 bytes are read, it is assumed to be the end of the file raw_file_write - write to a raw file raw_file_open_write - open a raw file for writing raw_file_open_read - open a raw file for reading raw_file_close - close a raw file */ #if base.PLATFORM_IS_UNIX { // @TODO: use syscall instead (it'll allow other functions called write) write ::= #foreign("write", base.libc) fn(#C int, #C &"const void", #C size_t) #C long; read ::= #foreign("read", base.libc) fn(#C int, &void, #C size_t) #C long; open ::= #foreign("open", base.libc) fn (#C &"const char", #C int, #C unsigned) #C int; close ::= #foreign("close", base.libc) fn (#C int) #C int; DEFAULT_MODE ::= 0o644; O_RDONLY ::= 0x00; O_WRONLY ::= 0x01; O_CREAT ::= 0x40; RawFile ::= int; raw_file_write ::= fn(file: RawFile, buf: &void, size: int) err : FileError { while size > 0 { bytes_written := write(file as #C int, buf, size as #C size_t); if bytes_written < 0 { err = FILE_ERR_MISC; // @TODO return; } size -= bytes_written; buf += bytes_written; } } raw_file_read ::= fn(file: RawFile, buf: &void, size: int) n: int, err : FileError { bytes_read := read(file as #C int, buf, size as #C size_t); if bytes_read < 0 { err = FILE_ERR_MISC; return; } n = bytes_read; } raw_stdout ::= fn() RawFile { return 1; } raw_stderr ::= fn() RawFile { return 2; } raw_stdin ::= fn() RawFile { return 0; } raw_file_open_write ::= fn(name: []char) f: RawFile, err : FileError { cstr := str_to_cstr(name); defer mem.dels(cstr); // @TODO: switch to bitwise or when that exists f = open(&cstr[0], (O_WRONLY + O_CREAT) as #C int, DEFAULT_MODE as #C unsigned) as RawFile; if f == -1 { err = FILE_ERR_MISC; // @TODO } } raw_file_open_read ::= fn(name: []char) f: RawFile, err : FileError { cstr := str_to_cstr(name); defer mem.dels(cstr); f = open(&cstr[0], O_RDONLY as #C int, 0) as RawFile; if f == -1 { err = FILE_ERR_MISC; // @TODO } } raw_file_close ::= fn(f: RawFile) err : FileError { if close(f as #C int) == -1 { err = FILE_ERR_MISC; // @TODO } } } else { // @TODO: test this interface // @TODO: on windows, use WriteFile fwrite ::= #foreign("fwrite", base.libc) fn(#C &"const void", #C size_t, #C size_t, &void) #C size_t; fread ::= #foreign("fread", base.libc) fn(&void, #C size_t, #C size_t, &void) #C size_t; RawFile ::= &void; // @TODO: Use FileErrors raw_file_write ::= fn(file: RawFile, buf: &void, size: int) bool { bytes_written := fwrite(buf, 1, size, file) as int; return bytes_written == size; } raw_file_read ::= fn(file: RawFile, buf: &void, size: int) bool { bytes_read := fread(buf, 1, size, file) as int; return bytes_read == size; } raw_stdout ::= fn() RawFile { return #builtin("stdout"); } raw_stderr ::= fn() RawFile { return #builtin("stderr"); } raw_stdin ::= fn() RawFile { return #builtin("stdin"); } raw_file_open_write ::= fn(name: []char) f: RawFile, err : FileError { cstr := base.str_to_cstr(name); defer mem.dels(cstr); mode := "wb\0"; f = fopen(&cstr[0], &mode[0]); if f == null { err = FILE_ERR_MISC; // @TODO } } raw_file_open_read ::= fn(name: []char) f: RawFile, err : FileError { cstr := base.str_to_cstr(name); defer mem.dels(cstr); mode := "rb\0"; f = fopen(&cstr[0], &mode[0]); if f == null { err = FILE_ERR_MISC; // @TODO } } raw_file_close ::= fn(f: RawFile) err : FileError { if fclose(f) != 0 { err = FILE_ERR_MISC; // @TODO } } } // file modes FileMode ::= u8; // @TODO: enum MODE_READ :: FileMode = 1; MODE_WRITE :: FileMode = 2; IO_DEBUG ::= base.DEBUG; // @TODO: error flag // @TODO: locking? File ::= struct { BUFSZ ::= 4096; raw : RawFile; buffer_used, buffer_len : int; // range from 0 to FILE_BUFSZ-1 mode : FileMode; eof : bool; nobuffer : bool; // if true, flush after every write buffer : [BUFSZ]char; } raw_file_to_file ::= fn(raw : RawFile, mode : FileMode, f: &File) { f.raw = raw; f.mode = mode; } std_in, std_out, std_err : File; fopen_write ::= fn(name: []char) f: &File, error: FileError { raw : RawFile; raw, error = raw_file_open_write(name); if !error { f = mem.new(File); raw_file_to_file(raw, MODE_WRITE, f); } } fopen_read ::= fn(name: []char) f: &File, error: FileError { raw : RawFile; raw, error = raw_file_open_read(name); if !error { f = mem.new(File); raw_file_to_file(raw, MODE_READ, f); } } flush ::= fn(use f: &File) err: FileError { if mode == MODE_READ { // discard buffer buffer_used = 0; buffer_len = 0; } else { err = raw_file_write(raw, &buffer[0], buffer_used); buffer_used = 0; } } fclose ::= fn(f: &File) err: FileError { flush(f); err = raw_file_close(f.raw); mem.del(f); } can_write ::= fn(mode: FileMode) bool { return mode == MODE_WRITE; } can_read ::= fn(mode: FileMode) bool { return mode == MODE_READ; } fwrites ::= fn(use f: &File, s : []char) FileError { #if IO_DEBUG { if !can_write(mode) { base.error("Writing to file which wasn't opened for writing."); } } if f.nobuffer { return raw_file_write(raw, &s[0], s.len); } if buffer_used + s.len > BUFSZ { flush(f); if s.len > BUFSZ { return raw_file_write(raw, &s[0], s.len); } else { mem.mem_copy(&buffer[0], &s[0], s.len); buffer_used = s.len; return FILE_ERR_OK; } } else { mem.mem_copy(&buffer[buffer_used], &s[0], s.len); buffer_used += s.len; return FILE_ERR_OK; } } fwritec ::= fn(use f: &File, c : char) err : FileError { if buffer_used > BUFSZ-1 { flush(f); buffer[0] = c; } else { buffer[buffer_used] = c; buffer_used += 1; } if nobuffer { flush(f); } } fputs ::= fn(f: &File, s: []char) err : FileError { err = fwrites(f, s); if !err { err = fwritec(f, '\n'); } } writes ::= fn(s: []char) { fwrites(&std_out, s); } writec ::= fn(c: char) { fwritec(&std_out, c); } puts ::= fn(s: []char) { fputs(&std_out, s); } fwritei ::= fn(use f: &File, x: int) err : FileError { #if IO_DEBUG { if !can_write(mode) { base.error("Writing to file which wasn't opened for writing."); } } max_int_len ::= 20; // maximum length of a 64-bit integer, converted to a string if buffer_used > BUFSZ - max_int_len { err = flush(f); if err { return; } } out := &buffer[buffer_used]; start := out; defer buffer_used = out - start; // flush at end if file is not buffered defer if nobuffer { err = flush(f); } x_unsigned : u64; if x > 0 { x_unsigned = x as u64; } elif x < 0 { *out = '-'; out += 1; // get the absolute value of x in a u64. this complicated stuff is needed because -INT_MIN doesn't fit in a 64-bit signed integer x_unsigned = (18446744073709551615 - (x as u64)) + 1; } else { *out = '0'; out += 1; return; } scan_digit : u64 = 1000000000000000000; started := false; while scan_digit > 0 { digit := (x_unsigned / scan_digit) % 10; if digit > 0 { started = true; } if started { *out = (('0' as u64) + digit) as char; out += 1; } scan_digit /= 10; } } writei ::= fn(x: int) { fwritei(&std_out, x); } puti ::= fn(x: int) { writei(x); writec('\n'); } putb ::= fn(x: bool) { if x { writes("true\n"); } else { writes("false\n"); } } // read into out, set its length appropriately fread ::= fn(use f: &File, out: &[]char) FileError { #if IO_DEBUG { if !can_read(mode) { base.error("Reading from file which wasn't opened for reading."); } } if eof { out.len = 0; return FILE_ERR_EOF; } to_read := out.len; buffer_left := buffer_len - buffer_used; if to_read <= buffer_left { mem.mem_copy(&out[0], &buffer[buffer_used], to_read); buffer_used += to_read; return FILE_ERR_OK; } else { mem.mem_copy(&out[0], &buffer[buffer_used], buffer_left); out_idx := buffer_left; to_read -= buffer_left; if to_read > BUFSZ { // very big read, just do it directly buffer_used = 0; buffer_len = 0; n, err := raw_file_read(raw, &out[out_idx], to_read); if n == 0 { eof = true; } out.len = n + buffer_left; if err { return err; } } elif to_read > 0 { n, err := raw_file_read(raw, &buffer[0], BUFSZ); if n == 0 { eof = true; } buffer_len = n; if n < to_read { // we didn't read out.len bytes out.len = n + buffer_left; to_read = n; } if err { buffer_used = 0; return err; } mem.mem_copy(&out[out_idx], &buffer[0], to_read); buffer_used = to_read; } return FILE_ERR_OK; } } // read a line from standard input. does not include newline gets ::= fn(out: &[]char) err : FileError { err = fread(&std_in, out); if out.len && out[out.len-1] == '\n' { out.len -= 1; } } // read a line of standard input, at most max_len (default: 256) characters // a read error might happen, but this function just ignores it read_line ::= fn(max_len := 256) []char { ret := mem.news(char, max_len); gets(&ret); return ret; } io_init ::= fn() { raw_file_to_file(raw_stdout(), MODE_WRITE, &std_out); std_out.nobuffer = true; raw_file_to_file(raw_stderr(), MODE_WRITE, &std_err); std_err.nobuffer = true; raw_file_to_file(raw_stdin(), MODE_READ, &std_in); } #init io_init();