Removing no-longer-useful comments.

Removing no-longer-useful comments.

Change requested by Vass.

git-svn-id: 88467cb1fb04b8a755be7e1ee1026be4190196ef@19862 3a8e244f-b0f2-452b-bcba-4c88e055c3ca

/* "channel" I/O contributed by Michael Ferguson

   Future Work:
    - Chapel-friendly ways to e.g. request JSON string format or hexadecimal numbers
    - formatted input/output, like printf, probably called writef/writefln/etc,
      and supporting ###.#### formats in addition to the usual % stuff.
    - Regular expression support (currently favoring RE2 integration)
    - We would like to have a 'serialization' system, including allowing
      the writing of data structures with circular references, and
      encoding the types of classes.
    - Currently, the union tag numbers are per-compile; you could not read
      binary union data written in one Chapel program with another. This problem
      will also apply to class tag numbers once we start encoding types.
    - The Chapel compiler does not currently allow RAII/reference counting
      to work correctly in all cases (bug report 'records containing pointers'
      and test files in test/users/ferguson/{byvalue.chpl,refcnt.chpl};
      as a result, current code might need to close channels and files
      explicitly and/or sharing of channels and files between variables might
      not work correctly.
    - Channels have a lock, and some methods on a channel only make sense
      when the lock is held. In the future (again, once RAII makes sense),
      we might replace this with 'get locked channel' that returns a different
      type that has the methods that can only be used on a locked channel.
    - The error handling strategy here is a bit awkward for the library-writer
      (not so much for the user). A broader exceptions strategy would help
      quite a bit - and some of these interfaces will change when that happens.
    - Fancy features, like adding a bytes or buffer object to a channel
      (so that the channel just refers to it and does not copy it) are
      implemented but not well tested.
    - It would be nice if ioBits:string printed itself in binary instead of decimal.
    - Cleaning up to reduce the number of exported symbols, and using enums for
      all constants once 'use enum' is available and we have a way to get
      C constants into Chapel enums.
    - Support for libevent/qthreads system call thread so that we have something
      suitable for writing (in a straightforward manner) a multithreaded webserver
    - Doing something reasonable with a file that moves from one node to another -
      namely, when a channel is created with a file that resides remotely, if that
      same file is accessible locally (e.g. with Lustre or NFS), we should open
      a local copy of that file and use that in the channel. (not sure how to avoid
      opening # channels copies of these files -- seems that we'd want some way
      to cache that...).
    - Create leader/follower iterators for ItemReader/ItemWriter so that these
      are as efficient as possible when working with fixed-size data types
      (ie, they can open up channels that are not shared).
*/

use SysBasic;
use Error;

enum iomode {
  r = 1,
  cw = 2,
  rw = 3,
  cwr = 4,
}

enum iokind {
  /* don't change these without updating qio_style.h QIO_NATIVE, etc
     a default of 0 is always reasonable, but you can avoid some
     branches to get faster I/O by setting this to native, big, or little.
     In that case, the style is only consulted for text or string I/O.
     */
  dynamic = 0, // look in iostyle
  native = 1,
  big = 2, /* aka "network" */
  little = 3
}
param iodynamic = iokind.dynamic;
param ionative = iokind.native;
param iobig = iokind.big;
param iolittle = iokind.little;

enum iostringstyle {
  len1b_data = -1,
  len2b_data = -2,
  len4b_data = -4,
  len8b_data = -8,
  lenVb_data = -10,
  data_null = -0x0100,
}
proc stringStyleTerminated(terminator:uint(8)) {
  return -(terminator - iostringstyle.data_null);
}
proc stringStyleNullTerminated() {
  return iostringstyle.data_null;
}
proc stringStyleExactLen(len:int(64)) {
  return len;
}
proc stringStyleWithVariableLength() {
  return iostringstyle.lenVb_data;
}
proc stringStyleWithLength(lengthBytes:int) {
  var x = iostringstyle.lenVb_data;
  select lengthBytes {
    when 0 do x = iostringstyle.lenVb_data;
    when 1 do x = iostringstyle.len1b_data;
    when 2 do x = iostringstyle.len2b_data;
    when 4 do x = iostringstyle.len4b_data;
    when 8 do x = iostringstyle.len8b_data;
    otherwise halt("Unhandled string length prefix size");
  }
  return x;
}

extern const QIO_FDFLAG_UNK:c_int;
extern const QIO_FDFLAG_READABLE:c_int;
extern const QIO_FDFLAG_WRITEABLE:c_int;
extern const QIO_FDFLAG_SEEKABLE:c_int;

extern const QIO_CH_ALWAYS_UNBUFFERED:c_int;
extern const QIO_CH_ALWAYS_BUFFERED:c_int;
extern const QIO_CH_BUFFERED:c_int;

extern const QIO_METHOD_DEFAULT:c_int;
extern const QIO_METHOD_READWRITE:c_int;
extern const QIO_METHOD_PREADPWRITE:c_int;
extern const QIO_METHOD_FREADFWRITE:c_int;
extern const QIO_METHOD_MMAP:c_int;
extern const QIO_METHODNUM:c_int;
extern const QIO_METHODMASK:c_int;
extern const QIO_HINT_RANDOM:c_int;
extern const QIO_HINT_SEQUENTIAL:c_int;
extern const QIO_HINT_LATENCY:c_int;
extern const QIO_HINT_BANDWIDTH:c_int;
extern const QIO_HINT_CACHED:c_int;
extern const QIO_HINT_PARALLEL:c_int;
extern const QIO_HINT_DIRECT:c_int;
extern const QIO_HINT_NOREUSE:c_int;

// NONE means normal operation, nothing special
// to hint. Expect to use NONE most of the time.
// The other hints can be bitwise-ORed in.
const IOHINT_NONE = 0:c_int;

// RANDOM means we expect random access to a file
const IOHINT_RANDOM = QIO_HINT_RANDOM;

// SEQUENTAL means expect sequential access. On
// Linux, this should double the readahead.
const IOHINT_SEQUENTIAL = QIO_HINT_SEQUENTIAL;

// CACHED means we expect the entire file
// to be cached and/or we pull it in all at
// once. May request readahead on the entire file.
const IOHINT_CACHED = QIO_HINT_CACHED;

// PARALLEL means that we expect to have many
// channels working with this file in parallel.
// It might change the reading/writing implementation
// to something more efficient in that scenario.
const IOHINT_PARALLEL = QIO_HINT_PARALLEL;

extern type qio_file_ptr_t;
extern const QIO_FILE_PTR_NULL:qio_file_ptr_t;

extern type qio_channel_ptr_t;
extern const QIO_CHANNEL_PTR_NULL:qio_channel_ptr_t;

// also the type for a buffer for qio_file_open_mem.
extern type qbuffer_ptr_t;
extern const QBUFFER_PTR_NULL:qbuffer_ptr_t;

extern type style_char_t = uint(8);

extern const QIO_STRING_FORMAT_WORD:uint(8);
extern const QIO_STRING_FORMAT_BASIC:uint(8);
extern const QIO_STRING_FORMAT_CHPL:uint(8);
extern const QIO_STRING_FORMAT_JSON:uint(8);
extern const QIO_STRING_FORMAT_TOEND:uint(8);

extern record iostyle { // aka qio_style_t
  var binary:uint(8) = 0;
  // binary style choices
  var byteorder:uint(8) = iokind.native:uint(8);
  // string binary style:
  // -1 -- 1 byte of length before
  // -2 -- 2 bytes of length before
  // -4 -- 4 bytes of length before
  // -8 -- 8 bytes of length before
  // -10 -- variable byte length before (hi-bit 1 means more, little endian)
  // -0x01XX -- read until terminator XX is read
  //  + -- nonzero positive -- read exactly this length.
  var str_style:int(64) = -10;
  // text style choices
  var min_width:uint(32) = 1;
  var max_width:uint(32) = max(uint(32));
  var string_start:style_char_t = 0x22; // "
  var string_end:style_char_t = 0x22; // "

  /* QIO_STRING_FORMAT_WORD  string is as-is; reading reads until whitespace.
     QIO_STRING_FORMAT_BASIC only escape string_end and \ with \
     QIO_STRING_FORMAT_CHPL  escape string_end \ ' " \n with \
                             and nonprinting characters c = 0xXY with \xXY
     QIO_STRING_FORMAT_JSON  escape string_end " and \ with \,
                             and nonprinting characters c = \uABCD
     QIO_STRING_FORMAT_TOEND string is as-is; reading reads until string_end
   */
  var string_format:uint(8) = 0;
  // numeric scanning/printing choices
  var base:uint(8) = 0;
  var point_char:style_char_t = 0x2e; // .
  var exponent_char:style_char_t = 0x65; // e
  var other_exponent_char:style_char_t = 0x70; // p
  var positive_char:style_char_t = 0x2b; // +;
  var negative_char:style_char_t = 0x2d; // -;
  var prefix_base:uint(8) = 1;
  // numeric printing choices
  var pad_char:style_char_t = 0x20; // ' '
  var showplus:uint(8) = 0;
  var uppercase:uint(8) = 0;
  var leftjustify:uint(8) = 0;
  var showpoint:uint(8) = 0;
  var showpointzero:uint(8) = 1;
  var precision:int(32) = -1;
  var significant_digits:int(32) = -1;
  var realfmt:uint(8) = 0;

  var complex_style:uint(8) = 0;
}

extern const QIO_STYLE_SIZE:size_t;

extern proc qio_style_init_default(inout s: iostyle);

/* Extern functions */
extern proc qio_file_retain(f:qio_file_ptr_t);
extern proc qio_file_release(f:qio_file_ptr_t);

extern proc qio_file_init(inout file_out:qio_file_ptr_t, fp:_file, fd:fd_t, iohints:c_int, inout style:iostyle, usefilestar:c_int):syserr;
extern proc qio_file_open_access(inout file_out:qio_file_ptr_t, path:string, access:string, iohints:c_int, inout style:iostyle):syserr;
extern proc qio_file_open_tmp(inout file_out:qio_file_ptr_t, iohints:c_int, inout style:iostyle):syserr;
extern proc qio_file_open_mem(inout file_out:qio_file_ptr_t, buf:qbuffer_ptr_t, inout style:iostyle):syserr;

/* Close a file (asserts ref count==1)
   This is not usually necessary to call, but a program will
   halt if it isn't called.
 */
extern proc qio_file_close(f:qio_file_ptr_t):syserr;

extern proc qio_file_lock(f:qio_file_ptr_t):syserr;
extern proc qio_file_unlock(f:qio_file_ptr_t);

/* The general way to make sure data is written without error */
extern proc qio_file_sync(f:qio_file_ptr_t):syserr;

//extern proc qio_file_style_ptr(f:qio_file_ptr_t):qio_style_ptr_t;
extern proc qio_file_get_style(f:qio_file_ptr_t, inout style:iostyle);
extern proc qio_file_set_style(f:qio_file_ptr_t, inout style:iostyle);

/* our wrapper for the read function;
   calls readv to read into our buffer.
   Will read data into buf (overwriting,
    not allocating) and advance the file descriptor's offset.
 */
//extern proc qio_readv(fd:fd_t, inout buf:qbuffer_t, start:qbuffer_iter_t, end:qbuffer_iter_t, inout num_read:int(64)):syserr;
/* our wrapper for the write function;
   calls writev to write our buffer.
   Will write data from buf and not change it.
   Will advance the file descriptor's offset.
 */
//extern proc qio_writev(fd:fd_t, inout buf:qbuffer_t, start:qbuffer_iter_t, end:qbuffer_iter_t, inout num_read:int(64)):syserr;

/* calls preadv where it exists;
   otherwise, will do a series of pread calls
   Will read data into buf (overwriting, not allocating).
   Will not advance the file descriptor's position.
 */
//extern proc qio_preadv(fd:fd_t, inout buf:qbuffer_t, start:qbuffer_iter_t, end:qbuffer_iter_t, offset:int(64), inout num_read:int(64)):syserr;
/* calls pwritev where it exists;
   otherwise, will do a series of pwrite calls
   Will write data from buf and not change it.
   Will not advance the file descriptor's position.
 */
//extern proc qio_pwritev(fd:fd_t, inout buf:qbuffer_t, start:qbuffer_iter_t, end:qbuffer_iter_t, offset:int(64), inout num_read:int(64)):syserr;

extern proc qio_channel_create(inout ch:qio_channel_ptr_t, file:qio_file_ptr_t, hints:c_int, readable:c_int, writeable:c_int, start:int(64), end:int(64), inout style:iostyle):syserr;

extern proc qio_channel_path_offset(threadsafe:c_int, ch:qio_channel_ptr_t, inout path:string, inout offset:int(64)):syserr;

extern proc qio_channel_retain(ch:qio_channel_ptr_t);
extern proc qio_channel_release(ch:qio_channel_ptr_t);

extern proc qio_channel_lock(ch:qio_channel_ptr_t):syserr;
extern proc qio_channel_unlock(ch:qio_channel_ptr_t);

extern proc qio_channel_get_style(ch:qio_channel_ptr_t, inout style:iostyle);
extern proc qio_channel_set_style(ch:qio_channel_ptr_t, inout style:iostyle);

extern proc qio_channel_binary(ch:qio_channel_ptr_t):uint(8);
extern proc qio_channel_byteorder(ch:qio_channel_ptr_t):uint(8);
extern proc qio_channel_str_style(ch:qio_channel_ptr_t):int(64);

extern proc qio_channel_flush(threadsafe:c_int, ch:qio_channel_ptr_t):syserr;
extern proc qio_channel_close(threadsafe:c_int, ch:qio_channel_ptr_t):syserr;

extern proc qio_channel_read(threadsafe:c_int, ch:qio_channel_ptr_t, inout ptr, len:ssize_t, inout amt_read:ssize_t):syserr;
extern proc qio_channel_read_amt(threadsafe:c_int, ch:qio_channel_ptr_t, inout ptr, len:ssize_t):syserr;
extern proc qio_channel_read_byte(threadsafe:c_int, ch:qio_channel_ptr_t):int(32);

extern proc qio_channel_write(threadsafe:c_int, ch:qio_channel_ptr_t, inout ptr, len:ssize_t, inout amt_written:ssize_t):syserr;
extern proc qio_channel_write_amt(threadsafe:c_int, ch:qio_channel_ptr_t, inout ptr, len:ssize_t):syserr;
extern proc qio_channel_write_byte(threadsafe:c_int, ch:qio_channel_ptr_t, byte:uint(8)):syserr;

extern proc qio_channel_offset_unlocked(ch:qio_channel_ptr_t):int(64);
extern proc qio_channel_mark(threadsafe:c_int, ch:qio_channel_ptr_t):syserr;
extern proc qio_channel_revert_unlocked(ch:qio_channel_ptr_t);
extern proc qio_channel_commit_unlocked(ch:qio_channel_ptr_t);

extern proc qio_channel_write_bits(threadsafe:c_int, ch:qio_channel_ptr_t, v:uint(64), nbits:int(8)):syserr;
extern proc qio_channel_flush_bits(threadsafe:c_int, ch:qio_channel_ptr_t):syserr;
extern proc qio_channel_read_bits(threadsafe:c_int, ch:qio_channel_ptr_t, inout v:uint(64), nbits:int(8)):syserr;

extern proc qio_file_path_for_fd(fd:fd_t, inout path:string):syserr;
extern proc qio_file_path_for_fp(fp:_file, inout path:string):syserr;
extern proc qio_file_path(f:qio_file_ptr_t, inout path:string):syserr;
extern proc qio_shortest_path(inout path_out:string, path_in:string):syserr;

extern proc qio_channel_read_int(threadsafe:c_int, byteorder:c_int, ch:qio_channel_ptr_t, inout ptr, len:size_t, issigned:c_int):syserr;
extern proc qio_channel_write_int(threadsafe:c_int, byteorder:c_int, ch:qio_channel_ptr_t, inout ptr, len:size_t, issigned:c_int):syserr;

extern proc qio_channel_read_float(threadsafe:c_int, byteorder:c_int, ch:qio_channel_ptr_t, inout ptr, len:size_t):syserr;
extern proc qio_channel_write_float(threadsafe:c_int, byteorder:c_int, ch:qio_channel_ptr_t, inout ptr, len:size_t):syserr;

extern proc qio_channel_read_complex(threadsafe:c_int, byteorder:c_int, ch:qio_channel_ptr_t, inout re_ptr, inout im_ptr, len:size_t):syserr;
extern proc qio_channel_write_complex(threadsafe:c_int, byteorder:c_int, ch:qio_channel_ptr_t, inout re_ptr, inout im_ptr, len:size_t):syserr;

extern proc qio_channel_read_string(threadsafe:c_int, byteorder:c_int, str_style:int(64), ch:qio_channel_ptr_t, inout s:string, inout len:ssize_t, maxlen:ssize_t):syserr;
extern proc qio_channel_write_string(threadsafe:c_int, byteorder:c_int, str_style:int(64), ch:qio_channel_ptr_t, s:string, len:ssize_t):syserr;

extern proc qio_channel_scan_int(threadsafe:c_int, ch:qio_channel_ptr_t, inout ptr, len:size_t, issigned:c_int):syserr;
extern proc qio_channel_print_int(threadsafe:c_int, ch:qio_channel_ptr_t, inout ptr, len:size_t, issigned:c_int):syserr;

extern proc qio_channel_scan_float(threadsafe:c_int, ch:qio_channel_ptr_t, inout ptr, len:size_t):syserr;
extern proc qio_channel_print_float(threadsafe:c_int, ch:qio_channel_ptr_t, inout ptr, len:size_t):syserr;

extern proc qio_channel_scan_complex(threadsafe:c_int, ch:qio_channel_ptr_t, inout re_ptr, inout im_ptr, len:size_t):syserr;
extern proc qio_channel_print_complex(threadsafe:c_int, ch:qio_channel_ptr_t, inout re_ptr, inout im_ptr, len:size_t):syserr;

extern proc qio_channel_read_char(threadsafe:c_int, ch:qio_channel_ptr_t, inout char:int(32)):syserr;

extern proc qio_nbytes_char(chr:int(32)):c_int;
extern proc qio_encode_to_string(chr:int(32)):string;
extern proc qio_decode_char_buf(inout chr:int(32), inout nbytes:c_int, buf:string, buflen:ssize_t):syserr;

extern proc qio_channel_write_char(threadsafe:c_int, ch:qio_channel_ptr_t, char:int(32)):syserr;
extern proc qio_channel_skip_past_newline(threadsafe:c_int, ch:qio_channel_ptr_t):syserr;
extern proc qio_channel_write_newline(threadsafe:c_int, ch:qio_channel_ptr_t):syserr;

extern proc qio_channel_scan_string(threadsafe:c_int, ch:qio_channel_ptr_t, inout ptr:string, inout len:ssize_t, maxlen:ssize_t):syserr;
extern proc qio_channel_print_string(threadsafe:c_int, ch:qio_channel_ptr_t, ptr:string, len:ssize_t):syserr;

extern proc qio_channel_scan_literal(threadsafe:c_int, ch:qio_channel_ptr_t, match:string, len:ssize_t, skipws:c_int):syserr;
extern proc qio_channel_print_literal(threadsafe:c_int, ch:qio_channel_ptr_t, match:string, len:ssize_t):syserr;
//type iostyle = qio_style_t;

proc defaultIOStyle():iostyle {
  var ret:iostyle;
  qio_style_init_default(ret);
  return ret;
}

proc iostyle.native(str_style:int(64)=stringStyleWithVariableLength()):iostyle {
  var ret = this;
  ret.binary = 1;
  ret.byteorder = iokind.native:uint(8);
  ret.str_style = str_style;
  return ret;
}
proc iostyle.big(str_style:int(64)=stringStyleWithVariableLength()):iostyle {
  var ret = this;
  ret.binary = 1;
  ret.byteorder = iokind.big:uint(8);
  ret.str_style = str_style;
  return ret;
}
proc iostyle.little(str_style:int(64)=stringStyleWithVariableLength()):iostyle  {
  var ret = this;
  ret.binary = 1;
  ret.byteorder = iokind.little:uint(8);
  ret.str_style = str_style;
  return ret;
}
proc iostyle.text(/* args coming later */):iostyle  {
  var ret = this;
  ret.binary = 0;
  return ret;
}

/*
  QIO_FDFLAG_UNK,
  QIO_FDFLAG_READABLE,
  QIO_FDFLAG_WRITEABLE,
  QIO_FDFLAG_SEEKABLE
*/

extern type fdflag_t = c_int;

/* Access hints describe how a file will be used.
   These can help optimize.
 */
/*
  QIO_METHOD_DEFAULT,
  QIO_METHOD_READWRITE,
  QIO_METHOD_P_READWRITE,
  QIO_METHOD_MMAP,
  QIO_HINT_RANDOM,
  QIO_HINT_SEQUENTIAL,
  QIO_HINT_LATENCY,
  QIO_HINT_BANDWIDTH,
  QIO_HINT_CACHED,
  QIO_HINT_NOREUSE
}
*/

extern type iohints = c_int;

inline
proc _current_locale():int {
  var tmp:int;
  return __primitive("_get_locale", tmp);
}

record file {
  var home_uid:int = _current_locale();
  var _file_internal:qio_file_ptr_t = QIO_FILE_PTR_NULL;
}

// used for giving old warnings anyways...
enum FileAccessMode { read, write };

param _oldioerr="This program is using old-style I/O which is no longer supported.\n" +
                "See doc/README.io.\n" +
                "You'll probably want something like:\n" +
                "var f = open(filename, iomode.w).writer()\n" +
                "or\n" +
                "var f = open(filename, iomode.r).reader()\n";

// This file constructor exists to throw an error for old I/O code.
proc file.file(filename:string="",
               mode:FileAccessMode=FileAccessMode.read,
               path:string=".") {
  compilerError(_oldioerr);
}
proc file.open() {
  compilerError(_oldioerr);
}
proc file.filename : string {
  compilerError(_oldioerr + "file.filename is no longer supported");
}
proc file.mode {
  compilerError(_oldioerr + "file.mode is no longer supported");
}
proc file.isOpen: bool {
  compilerError(_oldioerr + "file.isOpen is no longer supported");
}

// TODO -- shouldn't have to write this this way!
proc chpl__initCopy(x: file) {
  on __primitive("chpl_on_locale_num", x.home_uid) {
    qio_file_retain(x._file_internal);
  }
  return x;
}

proc =(ret:file, x:file) {
  // retain -- release
  on __primitive("chpl_on_locale_num", x.home_uid) {
    qio_file_retain(x._file_internal);
  }

  on __primitive("chpl_on_locale_num", ret.home_uid) {
    qio_file_release(ret._file_internal);
  }

  // compiler will do this copy.
  ret.home_uid = x.home_uid;
  ret._file_internal = x._file_internal;
  return ret;
}

proc file.check() {
  if(1 == is_c_nil(_file_internal)) {
    halt("Operation attempted on an invalid file");
  }
}

/*
proc file.file() {
  var tmp:int;
  this.home_uid = _current_locale();
  this._file_internal = QIO_FILE_PTR_NULL;
}
*/

proc file.~file() {
  on __primitive("chpl_on_locale_num", this.home_uid) {
    qio_file_release(_file_internal);
    this._file_internal = QIO_FILE_PTR_NULL;
  }
}

/*
   We could support file locking and unlocking, but
   at the moment I don't see any use case in which
   it would make sense.
proc file.lock() {
  on __primitive("chpl_on_locale_num", this.home_uid) {
    seterr(nil, qio_file_lock(_file_internal));
  }
}
proc file.unlock() {
  on __primitive("chpl_on_locale_num", this.home_uid) {
    qio_file_unlock(_file_internal);
  }
}
*/

// File style cannot be modified after the file is created;
// this prevents race conditions;
// channel style is protected by channel lock, can be modified.
proc file._style:iostyle {
  check();

  var ret:iostyle;
  on __primitive("chpl_on_locale_num", this.home_uid) {
    var local_style:iostyle;
    qio_file_get_style(_file_internal, local_style);
    ret = local_style;
  }
  return ret;
}

/* Close a file.
   Alternately, file will be closed when it is no longer referred to */
proc file.close(out error:syserr) {
  check();
  on __primitive("chpl_on_locale_num", this.home_uid) {
    error = qio_file_close(_file_internal);
  }
}

proc file.close() {
  var err:syserr = ENOERR;
  this.close(err);
  if err then ioerror(err, "in file.close", this.tryGetPath());
}

/* Sync a file to disk. */
proc file.fsync(out error:syserr) {
  check();
  on __primitive("chpl_on_locale_num", this.home_uid) {
    error = qio_file_sync(_file_internal);
  }
}
proc file.fsync() {
  var err:syserr = ENOERR;
  this.fsync(err);
  if err then ioerror(err, "in file.fsync", this.tryGetPath());
}

/* Get the path to a file. */
proc file.getPath(out error:syserr) : string {
  check();
  var ret:string;
  on __primitive("chpl_on_locale_num", this.home_uid) {
    var tmp:string;
    var tmp2:string;
    error = qio_file_path(_file_internal, tmp);
    if !error {
      error = qio_shortest_path(tmp2, tmp);
    }
    if !error {
      ret = tmp2;
    } else {
      ret = "unknown";
    }
  }
 return ret;
}

proc file.tryGetPath() : string {
  var err:syserr = ENOERR;
  var ret:string;
  ret = this.getPath(err);
  if err then return "unknown";
  else return ret;
}

proc file.path : string {
  var err:syserr = ENOERR;
  var ret:string;
  ret = this.getPath(err);
  if err then ioerror(err, "in file.path");
}

// these strings are here (vs in _modestring)
// in an attempt to avoid string copies, leaks,
// and unnecessary allocations.
const _r = "r";
const _rw  = "r+";
const _cw = "w";
const _cwr = "w+";

proc _modestring(mode:iomode) {
  select mode {
    when iomode.r do return _r;
    when iomode.rw do return _rw;
    when iomode.cw do return _cw;
    when iomode.cwr do return _cwr;
    otherwise halt("Invalid mode");
  }
}

proc open(out error:syserr, path:string, mode:iomode, hints:iohints=IOHINT_NONE, style:iostyle = defaultIOStyle()):file {
  var local_style = style;
  var ret:file;
  ret.home_uid = _current_locale();
  error = qio_file_open_access(ret._file_internal, path, _modestring(mode), hints, local_style);
  return ret;
}
proc open(path:string, mode:iomode, hints:iohints=IOHINT_NONE, style:iostyle = defaultIOStyle()):file {
  var err:syserr = ENOERR;
  var ret = open(err, path, mode, hints, style);
  if err then ioerror(err, "in open", path);
  return ret;
}
proc openfd(fd: fd_t, out error:syserr, hints:iohints=IOHINT_NONE, style:iostyle = defaultIOStyle()):file {
  var local_style = style;
  var ret:file;
  ret.home_uid = _current_locale();
  error = qio_file_init(ret._file_internal, chpl_cnullfile(), fd, hints, local_style, 0);
  return ret;
}
proc openfd(fd: fd_t, hints:iohints=IOHINT_NONE, style:iostyle = defaultIOStyle()):file {
  var err:syserr = ENOERR;
  var ret = openfd(fd, err, hints, style);
  if err {
    var path:string;
    var e2:syserr = ENOERR;
    e2 = qio_file_path_for_fd(fd, path);
    if e2 then path = "unknown";
    ioerror(err, "in openfd", path);
  }
  return ret;
}
proc openfp(fp: _file, out error:syserr, hints:iohints=IOHINT_NONE, style:iostyle = defaultIOStyle()):file {
  var local_style = style;
  var ret:file;
  ret.home_uid = _current_locale();
  error = qio_file_init(ret._file_internal, fp, -1, hints, local_style, 1);
  return ret;
}
proc openfp(fp: _file, hints:iohints=IOHINT_NONE, style:iostyle = defaultIOStyle()):file {
  var err:syserr = ENOERR;
  var ret = openfp(fp, err, hints, style);
  if err {
    var path:string;
    var e2:syserr = ENOERR;
    e2 = qio_file_path_for_fp(fp, path);
    if e2 then path = "unknown";
    ioerror(err, "in openfp", path);
  }
  return ret;
}

proc opentmp(out error:syserr, hints:iohints=IOHINT_NONE, style:iostyle = defaultIOStyle()):file {
  var local_style = style;
  var ret:file;
  ret.home_uid = _current_locale();
  error = qio_file_open_tmp(ret._file_internal, hints, local_style);
  return ret;
}
proc opentmp(hints:iohints=IOHINT_NONE, style:iostyle = defaultIOStyle()):file {
  var err:syserr = ENOERR;
  var ret = opentmp(err, hints, style);
  if err then ioerror(err, "in opentmp");
  return ret;
}

proc openmem(out error:syserr, style:iostyle = defaultIOStyle()) {
  var local_style = style;
  var ret:file;
  ret.home_uid = _current_locale();
  error = qio_file_open_mem(ret._file_internal, QBUFFER_PTR_NULL, local_style);
  return ret;
}
proc openmem(style:iostyle = defaultIOStyle()):file {
  var err:syserr = ENOERR;
  var ret = openmem(err, style);
  if err then ioerror(err, "in openmem");
  return ret;
}

/* in the future, this will be an interface.
   */
record channel {
  param writing:bool;
  param kind:iokind;
  param locking:bool;
  var home_uid:int;
  var _channel_internal:qio_channel_ptr_t = QIO_CHANNEL_PTR_NULL;
}

// TODO -- shouldn't have to write this this way!
proc chpl__initCopy(x: channel) {
  on __primitive("chpl_on_locale_num", x.home_uid) {
    qio_channel_retain(x._channel_internal);
  }
  return x;
}

proc =(ret:channel, x:channel) {
  // retain -- release
  on __primitive("chpl_on_locale_num", x.home_uid) {
    qio_channel_retain(x._channel_internal);
  }

  on __primitive("chpl_on_locale_num", ret.home_uid) {
    qio_channel_release(ret._channel_internal);
  }

  ret.home_uid = x.home_uid;
  ret._channel_internal = x._channel_internal;
  return ret;
}

proc channel.channel(param writing:bool, param kind:iokind, param locking:bool, f:file, out error:syserr, hints:c_int, start:int(64), end:int(64), style:iostyle) {
  on __primitive("chpl_on_locale_num", f.home_uid) {
    this.home_uid = f.home_uid;
    var local_style = style;
    error = qio_channel_create(this._channel_internal, f._file_internal, hints, !writing, writing, start, end, local_style);
  }
}

proc channel.~channel() {
  on __primitive("chpl_on_locale_num", this.home_uid) {
    qio_channel_release(_channel_internal);
    this._channel_internal = QIO_CHANNEL_PTR_NULL;
  }
}

// Used to represent a Unicode character
record ioChar {
  var ch:int(32);
  proc writeThis(f: Writer) {
    halt("ioChar.writeThis must be written in Writer subclasses");
  }
}
pragma "inline" proc _cast(type t, x: ioChar) where t == string {
  return qio_encode_to_string(x.ch);
}

// Used to represent "\n", but never escaped...
record ioNewline {
  proc writeThis(f: Writer) {
    // Normally this is handled explicitly in read/write.
    f.write("\n");
  }
}
pragma "inline" proc _cast(type t, x: ioNewline) where t == string {
  return "\n";
}

// Used to represent a constant string we want to read or write...
record ioLiteral {
  var val: string;
  var ignoreWhiteSpace: bool = true;
  proc writeThis(f: Writer) {
    // Normally this is handled explicitly in read/write.
    f.write(val);
  }
}

pragma "inline" proc _cast(type t, x: ioLiteral) where t == string {
  return x.val;
}

// Used to represent some number of bits we want to read or write...
record ioBits {
  var v:uint(64);
  var nbits:int(8);
  proc writeThis(f: Writer) {
    // Normally this is handled explicitly in read/write.
    f.write(v);
  }
}

pragma "inline" proc _cast(type t, x: ioBits) where t == string {
  return "ioBits(v=" + x.v:string + ", nbits=" + x.nbits:string + ")";
}

proc channel._ch_ioerror(error:syserr, msg:string) {
  var path:string = "unknown";
  var offset:int(64) = -1;
  on __primitive("chpl_on_locale_num", this.home_uid) {
    var tmp_path:string;
    var tmp_offset:int(64);
    var err:syserr = ENOERR;
    err = qio_channel_path_offset(locking, _channel_internal, tmp_path, tmp_offset);
    if !err {
      path = tmp_path;
      offset = tmp_offset;
    }
  }
  ioerror(error, msg, path, offset);
}
proc channel._ch_ioerror(errstr:string, msg:string) {
  var path:string = "unknown";
  var offset:int(64) = -1;
  on __primitive("chpl_on_locale_num", this.home_uid) {
    var tmp_path:string;
    var tmp_offset:int(64);
    var err:syserr = ENOERR;
    err = qio_channel_path_offset(locking, _channel_internal, tmp_path, tmp_offset);
    if !err {
      path = tmp_path;
      offset = tmp_offset;
    }
  }
  ioerror(errstr, msg, path, offset);
}

inline
proc channel.lock(out error:syserr) {
  error = ENOERR;
  if locking {
    on __primitive("chpl_on_locale_num", this.home_uid) {
      error = qio_channel_lock(_channel_internal);
    }
  }
}
inline
proc channel.lock() {
  var err:syserr = ENOERR;
  this.lock(err);
  if err then this._ch_ioerror(err, "in lock");
}

inline
proc channel.unlock() {
  if locking {
    on __primitive("chpl_on_locale_num", this.home_uid) {
      qio_channel_unlock(_channel_internal);
    }
  }
}

proc channel.offset():int(64) {
  var ret:int(64);
  on __primitive("chpl_on_locale_num", this.home_uid) {
    this.lock();
    ret = qio_channel_offset_unlocked(_channel_internal);
    this.unlock();
  }
  return ret;
}

// you should have a lock before you use these...

inline
proc channel._offset():int(64) {
  var ret:int(64);
  on __primitive("chpl_on_locale_num", this.home_uid) {
    ret = qio_channel_offset_unlocked(_channel_internal);
  }
  return ret;
}

inline
proc channel._mark():syserr {
  return qio_channel_mark(false, _channel_internal);
}
inline
proc channel._revert() {
  qio_channel_revert_unlocked(_channel_internal);
}
inline
proc channel._commit() {
  qio_channel_commit_unlocked(_channel_internal);
}
proc channel._style():iostyle {
  var ret:iostyle;
  on __primitive("chpl_on_locale_num", this.home_uid) {
    var local_style:iostyle;
    qio_channel_get_style(_channel_internal, local_style);
    ret = local_style;
  }
  return ret;
}
proc channel._set_style(style:iostyle) {
  on __primitive("chpl_on_locale_num", this.home_uid) {
    var local_style:iostyle = style;
    qio_channel_set_style(_channel_internal, local_style);
  }
}

proc file.reader(out error:syserr, param kind=iokind.dynamic, param locking=true, start:int(64) = 0, end:int(64) = max(int(64)), hints:iohints = IOHINT_NONE, style:iostyle = this._style): channel(false, kind, locking) {
  check();

  var ret:channel(false, kind, locking);
  on __primitive("chpl_on_locale_num", this.home_uid) {
    ret = new channel(false, kind, locking, this, error, hints, start, end, style);
  }
  return ret;
}
proc file.reader(param kind=iokind.dynamic, param locking=true, start:int(64) = 0, end:int(64) = max(int(64)), hints:iohints = IOHINT_NONE, style:iostyle = this._style): channel(false, kind, locking) {
  var err:syserr = ENOERR;
  var ret = this.reader(err, kind, locking, start, end, hints, style);
  if err then ioerror(err, "in file.reader", this.tryGetPath());
  return ret;
}
// for convenience..
proc file.lines(out error:syserr, param locking:bool = true, start:int(64) = 0, end:int(64) = max(int(64)), hints:iohints = IOHINT_NONE, style:iostyle = this._style) {
  check();

  style.string_format = QIO_STRING_FORMAT_TOEND;
  style.string_end = 0x0a; // '\n'

  param kind = iokind.dynamic;
  var ret:ItemReader(string, kind, locking);
  on __primitive("chpl_on_locale_num", this.home_uid) {
    var ch = new channel(false, kind, locking, this, error, hints, start, end, style);
    ret = new ItemReader(string, kind, locking, ch);
  }
  return ret;
}
proc file.lines(param locking:bool = true, start:int(64) = 0, end:int(64) = max(int(64)), hints:iohints = IOHINT_NONE, style:iostyle = this._style) {
  var err:syserr = ENOERR;
  var ret = this.lines(err, locking, start, end, hints, style);
  if err then ioerror(err, "in file.lines", this.tryGetPath());
  return ret;
}

proc file.writer(out error:syserr, param kind=iokind.dynamic, param locking=true, start:int(64) = 0, end:int(64) = max(int(64)), hints:iohints = IOHINT_NONE, style:iostyle = this._style): channel(true,kind,locking) {
  check();

  var ret:channel(true, kind, locking);
  on __primitive("chpl_on_locale_num", this.home_uid) {
    ret = new channel(true, kind, locking, this, error, hints, start, end, style);
  }
  return ret;
}
proc file.writer(param kind=iokind.dynamic, param locking=true, start:int(64) = 0, end:int(64) = max(int(64)), hints:c_int = 0, style:iostyle = this._style): channel(true,kind,locking)
{
  var err:syserr = ENOERR;
  var ret = this.writer(err, kind, locking, start, end, hints, style);

  if err then ioerror(err, "in file.writer", this.tryGetPath());
  return ret;
}

proc _isIoPrimitiveType(type t) param return
  _isPrimitiveType(t) || _isComplexType(t) ||
  _isImagType(t) || _isEnumeratedType(t);

 proc _isIoPrimitiveTypeOrNewline(type t) param return
  _isIoPrimitiveType(t) || t == ioNewline || t == ioLiteral || t == ioChar || t == ioBits;

    //var trues = ("true", "1", "yes");
    //var falses = ("false", "0", "no");
const _trues = tuple("true");
const _falses = tuple("false");
const _i = "i";

// Read routines for all primitive types.
proc _read_text_internal(_channel_internal:qio_channel_ptr_t, out x:?t):syserr where _isIoPrimitiveType(t) {
  if _isBooleanType(t) {
    var num = _trues.size;
    var err:syserr = ENOERR;
    var got:bool;

    err = EFORMAT;

    for i in 1..num {
      err = qio_channel_scan_literal(false, _channel_internal, _trues(i), _trues(i).length, 1);
      if !err {
        got = true;
        break;
      } else if err == EEOF {
        break;
      }
      err = qio_channel_scan_literal(false, _channel_internal, _falses(i), _falses(i).length, 1);
      if !err {
        got = false;
        break;
      } else if err == EEOF {
        break;
      }
    }

    if !err then x = got;
    return err;
  } else if _isIntegralType(t) {
    // handles int types
    return qio_channel_scan_int(false, _channel_internal, x, numBytes(t), _isSignedType(t));
  } else if _isRealType(t) {
    // handles real
    return qio_channel_scan_float(false, _channel_internal, x, numBytes(t));
  } else if _isImagType(t) {
    var err = qio_channel_mark(false, _channel_internal);
    if err then return err;

    err = qio_channel_scan_float(false, _channel_internal, x, numBytes(t));
    if !err {
      err = qio_channel_scan_literal(false, _channel_internal, _i, 1, false);
    }
    if !err {
      qio_channel_commit_unlocked(_channel_internal);
    } else {
      qio_channel_revert_unlocked(_channel_internal);
    }
    return err;
  } else if _isComplexType(t)  {
    // handle complex types
    var re:x.re.type;
    var im:x.im.type;
    var err:syserr = ENOERR;
    err = qio_channel_scan_complex(false, _channel_internal, re, im, numBytes(x.re.type));
    x = (re, im):t; // cast tuple to complex to get complex num.
    return err;
  } else if t == string {
    // handle string
    var len:ssize_t;
    return qio_channel_scan_string(false, _channel_internal, x, len, -1);
  } else if _isEnumeratedType(t) {
    var err:syserr = ENOERR;
    for i in chpl_enumerate(t) {
      var str:string = i:string;
      var slen:ssize_t = str.length;
      err = qio_channel_scan_literal(false, _channel_internal, str, slen, 1);
      if !err {
        x = i;
        break;
      } else if err != EFORMAT then break;
    }
    return err;
  } else {
    compilerError("Unknown primitive type in _read_text_internal ", typeToString(t));
  }
  return EINVAL;
}

proc _write_text_internal(_channel_internal:qio_channel_ptr_t, x:?t):syserr where _isIoPrimitiveType(t) {
  if _isBooleanType(t) {
    if x {
      return qio_channel_print_string(false, _channel_internal, _trues(1), _trues(1).length:ssize_t);
    } else {
      return qio_channel_print_string(false, _channel_internal, _falses(1), _falses(1).length:ssize_t);
    }
  } else if _isIntegralType(t) {
    // handles int types
    return qio_channel_print_int(false, _channel_internal, x, numBytes(t), _isSignedType(t));

  } else if _isRealType(t) {
    // handles real
    return qio_channel_print_float(false, _channel_internal, x, numBytes(t));
  } else if _isImagType(t) {
    var err = qio_channel_mark(false, _channel_internal);
    if err then return err;

    err = qio_channel_print_float(false, _channel_internal, x, numBytes(t));
    if !err {
      err = qio_channel_print_literal(false, _channel_internal, _i, 1);
    }
    if !err {
      qio_channel_commit_unlocked(_channel_internal);
    } else {
      qio_channel_revert_unlocked(_channel_internal);
    }
    return err;
  } else if _isComplexType(t)  {
    // handle complex types
    var re = x.re;
    var im = x.im;
    return qio_channel_print_complex(false, _channel_internal, re, im, numBytes(x.re.type));
  } else if t == string {
    // handle string
    return qio_channel_print_string(false, _channel_internal, x, x.length:ssize_t);
  } else if _isEnumeratedType(t) {
    var s = x:string;
    return qio_channel_print_string(false, _channel_internal, s, s.length:ssize_t);
  } else {
    compilerError("Unknown primitive type in _write_text_internal ", typeToString(t));
  }
  return EINVAL;
}

inline
proc _read_binary_internal(_channel_internal:qio_channel_ptr_t, param byteorder:iokind, out x:?t):syserr where _isIoPrimitiveType(t) {
  if _isBooleanType(t) {
    var got:int(32);
    got = qio_channel_read_byte(false, _channel_internal);
    if got >= 0 {
      x = (got != 0);
      return ENOERR;
    } else {
      return (-got):syserr;
    }
  } else if _isIntegralType(t) {
    if numBytes(t) == 1 {
      var got:int(32);
      got = qio_channel_read_byte(false, _channel_internal);
      if got >= 0 {
        x = (got:uint(8)):t;
        return ENOERR;
      } else {
        return (-got):syserr;
      }
    } else {
      // handles int types
      return qio_channel_read_int(false, byteorder, _channel_internal, x, numBytes(t), _isSignedType(t));
    }
  } else if _isFloatType(t) {
    // handles real, imag
    return qio_channel_read_float(false, byteorder, _channel_internal, x, numBytes(t));
  } else if _isComplexType(t)  {
    // handle complex types
    var re:x.re.type;
    var im:x.im.type;
    var err:syserr = ENOERR;
    err = qio_channel_read_complex(false, byteorder, _channel_internal, re, im, numBytes(x.re.type));
    x = (re, im):t; // cast tuple to complex to get complex num.
    return err;
  } else if t == string {
    // handle string
    var len:ssize_t;
    return qio_channel_read_string(false, byteorder, qio_channel_str_style(_channel_internal), _channel_internal, x, len, -1);
  } else if _isEnumeratedType(t) {
    var i:enum_mintype(t);
    var err:syserr = ENOERR;
    err = qio_channel_read_int(false, byteorder, _channel_internal, i, numBytes(i.type), _isSignedType(i.type));
    x = i:t;
    return err;
  } else {
    compilerError("Unknown primitive type in _read_binary_internal ", typeToString(t));
  }
}

inline
proc _write_binary_internal(_channel_internal:qio_channel_ptr_t, param byteorder:iokind, x:?t):syserr where _isIoPrimitiveType(t) {
  if _isBooleanType(t) {
    var zero_one:uint(8) = if x then 1:uint(8) else 0:uint(8);
    return qio_channel_write_byte(false, _channel_internal, zero_one);
  } else if _isIntegralType(t) {
    if numBytes(t) == 1 {
      return qio_channel_write_byte(false, _channel_internal, x:uint(8));
    } else {
      // handles int types
      return qio_channel_write_int(false, byteorder, _channel_internal, x, numBytes(t), _isSignedType(t));
    }
  } else if _isFloatType(t) {
    // handles real, imag
    return qio_channel_write_float(false, byteorder, _channel_internal, x, numBytes(t));
  } else if _isComplexType(t)  {
    // handle complex types
    var re = x.re;
    var im = x.im;
    return qio_channel_write_complex(false, byteorder, _channel_internal, re, im, numBytes(x.re.type));
  } else if t == string {
    return qio_channel_write_string(false, byteorder, qio_channel_str_style(_channel_internal), _channel_internal, x, x.length: ssize_t);
  } else if _isEnumeratedType(t) {
    var i:enum_mintype(t) = x:enum_mintype(t);
    return qio_channel_write_int(false, byteorder, _channel_internal, i, numBytes(i.type), _isSignedType(i.type));
  } else {
    halt("Unknown primitive type in write_binary_internal " + typeToString(t));
  }
}

// Channel must be locked, must be running on this.home
// x is inout because it might contain a literal string.
inline
proc _read_one_internal(_channel_internal:qio_channel_ptr_t, param kind:iokind, inout x:?t):syserr where _isIoPrimitiveTypeOrNewline(t) {
  var e:syserr = EINVAL;
  if t == ioNewline {
    return qio_channel_skip_past_newline(false, _channel_internal);
  } else if t == ioChar {
    return qio_channel_read_char(false, _channel_internal, x.ch);
  } else if t == ioLiteral {
    //writeln("in scan literal ", x.val);
    return qio_channel_scan_literal(false, _channel_internal, x.val, x.val.length, x.ignoreWhiteSpace);
    //e = qio_channel_scan_literal(false, _channel_internal, x.val, x.val.length, x.ignoreWhiteSpace);
    //writeln("Scanning literal ", x.val,  " yeilded error ", e);
    //return e;
  } else if t == ioBits {
    return qio_channel_read_bits(false, _channel_internal, x.v, x.nbits);
  } else if kind == iokind.dynamic {
    var binary:uint(8) = qio_channel_binary(_channel_internal);
    var byteorder:uint(8) = qio_channel_byteorder(_channel_internal);
    if binary {
      select byteorder {
        when iokind.big    do e = _read_binary_internal(_channel_internal, iokind.big, x);
        when iokind.little do e = _read_binary_internal(_channel_internal, iokind.little, x);
        otherwise             e = _read_binary_internal(_channel_internal, iokind.native, x);
      }
    } else {
      e = _read_text_internal(_channel_internal, x);
    }
  } else {
    e = _read_binary_internal(_channel_internal, kind, x);
  }
  return e;
}

// Channel must be locked, must be running on this.home
inline
proc _write_one_internal(_channel_internal:qio_channel_ptr_t, param kind:iokind, x:?t):syserr where _isIoPrimitiveTypeOrNewline(t) {
  var e:syserr = EINVAL;
  if t == ioNewline {
    return qio_channel_write_newline(false, _channel_internal);
  } else if t == ioChar {
    return qio_channel_write_char(false, _channel_internal, x.ch);
  } else if t == ioLiteral {
    return qio_channel_print_literal(false, _channel_internal, x.val, x.val.length:ssize_t);
  } else if t == ioBits {
    return qio_channel_write_bits(false, _channel_internal, x.v, x.nbits);
  } else if kind == iokind.dynamic {
    var binary:uint(8) = qio_channel_binary(_channel_internal);
    var byteorder:uint(8) = qio_channel_byteorder(_channel_internal);
    if binary {
      select byteorder {
        when iokind.big    do e = _write_binary_internal(_channel_internal, iokind.big, x);
        when iokind.little do e = _write_binary_internal(_channel_internal, iokind.little, x);
        otherwise             e = _write_binary_internal(_channel_internal, iokind.native, x);
      }
    } else {
      e = _write_text_internal(_channel_internal, x);
    }
  } else {
    e = _write_binary_internal(_channel_internal, kind, x);
  }
  return e;
}

inline
proc _read_one_internal(_channel_internal:qio_channel_ptr_t, param kind:iokind, inout x:?t):syserr {
  var reader = new ChannelReader(_channel_internal=_channel_internal);
  var err:syserr = ENOERR;
  reader.read(x);
  err = reader.err;
  delete reader;
  return err;
}

inline
proc _write_one_internal(_channel_internal:qio_channel_ptr_t, param kind:iokind, x:?t):syserr {
  var writer = new ChannelWriter(_channel_internal=_channel_internal);
  var err:syserr = ENOERR;
  writer.write(x);
  err = writer.err;
  delete writer;
  return err;
}

/* Returns true if we read all the args,
   false if we encountered EOF (or possibly another error and didn't halt)*/
inline
proc channel.read(inout args ...?k,
                  out error:syserr):bool {
  if writing then compilerError("read on write-only channel");
  error = ENOERR;
  on __primitive("chpl_on_locale_num", this.home_uid) {
    this.lock();
    for param i in 1..k {
      if !error {
        error = _read_one_internal(_channel_internal, kind, args[i]);
      }
    }
    this.unlock();
  }
  return !error;
}
var _arg_to_proto_names = ("a", "b", "c", "d", "e", "f");
proc _args_to_proto(args ...?k,
                    preArg:string) {
  var err_args:string = "";
  for param i in 1..k {
    var name:string;
    if i <= _arg_to_proto_names.size then name = _arg_to_proto_names(i);
    else name = "x" + i:string;
    err_args += preArg + name + ":" + typeToString(args(i).type);
    if i != k then err_args += ", ";
  }
  return err_args;
}

inline
proc channel.read(inout args ...?k):bool {
  var e:syserr = ENOERR;
  this.read((...args), error=e);
  if !e then return true;
  else if e == EEOF then return false;
  else {
    this._ch_ioerror(e, "in channel.read(" +
                        _args_to_proto((...args), preArg="inout ") +
                        ")");
    return false;
  }
}
proc channel.read(inout args ...?k,
                  style:iostyle,
                  out error:syserr):bool {
  if writing then compilerError("read on write-only channel");
  error = ENOERR;
  on __primitive("chpl_on_locale_num", this.home_uid) {
    this.lock();
    var save_style = this._style();
    this._set_style(style);
    for param i in 1..k {
      if !error {
        error = _read_one_internal(_channel_internal, kind, args[i]);
      }
    }
    this._set_style(save_style);
    this.unlock();
  }
  return !error;
}
proc channel.read(inout args ...?k,
                  style:iostyle):bool {
  var e:syserr = ENOERR;
  this.read((...args), style=iostyle, error=e);
  if !e then return true;
  else if e == EEOF then return false;
  else {
    this._ch_ioerror(e, "in channel.read(" +
                        _args_to_proto((...args), preArg="inout ") +
                        "style:iostyle)");
    return false;
  }
}

proc channel.readline(inout arg:string, out error:syserr):bool {
  if writing then compilerError("read on write-only channel");
  error = ENOERR;
  on __primitive("chpl_on_locale_num", this.home_uid) {
    this.lock();
    var save_style = this._style();
    var mystyle = save_style.text();
    mystyle.string_format = QIO_STRING_FORMAT_TOEND;
    mystyle.string_end = 0x0a; // ascii newline.
    this._set_style(mystyle);
    error = _read_one_internal(_channel_internal, iokind.dynamic, arg);
    this._set_style(save_style);
    this.unlock();
  }
  return !error;
}
proc channel.readline(inout arg:string):bool {
  var e:syserr = ENOERR;
  this.readline(arg, error=e);
  if !e then return true;
  else if e == EEOF then return false;
  else {
    this._ch_ioerror(e, "in channel.readline(inout arg:string)");
    return false;
  }
}

inline
proc channel.readbits(out v:uint(64), nbits:int(8), out error:syserr):bool {
  var tmp:ioBits;
  var ret:bool;

  error = ENOERR;

  tmp.nbits = nbits;
  ret = this.read(tmp, error=error);
  v = tmp.v;

  return ret;
}
proc channel.readbits(out v:uint(64), nbits:int(8)):bool {
  var e:syserr = ENOERR;
  this.readbits(v, nbits, error=e);
  if !e then return true;
  else if e == EEOF then return false;
  else {
    this._ch_ioerror(e, "in channel.readbits(out v:uint(64), nbits:int(8))");
    return false;
  }
}

inline
proc channel.writebits(v:uint(64), nbits:int(8), out error:syserr):bool {
  return this.write(new ioBits(v, nbits), error=error);
}
proc channel.writebits(v:uint(64), nbits:int(8)):bool {
  var e:syserr = ENOERR;
  this.writebits(v, nbits, error=e);
  if !e then return true;
  else {
    this._ch_ioerror(e, "in channel.writebits(v:uint(64), nbits:int(8))");
    return false;
  }
}

proc channel.readln(out error:syserr):bool {
  var nl = new ioNewline();
  return this.read(nl, error=error);
}
proc channel.readln():bool {
  var nl = new ioNewline();
  return this.read(nl);
}

proc channel.readln(inout args ...?k):bool {
  var nl = new ioNewline();
  return this.read((...args), nl);
}
proc channel.readln(inout args ...?k,
                    out error:syserr):bool {
  var nl = new ioNewline();
  return this.read((...args), nl, error=error);
}
proc channel.readln(inout args ...?k,
                    style:iostyle,
                    out error:syserr):bool {
  var nl = new ioNewline();
  return this.read((...args), nl, style=style, error=error);
}
proc channel.readln(inout args ...?k,
                    style:iostyle):bool {
  var nl = new ioNewline();
  return this.read((...args), nl, style=style);
}

proc channel.read(type t) {
  var tmp:t;
  var e:syserr = ENOERR;
  this.read(tmp, error=e);
  if e then this._ch_ioerror(e, "in channel.read(type)");
  return tmp;
}
proc channel.readln(type t) {
  var tmp:t;
  var e:syserr = ENOERR;
  this.readln(tmp, error=e);
  if e then this._ch_ioerror(e, "in channel.readln(type)");
  return tmp;
}
// Read/write tuples of types.
proc channel.readln(type t ...?numTypes) where numTypes > 1 {
  var tupleVal: t;
  for param i in 1..numTypes-1 do
    tupleVal(i) = this.read(t(i));
  tupleVal(numTypes) = this.readln(t(numTypes));
  return tupleVal;
}
proc channel.read(type t ...?numTypes) where numTypes > 1 {
  var tupleVal: t;
  for param i in 1..numTypes do
    tupleVal(i) = this.read(t(i));
  return tupleVal;
}

inline
proc channel.write(args ...?k, out error:syserr):bool {
  if !writing then compilerError("write on read-only channel");
  error = ENOERR;
  on __primitive("chpl_on_locale_num", this.home_uid) {
    this.lock();
    for param i in 1..k {
      if !error {
        error = _write_one_internal(_channel_internal, kind, args(i));
      }
    }
    this.unlock();
  }
  return !error;
}

inline
proc channel.write(args ...?k):bool {
  var e:syserr = ENOERR;
  this.write((...args), error=e);
  if !e then return true;
  else {
    this._ch_ioerror(e, "in channel.write(" +
                        _args_to_proto((...args), preArg="") +
                        ")");
    return false;
  }
}

proc channel.write(args ...?k,
                   style:iostyle,
                   out error:syserr):bool {
  if !writing then compilerError("write on read-only channel");
  error = ENOERR;
  on __primitive("chpl_on_locale_num", this.home_uid) {
    this.lock();
    var save_style = this._style();
    this._set_style(style);
    for param i in 1..k {
      if !error {
        error = _write_one_internal(_channel_internal, iokind.dynamic, args(i));
      }
    }
    this._set_style(save_style);
    this.unlock();
  }
  return !error;
}
proc channel.write(args ...?k,
                   style:iostyle):bool {
  var e:syserr = ENOERR;
  this.write((...args), style=style, error=e);
  if !e then return true;
  else {
    this._ch_ioerror(e, "in channel.write(" +
                        _args_to_proto((...args), preArg="") +
                        "style:iostyle)");
    return false;
  }
}

proc channel.writeln(out error:syserr):bool {
  return this.write(new ioNewline(), error=error);
}
proc channel.writeln():bool {
  return this.write(new ioNewline());
}
proc channel.writeln(args ...?k, out error:syserr):bool {
  return this.write((...args), new ioNewline(), error=error);
}
proc channel.writeln(args ...?k):bool {
  return this.write((...args), new ioNewline());
}
proc channel.writeln(args ...?k,
                     style:iostyle):bool {
  return this.write((...args), new ioNewline(), style=style);
}
proc channel.writeln(args ...?k,
                     style:iostyle,
                     out error:syserr):bool {
  return this.write((...args), new ioNewline(), style=style, error=error);
}

proc channel.flush(out error:syserr) {
  error = ENOERR;
  on __primitive("chpl_on_locale_num", this.home_uid) {
    error = qio_channel_flush(locking, _channel_internal);
  }
}
proc channel.flush() {
  var e:syserr = ENOERR;
  this.flush(error=e);
  if e then this._ch_ioerror(e, "in channel.flush");
}

proc channel.assertEOF(error:string) {
  if writing {
    this._ch_ioerror(EINVAL, "assertEOF on writing channel");
  } else {
    var tmp:uint(8);
    var err:syserr;
    this.read(tmp, error=err);
    if err != EEOF {
      this._ch_ioerror("assert failed", error);
    }
  }
}
proc channel.assertEOF() {
  this.assertEOF("- Not at EOF");
}

proc channel.close(out error:syserr) {
  error = ENOERR;
  on __primitive("chpl_on_locale_num", this.home_uid) {
    error = qio_channel_close(locking, _channel_internal);
  }
}

proc channel.close() {
  var e:syserr = ENOERR;
  this.close(error=e);
  if e then this._ch_ioerror(e, "in channel.close");
}

/*
proc channel.modifyStyle(f:func(iostyle, iostyle))
{
  on __primitive("chpl_on_locale_num", this.home_uid) {
    this.lock();
    var style = this._style();
    style = f(style);
    this._set_style(style);
    this.unlock();
  }
}
*/

/* Move between min_len and max_len bytes
   of data from a read channel to a write channel.
   Blocks on reading up to min_len; blocks on writing
   to the write channel. Returns the number of bytes
   moved and/or an error number. */
/*proc splice(input:read_channel, output:write_channel,
           min_length:int(64), max_length:int(64), onErr:ErrorHandler = nil) {
  halt("unimplemented");
}*/

/* Move between min_len and max_len bytes
   of data from a read channel to several write channels;
   the data is written to all of the write channels.
   Blocks on reading up to min_len; blocks on writing to
   all the write channels. Returns the number of bytes
   moved and/or an error number. */
/*proc tee(input:read_channel, output:[] write_channel, onErr:ErrorHandler = nil) {
  halt("unimplemented");
}*/

record ItemReader {
  type ItemType;
  param kind:iokind;
  param locking:bool;
  var ch:channel(false,kind,locking);
  proc read(out arg:ItemType, out error:syserr):bool {
    return ch.read(arg, error=error);
  }
  proc read(out arg:ItemType):bool {
    return ch.read(arg);
  }

  iter these() {
    while true {
      var x:ItemType;
      var gotany = ch.read(x);
      if ! gotany then break;
      yield x;
    }
  }
  /*
  iter these(out error:syserr) {
    while true {
      var x:ItemType;
      var gotany = ch.read(x, error=error);
      if ! gotany then break;
      yield x;
    }
  }*/
}

proc channel.itemReader(type ItemType, param kind:iokind=iokind.dynamic) {
  if writing then compilerError(".itemReader on write-only channel");
  return new ItemReader(ItemType, kind, locking, this);
}

record ItemWriter {
  type ItemType;
  param kind:iokind;
  param locking:bool;
  var ch:channel(false,kind);
  proc write(arg:ItemType, out error:syserr):bool {
    return ch.write(arg, error=error);
  }
  proc write(arg:ItemType):bool {
    return ch.write(arg);
  }
}

proc channel.itemWriter(type ItemType, param kind:iokind=iokind.dynamic) {
  if !writing then compilerError(".itemWriter on read-only channel");
  return new ItemWriter(ItemType, kind, locking, this);
}

// And now, the toplevel items.

const stdin:channel(false, iokind.dynamic, true) = openfd(0).reader();
const stdout:channel(true, iokind.dynamic, true) = openfp(chpl_cstdout()).writer();
const stderr:channel(true, iokind.dynamic, true) = openfp(chpl_cstderr()).writer();

proc write(args ...?n) {
  stdout.write((...args));
}
proc writeln(args ...?n) {
  stdout.writeln((...args));
}
proc writeln() {
  stdout.writeln();
}

proc read(inout args ...?n):bool {
  return stdin.read((...args));
}
proc readln(inout args ...?n):bool {
  return stdin.readln((...args));
}
proc readln():bool {
  return stdin.readln();
}

proc readln(type t ...?numTypes) {
  return stdin.readln((...t));
}
proc read(type t ...?numTypes) {
  return stdin.read((...t));
}

class ChannelWriter : Writer {
  var _channel_internal:qio_channel_ptr_t = QIO_CHANNEL_PTR_NULL;
  var err:syserr = ENOERR;
  proc binary:bool {
    var ret:uint(8) = qio_channel_binary(_channel_internal);
    return ret != 0;
  }
  proc error():syserr {
    return err;
  }
  proc setError(e:syserr) {
    err = e;
  }
  proc clearError() {
    err = 0;
  }
  proc writePrimitive(x) {
    if !err {
      err = _write_one_internal(_channel_internal, iokind.dynamic, x);
    }
  }
  proc writeThis(w:Writer) {
    // MPF - I don't understand why I had to add this,
    // but without it test/modules/diten/returnClassDiffModule5.chpl fails.
    compilerError("writeThis on ChannelWriter called");
  }
  // writeThis + no readThis -> ChannelWriter itself cannot be read
}
class ChannelReader : Reader {
  var _channel_internal:qio_channel_ptr_t = QIO_CHANNEL_PTR_NULL;
  var err:syserr = ENOERR;
  proc binary:bool {
    var ret:uint(8) = qio_channel_binary(_channel_internal);
    return ret != 0;
  }
  proc error():syserr {
    return err;
  }
  proc setError(e:syserr) {
    err = e;
  }
  proc clearError() {
    err = ENOERR;
  }
  proc readPrimitive(inout x:?t) where _isIoPrimitiveTypeOrNewline(t) {
    if !err {
      err = _read_one_internal(_channel_internal, iokind.dynamic, x);
    }
  }
  proc writeThis(w:Writer) {
    compilerError("writeThis on ChannelReader called");
  }
  // writeThis + no readThis -> ChannelReader itself cannot be read
}

// Delete a file.
proc unlink(path:string, out error:syserr) {
  extern proc sys_unlink(path:string):syserr;
  error = sys_unlink(path);
}
proc unlink(path:string) {
  var err:syserr = ENOERR;
  unlink(path, err);
  if err then ioerror(err, "in unlink", path);
}

proc unicodeSupported():bool {
  extern proc qio_unicode_supported():c_int;
  return qio_unicode_supported() > 0;
}

/* "channel" I/O contributed by Michael Ferguson

   Future Work:
    - Chapel-friendly ways to e.g. request JSON string format or hexadecimal numbers
    - formatted input/output, like printf, probably called writef/writefln/etc,
      and supporting ###.#### formats in addition to the usual % stuff.
    - Regular expression support (currently favoring RE2 integration)
    - Reader needs a Reader.readType method implemented for any custom types
      that want to provide their own reader method. We might rather implement
      this with a static method or a method on a default-constructed object
      of the type we're reading (so that the readThis method is somehow
      'in the class' along with writeThis).
    - We would like to have a 'serialization' system, including allowing
      the writing of data structures with circular references, and
      encoding the types of classes.
    - Currently, the union tag numbers are per-compile; you could not read
      binary union data written in one Chapel program with another. This problem
      will also apply to class tag numbers once we start encoding types.
    - The Chapel compiler does not currently allow RAII/reference counting
      to work correctly in all cases (bug report 'records containing pointers'
      and test files in test/users/ferguson/{byvalue.chpl,refcnt.chpl};
      as a result, current code might need to close channels and files
      explicitly and/or sharing of channels and files between variables might
      not work correctly.
    - Channels have a lock, and some methods on a channel only make sense
      when the lock is held. In the future (again, once RAII makes sense),
      we might replace this with 'get locked channel' that returns a different
      type that has the methods that can only be used on a locked channel.
    - The error handling strategy here is a bit awkward for the library-writer
      (not so much for the user). A broader exceptions strategy would help
      quite a bit - and some of these interfaces will change when that happens.
    - Fancy features, like adding a bytes or buffer object to a channel
      (so that the channel just refers to it and does not copy it) are
      implemented but not well tested.
    - It would be nice if ioBits:string printed itself in binary instead of decimal.
    - Cleaning up to reduce the number of exported symbols, and using enums for
      all constants once 'use enum' is available and we have a way to get
      C constants into Chapel enums.
    - Support for libevent/qthreads system call thread so that we have something
      suitable for writing (in a straightforward manner) a multithreaded webserver
    - Doing something reasonable with a file that moves from one node to another -
      namely, when a channel is created with a file that resides remotely, if that
      same file is accessible locally (e.g. with Lustre or NFS), we should open
      a local copy of that file and use that in the channel. (not sure how to avoid
      opening # channels copies of these files -- seems that we'd want some way
      to cache that...).
    - Create leader/follower iterators for ItemReader/ItemWriter so that these
      are as efficient as possible when working with fixed-size data types
      (ie, they can open up channels that are not shared).
*/

use SysBasic;
use Error;

enum iomode {
  r = 1,
  cw = 2,
  rw = 3,
  cwr = 4,
}

enum iokind {
  /* don't change these without updating qio_style.h QIO_NATIVE, etc
     a default of 0 is always reasonable, but you can avoid some
     branches to get faster I/O by setting this to native, big, or little.
     In that case, the style is only consulted for text or string I/O.
     */
  dynamic = 0, // look in iostyle
  native = 1,
  big = 2, /* aka "network" */
  little = 3
}
param iodynamic = iokind.dynamic;
param ionative = iokind.native;
param iobig = iokind.big;
param iolittle = iokind.little;

enum iostringstyle {
  len1b_data = -1,
  len2b_data = -2,
  len4b_data = -4,
  len8b_data = -8,
  lenVb_data = -10,
  data_null = -0x0100,
}
proc stringStyleTerminated(terminator:uint(8)) {
  return -(terminator - iostringstyle.data_null);
}
proc stringStyleNullTerminated() {
  return iostringstyle.data_null;
}
proc stringStyleExactLen(len:int(64)) {
  return len;
}
proc stringStyleWithVariableLength() {
  return iostringstyle.lenVb_data;
}
proc stringStyleWithLength(lengthBytes:int) {
  var x = iostringstyle.lenVb_data;
  select lengthBytes {
    when 0 do x = iostringstyle.lenVb_data;
    when 1 do x = iostringstyle.len1b_data;
    when 2 do x = iostringstyle.len2b_data;
    when 4 do x = iostringstyle.len4b_data;
    when 8 do x = iostringstyle.len8b_data;
    otherwise halt("Unhandled string length prefix size");
  }
  return x;
}

extern const QIO_FDFLAG_UNK:c_int;
extern const QIO_FDFLAG_READABLE:c_int;
extern const QIO_FDFLAG_WRITEABLE:c_int;
extern const QIO_FDFLAG_SEEKABLE:c_int;

extern const QIO_CH_ALWAYS_UNBUFFERED:c_int;
extern const QIO_CH_ALWAYS_BUFFERED:c_int;
extern const QIO_CH_BUFFERED:c_int;

extern const QIO_METHOD_DEFAULT:c_int;
extern const QIO_METHOD_READWRITE:c_int;
extern const QIO_METHOD_PREADPWRITE:c_int;
extern const QIO_METHOD_FREADFWRITE:c_int;
extern const QIO_METHOD_MMAP:c_int;
extern const QIO_METHODNUM:c_int;
extern const QIO_METHODMASK:c_int;
extern const QIO_HINT_RANDOM:c_int;
extern const QIO_HINT_SEQUENTIAL:c_int;
extern const QIO_HINT_LATENCY:c_int;
extern const QIO_HINT_BANDWIDTH:c_int;
extern const QIO_HINT_CACHED:c_int;
extern const QIO_HINT_PARALLEL:c_int;
extern const QIO_HINT_DIRECT:c_int;
extern const QIO_HINT_NOREUSE:c_int;

// NONE means normal operation, nothing special
// to hint. Expect to use NONE most of the time.
// The other hints can be bitwise-ORed in.
const IOHINT_NONE = 0:c_int;

// RANDOM means we expect random access to a file
const IOHINT_RANDOM = QIO_HINT_RANDOM;

// SEQUENTAL means expect sequential access. On
// Linux, this should double the readahead.
const IOHINT_SEQUENTIAL = QIO_HINT_SEQUENTIAL;

// CACHED means we expect the entire file
// to be cached and/or we pull it in all at
// once. May request readahead on the entire file.
const IOHINT_CACHED = QIO_HINT_CACHED;

// PARALLEL means that we expect to have many
// channels working with this file in parallel.
// It might change the reading/writing implementation
// to something more efficient in that scenario.
const IOHINT_PARALLEL = QIO_HINT_PARALLEL;

extern type qio_file_ptr_t;
extern const QIO_FILE_PTR_NULL:qio_file_ptr_t;

extern type qio_channel_ptr_t;
extern const QIO_CHANNEL_PTR_NULL:qio_channel_ptr_t;

// also the type for a buffer for qio_file_open_mem.
extern type qbuffer_ptr_t;
extern const QBUFFER_PTR_NULL:qbuffer_ptr_t;

extern type style_char_t = uint(8);

extern const QIO_STRING_FORMAT_WORD:uint(8);
extern const QIO_STRING_FORMAT_BASIC:uint(8);
extern const QIO_STRING_FORMAT_CHPL:uint(8);
extern const QIO_STRING_FORMAT_JSON:uint(8);
extern const QIO_STRING_FORMAT_TOEND:uint(8);

extern record iostyle { // aka qio_style_t
  var binary:uint(8) = 0;
  // binary style choices
  var byteorder:uint(8) = iokind.native:uint(8);
  // string binary style:
  // -1 -- 1 byte of length before
  // -2 -- 2 bytes of length before
  // -4 -- 4 bytes of length before
  // -8 -- 8 bytes of length before
  // -10 -- variable byte length before (hi-bit 1 means more, little endian)
  // -0x01XX -- read until terminator XX is read
  //  + -- nonzero positive -- read exactly this length.
  var str_style:int(64) = -10;
  // text style choices
  var min_width:uint(32) = 1;
  var max_width:uint(32) = max(uint(32));
  var string_start:style_char_t = 0x22; // "
  var string_end:style_char_t = 0x22; // "

  /* QIO_STRING_FORMAT_WORD  string is as-is; reading reads until whitespace.
     QIO_STRING_FORMAT_BASIC only escape string_end and \ with \
     QIO_STRING_FORMAT_CHPL  escape string_end \ ' " \n with \
                             and nonprinting characters c = 0xXY with \xXY
     QIO_STRING_FORMAT_JSON  escape string_end " and \ with \,
                             and nonprinting characters c = \uABCD
     QIO_STRING_FORMAT_TOEND string is as-is; reading reads until string_end
   */
  var string_format:uint(8) = 0;
  // numeric scanning/printing choices
  var base:uint(8) = 0;
  var point_char:style_char_t = 0x2e; // .
  var exponent_char:style_char_t = 0x65; // e
  var other_exponent_char:style_char_t = 0x70; // p
  var positive_char:style_char_t = 0x2b; // +;
  var negative_char:style_char_t = 0x2d; // -;
  var prefix_base:uint(8) = 1;
  // numeric printing choices
  var pad_char:style_char_t = 0x20; // ' '
  var showplus:uint(8) = 0;
  var uppercase:uint(8) = 0;
  var leftjustify:uint(8) = 0;
  var showpoint:uint(8) = 0;
  var showpointzero:uint(8) = 1;
  var precision:int(32) = -1;
  var significant_digits:int(32) = -1;
  var realfmt:uint(8) = 0;

  var complex_style:uint(8) = 0;
}

extern const QIO_STYLE_SIZE:size_t;

extern proc qio_style_init_default(inout s: iostyle);

/* Extern functions */
extern proc qio_file_retain(f:qio_file_ptr_t);
extern proc qio_file_release(f:qio_file_ptr_t);

extern proc qio_file_init(inout file_out:qio_file_ptr_t, fp:_file, fd:fd_t, iohints:c_int, inout style:iostyle, usefilestar:c_int):syserr;
extern proc qio_file_open_access(inout file_out:qio_file_ptr_t, path:string, access:string, iohints:c_int, inout style:iostyle):syserr;
extern proc qio_file_open_tmp(inout file_out:qio_file_ptr_t, iohints:c_int, inout style:iostyle):syserr;
extern proc qio_file_open_mem(inout file_out:qio_file_ptr_t, buf:qbuffer_ptr_t, inout style:iostyle):syserr;

/* Close a file (asserts ref count==1)
   This is not usually necessary to call, but a program will
   halt if it isn't called.
 */
extern proc qio_file_close(f:qio_file_ptr_t):syserr;

extern proc qio_file_lock(f:qio_file_ptr_t):syserr;
extern proc qio_file_unlock(f:qio_file_ptr_t);

/* The general way to make sure data is written without error */
extern proc qio_file_sync(f:qio_file_ptr_t):syserr;

//extern proc qio_file_style_ptr(f:qio_file_ptr_t):qio_style_ptr_t;
extern proc qio_file_get_style(f:qio_file_ptr_t, inout style:iostyle);
extern proc qio_file_set_style(f:qio_file_ptr_t, inout style:iostyle);

/* our wrapper for the read function;
   calls readv to read into our buffer.
   Will read data into buf (overwriting,
    not allocating) and advance the file descriptor's offset.
 */
//extern proc qio_readv(fd:fd_t, inout buf:qbuffer_t, start:qbuffer_iter_t, end:qbuffer_iter_t, inout num_read:int(64)):syserr;
/* our wrapper for the write function;
   calls writev to write our buffer.
   Will write data from buf and not change it.
   Will advance the file descriptor's offset.
 */
//extern proc qio_writev(fd:fd_t, inout buf:qbuffer_t, start:qbuffer_iter_t, end:qbuffer_iter_t, inout num_read:int(64)):syserr;

/* calls preadv where it exists;
   otherwise, will do a series of pread calls
   Will read data into buf (overwriting, not allocating).
   Will not advance the file descriptor's position.
 */
//extern proc qio_preadv(fd:fd_t, inout buf:qbuffer_t, start:qbuffer_iter_t, end:qbuffer_iter_t, offset:int(64), inout num_read:int(64)):syserr;
/* calls pwritev where it exists;
   otherwise, will do a series of pwrite calls
   Will write data from buf and not change it.
   Will not advance the file descriptor's position.
 */
//extern proc qio_pwritev(fd:fd_t, inout buf:qbuffer_t, start:qbuffer_iter_t, end:qbuffer_iter_t, offset:int(64), inout num_read:int(64)):syserr;

extern proc qio_channel_create(inout ch:qio_channel_ptr_t, file:qio_file_ptr_t, hints:c_int, readable:c_int, writeable:c_int, start:int(64), end:int(64), inout style:iostyle):syserr;

extern proc qio_channel_path_offset(threadsafe:c_int, ch:qio_channel_ptr_t, inout path:string, inout offset:int(64)):syserr;

extern proc qio_channel_retain(ch:qio_channel_ptr_t);
extern proc qio_channel_release(ch:qio_channel_ptr_t);

extern proc qio_channel_lock(ch:qio_channel_ptr_t):syserr;
extern proc qio_channel_unlock(ch:qio_channel_ptr_t);

extern proc qio_channel_get_style(ch:qio_channel_ptr_t, inout style:iostyle);
extern proc qio_channel_set_style(ch:qio_channel_ptr_t, inout style:iostyle);

extern proc qio_channel_binary(ch:qio_channel_ptr_t):uint(8);
extern proc qio_channel_byteorder(ch:qio_channel_ptr_t):uint(8);
extern proc qio_channel_str_style(ch:qio_channel_ptr_t):int(64);

extern proc qio_channel_flush(threadsafe:c_int, ch:qio_channel_ptr_t):syserr;
extern proc qio_channel_close(threadsafe:c_int, ch:qio_channel_ptr_t):syserr;

extern proc qio_channel_read(threadsafe:c_int, ch:qio_channel_ptr_t, inout ptr, len:ssize_t, inout amt_read:ssize_t):syserr;
extern proc qio_channel_read_amt(threadsafe:c_int, ch:qio_channel_ptr_t, inout ptr, len:ssize_t):syserr;
extern proc qio_channel_read_byte(threadsafe:c_int, ch:qio_channel_ptr_t):int(32);

extern proc qio_channel_write(threadsafe:c_int, ch:qio_channel_ptr_t, inout ptr, len:ssize_t, inout amt_written:ssize_t):syserr;
extern proc qio_channel_write_amt(threadsafe:c_int, ch:qio_channel_ptr_t, inout ptr, len:ssize_t):syserr;
extern proc qio_channel_write_byte(threadsafe:c_int, ch:qio_channel_ptr_t, byte:uint(8)):syserr;

extern proc qio_channel_offset_unlocked(ch:qio_channel_ptr_t):int(64);
extern proc qio_channel_mark(threadsafe:c_int, ch:qio_channel_ptr_t):syserr;
extern proc qio_channel_revert_unlocked(ch:qio_channel_ptr_t);
extern proc qio_channel_commit_unlocked(ch:qio_channel_ptr_t);

extern proc qio_channel_write_bits(threadsafe:c_int, ch:qio_channel_ptr_t, v:uint(64), nbits:int(8)):syserr;
extern proc qio_channel_flush_bits(threadsafe:c_int, ch:qio_channel_ptr_t):syserr;
extern proc qio_channel_read_bits(threadsafe:c_int, ch:qio_channel_ptr_t, inout v:uint(64), nbits:int(8)):syserr;

extern proc qio_file_path_for_fd(fd:fd_t, inout path:string):syserr;
extern proc qio_file_path_for_fp(fp:_file, inout path:string):syserr;
extern proc qio_file_path(f:qio_file_ptr_t, inout path:string):syserr;
extern proc qio_shortest_path(inout path_out:string, path_in:string):syserr;

extern proc qio_channel_read_int(threadsafe:c_int, byteorder:c_int, ch:qio_channel_ptr_t, inout ptr, len:size_t, issigned:c_int):syserr;
extern proc qio_channel_write_int(threadsafe:c_int, byteorder:c_int, ch:qio_channel_ptr_t, inout ptr, len:size_t, issigned:c_int):syserr;

extern proc qio_channel_read_float(threadsafe:c_int, byteorder:c_int, ch:qio_channel_ptr_t, inout ptr, len:size_t):syserr;
extern proc qio_channel_write_float(threadsafe:c_int, byteorder:c_int, ch:qio_channel_ptr_t, inout ptr, len:size_t):syserr;

extern proc qio_channel_read_complex(threadsafe:c_int, byteorder:c_int, ch:qio_channel_ptr_t, inout re_ptr, inout im_ptr, len:size_t):syserr;
extern proc qio_channel_write_complex(threadsafe:c_int, byteorder:c_int, ch:qio_channel_ptr_t, inout re_ptr, inout im_ptr, len:size_t):syserr;

extern proc qio_channel_read_string(threadsafe:c_int, byteorder:c_int, str_style:int(64), ch:qio_channel_ptr_t, inout s:string, inout len:ssize_t, maxlen:ssize_t):syserr;
extern proc qio_channel_write_string(threadsafe:c_int, byteorder:c_int, str_style:int(64), ch:qio_channel_ptr_t, s:string, len:ssize_t):syserr;

extern proc qio_channel_scan_int(threadsafe:c_int, ch:qio_channel_ptr_t, inout ptr, len:size_t, issigned:c_int):syserr;
extern proc qio_channel_print_int(threadsafe:c_int, ch:qio_channel_ptr_t, inout ptr, len:size_t, issigned:c_int):syserr;

extern proc qio_channel_scan_float(threadsafe:c_int, ch:qio_channel_ptr_t, inout ptr, len:size_t):syserr;
extern proc qio_channel_print_float(threadsafe:c_int, ch:qio_channel_ptr_t, inout ptr, len:size_t):syserr;

extern proc qio_channel_scan_complex(threadsafe:c_int, ch:qio_channel_ptr_t, inout re_ptr, inout im_ptr, len:size_t):syserr;
extern proc qio_channel_print_complex(threadsafe:c_int, ch:qio_channel_ptr_t, inout re_ptr, inout im_ptr, len:size_t):syserr;

extern proc qio_channel_read_char(threadsafe:c_int, ch:qio_channel_ptr_t, inout char:int(32)):syserr;

extern proc qio_nbytes_char(chr:int(32)):c_int;
extern proc qio_encode_to_string(chr:int(32)):string;
extern proc qio_decode_char_buf(inout chr:int(32), inout nbytes:c_int, buf:string, buflen:ssize_t):syserr;

extern proc qio_channel_write_char(threadsafe:c_int, ch:qio_channel_ptr_t, char:int(32)):syserr;
extern proc qio_channel_skip_past_newline(threadsafe:c_int, ch:qio_channel_ptr_t):syserr;
extern proc qio_channel_write_newline(threadsafe:c_int, ch:qio_channel_ptr_t):syserr;

extern proc qio_channel_scan_string(threadsafe:c_int, ch:qio_channel_ptr_t, inout ptr:string, inout len:ssize_t, maxlen:ssize_t):syserr;
extern proc qio_channel_print_string(threadsafe:c_int, ch:qio_channel_ptr_t, ptr:string, len:ssize_t):syserr;

extern proc qio_channel_scan_literal(threadsafe:c_int, ch:qio_channel_ptr_t, match:string, len:ssize_t, skipws:c_int):syserr;
extern proc qio_channel_print_literal(threadsafe:c_int, ch:qio_channel_ptr_t, match:string, len:ssize_t):syserr;
//type iostyle = qio_style_t;

proc defaultIOStyle():iostyle {
  var ret:iostyle;
  qio_style_init_default(ret);
  return ret;
}

proc iostyle.native(str_style:int(64)=stringStyleWithVariableLength()):iostyle {
  var ret = this;
  ret.binary = 1;
  ret.byteorder = iokind.native:uint(8);
  ret.str_style = str_style;
  return ret;
}
proc iostyle.big(str_style:int(64)=stringStyleWithVariableLength()):iostyle {
  var ret = this;
  ret.binary = 1;
  ret.byteorder = iokind.big:uint(8);
  ret.str_style = str_style;
  return ret;
}
proc iostyle.little(str_style:int(64)=stringStyleWithVariableLength()):iostyle  {
  var ret = this;
  ret.binary = 1;
  ret.byteorder = iokind.little:uint(8);
  ret.str_style = str_style;
  return ret;
}
proc iostyle.text(/* args coming later */):iostyle  {
  var ret = this;
  ret.binary = 0;
  return ret;
}

/*
  QIO_FDFLAG_UNK,
  QIO_FDFLAG_READABLE,
  QIO_FDFLAG_WRITEABLE,
  QIO_FDFLAG_SEEKABLE
*/

extern type fdflag_t = c_int;

/* Access hints describe how a file will be used.
   These can help optimize.
 */
/*
  QIO_METHOD_DEFAULT,
  QIO_METHOD_READWRITE,
  QIO_METHOD_P_READWRITE,
  QIO_METHOD_MMAP,
  QIO_HINT_RANDOM,
  QIO_HINT_SEQUENTIAL,
  QIO_HINT_LATENCY,
  QIO_HINT_BANDWIDTH,
  QIO_HINT_CACHED,
  QIO_HINT_NOREUSE
}
*/

extern type iohints = c_int;

inline
proc _current_locale():int {
  var tmp:int;
  return __primitive("_get_locale", tmp);
}

record file {
  var home_uid:int = _current_locale();
  var _file_internal:qio_file_ptr_t = QIO_FILE_PTR_NULL;
}

// used for giving old warnings anyways...
enum FileAccessMode { read, write };

param _oldioerr="This program is using old-style I/O which is no longer supported.\n" +
                "See doc/README.io.\n" +
                "You'll probably want something like:\n" +
                "var f = open(filename, iomode.w).writer()\n" +
                "or\n" +
                "var f = open(filename, iomode.r).reader()\n";

// This file constructor exists to throw an error for old I/O code.
proc file.file(filename:string="",
               mode:FileAccessMode=FileAccessMode.read,
               path:string=".") {
  compilerError(_oldioerr);
}
proc file.open() {
  compilerError(_oldioerr);
}
proc file.filename : string {
  compilerError(_oldioerr + "file.filename is no longer supported");
}
proc file.mode {
  compilerError(_oldioerr + "file.mode is no longer supported");
}
proc file.isOpen: bool {
  compilerError(_oldioerr + "file.isOpen is no longer supported");
}

// TODO -- shouldn't have to write this this way!
proc chpl__initCopy(x: file) {
  on __primitive("chpl_on_locale_num", x.home_uid) {
    qio_file_retain(x._file_internal);
  }
  return x;
}

proc =(ret:file, x:file) {
  // retain -- release
  on __primitive("chpl_on_locale_num", x.home_uid) {
    qio_file_retain(x._file_internal);
  }

  on __primitive("chpl_on_locale_num", ret.home_uid) {
    qio_file_release(ret._file_internal);
  }

  // compiler will do this copy.
  ret.home_uid = x.home_uid;
  ret._file_internal = x._file_internal;
  return ret;
}

proc file.check() {
  if(1 == is_c_nil(_file_internal)) {
    halt("Operation attempted on an invalid file");
  }
}

/*
proc file.file() {
  var tmp:int;
  this.home_uid = _current_locale();
  this._file_internal = QIO_FILE_PTR_NULL;
}
*/

proc file.~file() {
  on __primitive("chpl_on_locale_num", this.home_uid) {
    qio_file_release(_file_internal);
    this._file_internal = QIO_FILE_PTR_NULL;
  }
}

/*
   We could support file locking and unlocking, but
   at the moment I don't see any use case in which
   it would make sense.
proc file.lock() {
  on __primitive("chpl_on_locale_num", this.home_uid) {
    seterr(nil, qio_file_lock(_file_internal));
  }
}
proc file.unlock() {
  on __primitive("chpl_on_locale_num", this.home_uid) {
    qio_file_unlock(_file_internal);
  }
}
*/

// File style cannot be modified after the file is created;
// this prevents race conditions;
// channel style is protected by channel lock, can be modified.
proc file._style:iostyle {
  check();

  var ret:iostyle;
  on __primitive("chpl_on_locale_num", this.home_uid) {
    var local_style:iostyle;
    qio_file_get_style(_file_internal, local_style);
    ret = local_style;
  }
  return ret;
}

/* Close a file.
   Alternately, file will be closed when it is no longer referred to */
proc file.close(out error:syserr) {
  check();
  on __primitive("chpl_on_locale_num", this.home_uid) {
    error = qio_file_close(_file_internal);
  }
}

proc file.close() {
  var err:syserr = ENOERR;
  this.close(err);
  if err then ioerror(err, "in file.close", this.tryGetPath());
}

/* Sync a file to disk. */
proc file.fsync(out error:syserr) {
  check();
  on __primitive("chpl_on_locale_num", this.home_uid) {
    error = qio_file_sync(_file_internal);
  }
}
proc file.fsync() {
  var err:syserr = ENOERR;
  this.fsync(err);
  if err then ioerror(err, "in file.fsync", this.tryGetPath());
}

/* Get the path to a file. */
proc file.getPath(out error:syserr) : string {
  check();
  var ret:string;
  on __primitive("chpl_on_locale_num", this.home_uid) {
    var tmp:string;
    var tmp2:string;
    error = qio_file_path(_file_internal, tmp);
    if !error {
      error = qio_shortest_path(tmp2, tmp);
    }
    if !error {
      ret = tmp2;
    } else {
      ret = "unknown";
    }
  }
 return ret;
}

proc file.tryGetPath() : string {
  var err:syserr = ENOERR;
  var ret:string;
  ret = this.getPath(err);
  if err then return "unknown";
  else return ret;
}

proc file.path : string {
  var err:syserr = ENOERR;
  var ret:string;
  ret = this.getPath(err);
  if err then ioerror(err, "in file.path");
}

// these strings are here (vs in _modestring)
// in an attempt to avoid string copies, leaks,
// and unnecessary allocations.
const _r = "r";
const _rw  = "r+";
const _cw = "w";
const _cwr = "w+";

proc _modestring(mode:iomode) {
  select mode {
    when iomode.r do return _r;
    when iomode.rw do return _rw;
    when iomode.cw do return _cw;
    when iomode.cwr do return _cwr;
    otherwise halt("Invalid mode");
  }
}

proc open(out error:syserr, path:string, mode:iomode, hints:iohints=IOHINT_NONE, style:iostyle = defaultIOStyle()):file {
  var local_style = style;
  var ret:file;
  ret.home_uid = _current_locale();
  error = qio_file_open_access(ret._file_internal, path, _modestring(mode), hints, local_style);
  return ret;
}
proc open(path:string, mode:iomode, hints:iohints=IOHINT_NONE, style:iostyle = defaultIOStyle()):file {
  var err:syserr = ENOERR;
  var ret = open(err, path, mode, hints, style);
  if err then ioerror(err, "in open", path);
  return ret;
}
proc openfd(fd: fd_t, out error:syserr, hints:iohints=IOHINT_NONE, style:iostyle = defaultIOStyle()):file {
  var local_style = style;
  var ret:file;
  ret.home_uid = _current_locale();
  error = qio_file_init(ret._file_internal, chpl_cnullfile(), fd, hints, local_style, 0);
  return ret;
}
proc openfd(fd: fd_t, hints:iohints=IOHINT_NONE, style:iostyle = defaultIOStyle()):file {
  var err:syserr = ENOERR;
  var ret = openfd(fd, err, hints, style);
  if err {
    var path:string;
    var e2:syserr = ENOERR;
    e2 = qio_file_path_for_fd(fd, path);
    if e2 then path = "unknown";
    ioerror(err, "in openfd", path);
  }
  return ret;
}
proc openfp(fp: _file, out error:syserr, hints:iohints=IOHINT_NONE, style:iostyle = defaultIOStyle()):file {
  var local_style = style;
  var ret:file;
  ret.home_uid = _current_locale();
  error = qio_file_init(ret._file_internal, fp, -1, hints, local_style, 1);
  return ret;
}
proc openfp(fp: _file, hints:iohints=IOHINT_NONE, style:iostyle = defaultIOStyle()):file {
  var err:syserr = ENOERR;
  var ret = openfp(fp, err, hints, style);
  if err {
    var path:string;
    var e2:syserr = ENOERR;
    e2 = qio_file_path_for_fp(fp, path);
    if e2 then path = "unknown";
    ioerror(err, "in openfp", path);
  }
  return ret;
}

proc opentmp(out error:syserr, hints:iohints=IOHINT_NONE, style:iostyle = defaultIOStyle()):file {
  var local_style = style;
  var ret:file;
  ret.home_uid = _current_locale();
  error = qio_file_open_tmp(ret._file_internal, hints, local_style);
  return ret;
}
proc opentmp(hints:iohints=IOHINT_NONE, style:iostyle = defaultIOStyle()):file {
  var err:syserr = ENOERR;
  var ret = opentmp(err, hints, style);
  if err then ioerror(err, "in opentmp");
  return ret;
}

proc openmem(out error:syserr, style:iostyle = defaultIOStyle()) {
  var local_style = style;
  var ret:file;
  ret.home_uid = _current_locale();
  error = qio_file_open_mem(ret._file_internal, QBUFFER_PTR_NULL, local_style);
  return ret;
}
proc openmem(style:iostyle = defaultIOStyle()):file {
  var err:syserr = ENOERR;
  var ret = openmem(err, style);
  if err then ioerror(err, "in openmem");
  return ret;
}

/* in the future, this will be an interface.
   */
record channel {
  param writing:bool;
  param kind:iokind;
  param locking:bool;
  var home_uid:int;
  var _channel_internal:qio_channel_ptr_t = QIO_CHANNEL_PTR_NULL;
}

// TODO -- shouldn't have to write this this way!
proc chpl__initCopy(x: channel) {
  on __primitive("chpl_on_locale_num", x.home_uid) {
    qio_channel_retain(x._channel_internal);
  }
  return x;
}

proc =(ret:channel, x:channel) {
  // retain -- release
  on __primitive("chpl_on_locale_num", x.home_uid) {
    qio_channel_retain(x._channel_internal);
  }

  on __primitive("chpl_on_locale_num", ret.home_uid) {
    qio_channel_release(ret._channel_internal);
  }

  ret.home_uid = x.home_uid;
  ret._channel_internal = x._channel_internal;
  return ret;
}

proc channel.channel(param writing:bool, param kind:iokind, param locking:bool, f:file, out error:syserr, hints:c_int, start:int(64), end:int(64), style:iostyle) {
  on __primitive("chpl_on_locale_num", f.home_uid) {
    this.home_uid = f.home_uid;
    var local_style = style;
    error = qio_channel_create(this._channel_internal, f._file_internal, hints, !writing, writing, start, end, local_style);
  }
}

proc channel.~channel() {
  on __primitive("chpl_on_locale_num", this.home_uid) {
    qio_channel_release(_channel_internal);
    this._channel_internal = QIO_CHANNEL_PTR_NULL;
  }
}

// Used to represent a Unicode character
record ioChar {
  var ch:int(32);
  proc writeThis(f: Writer) {
    halt("ioChar.writeThis must be written in Writer subclasses");
  }
}
pragma "inline" proc _cast(type t, x: ioChar) where t == string {
  return qio_encode_to_string(x.ch);
}

// Used to represent "\n", but never escaped...
record ioNewline {
  proc writeThis(f: Writer) {
    // Normally this is handled explicitly in read/write.
    f.write("\n");
  }
}
pragma "inline" proc _cast(type t, x: ioNewline) where t == string {
  return "\n";
}

// Used to represent a constant string we want to read or write...
record ioLiteral {
  var val: string;
  var ignoreWhiteSpace: bool = true;
  proc writeThis(f: Writer) {
    // Normally this is handled explicitly in read/write.
    f.write(val);
  }
}

pragma "inline" proc _cast(type t, x: ioLiteral) where t == string {
  return x.val;
}

// Used to represent some number of bits we want to read or write...
record ioBits {
  var v:uint(64);
  var nbits:int(8);
  proc writeThis(f: Writer) {
    // Normally this is handled explicitly in read/write.
    f.write(v);
  }
}

pragma "inline" proc _cast(type t, x: ioBits) where t == string {
  return "ioBits(v=" + x.v:string + ", nbits=" + x.nbits:string + ")";
}

proc channel._ch_ioerror(error:syserr, msg:string) {
  var path:string = "unknown";
  var offset:int(64) = -1;
  on __primitive("chpl_on_locale_num", this.home_uid) {
    var tmp_path:string;
    var tmp_offset:int(64);
    var err:syserr = ENOERR;
    err = qio_channel_path_offset(locking, _channel_internal, tmp_path, tmp_offset);
    if !err {
      path = tmp_path;
      offset = tmp_offset;
    }
  }
  ioerror(error, msg, path, offset);
}
proc channel._ch_ioerror(errstr:string, msg:string) {
  var path:string = "unknown";
  var offset:int(64) = -1;
  on __primitive("chpl_on_locale_num", this.home_uid) {
    var tmp_path:string;
    var tmp_offset:int(64);
    var err:syserr = ENOERR;
    err = qio_channel_path_offset(locking, _channel_internal, tmp_path, tmp_offset);
    if !err {
      path = tmp_path;
      offset = tmp_offset;
    }
  }
  ioerror(errstr, msg, path, offset);
}

inline
proc channel.lock(out error:syserr) {
  error = ENOERR;
  if locking {
    on __primitive("chpl_on_locale_num", this.home_uid) {
      error = qio_channel_lock(_channel_internal);
    }
  }
}
inline
proc channel.lock() {
  var err:syserr = ENOERR;
  this.lock(err);
  if err then this._ch_ioerror(err, "in lock");
}

inline
proc channel.unlock() {
  if locking {
    on __primitive("chpl_on_locale_num", this.home_uid) {
      qio_channel_unlock(_channel_internal);
    }
  }
}

proc channel.offset():int(64) {
  var ret:int(64);
  on __primitive("chpl_on_locale_num", this.home_uid) {
    this.lock();
    ret = qio_channel_offset_unlocked(_channel_internal);
    this.unlock();
  }
  return ret;
}

// you should have a lock before you use these...

inline
proc channel._offset():int(64) {
  var ret:int(64);
  on __primitive("chpl_on_locale_num", this.home_uid) {
    ret = qio_channel_offset_unlocked(_channel_internal);
  }
  return ret;
}

inline
proc channel._mark():syserr {
  return qio_channel_mark(false, _channel_internal);
}
inline
proc channel._revert() {
  qio_channel_revert_unlocked(_channel_internal);
}
inline
proc channel._commit() {
  qio_channel_commit_unlocked(_channel_internal);
}
proc channel._style():iostyle {
  var ret:iostyle;
  on __primitive("chpl_on_locale_num", this.home_uid) {
    var local_style:iostyle;
    qio_channel_get_style(_channel_internal, local_style);
    ret = local_style;
  }
  return ret;
}
proc channel._set_style(style:iostyle) {
  on __primitive("chpl_on_locale_num", this.home_uid) {
    var local_style:iostyle = style;
    qio_channel_set_style(_channel_internal, local_style);
  }
}

proc file.reader(out error:syserr, param kind=iokind.dynamic, param locking=true, start:int(64) = 0, end:int(64) = max(int(64)), hints:iohints = IOHINT_NONE, style:iostyle = this._style): channel(false, kind, locking) {
  check();

  var ret:channel(false, kind, locking);
  on __primitive("chpl_on_locale_num", this.home_uid) {
    ret = new channel(false, kind, locking, this, error, hints, start, end, style);
  }
  return ret;
}
proc file.reader(param kind=iokind.dynamic, param locking=true, start:int(64) = 0, end:int(64) = max(int(64)), hints:iohints = IOHINT_NONE, style:iostyle = this._style): channel(false, kind, locking) {
  var err:syserr = ENOERR;
  var ret = this.reader(err, kind, locking, start, end, hints, style);
  if err then ioerror(err, "in file.reader", this.tryGetPath());
  return ret;
}
// for convenience..
proc file.lines(out error:syserr, param locking:bool = true, start:int(64) = 0, end:int(64) = max(int(64)), hints:iohints = IOHINT_NONE, style:iostyle = this._style) {
  check();

  style.string_format = QIO_STRING_FORMAT_TOEND;
  style.string_end = 0x0a; // '\n'

  param kind = iokind.dynamic;
  var ret:ItemReader(string, kind, locking);
  on __primitive("chpl_on_locale_num", this.home_uid) {
    var ch = new channel(false, kind, locking, this, error, hints, start, end, style);
    ret = new ItemReader(string, kind, locking, ch);
  }
  return ret;
}
proc file.lines(param locking:bool = true, start:int(64) = 0, end:int(64) = max(int(64)), hints:iohints = IOHINT_NONE, style:iostyle = this._style) {
  var err:syserr = ENOERR;
  var ret = this.lines(err, locking, start, end, hints, style);
  if err then ioerror(err, "in file.lines", this.tryGetPath());
  return ret;
}

proc file.writer(out error:syserr, param kind=iokind.dynamic, param locking=true, start:int(64) = 0, end:int(64) = max(int(64)), hints:iohints = IOHINT_NONE, style:iostyle = this._style): channel(true,kind,locking) {
  check();

  var ret:channel(true, kind, locking);
  on __primitive("chpl_on_locale_num", this.home_uid) {
    ret = new channel(true, kind, locking, this, error, hints, start, end, style);
  }
  return ret;
}
proc file.writer(param kind=iokind.dynamic, param locking=true, start:int(64) = 0, end:int(64) = max(int(64)), hints:c_int = 0, style:iostyle = this._style): channel(true,kind,locking)
{
  var err:syserr = ENOERR;
  var ret = this.writer(err, kind, locking, start, end, hints, style);

  if err then ioerror(err, "in file.writer", this.tryGetPath());
  return ret;
}

proc _isIoPrimitiveType(type t) param return
  _isPrimitiveType(t) || _isComplexType(t) ||
  _isImagType(t) || _isEnumeratedType(t);

 proc _isIoPrimitiveTypeOrNewline(type t) param return
  _isIoPrimitiveType(t) || t == ioNewline || t == ioLiteral || t == ioChar || t == ioBits;

    //var trues = ("true", "1", "yes");
    //var falses = ("false", "0", "no");
const _trues = tuple("true");
const _falses = tuple("false");
const _i = "i";

// Read routines for all primitive types.
proc _read_text_internal(_channel_internal:qio_channel_ptr_t, out x:?t):syserr where _isIoPrimitiveType(t) {
  if _isBooleanType(t) {
    var num = _trues.size;
    var err:syserr = ENOERR;
    var got:bool;

    err = EFORMAT;

    for i in 1..num {
      err = qio_channel_scan_literal(false, _channel_internal, _trues(i), _trues(i).length, 1);
      if !err {
        got = true;
        break;
      } else if err == EEOF {
        break;
      }
      err = qio_channel_scan_literal(false, _channel_internal, _falses(i), _falses(i).length, 1);
      if !err {
        got = false;
        break;
      } else if err == EEOF {
        break;
      }
    }

    if !err then x = got;
    return err;
  } else if _isIntegralType(t) {
    // handles int types
    return qio_channel_scan_int(false, _channel_internal, x, numBytes(t), _isSignedType(t));
  } else if _isRealType(t) {
    // handles real
    return qio_channel_scan_float(false, _channel_internal, x, numBytes(t));
  } else if _isImagType(t) {
    var err = qio_channel_mark(false, _channel_internal);
    if err then return err;

    err = qio_channel_scan_float(false, _channel_internal, x, numBytes(t));
    if !err {
      err = qio_channel_scan_literal(false, _channel_internal, _i, 1, false);
    }
    if !err {
      qio_channel_commit_unlocked(_channel_internal);
    } else {
      qio_channel_revert_unlocked(_channel_internal);
    }
    return err;
  } else if _isComplexType(t)  {
    // handle complex types
    var re:x.re.type;
    var im:x.im.type;
    var err:syserr = ENOERR;
    err = qio_channel_scan_complex(false, _channel_internal, re, im, numBytes(x.re.type));
    x = (re, im):t; // cast tuple to complex to get complex num.
    return err;
  } else if t == string {
    // handle string
    var len:ssize_t;
    return qio_channel_scan_string(false, _channel_internal, x, len, -1);
  } else if _isEnumeratedType(t) {
    var err:syserr = ENOERR;
    for i in chpl_enumerate(t) {
      var str:string = i:string;
      var slen:ssize_t = str.length;
      err = qio_channel_scan_literal(false, _channel_internal, str, slen, 1);
      if !err {
        x = i;
        break;
      } else if err != EFORMAT then break;
    }
    return err;
  } else {
    compilerError("Unknown primitive type in _read_text_internal ", typeToString(t));
  }
  return EINVAL;
}

proc _write_text_internal(_channel_internal:qio_channel_ptr_t, x:?t):syserr where _isIoPrimitiveType(t) {
  if _isBooleanType(t) {
    if x {
      return qio_channel_print_string(false, _channel_internal, _trues(1), _trues(1).length:ssize_t);
    } else {
      return qio_channel_print_string(false, _channel_internal, _falses(1), _falses(1).length:ssize_t);
    }
  } else if _isIntegralType(t) {
    // handles int types
    return qio_channel_print_int(false, _channel_internal, x, numBytes(t), _isSignedType(t));

  } else if _isRealType(t) {
    // handles real
    return qio_channel_print_float(false, _channel_internal, x, numBytes(t));
  } else if _isImagType(t) {
    var err = qio_channel_mark(false, _channel_internal);
    if err then return err;

    err = qio_channel_print_float(false, _channel_internal, x, numBytes(t));
    if !err {
      err = qio_channel_print_literal(false, _channel_internal, _i, 1);
    }
    if !err {
      qio_channel_commit_unlocked(_channel_internal);
    } else {
      qio_channel_revert_unlocked(_channel_internal);
    }
    return err;
  } else if _isComplexType(t)  {
    // handle complex types
    var re = x.re;
    var im = x.im;
    return qio_channel_print_complex(false, _channel_internal, re, im, numBytes(x.re.type));
  } else if t == string {
    // handle string
    return qio_channel_print_string(false, _channel_internal, x, x.length:ssize_t);
  } else if _isEnumeratedType(t) {
    var s = x:string;
    return qio_channel_print_string(false, _channel_internal, s, s.length:ssize_t);
  } else {
    compilerError("Unknown primitive type in _write_text_internal ", typeToString(t));
  }
  return EINVAL;
}

inline
proc _read_binary_internal(_channel_internal:qio_channel_ptr_t, param byteorder:iokind, out x:?t):syserr where _isIoPrimitiveType(t) {
  if _isBooleanType(t) {
    var got:int(32);
    got = qio_channel_read_byte(false, _channel_internal);
    if got >= 0 {
      x = (got != 0);
      return ENOERR;
    } else {
      return (-got):syserr;
    }
  } else if _isIntegralType(t) {
    if numBytes(t) == 1 {
      var got:int(32);
      got = qio_channel_read_byte(false, _channel_internal);
      if got >= 0 {
        x = (got:uint(8)):t;
        return ENOERR;
      } else {
        return (-got):syserr;
      }
    } else {
      // handles int types
      return qio_channel_read_int(false, byteorder, _channel_internal, x, numBytes(t), _isSignedType(t));
    }
  } else if _isFloatType(t) {
    // handles real, imag
    return qio_channel_read_float(false, byteorder, _channel_internal, x, numBytes(t));
  } else if _isComplexType(t)  {
    // handle complex types
    var re:x.re.type;
    var im:x.im.type;
    var err:syserr = ENOERR;
    err = qio_channel_read_complex(false, byteorder, _channel_internal, re, im, numBytes(x.re.type));
    x = (re, im):t; // cast tuple to complex to get complex num.
    return err;
  } else if t == string {
    // handle string
    var len:ssize_t;
    return qio_channel_read_string(false, byteorder, qio_channel_str_style(_channel_internal), _channel_internal, x, len, -1);
  } else if _isEnumeratedType(t) {
    var i:enum_mintype(t);
    var err:syserr = ENOERR;
    err = qio_channel_read_int(false, byteorder, _channel_internal, i, numBytes(i.type), _isSignedType(i.type));
    x = i:t;
    return err;
  } else {
    compilerError("Unknown primitive type in _read_binary_internal ", typeToString(t));
  }
}

inline
proc _write_binary_internal(_channel_internal:qio_channel_ptr_t, param byteorder:iokind, x:?t):syserr where _isIoPrimitiveType(t) {
  if _isBooleanType(t) {
    var zero_one:uint(8) = if x then 1:uint(8) else 0:uint(8);
    return qio_channel_write_byte(false, _channel_internal, zero_one);
  } else if _isIntegralType(t) {
    if numBytes(t) == 1 {
      return qio_channel_write_byte(false, _channel_internal, x:uint(8));
    } else {
      // handles int types
      return qio_channel_write_int(false, byteorder, _channel_internal, x, numBytes(t), _isSignedType(t));
    }
  } else if _isFloatType(t) {
    // handles real, imag
    return qio_channel_write_float(false, byteorder, _channel_internal, x, numBytes(t));
  } else if _isComplexType(t)  {
    // handle complex types
    var re = x.re;
    var im = x.im;
    return qio_channel_write_complex(false, byteorder, _channel_internal, re, im, numBytes(x.re.type));
  } else if t == string {
    return qio_channel_write_string(false, byteorder, qio_channel_str_style(_channel_internal), _channel_internal, x, x.length: ssize_t);
  } else if _isEnumeratedType(t) {
    var i:enum_mintype(t) = x:enum_mintype(t);
    return qio_channel_write_int(false, byteorder, _channel_internal, i, numBytes(i.type), _isSignedType(i.type));
  } else {
    halt("Unknown primitive type in write_binary_internal " + typeToString(t));
  }
}

// Channel must be locked, must be running on this.home
// x is inout because it might contain a literal string.
inline
proc _read_one_internal(_channel_internal:qio_channel_ptr_t, param kind:iokind, inout x:?t):syserr where _isIoPrimitiveTypeOrNewline(t) {
  var e:syserr = EINVAL;
  if t == ioNewline {
    return qio_channel_skip_past_newline(false, _channel_internal);
  } else if t == ioChar {
    return qio_channel_read_char(false, _channel_internal, x.ch);
  } else if t == ioLiteral {
    //writeln("in scan literal ", x.val);
    return qio_channel_scan_literal(false, _channel_internal, x.val, x.val.length, x.ignoreWhiteSpace);
    //e = qio_channel_scan_literal(false, _channel_internal, x.val, x.val.length, x.ignoreWhiteSpace);
    //writeln("Scanning literal ", x.val,  " yeilded error ", e);
    //return e;
  } else if t == ioBits {
    return qio_channel_read_bits(false, _channel_internal, x.v, x.nbits);
  } else if kind == iokind.dynamic {
    var binary:uint(8) = qio_channel_binary(_channel_internal);
    var byteorder:uint(8) = qio_channel_byteorder(_channel_internal);
    if binary {
      select byteorder {
        when iokind.big    do e = _read_binary_internal(_channel_internal, iokind.big, x);
        when iokind.little do e = _read_binary_internal(_channel_internal, iokind.little, x);
        otherwise             e = _read_binary_internal(_channel_internal, iokind.native, x);
      }
    } else {
      e = _read_text_internal(_channel_internal, x);
    }
  } else {
    e = _read_binary_internal(_channel_internal, kind, x);
  }
  return e;
}

// Channel must be locked, must be running on this.home
inline
proc _write_one_internal(_channel_internal:qio_channel_ptr_t, param kind:iokind, x:?t):syserr where _isIoPrimitiveTypeOrNewline(t) {
  var e:syserr = EINVAL;
  if t == ioNewline {
    return qio_channel_write_newline(false, _channel_internal);
  } else if t == ioChar {
    return qio_channel_write_char(false, _channel_internal, x.ch);
  } else if t == ioLiteral {
    return qio_channel_print_literal(false, _channel_internal, x.val, x.val.length:ssize_t);
  } else if t == ioBits {
    return qio_channel_write_bits(false, _channel_internal, x.v, x.nbits);
  } else if kind == iokind.dynamic {
    var binary:uint(8) = qio_channel_binary(_channel_internal);
    var byteorder:uint(8) = qio_channel_byteorder(_channel_internal);
    if binary {
      select byteorder {
        when iokind.big    do e = _write_binary_internal(_channel_internal, iokind.big, x);
        when iokind.little do e = _write_binary_internal(_channel_internal, iokind.little, x);
        otherwise             e = _write_binary_internal(_channel_internal, iokind.native, x);
      }
    } else {
      e = _write_text_internal(_channel_internal, x);
    }
  } else {
    e = _write_binary_internal(_channel_internal, kind, x);
  }
  return e;
}

inline
proc _read_one_internal(_channel_internal:qio_channel_ptr_t, param kind:iokind, inout x:?t):syserr {
  var reader = new ChannelReader(_channel_internal=_channel_internal);
  var err:syserr = ENOERR;
  reader.read(x);
  err = reader.err;
  delete reader;
  return err;
}

inline
proc _write_one_internal(_channel_internal:qio_channel_ptr_t, param kind:iokind, x:?t):syserr {
  var writer = new ChannelWriter(_channel_internal=_channel_internal);
  var err:syserr = ENOERR;
  writer.write(x);
  err = writer.err;
  delete writer;
  return err;
}

/* Returns true if we read all the args,
   false if we encountered EOF (or possibly another error and didn't halt)*/
inline
proc channel.read(inout args ...?k,
                  out error:syserr):bool {
  if writing then compilerError("read on write-only channel");
  error = ENOERR;
  on __primitive("chpl_on_locale_num", this.home_uid) {
    this.lock();
    for param i in 1..k {
      if !error {
        error = _read_one_internal(_channel_internal, kind, args[i]);
      }
    }
    this.unlock();
  }
  return !error;
}
var _arg_to_proto_names = ("a", "b", "c", "d", "e", "f");
proc _args_to_proto(args ...?k,
                    preArg:string) {
  var err_args:string = "";
  for param i in 1..k {
    var name:string;
    if i <= _arg_to_proto_names.size then name = _arg_to_proto_names(i);
    else name = "x" + i:string;
    err_args += preArg + name + ":" + typeToString(args(i).type);
    if i != k then err_args += ", ";
  }
  return err_args;
}

inline
proc channel.read(inout args ...?k):bool {
  var e:syserr = ENOERR;
  this.read((...args), error=e);
  if !e then return true;
  else if e == EEOF then return false;
  else {
    this._ch_ioerror(e, "in channel.read(" +
                        _args_to_proto((...args), preArg="inout ") +
                        ")");
    return false;
  }
}
proc channel.read(inout args ...?k,
                  style:iostyle,
                  out error:syserr):bool {
  if writing then compilerError("read on write-only channel");
  error = ENOERR;
  on __primitive("chpl_on_locale_num", this.home_uid) {
    this.lock();
    var save_style = this._style();
    this._set_style(style);
    for param i in 1..k {
      if !error {
        error = _read_one_internal(_channel_internal, kind, args[i]);
      }
    }
    this._set_style(save_style);
    this.unlock();
  }
  return !error;
}
proc channel.read(inout args ...?k,
                  style:iostyle):bool {
  var e:syserr = ENOERR;
  this.read((...args), style=iostyle, error=e);
  if !e then return true;
  else if e == EEOF then return false;
  else {
    this._ch_ioerror(e, "in channel.read(" +
                        _args_to_proto((...args), preArg="inout ") +
                        "style:iostyle)");
    return false;
  }
}

proc channel.readline(inout arg:string, out error:syserr):bool {
  if writing then compilerError("read on write-only channel");
  error = ENOERR;
  on __primitive("chpl_on_locale_num", this.home_uid) {
    this.lock();
    var save_style = this._style();
    var mystyle = save_style.text();
    mystyle.string_format = QIO_STRING_FORMAT_TOEND;
    mystyle.string_end = 0x0a; // ascii newline.
    this._set_style(mystyle);
    error = _read_one_internal(_channel_internal, iokind.dynamic, arg);
    this._set_style(save_style);
    this.unlock();
  }
  return !error;
}
proc channel.readline(inout arg:string):bool {
  var e:syserr = ENOERR;
  this.readline(arg, error=e);
  if !e then return true;
  else if e == EEOF then return false;
  else {
    this._ch_ioerror(e, "in channel.readline(inout arg:string)");
    return false;
  }
}

inline
proc channel.readbits(out v:uint(64), nbits:int(8), out error:syserr):bool {
  var tmp:ioBits;
  var ret:bool;

  error = ENOERR;

  tmp.nbits = nbits;
  ret = this.read(tmp, error=error);
  v = tmp.v;

  return ret;
}
proc channel.readbits(out v:uint(64), nbits:int(8)):bool {
  var e:syserr = ENOERR;
  this.readbits(v, nbits, error=e);
  if !e then return true;
  else if e == EEOF then return false;
  else {
    this._ch_ioerror(e, "in channel.readbits(out v:uint(64), nbits:int(8))");
    return false;
  }
}

inline
proc channel.writebits(v:uint(64), nbits:int(8), out error:syserr):bool {
  return this.write(new ioBits(v, nbits), error=error);
}
proc channel.writebits(v:uint(64), nbits:int(8)):bool {
  var e:syserr = ENOERR;
  this.writebits(v, nbits, error=e);
  if !e then return true;
  else {
    this._ch_ioerror(e, "in channel.writebits(v:uint(64), nbits:int(8))");
    return false;
  }
}

proc channel.readln(out error:syserr):bool {
  var nl = new ioNewline();
  return this.read(nl, error=error);
}
proc channel.readln():bool {
  var nl = new ioNewline();
  return this.read(nl);
}

proc channel.readln(inout args ...?k):bool {
  var nl = new ioNewline();
  return this.read((...args), nl);
}
proc channel.readln(inout args ...?k,
                    out error:syserr):bool {
  var nl = new ioNewline();
  return this.read((...args), nl, error=error);
}
proc channel.readln(inout args ...?k,
                    style:iostyle,
                    out error:syserr):bool {
  var nl = new ioNewline();
  return this.read((...args), nl, style=style, error=error);
}
proc channel.readln(inout args ...?k,
                    style:iostyle):bool {
  var nl = new ioNewline();
  return this.read((...args), nl, style=style);
}

proc channel.read(type t) {
  var tmp:t;
  var e:syserr = ENOERR;
  this.read(tmp, error=e);
  if e then this._ch_ioerror(e, "in channel.read(type)");
  return tmp;
}
proc channel.readln(type t) {
  var tmp:t;
  var e:syserr = ENOERR;
  this.readln(tmp, error=e);
  if e then this._ch_ioerror(e, "in channel.readln(type)");
  return tmp;
}
// Read/write tuples of types.
proc channel.readln(type t ...?numTypes) where numTypes > 1 {
  var tupleVal: t;
  for param i in 1..numTypes-1 do
    tupleVal(i) = this.read(t(i));
  tupleVal(numTypes) = this.readln(t(numTypes));
  return tupleVal;
}
proc channel.read(type t ...?numTypes) where numTypes > 1 {
  var tupleVal: t;
  for param i in 1..numTypes do
    tupleVal(i) = this.read(t(i));
  return tupleVal;
}

inline
proc channel.write(args ...?k, out error:syserr):bool {
  if !writing then compilerError("write on read-only channel");
  error = ENOERR;
  on __primitive("chpl_on_locale_num", this.home_uid) {
    this.lock();
    for param i in 1..k {
      if !error {
        error = _write_one_internal(_channel_internal, kind, args(i));
      }
    }
    this.unlock();
  }
  return !error;
}

inline
proc channel.write(args ...?k):bool {
  var e:syserr = ENOERR;
  this.write((...args), error=e);
  if !e then return true;
  else {
    this._ch_ioerror(e, "in channel.write(" +
                        _args_to_proto((...args), preArg="") +
                        ")");
    return false;
  }
}

proc channel.write(args ...?k,
                   style:iostyle,
                   out error:syserr):bool {
  if !writing then compilerError("write on read-only channel");
  error = ENOERR;
  on __primitive("chpl_on_locale_num", this.home_uid) {
    this.lock();
    var save_style = this._style();
    this._set_style(style);
    for param i in 1..k {
      if !error {
        error = _write_one_internal(_channel_internal, iokind.dynamic, args(i));
      }
    }
    this._set_style(save_style);
    this.unlock();
  }
  return !error;
}
proc channel.write(args ...?k,
                   style:iostyle):bool {
  var e:syserr = ENOERR;
  this.write((...args), style=style, error=e);
  if !e then return true;
  else {
    this._ch_ioerror(e, "in channel.write(" +
                        _args_to_proto((...args), preArg="") +
                        "style:iostyle)");
    return false;
  }
}

proc channel.writeln(out error:syserr):bool {
  return this.write(new ioNewline(), error=error);
}
proc channel.writeln():bool {
  return this.write(new ioNewline());
}
proc channel.writeln(args ...?k, out error:syserr):bool {
  return this.write((...args), new ioNewline(), error=error);
}
proc channel.writeln(args ...?k):bool {
  return this.write((...args), new ioNewline());
}
proc channel.writeln(args ...?k,
                     style:iostyle):bool {
  return this.write((...args), new ioNewline(), style=style);
}
proc channel.writeln(args ...?k,
                     style:iostyle,
                     out error:syserr):bool {
  return this.write((...args), new ioNewline(), style=style, error=error);
}

proc channel.flush(out error:syserr) {
  error = ENOERR;
  on __primitive("chpl_on_locale_num", this.home_uid) {
    error = qio_channel_flush(locking, _channel_internal);
  }
}
proc channel.flush() {
  var e:syserr = ENOERR;
  this.flush(error=e);
  if e then this._ch_ioerror(e, "in channel.flush");
}

proc channel.assertEOF(error:string) {
  if writing {
    this._ch_ioerror(EINVAL, "assertEOF on writing channel");
  } else {
    var tmp:uint(8);
    var err:syserr;
    this.read(tmp, error=err);
    if err != EEOF {
      this._ch_ioerror("assert failed", error);
    }
  }
}
proc channel.assertEOF() {
  this.assertEOF("- Not at EOF");
}

proc channel.close(out error:syserr) {
  error = ENOERR;
  on __primitive("chpl_on_locale_num", this.home_uid) {
    error = qio_channel_close(locking, _channel_internal);
  }
}

proc channel.close() {
  var e:syserr = ENOERR;
  this.close(error=e);
  if e then this._ch_ioerror(e, "in channel.close");
}

/*
proc channel.modifyStyle(f:func(iostyle, iostyle))
{
  on __primitive("chpl_on_locale_num", this.home_uid) {
    this.lock();
    var style = this._style();
    style = f(style);
    this._set_style(style);
    this.unlock();
  }
}
*/

/* Move between min_len and max_len bytes
   of data from a read channel to a write channel.
   Blocks on reading up to min_len; blocks on writing
   to the write channel. Returns the number of bytes
   moved and/or an error number. */
/*proc splice(input:read_channel, output:write_channel,
           min_length:int(64), max_length:int(64), onErr:ErrorHandler = nil) {
  halt("unimplemented");
}*/

/* Move between min_len and max_len bytes
   of data from a read channel to several write channels;
   the data is written to all of the write channels.
   Blocks on reading up to min_len; blocks on writing to
   all the write channels. Returns the number of bytes
   moved and/or an error number. */
/*proc tee(input:read_channel, output:[] write_channel, onErr:ErrorHandler = nil) {
  halt("unimplemented");
}*/

record ItemReader {
  type ItemType;
  param kind:iokind;
  param locking:bool;
  var ch:channel(false,kind,locking);
  proc read(out arg:ItemType, out error:syserr):bool {
    return ch.read(arg, error=error);
  }
  proc read(out arg:ItemType):bool {
    return ch.read(arg);
  }

  iter these() {
    while true {
      var x:ItemType;
      var gotany = ch.read(x);
      if ! gotany then break;
      yield x;
    }
  }
  /*
  iter these(out error:syserr) {
    while true {
      var x:ItemType;
      var gotany = ch.read(x, error=error);
      if ! gotany then break;
      yield x;
    }
  }*/
}

proc channel.itemReader(type ItemType, param kind:iokind=iokind.dynamic) {
  if writing then compilerError(".itemReader on write-only channel");
  return new ItemReader(ItemType, kind, locking, this);
}

record ItemWriter {
  type ItemType;
  param kind:iokind;
  param locking:bool;
  var ch:channel(false,kind);
  proc write(arg:ItemType, out error:syserr):bool {
    return ch.write(arg, error=error);
  }
  proc write(arg:ItemType):bool {
    return ch.write(arg);
  }
}

proc channel.itemWriter(type ItemType, param kind:iokind=iokind.dynamic) {
  if !writing then compilerError(".itemWriter on read-only channel");
  return new ItemWriter(ItemType, kind, locking, this);
}

// And now, the toplevel items.

const stdin:channel(false, iokind.dynamic, true) = openfd(0).reader();
const stdout:channel(true, iokind.dynamic, true) = openfp(chpl_cstdout()).writer();
const stderr:channel(true, iokind.dynamic, true) = openfp(chpl_cstderr()).writer();

proc write(args ...?n) {
  stdout.write((...args));
}
proc writeln(args ...?n) {
  stdout.writeln((...args));
}
proc writeln() {
  stdout.writeln();
}

proc read(inout args ...?n):bool {
  return stdin.read((...args));
}
proc readln(inout args ...?n):bool {
  return stdin.readln((...args));
}
proc readln():bool {
  return stdin.readln();
}

proc readln(type t ...?numTypes) {
  return stdin.readln((...t));
}
proc read(type t ...?numTypes) {
  return stdin.read((...t));
}

// readThis and writeThis methods for the basic types
// these just need to call writer.writePrimitive or reader.readPrimitive
/* commented out because these should never be called directly;
   readPrimitive should be called instead. It used to be the case that
   some writeThis methods would directly call writeThis on
   the elements of an aggregate data structure; these were
   needed to support that operation. But, there should be no
   more direct calls to writeThis.

   Note that readThis doesn't work as a method
   on e.g. an int because this is not an lvalue.

proc numeric.readThis(r: Reader) { r.readPrimitive(this); }
proc enumerated.readThis(r: Reader) { r.readPrimitive(this); }
proc bool.readThis(r: Reader) { r.readPrimitive(this); }
proc string.readThis(r: Reader) { r.readPrimitive(this); }

proc numeric.writeThis(r: Writer) { r.writePrimitive(this); }
proc enumerated.writeThis(r: Writer) { r.writePrimitive(this); }
proc bool.writeThis(r: Writer) { r.writePrimitive(this); }
proc string.writeThis(r: Writer) { r.writePrimitive(this); }
*/

class ChannelWriter : Writer {
  var _channel_internal:qio_channel_ptr_t = QIO_CHANNEL_PTR_NULL;
  var err:syserr = ENOERR;
  proc binary:bool {
    var ret:uint(8) = qio_channel_binary(_channel_internal);
    return ret != 0;
  }
  proc error():syserr {
    return err;
  }
  proc setError(e:syserr) {
    err = e;
  }
  proc clearError() {
    err = 0;
  }
  proc writePrimitive(x) {
    if !err {
      err = _write_one_internal(_channel_internal, iokind.dynamic, x);
    }
  }
  proc writeThis(w:Writer) {
    // MPF - I don't understand why I had to add this,
    // but without it test/modules/diten/returnClassDiffModule5.chpl fails.
    compilerError("writeThis on ChannelWriter called");
  }
  // writeThis + no readThis -> ChannelWriter itself cannot be read
}
class ChannelReader : Reader {
  var _channel_internal:qio_channel_ptr_t = QIO_CHANNEL_PTR_NULL;
  var err:syserr = ENOERR;
  proc binary:bool {
    var ret:uint(8) = qio_channel_binary(_channel_internal);
    return ret != 0;
  }
  proc error():syserr {
    return err;
  }
  proc setError(e:syserr) {
    err = e;
  }
  proc clearError() {
    err = ENOERR;
  }
  proc readPrimitive(inout x:?t) where _isIoPrimitiveTypeOrNewline(t) {
    if !err {
      err = _read_one_internal(_channel_internal, iokind.dynamic, x);
    }
  }
  proc writeThis(w:Writer) {
    compilerError("writeThis on ChannelReader called");
  }
  // writeThis + no readThis -> ChannelReader itself cannot be read
}

// Delete a file.
proc unlink(path:string, out error:syserr) {
  extern proc sys_unlink(path:string):syserr;
  error = sys_unlink(path);
}
proc unlink(path:string) {
  var err:syserr = ENOERR;
  unlink(path, err);
  if err then ioerror(err, "in unlink", path);
}

proc unicodeSupported():bool {
  extern proc qio_unicode_supported():c_int;
  return qio_unicode_supported() > 0;
}