Add some comments to the new module functions.

/*
 * Copyright 2004-2015 Cray Inc.
 * Other additional copyright holders may be indicated within.
 *
 * The entirety of this work is licensed under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License.
 *
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
use Error;

/* Change the current working directory of the current locale to the specified
   name. Returns any errors that occurred via an out parameter.
   err: a syserr used to indicate if an error occurred
   name: a string indicating a new directory

   Note: this is not safe within a parallel context.  A chdir call in one task
   will affect the current working directory of all tasks for that locale.
*/
proc locale.chdir(out err: syserr, name: string) {
  extern proc chpl_fs_chdir(name: c_string):syserr;

  on this {
    err = chpl_fs_chdir(name.c_str());
  }
}

/* Change the current working directory of the current locale to the specified
   name. Generates an error message if one occurred.
   name: a string indicating a new directory

   Note: this is not safe within a parallel context.  A chdir call in one task
   will affect the current working directory of all tasks for that locale.
*/
proc locale.chdir(name: string) {
  var err: syserr = ENOERR;
  chdir(err, name);
  if err != ENOERR then ioerror(err, "in chdir", name);
}

/* Set the permissions of the file or directory specified by name to that
   indicated by settings.  Returns any errors that occurred via an out
   parameter.

   :arg err: a syserr used to indicate if an error occurred
   :arg name: the name of the file/directory which should have its permissions
              alterred.
   :arg mode: an integer representing the permissions desired for the file in
              question.  See description of the provided constants for
              potential values.
*/
proc chmod(out err: syserr, name: string, mode: int) {
  extern proc chpl_fs_chmod(name: c_string, mode: int): syserr;

  err = chpl_fs_chmod(name.c_str(), mode);
}

/* Set the permissions of the file or directory specified by name to that
   indicated by settings, and may generate an error message

   :arg name: the name of the file/directory which should have its permissions
              alterred.
   :arg mode: an integer representing the permissions desired for the file
              in question.  See description of the provided constants for potential
              values.
*/
proc chmod(name: string, mode: int){
  var err: syserr = ENOERR;
  chmod(err, name, mode);
  if err != ENOERR then ioerror(err, "in chmod", name);
}

/* Changes one or both of the owner and group id of the named file to the
   specified values.  If uid or gid are -1, the value in question will remain
   unchanged.
   err: a syserr used to indicate if an error occurred
   name: the name of the file to be changed.
   uid: user id to use as new owner, or -1 if it should remain the same.
   gid: group id to use as the new group owner, or -1 if it should remain the same.
*/
proc chown(out err: syserr, name: string, uid: int, gid: int) {
  extern proc chpl_fs_chown(name: c_string, uid: c_int, gid: c_int):syserr;

  err = chpl_fs_chown(name.c_str(), uid:c_int, gid:c_int);
}

/* Changes one or both of the owner and group id of the named file to the
   specified values.  If uid or gid are -1, the value in question will remain
   unchanged. Generates an error message if one occurred.
   name: the name of the file to be changed.
   uid: user id to use as new owner, or -1 if it should remain the same.
   gid: group id to use as the new group owner, or -1 if it should remain the same.
*/
proc chown(name: string, uid: int, gid: int) {
  var err: syserr = ENOERR;
  chown(err, name, uid, gid);
  if err != ENOERR then ioerror(err, "in chown", name);
}

/* Returns the current working directory for the current locale.
   err: a syserr used to indicate if an error occurred

   Note: another task on this locale can change the current working
   directory from underneath this task, so use caution when making use
   of this function in a parallel environment.
*/
proc locale.cwd(out err: syserr): string {
  extern proc chpl_fs_cwd(ref working_dir:c_string_copy):syserr;

  var ret:string;
  on this {
    var tmp:c_string_copy;
    // c_strings and c_string_copy's can't cross on statements.
    err = chpl_fs_cwd(tmp);
    if (err != ENOERR) {
      ret = "";
    } else {
      // This version of toString steals its operand.  No need to free.
      ret = toString(tmp);
    }
  }
  return ret;
}

/* Returns the current working directory for the current locale. Generates an
   error message if one occurred.

   Note: another task on this locale can change the current working
   directory from underneath this task, so use caution when making use
   of this function in a parallel environment.
*/
proc locale.cwd(): string {
  var err: syserr = ENOERR;
  var ret = cwd(err);
  if err != ENOERR then ioerror(err, "in cwd");
  return ret;
}

/* Returns true if the provided filename corresponds to an existing file, false
   otherwise.  Returns false for broken symbolic links.  Returns any errors that
   occurred via an out parameter
   name: a string used to attempt to find the file specified.
*/
proc exists(out err: syserr, name: string): bool {
  extern proc chpl_fs_exists(ref result:c_int, name: c_string): syserr;

  var ret:c_int;
  err = chpl_fs_exists(ret, name.c_str());
  return ret != 0;
}

/* Returns true if the provided filename corresponds to an existing file, false
   otherwise.  Returns false for broken symbolic links.  Generates an error
   message if one occurred.
   name: a string used to attempt to find the file specified.
*/
proc exists(name: string): bool {
  var err: syserr = ENOERR;
  var ret = exists(err, name);
  if err != ENOERR then ioerror(err, "in exists");
  return ret;
}

/* Returns the user id associated with the file or directory specified by
   name.  Returns an errors that occurred via an out parameter.
   err: a syserr used to indicate if an error occurred
   name: a string used to indicate the file in question
*/
proc getUID(out err: syserr, name: string): int {
  extern proc chpl_fs_get_uid(ref result: c_int, filename: c_string): syserr;

  var result: c_int;
  err = chpl_fs_get_uid(result, name.c_str());
  return result;
}

/* Returns the user id associated with the file or directory specified by
   name.  Returns an errors that occurred via an out parameter.
   name: a string used to indicate the file in question
*/
proc getUID(name: string): int {
  var err: syserr = ENOERR;
  var ret = getUID(err, name);
  if err != ENOERR then ioerror(err, "in getUID");
  return ret;
}

/* Returns the group id associated with the file or directory specified by
   name.  Returns an errors that occurred via an out parameter.
   err: a syserr used to indicate if an error occurred
   name: a string used to indicate the file in question
*/
proc getGID(out err: syserr, name: string): int {
  extern proc chpl_fs_get_gid(ref result: c_int, filename: c_string): syserr;

  var result: c_int;
  err = chpl_fs_get_gid(result, name.c_str());
  return result;
}

/* Returns the group id associated with the file or directory specified by
   name.  Returns an errors that occurred via an out parameter.
   name: a string used to indicate the file in question
*/
proc getGID(name: string): int {
  var err: syserr = ENOERR;
  var ret = getGID(err, name);
  if err != ENOERR then ioerror(err, "in getGID");
  return ret;
}

/* Returns true if the name corresponds to a directory, false otherwise.
   err: a syserr used to indicate if an error occurred
   name: a string that could be the name of a directory.
*/
proc isDir(out err:syserr, name:string):bool {
  extern proc chpl_fs_is_dir(ref result:c_int, name: c_string):syserr;

  var ret:c_int;
  err = chpl_fs_is_dir(ret, name.c_str());
  return ret != 0;
}

/* Returns true if the name corresponds to a directory, false otherwise.
   Generates an error message if one occurs.
   name: a string that could be the name of a directory.
*/
proc isDir(name:string):bool {
  var err:syserr;
  var ret = isDir(err, name);
  if err != ENOERR then ioerror(err, "in isDir", name);
  return ret;
}

/* Returns true if the name corresponds to a file, false otherwise.
   err: a syserr used to indicate if an error occurred
   name: a string that could be the name of a file.
*/
proc isFile(out err:syserr, name:string):bool {
  extern proc chpl_fs_is_file(ref result:c_int, name: c_string):syserr;

  var ret:c_int;
  err = chpl_fs_is_file(ret, name.c_str());
  return ret != 0;
}

/* Returns true if the name corresponds to a file, false otherwise.
   Generates an error message if one occurs.
   name: a string that could be the name of a file.
*/
proc isFile(name:string):bool {
  var err:syserr;
  var ret = isFile(err, name);
  if err != ENOERR then ioerror(err, "in isFile", name);
  return ret;
}

/* Returns true if the name corresponds to a symbolic link, false if not
   or if symbolic links are not supported.
   name: a string that could be the name of a symbolic link.
*/
proc isLink(out err:syserr, name: string): bool {
  extern proc chpl_fs_is_link(ref result:c_int, name: c_string): syserr;

  var ret:c_int;
  err = chpl_fs_is_link(ret, name.c_str());
  return ret != 0;
}

/* Returns true if the name corresponds to a symbolic link, false if not
   or if symbolic links are not supported.  Generates an error message if one
   occurs
   name: a string that could be the name of a symbolic link.
*/
proc isLink(name: string): bool {
  var err:syserr;
  var ret = isLink(err, name);
  if err != ENOERR then ioerror(err, "in isLink", name);
  return ret;
}

/* These are constant values of the form S_I[R | W | X][USR | GRP | OTH],
   S_IRWX[U | G | O], S_ISUID, S_ISGID, or S_ISVTX, where R corresponds to
   readable, W corresponds to writable, X corresponds to executable, USR and
   U correspond to user, GRP and G correspond to group, OTH and O correspond
   to other, directly tied to the C idea of these constants.  They are intended
   for use with functions that alter the permissions of files or directories.
*/
extern const S_IRUSR: int;
extern const S_IWUSR: int;
extern const S_IXUSR: int;
extern const S_IRWXU: int;

extern const S_IRGRP: int;
extern const S_IWGRP: int;
extern const S_IXGRP: int;
extern const S_IRWXG: int;

extern const S_IROTH: int;
extern const S_IWOTH: int;
extern const S_IXOTH: int;
extern const S_IRWXO: int;

extern const S_ISUID: int;
extern const S_ISGID: int;
extern const S_ISVTX: int;

/* Attempt to create a directory with the given path.  If parents is true,
   will attempt to create any directory in the path that did not previously
   exist.  Returns any errors that occurred via an out parameter

   :arg err: a syserr used to indicate if an error occurred
   :arg name: the name of the directory to be created, fully specified.
   :arg mode: an integer representing the permissions desired for the file in
              question. See description of the provided constants for
              potential values.
   :arg parents: a boolean indicating if parent directories should be created.
                 If set to false, any nonexistent parent will cause an error to
                 occur.

   Important note: In the case where parents is true, there is a potential
   security vulnerability.  Checking whether parent directories exist and
   creating them if not are separate events. So even if parents==true and a
   parent directory didn't exist before this function is called but does exist
   afterward, it's still not necessarily true that this function created that
   parent. Some other concurrent operation could have done so.
*/
proc mkdir(out err: syserr, name: string, mode: int = 0o777,
           parents: bool=false) {
  extern proc chpl_fs_mkdir(name: c_string, mode: int, parents: bool):syserr;

  err = chpl_fs_mkdir(name.c_str(), mode, parents);
}

/* Attempt to create a directory with the given path.  If parents is true,
   will attempt to create any directory in the path that did not previously
   exist.  Generates an error message if one occurred.

   :arg name: the name of the directory to be created, fully specified.
   :arg mode: an integer representing the permissions desired for the file in
              question. See description of the provided constants for
              potential values.
   :arg parents: a boolean indicating if parent directories should be created.
                 If set to false, any nonexistent parent will cause an error to
                 occur.

   Important note: In the case where parents is true, there is a potential
   security vulnerability.  Checking whether parent directories exist and
   creating them if not are separate events. So even if parents==true and a
   parent directory didn't exist before this function is called but does exist
   afterward, it's still not necessarily true that this function created that
   parent. Some other concurrent operation could have done so.
*/
proc mkdir(name: string, mode: int = 0o777, parents: bool=false) {
  var err: syserr = ENOERR;
  mkdir(err, name, mode, parents);
  if err != ENOERR then ioerror(err, "in mkdir", name);
}

/* Renames the file specified by oldname to newname, returning an error
   if one occurred.  The file is not opened during this operation.
   error: a syserr used to indicate if an error occurred during renaming.
   oldname: current name of the file
   newname: name which should refer to the file in the future.*/
proc rename(out error: syserr, oldname, newname: string) {
  extern proc chpl_fs_rename(oldname: c_string, newname: c_string):syserr;

  error = chpl_fs_rename(oldname.c_str(), newname.c_str());
}

/* Renames the file specified by oldname to newname, generating an error message
   if one occurred.  The file is not opened during this operation.
   oldname: current name of the file
   newname: name which should refer to the file in the future.*/
proc rename(oldname, newname: string) {
  var err:syserr = ENOERR;
  rename(err, oldname, newname);
  if err != ENOERR then ioerror(err, "in rename", oldname);
}

/* Removes the file or directory specified by name, returning an error
   if one occurred via an out parameter.
   err: a syserr used to indicate if an error occurred during removal
   name: the name of the file/directory to remove */
proc remove(out err: syserr, name: string) {
  extern proc chpl_fs_remove(name: c_string):syserr;

  err = chpl_fs_remove(name.c_str());
}

/* Removes the file or directory specified by name, generating an error message
   if one occurred.
   name: the name of the file/directory to remove */
proc remove(name: string) {
  var err:syserr = ENOERR;
  remove(err, name);
  if err != ENOERR then ioerror(err, "in remove", name);
}

/* Returns true if both pathnames refer to the same file or directory
   (utilizing operating system operations rather than string ones), returns
   false otherwise.
   err: a syserr used to indicate if an error occurred during comparison
   file1, file2: string representations of paths to be compared.
*/
proc sameFile(out err: syserr, file1: string, file2: string): bool {
  extern proc chpl_fs_samefile_string(ref ret: c_int, file1: c_string, file2: c_string): syserr;

  var ret:c_int;
  err = chpl_fs_samefile_string(ret, file1.c_str(), file2.c_str());
  return ret != 0;
}

/* Returns true if both pathnames refer to the same file or directory
   (utilizing operating system operations rather than string ones), returns
   false otherwise.  May generate an error message.
   file1, file2: string representations of paths to be compared.
*/
proc sameFile(file1: string, file2: string): bool {
  var err:syserr = ENOERR;
  var result = sameFile(err, file1, file2);
  if err != ENOERR then ioerror(err, "in sameFile " + file1, file2);
  return result;
}

/* Same as the above function, but taking file records instead of string
   pathnames as arguments.
*/
proc sameFile(out err: syserr, file1: file, file2: file): bool {
  extern proc chpl_fs_samefile(ref ret: c_int, file1: qio_file_ptr_t,
                               file2: qio_file_ptr_t): syserr;

  var ret:c_int;
  if (is_c_nil(file1._file_internal) || is_c_nil(file2._file_internal)) {
    // Implementation note on program design tradeoffs:
    // I could use file.check() here.  That would not rely on the understanding
    // of file's internals.  However, it would cause this method to either
    // return error messages or halt, depending on the error encountered.
    // This check could be moved to the version w/o the err argument, but if
    // someone called this function w/o going through that, we'd lose the
    // check.  Also, we already must make use of the record internals to do the
    // inner comparison (since the record is a chapel construct), so there's no
    // additional harm.

    // The file is referencing a null file.  We'll get a segfault if we
    // continue.
    err = EBADF;
    return false; // This part isn't as important as the error.
  }

  err = chpl_fs_samefile(ret, file1._file_internal, file2._file_internal);
  return ret != 0;
}

/* Same as the above function, but taking file records instead of string
   pathnames as arguments.  May generate an error message.
*/
proc sameFile(file1: file, file2: file): bool {
  var err:syserr = ENOERR;
  var result = sameFile(err, file1, file2);
  if err != ENOERR then ioerror(err, "in sameFile " + file1.path, file2.path);
  return result;
}

/* Create a symbolic link pointing to oldName named newName.  May generate an
   error message.
   err: a syserr used to indicate if an error occurred during creation
   oldName: the source file to be linked
   newName: the location the symbolic link should live
*/
proc symlink(out err: syserr, oldName: string, newName: string) {
  extern proc chpl_fs_symlink(orig: c_string, linkName: c_string): syserr;

  err = chpl_fs_symlink(oldName.c_str(), newName.c_str());
}

/* Create a symbolic link pointing to oldName named sym.  May generate an error
   message.
   oldName: the source file to be linked
   newName: the location the symbolic link should live
*/
proc symlink(oldName: string, newName: string) {
  var err:syserr = ENOERR;
  symlink(err, oldName, newName);
  if err != ENOERR then ioerror(err, "in symlink " + oldName, newName);
}

/* Returns an integer representing the current permissions of the file specified
   by name.  May generate an error message.
   err: a syserr used to indicate if an error occurred during this function
   name: the name of the file that you want to know the permissions of.
*/
proc viewMode(out err: syserr, name: string): int {
  extern proc chpl_fs_viewmode(ref result:c_int, name: c_string): syserr;

  var ret:c_int;
  err = chpl_fs_viewmode(ret, name.c_str());
  return ret;
}

/* Returns an integer representing the current permissions of the file specified
   by name.  May generate an error message.
   name: the name of the file that you want to know the permissions of.
*/
proc viewMode(name: string): int {
  var err:syserr = ENOERR;
  var result = viewMode(err, name);
  if err != ENOERR then ioerror(err, "in viewMode", name);
  return result;
}

/*
 * Copyright 2004-2015 Cray Inc.
 * Other additional copyright holders may be indicated within.
 *
 * The entirety of this work is licensed under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License.
 *
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
use Error;

/* Change the current working directory of the current locale to the specified
   name. Returns any errors that occurred via an out parameter.
   err: a syserr used to indicate if an error occurred
   name: a string indicating a new directory

   Note: this is not safe within a parallel context.  A chdir call in one task
   will affect the current working directory of all tasks for that locale.
*/
proc locale.chdir(out err: syserr, name: string) {
  extern proc chpl_fs_chdir(name: c_string):syserr;

  on this {
    err = chpl_fs_chdir(name.c_str());
  }
}

/* Change the current working directory of the current locale to the specified
   name. Generates an error message if one occurred.
   name: a string indicating a new directory

   Note: this is not safe within a parallel context.  A chdir call in one task
   will affect the current working directory of all tasks for that locale.
*/
proc locale.chdir(name: string) {
  var err: syserr = ENOERR;
  chdir(err, name);
  if err != ENOERR then ioerror(err, "in chdir", name);
}

/* Set the permissions of the file or directory specified by name to that
   indicated by settings.  Returns any errors that occurred via an out
   parameter.

   :arg err: a syserr used to indicate if an error occurred
   :arg name: the name of the file/directory which should have its permissions
              alterred.
   :arg mode: an integer representing the permissions desired for the file in
              question.  See description of the provided constants for
              potential values.
*/
proc chmod(out err: syserr, name: string, mode: int) {
  extern proc chpl_fs_chmod(name: c_string, mode: int): syserr;

  err = chpl_fs_chmod(name.c_str(), mode);
}

/* Set the permissions of the file or directory specified by name to that
   indicated by settings, and may generate an error message

   :arg name: the name of the file/directory which should have its permissions
              alterred.
   :arg mode: an integer representing the permissions desired for the file
              in question.  See description of the provided constants for potential
              values.
*/
proc chmod(name: string, mode: int){
  var err: syserr = ENOERR;
  chmod(err, name, mode);
  if err != ENOERR then ioerror(err, "in chmod", name);
}

/* Changes one or both of the owner and group id of the named file to the
   specified values.  If uid or gid are -1, the value in question will remain
   unchanged.
   err: a syserr used to indicate if an error occurred
   name: the name of the file to be changed.
   uid: user id to use as new owner, or -1 if it should remain the same.
   gid: group id to use as the new group owner, or -1 if it should remain the same.
*/
proc chown(out err: syserr, name: string, uid: int, gid: int) {
  extern proc chpl_fs_chown(name: c_string, uid: c_int, gid: c_int):syserr;

  err = chpl_fs_chown(name.c_str(), uid:c_int, gid:c_int);
}

/* Changes one or both of the owner and group id of the named file to the
   specified values.  If uid or gid are -1, the value in question will remain
   unchanged. Generates an error message if one occurred.
   name: the name of the file to be changed.
   uid: user id to use as new owner, or -1 if it should remain the same.
   gid: group id to use as the new group owner, or -1 if it should remain the same.
*/
proc chown(name: string, uid: int, gid: int) {
  var err: syserr = ENOERR;
  chown(err, name, uid, gid);
  if err != ENOERR then ioerror(err, "in chown", name);
}

/* Returns the current working directory for the current locale.
   err: a syserr used to indicate if an error occurred

   Note: another task on this locale can change the current working
   directory from underneath this task, so use caution when making use
   of this function in a parallel environment.
*/
proc locale.cwd(out err: syserr): string {
  extern proc chpl_fs_cwd(ref working_dir:c_string_copy):syserr;

  var ret:string;
  on this {
    var tmp:c_string_copy;
    // c_strings and c_string_copy's can't cross on statements.
    err = chpl_fs_cwd(tmp);
    if (err != ENOERR) {
      ret = "";
    } else {
      // This version of toString steals its operand.  No need to free.
      ret = toString(tmp);
    }
  }
  return ret;
}

/* Returns the current working directory for the current locale. Generates an
   error message if one occurred.

   Note: another task on this locale can change the current working
   directory from underneath this task, so use caution when making use
   of this function in a parallel environment.
*/
proc locale.cwd(): string {
  var err: syserr = ENOERR;
  var ret = cwd(err);
  if err != ENOERR then ioerror(err, "in cwd");
  return ret;
}

/* Returns true if the provided filename corresponds to an existing file, false
   otherwise.  Returns false for broken symbolic links.  Returns any errors that
   occurred via an out parameter
   name: a string used to attempt to find the file specified.
*/
proc exists(out err: syserr, name: string): bool {
  extern proc chpl_fs_exists(ref result:c_int, name: c_string): syserr;

  var ret:c_int;
  err = chpl_fs_exists(ret, name.c_str());
  return ret != 0;
}

/* Returns true if the provided filename corresponds to an existing file, false
   otherwise.  Returns false for broken symbolic links.  Generates an error
   message if one occurred.
   name: a string used to attempt to find the file specified.
*/
proc exists(name: string): bool {
  var err: syserr = ENOERR;
  var ret = exists(err, name);
  if err != ENOERR then ioerror(err, "in exists");
  return ret;
}

proc getUID(out err: syserr, name: string): int {
  extern proc chpl_fs_get_uid(ref result: c_int, filename: c_string): syserr;

  var result: c_int;
  err = chpl_fs_get_uid(result, name.c_str());
  return result;
}

proc getUID(name: string): int {
  var err: syserr = ENOERR;
  var ret = getUID(err, name);
  if err != ENOERR then ioerror(err, "in getUID");
  return ret;
}

proc getGID(out err: syserr, name: string): int {
  extern proc chpl_fs_get_gid(ref result: c_int, filename: c_string): syserr;

  var result: c_int;
  err = chpl_fs_get_gid(result, name.c_str());
  return result;
}

proc getGID(name: string): int {
  var err: syserr = ENOERR;
  var ret = getGID(err, name);
  if err != ENOERR then ioerror(err, "in getGID");
  return ret;
}

/* Returns true if the name corresponds to a directory, false otherwise.
   err: a syserr used to indicate if an error occurred
   name: a string that could be the name of a directory.
*/
proc isDir(out err:syserr, name:string):bool {
  extern proc chpl_fs_is_dir(ref result:c_int, name: c_string):syserr;

  var ret:c_int;
  err = chpl_fs_is_dir(ret, name.c_str());
  return ret != 0;
}

/* Returns true if the name corresponds to a directory, false otherwise.
   Generates an error message if one occurs.
   name: a string that could be the name of a directory.
*/
proc isDir(name:string):bool {
  var err:syserr;
  var ret = isDir(err, name);
  if err != ENOERR then ioerror(err, "in isDir", name);
  return ret;
}

/* Returns true if the name corresponds to a file, false otherwise.
   err: a syserr used to indicate if an error occurred
   name: a string that could be the name of a file.
*/
proc isFile(out err:syserr, name:string):bool {
  extern proc chpl_fs_is_file(ref result:c_int, name: c_string):syserr;

  var ret:c_int;
  err = chpl_fs_is_file(ret, name.c_str());
  return ret != 0;
}

/* Returns true if the name corresponds to a file, false otherwise.
   Generates an error message if one occurs.
   name: a string that could be the name of a file.
*/
proc isFile(name:string):bool {
  var err:syserr;
  var ret = isFile(err, name);
  if err != ENOERR then ioerror(err, "in isFile", name);
  return ret;
}

/* Returns true if the name corresponds to a symbolic link, false if not
   or if symbolic links are not supported.
   name: a string that could be the name of a symbolic link.
*/
proc isLink(out err:syserr, name: string): bool {
  extern proc chpl_fs_is_link(ref result:c_int, name: c_string): syserr;

  var ret:c_int;
  err = chpl_fs_is_link(ret, name.c_str());
  return ret != 0;
}

/* Returns true if the name corresponds to a symbolic link, false if not
   or if symbolic links are not supported.  Generates an error message if one
   occurs
   name: a string that could be the name of a symbolic link.
*/
proc isLink(name: string): bool {
  var err:syserr;
  var ret = isLink(err, name);
  if err != ENOERR then ioerror(err, "in isLink", name);
  return ret;
}

/* These are constant values of the form S_I[R | W | X][USR | GRP | OTH],
   S_IRWX[U | G | O], S_ISUID, S_ISGID, or S_ISVTX, where R corresponds to
   readable, W corresponds to writable, X corresponds to executable, USR and
   U correspond to user, GRP and G correspond to group, OTH and O correspond
   to other, directly tied to the C idea of these constants.  They are intended
   for use with functions that alter the permissions of files or directories.
*/
extern const S_IRUSR: int;
extern const S_IWUSR: int;
extern const S_IXUSR: int;
extern const S_IRWXU: int;

extern const S_IRGRP: int;
extern const S_IWGRP: int;
extern const S_IXGRP: int;
extern const S_IRWXG: int;

extern const S_IROTH: int;
extern const S_IWOTH: int;
extern const S_IXOTH: int;
extern const S_IRWXO: int;

extern const S_ISUID: int;
extern const S_ISGID: int;
extern const S_ISVTX: int;

/* Attempt to create a directory with the given path.  If parents is true,
   will attempt to create any directory in the path that did not previously
   exist.  Returns any errors that occurred via an out parameter

   :arg err: a syserr used to indicate if an error occurred
   :arg name: the name of the directory to be created, fully specified.
   :arg mode: an integer representing the permissions desired for the file in
              question. See description of the provided constants for
              potential values.
   :arg parents: a boolean indicating if parent directories should be created.
                 If set to false, any nonexistent parent will cause an error to
                 occur.

   Important note: In the case where parents is true, there is a potential
   security vulnerability.  Checking whether parent directories exist and
   creating them if not are separate events. So even if parents==true and a
   parent directory didn't exist before this function is called but does exist
   afterward, it's still not necessarily true that this function created that
   parent. Some other concurrent operation could have done so.
*/
proc mkdir(out err: syserr, name: string, mode: int = 0o777,
           parents: bool=false) {
  extern proc chpl_fs_mkdir(name: c_string, mode: int, parents: bool):syserr;

  err = chpl_fs_mkdir(name.c_str(), mode, parents);
}

/* Attempt to create a directory with the given path.  If parents is true,
   will attempt to create any directory in the path that did not previously
   exist.  Generates an error message if one occurred.

   :arg name: the name of the directory to be created, fully specified.
   :arg mode: an integer representing the permissions desired for the file in
              question. See description of the provided constants for
              potential values.
   :arg parents: a boolean indicating if parent directories should be created.
                 If set to false, any nonexistent parent will cause an error to
                 occur.

   Important note: In the case where parents is true, there is a potential
   security vulnerability.  Checking whether parent directories exist and
   creating them if not are separate events. So even if parents==true and a
   parent directory didn't exist before this function is called but does exist
   afterward, it's still not necessarily true that this function created that
   parent. Some other concurrent operation could have done so.
*/
proc mkdir(name: string, mode: int = 0o777, parents: bool=false) {
  var err: syserr = ENOERR;
  mkdir(err, name, mode, parents);
  if err != ENOERR then ioerror(err, "in mkdir", name);
}

/* Renames the file specified by oldname to newname, returning an error
   if one occurred.  The file is not opened during this operation.
   error: a syserr used to indicate if an error occurred during renaming.
   oldname: current name of the file
   newname: name which should refer to the file in the future.*/
proc rename(out error: syserr, oldname, newname: string) {
  extern proc chpl_fs_rename(oldname: c_string, newname: c_string):syserr;

  error = chpl_fs_rename(oldname.c_str(), newname.c_str());
}

/* Renames the file specified by oldname to newname, generating an error message
   if one occurred.  The file is not opened during this operation.
   oldname: current name of the file
   newname: name which should refer to the file in the future.*/
proc rename(oldname, newname: string) {
  var err:syserr = ENOERR;
  rename(err, oldname, newname);
  if err != ENOERR then ioerror(err, "in rename", oldname);
}

/* Removes the file or directory specified by name, returning an error
   if one occurred via an out parameter.
   err: a syserr used to indicate if an error occurred during removal
   name: the name of the file/directory to remove */
proc remove(out err: syserr, name: string) {
  extern proc chpl_fs_remove(name: c_string):syserr;

  err = chpl_fs_remove(name.c_str());
}

/* Removes the file or directory specified by name, generating an error message
   if one occurred.
   name: the name of the file/directory to remove */
proc remove(name: string) {
  var err:syserr = ENOERR;
  remove(err, name);
  if err != ENOERR then ioerror(err, "in remove", name);
}

/* Returns true if both pathnames refer to the same file or directory
   (utilizing operating system operations rather than string ones), returns
   false otherwise.
   err: a syserr used to indicate if an error occurred during comparison
   file1, file2: string representations of paths to be compared.
*/
proc sameFile(out err: syserr, file1: string, file2: string): bool {
  extern proc chpl_fs_samefile_string(ref ret: c_int, file1: c_string, file2: c_string): syserr;

  var ret:c_int;
  err = chpl_fs_samefile_string(ret, file1.c_str(), file2.c_str());
  return ret != 0;
}

/* Returns true if both pathnames refer to the same file or directory
   (utilizing operating system operations rather than string ones), returns
   false otherwise.  May generate an error message.
   file1, file2: string representations of paths to be compared.
*/
proc sameFile(file1: string, file2: string): bool {
  var err:syserr = ENOERR;
  var result = sameFile(err, file1, file2);
  if err != ENOERR then ioerror(err, "in sameFile " + file1, file2);
  return result;
}

/* Same as the above function, but taking file records instead of string
   pathnames as arguments.
*/
proc sameFile(out err: syserr, file1: file, file2: file): bool {
  extern proc chpl_fs_samefile(ref ret: c_int, file1: qio_file_ptr_t,
                               file2: qio_file_ptr_t): syserr;

  var ret:c_int;
  if (is_c_nil(file1._file_internal) || is_c_nil(file2._file_internal)) {
    // Implementation note on program design tradeoffs:
    // I could use file.check() here.  That would not rely on the understanding
    // of file's internals.  However, it would cause this method to either
    // return error messages or halt, depending on the error encountered.
    // This check could be moved to the version w/o the err argument, but if
    // someone called this function w/o going through that, we'd lose the
    // check.  Also, we already must make use of the record internals to do the
    // inner comparison (since the record is a chapel construct), so there's no
    // additional harm.

    // The file is referencing a null file.  We'll get a segfault if we
    // continue.
    err = EBADF;
    return false; // This part isn't as important as the error.
  }

  err = chpl_fs_samefile(ret, file1._file_internal, file2._file_internal);
  return ret != 0;
}

/* Same as the above function, but taking file records instead of string
   pathnames as arguments.  May generate an error message.
*/
proc sameFile(file1: file, file2: file): bool {
  var err:syserr = ENOERR;
  var result = sameFile(err, file1, file2);
  if err != ENOERR then ioerror(err, "in sameFile " + file1.path, file2.path);
  return result;
}

/* Create a symbolic link pointing to oldName named newName.  May generate an
   error message.
   err: a syserr used to indicate if an error occurred during creation
   oldName: the source file to be linked
   newName: the location the symbolic link should live
*/
proc symlink(out err: syserr, oldName: string, newName: string) {
  extern proc chpl_fs_symlink(orig: c_string, linkName: c_string): syserr;

  err = chpl_fs_symlink(oldName.c_str(), newName.c_str());
}

/* Create a symbolic link pointing to oldName named sym.  May generate an error
   message.
   oldName: the source file to be linked
   newName: the location the symbolic link should live
*/
proc symlink(oldName: string, newName: string) {
  var err:syserr = ENOERR;
  symlink(err, oldName, newName);
  if err != ENOERR then ioerror(err, "in symlink " + oldName, newName);
}

/* Returns an integer representing the current permissions of the file specified
   by name.  May generate an error message.
   err: a syserr used to indicate if an error occurred during this function
   name: the name of the file that you want to know the permissions of.
*/
proc viewMode(out err: syserr, name: string): int {
  extern proc chpl_fs_viewmode(ref result:c_int, name: c_string): syserr;

  var ret:c_int;
  err = chpl_fs_viewmode(ret, name.c_str());
  return ret;
}

/* Returns an integer representing the current permissions of the file specified
   by name.  May generate an error message.
   name: the name of the file that you want to know the permissions of.
*/
proc viewMode(name: string): int {
  var err:syserr = ENOERR;
  var result = viewMode(err, name);
  if err != ENOERR then ioerror(err, "in viewMode", name);
  return result;
}


Refactored assert such that its first argument
is now a bool, and did some other minor cleanup.

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

pragma "rename _chpl_fflush"
fun fflush(f: file) {
  fflush(f.fp);
}

pragma "rename _chpl_fflush_nil"
fun fflush(f: _nilType) {
  halt("***Error: called fflush on nil***");
}

pragma "rename _chpl_assert_no_args"
fun assert(test: bool) {
  if (!test) {
    _writeAssertFailed();
    if (!chpl_input_lineno) {
      fwriteln(stderr);
    }
    exit(0);
  }
}

pragma "rename _chpl_assert"
fun assert(test: bool, args ...?numArgs) {
  if (!test) {
    _writeAssertFailed();
    if (!chpl_input_lineno) {
      fwrite(stderr, ": ");
    }
    for param i in 1..numArgs {
      fwrite(stderr, args(i));
    }
    fwriteln(stderr);
    exit(0);
  }
}

fun _writeAssertFailed() {
  fflush(stdout);
  fwrite(stderr, "Assertion failed");
  if (chpl_input_lineno) {
    fwriteln(stderr, ": ", chpl_input_filename, ":", chpl_input_lineno);
  }
}

pragma "rename _chpl_halt"
fun halt(args ...?numArgs) {
  fflush(stdout);
  fwrite(stderr, "Halt reached: ");
  if (chpl_input_lineno) {
    fwriteln(stderr, chpl_input_filename, ":", chpl_input_lineno);
  }
  for param i in 1..numArgs {
    fwrite(stderr, args(i));
  }
  fwriteln(stderr);
  exit(0);
}

pragma "rename _chpl_halt_no_args"
fun halt() {
  halt("");
}

class file {
  var filename : string = "";
  var mode : string = "r";
  var path : string = ".";
  var fp : CFILEPTR = _NULLCFILEPTR;

  fun open {
    if (fp == _STDINCFILEPTR || fp == _STDOUTCFILEPTR ||
        fp == _STDERRCFILEPTR) {
      halt("***Error: It is not necessary to open \"", filename, "\"***");
    }
    if (fp != _NULLCFILEPTR) {
      this.close;
    }

    var fullFilename = path + "/" + filename;
    fp = _fopen(fullFilename, mode);

    if (fp == _NULLCFILEPTR) {
      halt("***Error: Unable to open \"", fullFilename, "\": ",
           strerror(errno), "***");
    }
  }

  fun isOpen: bool {
    var openStatus: bool = false;
    if (fp != _NULLCFILEPTR) {
      openStatus = true;
    }
    return openStatus;
  }

  fun close {
    if (fp == _STDINCFILEPTR || fp == _STDOUTCFILEPTR ||
        fp == _STDERRCFILEPTR) {
      halt("***Error: You may not close \"", filename, "\"***");
    }
    if (fp == _NULLCFILEPTR) {
      var fullFilename = path + "/" + filename;
      halt("***Error: Trying to close \"", fullFilename,
           "\" which isn't open***");
    }
    var returnVal: int = _fclose(fp);
    if (returnVal < 0) {
      var fullFilename = path + "/" + filename;
      halt("***Error: The close of \"", fullFilename, "\" failed: ",
           strerror(errno), "***");
    }
    fp = _NULLCFILEPTR;
  }
}

const stdin  : file = file("stdin", "r", "/dev", _STDINCFILEPTR);
const stdout : file = file("stdout", "w", "/dev", _STDOUTCFILEPTR);
const stderr : file = file("stderr", "w", "/dev", _STDERRCFILEPTR);

fun fopenError(f: file, isRead: bool) {
  var fullFilename:string = f.path + "/" + f.filename;
  if (isRead) {
    halt("***Error: You must open \"", fullFilename,
         "\" before trying to read from it***");
  } else {
    halt("***Error: You must open \"", fullFilename,
         "\" before trying to write to it***");
  }
}

fun fprintfError() {
  halt("***Error: Write failed: ", strerror(errno), "***");
}

fun fscanfError() {
  halt("***Error: Read failed: ", strerror(errno), "***");
}

pragma "rename _chpl_fread_int"
fun fread(f: file = stdin, inout val: int) {
  if (f.isOpen) {
    var returnVal: int = fscanf(f.fp, "%lld", val);
    if (returnVal == EOF) {
      halt("***Error: Read failed: EOF***");
    }
    if (returnVal < 0) {
      fscanfError();
    } else if (returnVal == 0) {
      halt("***Error: No int was read***");
    }
  } else {
    fopenError(f, isRead = true);
  }
}

pragma "rename _chpl_fwrite"
fun fwrite(f: file = stdout, args ...?numArgs) {
  for param i in 1..numArgs {
    fwrite(f, args(i));
  }
}

pragma "rename _chpl_fwrite_int"
fun fwrite(f: file = stdout, val: int) {
  if (f.isOpen) {
    var returnVal: int = fprintf(f.fp, "%lld", val);
    if (returnVal < 0) {
      fprintfError();
    } else if (returnVal == 0) {
      halt("***Error: No int was written***");
    }
  } else {
    fopenError(f, isRead = false);
  }
}

pragma "rename _chpl_fread_float"
fun fread(f: file = stdin, inout val: float) {
  if (f.isOpen) {
    var returnVal: int = fscanf(f.fp, "%lg", val);
    if (returnVal == EOF) {
      halt("***Error: Read failed: EOF***");
    }
    if (returnVal < 0) {
      fscanfError();
    } else if (returnVal == 0) {
      halt("***Error: No float was read***");
    }
  } else {
    fopenError(f, isRead = true);
  }
}

pragma "rename _chpl_fwrite_float"
fun fwrite(f: file = stdout, val: float) {
  if (f.isOpen) {
     _chpl_fwrite_float_help(f.fp, val);
  } else {
    fopenError(f, isRead = false);
  }
}

pragma "rename _chpl_fread_string"
fun fread(f: file = stdin, inout val: string) {
  if (f.isOpen) {
    _chpl_fread_string_help(f.fp, val);
  } else {
    fopenError(f, isRead = true);
  }
}

pragma "rename _chpl_fwrite_string"
fun fwrite(f: file = stdout, val: string) {
  if (f.isOpen) {
    var returnVal: int = fprintf(f.fp, "%s", val);
    if (returnVal < 0) {
      fprintfError();
    }
  } else {
    fopenError(f, isRead = false);
  }
}

pragma "rename _chpl_fread_bool"
fun fread(f: file = stdin, inout val: bool) {
  if (f.isOpen) {
    var valString : string;
    _chpl_fread_string_help(f.fp, valString);
    if (valString == "true") {
      val = true;
    } else if (valString == "false") {
      val = false;
    } else {
      halt("***Error: Not of bool type***");
    }
  } else {
    fopenError(f, isRead = true);
  }
}

pragma "rename _chpl_fwrite_bool"
fun fwrite(f: file = stdout, val: bool) {
  if (f.isOpen) {
    var returnVal: int;
    if (val == true) {
      returnVal = fprintf(f.fp, "%s", "true");
    } else {
      returnVal = fprintf(f.fp, "%s", "false");
    }
    if (returnVal < 0) {
      fprintfError();
    }
  } else {
    fopenError(f, isRead = false);
  }
}

pragma "rename _chpl_fwriteln"
fun fwriteln(f: file, args ...?numArgs) {
  for param i in 1..numArgs {
    fwrite(f, args(i));
  }
  fwriteln(f);
}

pragma "rename _chpl_fwriteln_no_args"
fun fwriteln(f: file = stdout) {
  fwrite(f, "\n");
}

pragma "rename _chpl_fwrite_nil"
fun fwrite(f: file = stdout, x : _nilType) : void {
  if (f.isOpen) {
    var returnVal: int = fprintf(f.fp, "%s", "nil");
    if (returnVal < 0) {
      fprintfError();
    }
  } else {
    fopenError(f, isRead = false);
  }
}

pragma "rename _chpl_write"
fun write(args ...?numArgs) {
  for param i in 1..numArgs {
    fwrite(stdout, args(i));
  }
}

pragma "rename _chpl_writeln"
fun writeln(args ...?numArgs) {
  for param i in 1..numArgs {
    fwrite(stdout, args(i));
  }
  fwriteln(stdout);
}

pragma "rename _chpl_writeln_no_args"
fun writeln() {
  fwriteln(stdout);
}

fun read() {
  halt("***Error: This should never be called.  All read calls should be converted to freads***");
}

pragma "rename _chpl_fflush"
fun fflush(f: file) {
  fflush(f.fp);
}

pragma "rename _chpl_fflush_nil"
fun fflush(f: _nilType) {
  halt("***Error: called fflush on nil***");
}

pragma "rename _chpl_assert"
fun assert(args ...?numArgs) {
  fflush(stdout);
  if (!args(1)) {
    fwrite(stderr, "Assertion failed: ");
    if (chpl_input_lineno) {
      fwriteln(stderr, chpl_input_filename, ":", chpl_input_lineno);
    } else {
      if (numArgs == 1) {
        fwriteln(stderr);
      }
    }
    if (numArgs > 1) {
      for param i in 2..numArgs {
        fwrite(stderr, args(i));
      }
      fwriteln(stderr);
    }
    exit(0);
  }
}

pragma "rename _chpl_halt"
fun halt(args ...?numArgs) {
  fflush(stdout);
  fwrite(stderr, "Halt reached: ");
  if (chpl_input_lineno) {
    fwriteln(stderr, chpl_input_filename, ":", chpl_input_lineno);
  }
  for param i in 1..numArgs {
    fwrite(stderr, args(i));
  }
  fwriteln(stderr);
  exit(0);
}

pragma "rename _chpl_halt_no_args"
fun halt() {
  halt("");
}

class file {
  var filename : string = "";
  var mode : string = "r";
  var path : string = ".";
  var fp : CFILEPTR = _NULLCFILEPTR;

  fun open {
    if (fp == _STDINCFILEPTR || fp == _STDOUTCFILEPTR ||
        fp == _STDERRCFILEPTR) {
      halt("***Error: It is not necessary to open \"", filename, "\"***");
    }
    if (fp != _NULLCFILEPTR) {
      this.close;
    }

    var fullFilename = path + "/" + filename;
    fp = _fopen(fullFilename, mode);

    if (fp == _NULLCFILEPTR) {
      halt("***Error: Unable to open \"", fullFilename, "\": ",
           strerror(errno), "***");
    }
  }

  fun isOpen: bool {
    var openStatus: bool = false;
    if (fp != _NULLCFILEPTR) {
      openStatus = true;
    }
    return openStatus;
  }

  fun close {
    if (fp == _STDINCFILEPTR || fp == _STDOUTCFILEPTR ||
        fp == _STDERRCFILEPTR) {
      halt("***Error: You may not close \"", filename, "\"***");
    }
    if (fp == _NULLCFILEPTR) {
      var fullFilename = path + "/" + filename;
      halt("***Error: Trying to close \"", fullFilename,
           "\" which isn't open***");
    }
    var returnVal: int = _fclose(fp);
    if (returnVal < 0) {
      var fullFilename = path + "/" + filename;
      halt("***Error: The close of \"", fullFilename, "\" failed: ",
           strerror(errno), "***");
    }
    fp = _NULLCFILEPTR;
  }
}

const stdin  : file = file("stdin", "r", "/dev", _STDINCFILEPTR);
const stdout : file = file("stdout", "w", "/dev", _STDOUTCFILEPTR);
const stderr : file = file("stderr", "w", "/dev", _STDERRCFILEPTR);

fun fopenError(f: file, isRead: bool) {
  var fullFilename:string = f.path + "/" + f.filename;
  if (isRead) {
    halt("***Error: You must open \"", fullFilename,
         "\" before trying to read from it***");
  } else {
    halt("***Error: You must open \"", fullFilename,
         "\" before trying to write to it***");
  }
}

fun fprintfError() {
  halt("***Error: Write failed: ", strerror(errno), "***");
}

fun fscanfError() {
  halt("***Error: Read failed: ", strerror(errno), "***");
}

pragma "rename _chpl_fread_int"
fun fread(f: file = stdin, inout val: int) {
  if (f.isOpen) {
    var returnVal: int = fscanf(f.fp, "%lld", val);
    if (returnVal == EOF) {
      halt("***Error: Read failed: EOF***");
    }
    if (returnVal < 0) {
      fscanfError();
    } else if (returnVal == 0) {
      halt("***Error: No int was read***");
    }
  } else {
    fopenError(f, isRead = true);
  }
}

pragma "rename _chpl_fwrite"
fun fwrite(f: file = stdout, args ...?numArgs) {
  for param i in 1..numArgs {
    fwrite(f, args(i));
  }
}

pragma "rename _chpl_fwrite_int"
fun fwrite(f: file = stdout, val: int) {
  if (f.isOpen) {
    var returnVal: int = fprintf(f.fp, "%lld", val);
    if (returnVal < 0) {
      fprintfError();
    } else if (returnVal == 0) {
      halt("***Error: No int was written***");
    }
  } else {
    fopenError(f, isRead = false);
  }
}

pragma "rename _chpl_fread_float"
fun fread(f: file = stdin, inout val: float) {
  if (f.isOpen) {
    var returnVal: int = fscanf(f.fp, "%lg", val);
    if (returnVal == EOF) {
      halt("***Error: Read failed: EOF***");
    }
    if (returnVal < 0) {
      fscanfError();
    } else if (returnVal == 0) {
      halt("***Error: No float was read***");
    }
  } else {
    fopenError(f, isRead = true);
  }
}

pragma "rename _chpl_fwrite_float"
fun fwrite(f: file = stdout, val: float) {
  if (f.isOpen) {
     _chpl_fwrite_float_help(f.fp, val);
  } else {
    fopenError(f, isRead = false);
  }
}

pragma "rename _chpl_fread_string"
fun fread(f: file = stdin, inout val: string) {
  if (f.isOpen) {
    _chpl_fread_string_help(f.fp, val);
  } else {
    fopenError(f, isRead = true);
  }
}

pragma "rename _chpl_fwrite_string"
fun fwrite(f: file = stdout, val: string) {
  if (f.isOpen) {
    var returnVal: int = fprintf(f.fp, "%s", val);
    if (returnVal < 0) {
      fprintfError();
    }
  } else {
    fopenError(f, isRead = false);
  }
}

pragma "rename _chpl_fread_bool"
fun fread(f: file = stdin, inout val: bool) {
  if (f.isOpen) {
    var valString : string;
    _chpl_fread_string_help(f.fp, valString);
    if (valString == "true") {
      val = true;
    } else if (valString == "false") {
      val = false;
    } else {
      halt("***Error: Not of bool type***");
    }
  } else {
    fopenError(f, isRead = true);
  }
}

pragma "rename _chpl_fwrite_bool"
fun fwrite(f: file = stdout, val: bool) {
  if (f.isOpen) {
    var returnVal: int;
    if (val == true) {
      returnVal = fprintf(f.fp, "%s", "true");
    } else {
      returnVal = fprintf(f.fp, "%s", "false");
    }
    if (returnVal < 0) {
      fprintfError();
    }
  } else {
    fopenError(f, isRead = false);
  }
}

pragma "rename _chpl_fwriteln"
fun fwriteln(f: file, args ...?numArgs) {
  for param i in 1..numArgs {
    fwrite(f, args(i));
  }
  fwriteln(f);
}

pragma "rename _chpl_fwriteln_no_args"
fun fwriteln(f: file = stdout) {
  fwrite(f, "\n");
}

pragma "rename _chpl_fwrite_nil"
fun fwrite(f: file = stdout, x : _nilType) : void {
  if (f.isOpen) {
    var returnVal: int = fprintf(f.fp, "%s", "nil");
    if (returnVal < 0) {
      fprintfError();
    }
  } else {
    fopenError(f, isRead = false);
  }
}

pragma "rename _chpl_write"
fun write(args ...?numArgs) {
  for param i in 1..numArgs {
    fwrite(stdout, args(i));
  }
}

pragma "rename _chpl_writeln"
fun writeln(args ...?numArgs) {
  for param i in 1..numArgs {
    fwrite(stdout, args(i));
  }
  fwriteln(stdout);
}

pragma "rename _chpl_writeln_no_args"
fun writeln() {
  fwriteln(stdout);
}

fun read() {
  halt("***Error: This should never be called.  All read calls should be converted to freads***");
}


forward is now operable, need to do backwards typo

/*
 Is this a package level comment?

 Algorithm 6.3 (Feed Forward) from Bengio, et al.
 Require: Network depth l
 Require: W for 1...l
 Require: b for 1...l
 Require: x, input data
 Require: y, target output (e.g. labels)
 h_0 = x  // dims batchSize x observationDim
 for k = 1..l do
  a_k = b_k + W_k h_{k-1}  // Notice, this is equiv to adding b to each ROW
                           // W_k units_k x batchSize
                           // b_k 1 x units_k
  h_k = f(a_k) // f = activation function
 end for
 y = h_l
 J = L(y,y) + lambda * Omega(theta)  // described later

 */
module NNModels {
  //use MLP;
  use LinearAlgebra;
  use Random;

  class Sequential {
    var epochs: int,
        lr: real;

    var layerDom: domain(1) = {1..0}; // Is now 1 long
    var layers: [layerDom] Layer;
    var needSomeLayers = true;
    proc add(d: Dense) {
      // Is it the first?
      var curidx = layerDom.size;
      writeln("curidx ", curidx);
      // layerDom has a default size of 1, even if empty
      // So curidx will be 0 twice

      if curidx == 0 {
        layerDom = {1..#layerDom.size+1};
        writeln("  Very first dense layer %i".format(layerDom.size));
        var l = new Layer(dataLayer=d
          , inputDim=d.inputDim
          , units=d.units
          , batchSize=d.batchSize
          , layerId=curidx
          , activation = new Activation(name="linear")
          );
        layers[layerDom.size] = l;
        layers[layerDom.size].summarize();
      } else {
        layerDom = {1..#layerDom.size+1};
        writeln(" Adding Dense layer %i".format(layerDom.size));
        var l = new Layer(dataLayer=d
          , inputDim=layers[layerDom.size-1].inputDim
          , units=d.units
          , batchSize=layers[layerDom.size-1].batchSize
          , layerId=curidx
          , activation=new Activation(name="linear"));
        layers[layerDom.size] = l;
        layers[layerDom.size].summarize();
      }
    }
    /*
     Must be added sequentially
     */
    proc add(a: Activation) {
      writeln("  Adding activation to layer %i".format(layerDom.size));
      layers[layerDom.size].activation = a;
    }

    /*
     */
    proc fit(xTrain:[], yTrain:[], epochs:int, lr: real) {
      for l in layerDom {
        if l == 1 {
          writeln(" layers[l].h domain " , layers[l].h.domain);
          layers[l].h = xTrain.T;
          continue;
        }
        writeln("Prepping layer %i".format(l));
        fillRandom(layers[l].W);
        fillRandom(layers[l].b);
        writeln(layers[l].bias.domain.dim(2));
        for i in layers[l].bias.domain.dim(2) {
          layers[l].bias[..,i] = layers[l].b;
        }

      }
      for e in 1..#epochs {
          writeln("epoch %i".format(e));
          writeln("Going FORWARD");
          for l in layerDom {
            if l == 1 {
              continue;
            }
            //writeln("%i: I need to \n\t1. multiply W_t, h_t-1, W_t\n\t2. Add the bias to each row\n\t3. Apply the activation function".format(l));
            // a = b + wh_{l-1}
            layers[l].a = matPlus(layers[l].bias, dot(layers[l].W, layers[l-1].h));
            layers[l].h = layers[l].activation.sigmoid(layers[l].a);
          }
          writeln("Going BACKWARD");
      }
      return 0;
    }
  }

  /*
  inputDim: dimension of the input
  units: dimension of the output
   */
  class Layer {
    var dataLayer: Dense,
        activation: Activation,
        batchSize: int,
        inputDim: int,
        units: int,
        layerId: int,
        W: [{0..#units, 0..#inputDim}] real,
        a: [{0..#units, 0..#batchSize}] real,
        h: [{0..#inputDim, 0..#batchSize}] real,
        b: [{0..#units}] real, // The single column of bias
        bias: [{0..#units, 0..#batchSize}] real; // bias = [b,b,..]

        proc summarize() {
          writeln("\tinputDim: %i\n\tunits: %i\n\tbatchSize: %i".format(inputDim,units, batchSize ));
          writeln("\tW size ", W.shape);
          writeln("\th size ", h.shape);
          writeln("\ta size ", a.shape);
          writeln("\tb size ", b.shape);
          writeln("\tbias size ", bias.shape);
        }
  }

  /*
   Holds a layer of data.
   */
  class Dense {
    var units: int,  // dimension of the output space, to be fed to the next layer
        inputDim: int,  // Size of the vectors being considered
        batchSize: int;  // How many vectors are being sent it
  }

  class Activation {
    var name: string;

    /* @TODO abstract function and derivative to f() and df() */
    proc sigmoid(x: real) {
      return (1/(1 + exp(-x)));
    }

    proc derivativesSigmoid(x) {
      return x * (1-x);
    }
  }
}

/*
 Is this a package level comment?

 Algorithm 6.3 (Feed Forward) from Bengio, et al.
 Require: Network depth l
 Require: W for 1...l
 Require: b for 1...l
 Require: x, input data
 Require: y, target output (e.g. labels)
 h_0 = x  // dims batchSize x observationDim
 for k = 1..l do
  a_k = b_k + W_k h_{k-1}  // Notice, this is equiv to adding b to each ROW
                           // W_k units_k x batchSize
                           // b_k 1 x units_k
  h_k = f(a_k) // f = activation function
 end for
 y = h_l
 J = L(y,y) + lambda * Omega(theta)  // described later

 */
module NNModels {
  //use MLP;
  use LinearAlgebra;
  use Random;

  class Sequential {
    var epochs: int,
        lr: real;

    var layerDom: domain(1) = {1..0}; // Is now 1 long
    var layers: [layerDom] Layer;
    var needSomeLayers = true;
    proc add(d: Dense) {
      // Is it the first?
      var curidx = layerDom.size;
      writeln("curidx ", curidx);
      // layerDom has a default size of 1, even if empty
      // So curidx will be 0 twice

      if curidx == 0 {
        layerDom = {1..#layerDom.size+1};
        writeln("  Very first dense layer %i".format(layerDom.size));
        var l = new Layer(dataLayer=d
          , inputDim=d.inputDim
          , units=d.units
          , batchSize=d.batchSize
          , layerId=curidx
          , activation = new Activation(name="linear")
          );
        layers[layerDom.size] = l;
        layers[layerDom.size].summarize();
      } else {
        layerDom = {1..#layerDom.size+1};
        writeln(" Adding Dense layer %i".format(layerDom.size));
        var l = new Layer(dataLayer=d
          , inputDim=layers[layerDom.size-1].inputDim
          , units=d.units
          , batchSize=layers[layerDom.size-1].batchSize
          , layerId=curidx
          , activation=new Activation(name="linear"));
        layers[layerDom.size] = l;
        layers[layerDom.size].summarize();
      }
    }
    /*
     Must be added sequentially
     */
    proc add(a: Activation) {
      writeln("  Adding activation to layer %i".format(layerDom.size));
      layers[layerDom.size].activation = a;
    }

    /*
     */
    proc fit(xTrain:[], yTrain:[], epochs:int, lr: real) {
      for l in layerDom {
        if l == 1 {
          writeln(" layers[l].h domain " , layers[l].h.domain);
          layers[l].h = xTrain.T;
          continue;
        }
        writeln("Prepping layer %i".format(l));
        fillRandom(layers[l].W);
        fillRandom(layers[l].b);
        writeln(layers[l].bias.domain.dim(2));
        for i in layers[l].bias.domain.dim(2) {
          layers[l].bias[..,i] = layers[l].b;
        }

      }
      for e in 1..#epochs {
          writeln("epoch %i".format(e));
          writeln("Going FORWARD");
          for l in layerDom {
            if l == 1 {
              continue;
            }
            //writeln("%i: I need to \n\t1. multiply W_t, h_t-1, W_t\n\t2. Add the bias to each row\n\t3. Apply the activation function".format(l));
            // a = b + wh_{l-1}
            layers[l].a = matPlus(layers[l].bias, dot(layers[l].W, layers[l-1].h));
            layers[l].h = layers[l].activation.sigmoid(layers[l].a);
          }
          writln("Going BACKWARD");
      }
      return 0;
    }
  }

  /*
  inputDim: dimension of the input
  units: dimension of the output
   */
  class Layer {
    var dataLayer: Dense,
        activation: Activation,
        batchSize: int,
        inputDim: int,
        units: int,
        layerId: int,
        W: [{0..#units, 0..#inputDim}] real,
        a: [{0..#units, 0..#batchSize}] real,
        h: [{0..#inputDim, 0..#batchSize}] real,
        b: [{0..#units}] real, // The single column of bias
        bias: [{0..#units, 0..#batchSize}] real; // bias = [b,b,..]

        proc summarize() {
          writeln("\tinputDim: %i\n\tunits: %i\n\tbatchSize: %i".format(inputDim,units, batchSize ));
          writeln("\tW size ", W.shape);
          writeln("\th size ", h.shape);
          writeln("\ta size ", a.shape);
          writeln("\tb size ", b.shape);
          writeln("\tbias size ", bias.shape);
        }
  }

  /*
   Holds a layer of data.
   */
  class Dense {
    var units: int,  // dimension of the output space, to be fed to the next layer
        inputDim: int,  // Size of the vectors being considered
        batchSize: int;  // How many vectors are being sent it
  }

  class Activation {
    var name: string;

    /* @TODO abstract function and derivative to f() and df() */
    proc sigmoid(x: real) {
      return (1/(1 + exp(-x)));
    }

    proc derivativesSigmoid(x) {
      return x * (1-x);
    }
  }
}


Refactoring module initialization

Refactoring module initialization

Based on feedback from Nikhil and Thomas, adding a config param
that permits a user to avoid FFTW_MT auto-initialization and
put the code that does the initialization into a routine so
that it can be invoked manually (this also has the nice effect
of cleaning up module initialization.

Noted the TODO that Nikhil and I discussed, but decided to wait
on based on user feedback, to make these routines methods on
locales if the "on all locales" nature is offensive to people.

Made the plan_with_nthreads() routine accept a sentinel value
by default that will cause it to use here.maxTaskPar on each
locale.  This is better than the old approach that assumed
that the value for locale #0 made sense for other locales as
well.

/*
 * Copyright 2004-2015 Cray Inc.
 * Other additional copyright holders may be indicated within.
 *
 * The entirety of this work is licensed under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License.
 *
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Wrapper for multithreaded FFTW version 3.

/*
  Support for key multithreaded FFTW version 3 routines in Chapel trappings

  This module defines a Chapel wrapper around key routines from the
  multithreaded support of FFTW version 3.  This module builds on the
  FFTW module itself and inherits all of its functionality, so refer there
  for most of the documentation.

  The main differences in using this module are:

  0) By default, the module will initialize itself to use
     here.maxTaskPar threads per locale when calling plan_dft*()
     routines.  To override this default or change it during program
     execution, call plan_with_nthreads() manually.

  1-3) As in FFTW

  3a) Call cleanup_threads() to release memory used by the FFTW threads.

  Note that whereas the main FFTW.chpl module is a single-locale
  module, this module is multi-locale in that, once it is loaded, any
  locale can create and execute plans, though those plans will only be
  valid on that locale.  Calls to plan_with_nthreads() and
  cleanup_threads() will make those actions occur on all locales.

  At present, clients of the FFTW module must specify fftw3.h,
  -lfftw3, -lfftw3_threads on the command line with the appropriate -I
  and -L flags sufficient to find the files.
*/

module FFTW_MT {
  use FFTW;

  /*
    By default, this module will call init_FFTW_MT() and
    plan_with_nthreads() as part of its initialization.  Users can
    diable this by setting autoInitFFTW_MT at compile-time, in which
    case, they will need to make the calls manually.
  */
  config param autoInitFFTW_MT = true;

  if autoInitFFTW_MT {
    //
    // If we're auto-initializing, when this module is used, it
    // calls init_FFTW_MT() and plan_with_nthreads().
    //
    init_FFTW_MT();
    plan_with_nthreads();
  }

  //
  // TODO: If users don't like the "on all locales" property, we
  // could consider making this a method on locale later to permit
  // either calling here.init_FFTW_MT or Locales.init_FFTW_MT...
  //
  // TODO: Incorporate a better error handling story
  //
  /*
    Initialize the FFTW_MT module.  This has the effect of calling
    fftw_init_threads() on all locales.  This routine halts the Chapel
    program if any of the calls generate an error.
  */
  proc init_FFTW_MT() {
    coforall loc in Locales {
      on loc do {
        if (C_FFTW.fftw_init_threads() == 0) then
          halt("Failed to properly initialize FFTW threads on locale ",
               here.id);
      }
    }
  }

  /*
    Registers the number of threads to use for multi-threaded FFTW
    plans on all locales.  If fewer than one thread is requested, each
    locale will default to here.maxTaskPar threads.

    :arg nthreads: The number of threads to use.
    :type nthreads: int
  */
  proc plan_with_nthreads(nthreads: int = 0) {
    coforall loc in Locales {
      on loc do {
        const myNThreads = if nthreads < 1 then here.maxTaskPar else nthreads;
        C_FFTW.fftw_plan_with_nthreads(myNThreads.safeCast(c_int));
      }
    }
  }

  /*
    Cleans up the threads if multi-threaded FFTW's were enabled using
    :proc:`init_threads()`.
  */
  proc cleanup_threads() {
    coforall loc in Locales {
      on loc do {
        C_FFTW.fftw_cleanup_threads();
      }
    }
  }
}

/*
 * Copyright 2004-2015 Cray Inc.
 * Other additional copyright holders may be indicated within.
 *
 * The entirety of this work is licensed under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License.
 *
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Wrapper for multithreaded FFTW version 3.

/*
  Support for key multithreaded FFTW version 3 routines in Chapel trappings

  This module defines a Chapel wrapper around key routines from the
  multithreaded support of FFTW version 3.  This module builds on the
  FFTW module itself and inherits all of its functionality, so refer there
  for most of the documentation.

  The main differences in using this module are:

  0) By default, the module will initialize itself to use
     here.maxTaskPar threads per locale when calling plan_dft*()
     routines.  To override this default or change it during program
     execution, call plan_with_nthreads() manually.

  1-3) As in FFTW

  3a) Call cleanup_threads() to release memory used by the FFTW threads.

  Note that whereas the main FFTW.chpl module is a single-locale
  module, this module is multi-locale in that, once it is loaded, any
  locale can create and execute plans, though those plans will only be
  valid on that locale.  Calls to plan_with_nthreads() and
  cleanup_threads() will make those actions occur on all locales.

  At present, clients of the FFTW module must specify fftw3.h,
  -lfftw3, -lfftw3_threads on the command line with the appropriate -I
  and -L flags sufficient to find the files.
*/

module FFTW_MT {
  use FFTW;

  //
  // If this module is used, it should init_threads() upon start-up on
  // all locales and request here.maxTaskPar threads by default.
  //
  coforall loc in Locales {
    on loc do {
      if (C_FFTW.fftw_init_threads() == 0) then
        halt("Failed to properly initialize FFTW threads on locale ", here.id);
    }
  }
  plan_with_nthreads(here.maxTaskPar);

  /*
    Registers the number of threads to use for multi-threaded FFTW
    plans on all locales.

    :arg nthreads: The number of threads to use.
    :type nthreads: int
   */
  proc plan_with_nthreads(nthreads: int) {
    coforall loc in Locales {
      on loc do {
        C_FFTW.fftw_plan_with_nthreads(nthreads.safeCast(c_int));
      }
    }
  }

  /*
    Cleans up the threads if multi-threaded FFTW's were enabled using
    :proc:`init_threads()`.
  */
  proc cleanup_threads() {
    coforall loc in Locales {
      on loc do {
        C_FFTW.fftw_cleanup_threads();
      }
    }
  }
}