############################################################################
#
# File: io.icn
#
# Subject: Procedures for input and output
#
# Author: Ralph E. Griswold, tweaks by Kostas Oikonomou
#
# Date: August 2, 2012
#
############################################################################
#
# This file is in the public domain.
#
############################################################################
#
# Contributors: Paul Abrahams, Bob Alexander, Will Evans, David A. Gamey,
# Richard L. Goerwitz, Will Menagarini, Charles Sharstis,
# Gregg Townsend, and Jafar Al-Gharaibeh.
#
############################################################################
#
# They provide facilities for handling input, output, and files.
#
# There are other modules in the Icon program library that deal with
# input and output. They are not included here because they conflict
# with procedures here or each other.
#
############################################################################
#
# Requires: Appropriate operating system for procedures used. Some
# require loadfunc().
#
############################################################################
#
# Links: random, strings
#
############################################################################
#
# File copying:
#
# fcopy(fn1, fn2) copies a file named fn1 to file named fn2.
#
############################################################################
#
# File existence:
#
# exists(name) succeeds if name exists as a file but fails
# otherwise.
#
# directory(name) succeeds if name exists as a directory
# but fails otherwise.
#
############################################################################
#
# File lists:
#
# filelist(s,x) returns a list of the file names that match the
# specification s. If x is nonnull, any direcory
# is stripped off. At present it only works for
# UNIX. Users of other platforms are invited to add
# code for their platforms.
#
############################################################################
#
# Reading and writing files:
#
# filetext(f) reads the lines of f into a list and returns that
# list
#
# readline(file) assembles backslash-continued lines from the specified
# file into a single line. If the last line in a file
# ends in a backslash, that character is included in the
# last line read.
#
# splitline(file, line, limit)
# splits line into pieces at first blank after
# the limit, appending a backslash to identify split
# lines (if a line ends in a backslash already, that's
# too bad). The pieces are written to the specified file.
#
############################################################################
#
# Buffered input and output:
#
# ClearOut() remove contents of output buffer without writing
# Flush() flush output buffer
# GetBack() get back line writen
# LookAhead() look ahead at next line
# PutBack(s) put back a line
# Read() read a line
# ReadAhead(n) read ahead n lines
# Write(s) write a line
#
############################################################################
#
# Path searching:
#
# dopen(s) opens and returns the file s on DPATH.
#
# dpath(s) returns the path to s on DPATH.
#
# Both fail if the file is not found.
# isabspath(fname)
# succeeds if fname is a file/dir name with an absolute path and
# returns fname in that case. Otherwise it fails.
#
# pathfind(fname, path)
# returns the full path of fname if found along the list of
# directories in "path", else fails. If no path is given,
# getenv("DPATH") || getenv("PATH") is the default. As is
# customary in Icon path searching, "." is prepended to the path.
#
# pathload(fname, entry)
# calls loadfunc() to load entry from the file fname found on the
# function path. If the file or entry point cannot be found, the
# program is aborted. The function path consists of the current
# directory, then getenv("FPATH"), to which iconx automatically
# appends the directory containing the standard libcfunc.so file.
#
############################################################################
#
# Parsing file names:
#
# suffix() parses a hierarchical file name, returning a 2-element
# list: [prefix,suffix]. E.g. suffix("/a/b/c.d") ->
# ["/a/b/c","d"]
#
# tail() parses a hierarchical file name, returning a 2-element
# list: [head,tail]. E.g. tail("/a/b/c.d") ->
# ["/a/b","c.d"].
#
# components() parses a hierarchical file name, returning a list of
# all directory names in the file path, with the file
# name (tail) as the last element. For example,
# components("/a/b/c.d") -> ["/","a","b","c.d"].
#
############################################################################
#
# Temporary files:
#
# tempfile(prefix, suffix, path, len)
# produces a "temporary" file that can be written. The name
# is chosen so as not to overwrite an existing file.
# The prefix and suffix are prepended and appended, respectively,
# to a randomly chosen number. They default to the empty
# string. The path is prepended to the file name; its default
# is "." The randomly chosen number is fit into a field of len
# (default 8) by truncation or right filling with zeros as
# necessary.
#
# It is the user's responsibility to remove the file when it is
# no longer needed.
#
# tempname(prefix, suffix, path, len)
# produces the name of a temporary file.
#
############################################################################
#
# DOS helpers:
#
# dosdir(diropts) generates records of type dirinfo for each file
# found in the directory, failing when no more files
# are available, as in
#
# every dirent := dosdir("*.*") do ....
#
# known problems:
#
# When used to traverse directories and sub-directories in nested every
# loops it doesn't work as expected - requires further investigation.
# Bypass by building lists of the subdirectories to be traversed.
#
# dosdirlist( dpath, dpart2, infotab )
# returns a list containing the qualified file names for files
# in dpath and matching file patterns and/or options specified
# in dpart2. For example,
#
# dirlist := dosdirlist( "..", "*.* /o:n /a:r-d" )
#
# returns a list of all read-only-non-directory files in the
# parent directory on a MS-DOS compatible system.
#
# If the optional infotab is specified,
#
# (1) it must be a table or a run time error will result
# (2) the contents of the table will be updated as follows
# a dirinfo record will be created for each filename
# (3) the filename will be the key to the table
#
# For example,
#
# t := table()
# dirlist := dosdirlist( "..", "*.* /o:n /a:r-d", t )
# maxsize := 0 ; every maxsize <:= t[!dirlist].size
#
# calculates the maximum size of the files.
#
# dosfiles(pfn) accepts a DOS filename possibly containing wildcards.
# The filename can also include a drive letter and path.
# If the filename ends in "\" or ":", "*.*" is appended.
# The result sequence is a sequence of the filenames
# corresponding to pfn.
#
# dosname(s) converts a file name by truncating to the
# MS-DOS 8.3 format. Forward slashes are converted
# to backslashes and all letters are converted to
# lower case.
#
# Every disk drive on a MS-DOS system has a "working directory", which is
# the directory referred to by any references to that drive that don't begin
# with a backslash (& so are either direct references to that working
# directory, or paths relative to it). There is also 1 "current drive", &
# its working directory is called the "current working directory". Any paths
# that don't explicitly specify a drive refer to the current drive. For
# example, "name.ext" refers to the current drive's working directory, aka
# the current working directory; "\name.ext" refers to the current drive's
# root directory; & "d:name.ext" refers to the working directory on d:.
#
# It's reasonable to want to inquire any of these values. The CD command
# displays both, in the form of a complete path to the current working
# directory. However, passing such a path to either CD or the Icon function
# chdir() doesn't change to that dir on that drive; it changes that drive's
# working directory to the specified path without changing the current
# drive. The command to change the current drive is the system() function
# of a command consisting of just the drive letter followed by ":".
#
# This affects the design of inquiry functions. They could be implemented
# with system( "CD >" || ( name := tempname() ) ) & read(open(name)), but
# because this requires a slow disk access (which could fail on a full disk)
# it's unacceptable to need to do that *twice*, once for the drive & again
# for the dir; so if that strategy were used, it'd be necessary to return a
# structure containing the current drive & the working directory. That
# structure, whether table, list, or string, would then need to be either
# indexed or string-scanned to get the individual values, making the code
# cumbersome & obscure. It's much better to have 2 separate inquiry
# functions, 1 for each value; but for this to be acceptably efficient, it's
# necessary to forgo the disk access & implement the functions with
# interrupts.
#
# getdrive() returns the current drive as a lowercase string with
# the ":".
#
# getwd("g")
# getwd("g:") return the working directory on drive g:, or
# fail if g: doesn't exist. getwd() returns the current
# working directory. getwd(...) always returns
# lowercase. It prepends the relevant drive letter
# with its colon; that's harmless in a chdir(), & useful
# in an open().
#
# DOS_FileParts(s) takes a DOS file name and returns
# a record containing various representations of
# the file name and its components. The name
# given is returned in the fullname field.
# Fields that cannot be determined are returned
# as empty strings.
#
############################################################################
link random
link strings
link getpaths
$ifdef _MS_WINDOWS_NT
$define PS "\\"
$else
$define PS "/"
$endif
global buffer_in, buffer_out, Eof
record _DOS_FileParts_(fullname,devpath,device,path,name,extension)
record dirinfo( name, ext, size, date, time )
procedure ClearOut() #: remove contents of output buffer
buffer_out := []
end
procedure DOS_FileParts(filename) #: parse DOSfile name
local dev, path, name, ext, p, d
filename ? {
dev := 1( tab( upto(':') ), move(1) ) | ""
d := &pos - 1
tab(0)
} ? {
p := 1
path := tab( ( every p := upto('\\') + 1 ) | p )
tab(0)
} ? {
name := 1( tab( upto('.') ), move(1) ) | tab(0)
ext := tab(0)
}
return _DOS_FileParts_(filename,filename[1:d + p],dev,path,name,ext)
end
procedure Flush(f) #: flush output buffer
/f := &output
while write(f,pull(buffer_out))
return
end
procedure GetBack() #: get back line written
return get(buffer_out)
end
procedure LookAhead() #: look at next line
return buffer_in[1]
end
procedure PutBack(s) #: put back line read
push(buffer_in,s)
return
end
procedure Read(f) #: read a line in buffered mode
initial{
buffer_in := []
}
/f := &input
if *buffer_in = 0 then
put(buffer_in,read(f)) | (Eof := 1)
return get(buffer_in)
end
procedure ReadAhead(f, n) #: read ahead
if integer(f) & /n then f :=: n
/f := &input
while *buffer_in < n do
put(buffer_in,read()) | {
Eof := 1
fail
}
return
end
procedure Write(s) #: write in buffered mode
initial buffer_out := []
push(buffer_out,s)
return s
end
procedure components(s,separator) #: get components of file name
local x,head
/separator := "/"
x := tail(s,separator)
return case head := x[1] of {
separator: [separator]
"": []
# C. Shartsis: 4/23/95 - fix for MS-DOS
# default: components(head)
default: components(head, separator)
} ||| ([&null ~=== x[2]] | [])
end
procedure dopen(s) #: open file on DPATH
local file, paths, path, binpath, f
if file := open(s) then return file # look in current directory
paths := getenv("DPATH") | "."
if (binpath := (&features ? (="Binaries at " & tab(0)))) |
((f := open((binpath:=getpaths()) ||
("iconx"|"iconx.exe"|"wiconx.exe")))) then {
close(\f)
paths ||:= " " || binpath || "../ipl/gdata " ||
binpath || "../ipl/mdata " || binpath || "../ipl/data"
}
s := "/" || s # platform-specific
paths ? {
while path := tab(upto(' ') | 0) do {
if file := open(path || s) then return file
tab(many(' ')) | break
}
}
fail
end
procedure dosdir( diropts ) #: process DOS directory
local de, line
static tempfn, tempf, dosdir_ver
initial {
close(open(tempfn := tempname(),"w"))
system("ver > " || tempfn)
(tempf := open(tempfn,"r")) |
stop("Unable to open ",tempfn," from dosdir.")
while line := read(tempf) do
if find("MS-DOS",line) then
if find("6.20",line) then
dosdir_ver := dosdir_62
else
dosdir_ver := dosdir_xx
close(tempf)
system("erase " || tempfn)
}
close(open(tempfn := tempname(),"w")) # ensure useable file
system("dir " || diropts || " > " || tempfn) # get dir
tempf := open(tempfn,"r") # open file
while line := map(read(tempf)) do {
line ?
if de := dosdir_ver() then
suspend de
else
next
}
close(tempf)
system("erase " || tempfn)
end
procedure dosdir_62()
static nb
local de
initial nb := ~' '
if *&subject = 43 & (tab(any(nb)), move(-1)) then {
de := dirinfo()
(de.name := trim(move(8)), move(1),
de.ext := trim(move(3)), move(1),
de.size := move(13), move(1),
de.date := move(8), move(2),
de.time := tab(0))
every de.size ?:= 1(tab(upto(',')),move(1)) || tab(0)
return de
}
end
procedure dosdir_xx()
static nb
local de
initial nb := ~' '
if *&subject = 39 & (tab(any(nb)), move(-1)) then {
de := dirinfo()
(de.name := trim(move(8)), move(1),
de.ext := trim(move(3)), move(1),
de.size := integer(move(9)), move(1),
de.date := move(8), move(2),
de.time := tab(0))
return de
}
end
procedure dosdirlist( #: get list of DOS directory
dpath, dpart2, infotab
)
local dl, di, fn
if type(\infotab) ~== "table" then
runerr( 124, infotab )
dpath ||:= dpath[-1] ~== "\\"
/dpart2 := "*.*"
dl := []
every di := dosdir( dpath || dpart2 ) do
if not ( di.name == ("." | "..") ) then {
put( dl, fn := ( dpath || di.name || "." || trim(di.ext) ) )
(\infotab)[fn] := di
}
return dl
end
$ifdef _MSDOS
procedure dosfiles(pfn) #: DOS file names
local asciiz, fnr, prefix, k, name
local ds, dx, result, fnloc, string_block
# Get Disk Transfer Address; filename locn is 30 beyond that.
result := Int86([16r21, 16r2f00] ||| list(7,0))
# pointer arithmetic wrong: fnloc := 16 * result[8] + result[3]+ 30
fnloc := ishift( result[8], 16 ) + result[3] + 30
# Get the generalized filename.
fnr := reverse(pfn)
k := upto("\\:", fnr) | *fnr + 1
prefix := reverse(fnr[k:0])
name := "" ~== reverse(fnr[1:k]) | "*.*"
# Get the first file in the sequence.
asciiz := prefix || name || "\x00"
Poke(string_block := GetSpace(*asciiz), asciiz) |
stop( "dosfiles(): GetSpace() failed." )
# pointer arithmetic wrong: ds := string_block / 16
# pointer arithmetic wrong: dx := string_block % 16
ds := ishift( string_block, -16 )
dx := iand( string_block, 16rffff )
result := Int86([16r21, 16r4e00, 0, 0, dx, 0, 0, 0, ds])
FreeSpace(string_block)
case result[2] of {
0 : {}
18 : fail
default : stop("I/O Error ", result[2])
}
suspend prefix || extract_name(fnloc)
# Get the remaining files in the sequence.
while Int86([16r21, 16r4f00, 0, 0, 0, 0, 0, 0, 0])[2] = 0 do
suspend prefix || extract_name(fnloc)
end
$endif
procedure dosname(namein) #: convert file name to DOS format
local prefix, base, extension, pair, extended_name
namein := replace(namein, "/", "\\")
pair := tail(namein, "\\")
prefix := pair[1]
extended_name := pair[2]
pair := suffix(extended_name)
base := pair[1]
extension := pair[2]
base := base[1:9]
extension := extension[1:4]
return map(prefix || "\\" || base || "." || extension)
end
procedure dpath(s) #: full path to file on DPATH
local file, paths, path, result
if exists(s) then return s # look in current directory
paths := getenv("DPATH") | fail
s := "/" || s # platform-specific
paths ? {
while path := tab(upto(' ') | 0) do {
if exists(result := path || s) then return result
tab(many(' ')) | break
}
}
fail
end
procedure exists(name) #: test file existence
return stat(name)
end
procedure directory(name) #: succeed if name is a directory
if close(open(name || "/.")) then
return name
else
fail
end
$ifdef _MSDOS
procedure extract_name(fnloc)
local asciiz
asciiz := Peek(fnloc, 13)
return asciiz[1:upto("\x00", asciiz)]
end
$endif
procedure fcopy(fn1,fn2) #: copy file
local f1, f2, buf
f1 := open(fn1,"ru") | stop("Can't open ",fn1)
f2 := open(fn2,"wu") | stop("Can't open ",fn2," for writing")
while buf := reads(f1,512) do writes(f2,buf)
every close(f2 | f1)
return fn2
end
procedure filelist(spec, x) #: get list of files
local flist, ls, f
/spec := ""
flist := []
if &features == "UNIX" then {
ls := open("ls " || spec || " 2>/dev/null", "p")
every f := !ls do {
if \x then f ?:= {
while tab(upto("/") + 1)
tab(0)
}
put(flist, f)
}
close(ls)
return flist
}
else fail # don't take control away from caller
end
procedure filetext(f) #: read file into list
local input, file, text
input := open(f) | stop("cannot open input file")
text := []
while put(text,read(input))
close(input)
return text
end
$ifdef _MSDOS
procedure getdrive() #: get current DOS drive
return &lcase[iand( Int86([33,16r1900,0,0,0,0,0,0,0])[2], 255 )+1] || ":"
end
procedure getwd(drive) #: get DOS working directory
local A, dx, si, cf, ds
A := GetSpace(64) | stop( "getwd(): GetSpace() failed." )
dx := ("36r" || !\drive) - 9 | 0
si := iand( A, 16rffff ); ds := ishift( A, -16 )
cf := !Int86([33,16r4700,0,0,dx,si,0,0,ds]) % 2
Peek( A , 64 ) ? path := tab(many(~'\0')) | ""
FreeSpace(A)
cf = 0 | fail
return ( map(!\drive) || ":" | getdrive() ) || "\\" || map(path)
end
$endif
procedure isabspath(fname)
fname ? {
if ="/" & stat(fname) then return fname
$ifdef _MS_WINDOWS_NT
("a" <<= map(move(1)) <<= "z") | fail
if =":\\" & stat(fname | (fname ||:=".exe")) then return fname
$endif
}
end
procedure pathfind(fname, path, psep) #: find file on path
local f, dir, fullname
$ifdef _UNIX
/psep := ':'
$else
$ifdef _MS_WINDOWS_NT
/psep := ';'
$else
/psep := ' '
$endif
$endif
if fname := isabspath(fname) then
return fname # full absolute path works
fname ? {
while tab(find(PS) + 1)
fname := tab(0) # get final component of path
}
/path := (getenv("DPATH")|"") || (getenv("PATH")|"")
path := "." || psep || path
path ? while not pos(0) do {
dir := tab(upto(psep) | 0)
fullname := trim(dir, PS) || PS || fname
if stat(fullname) then
return fullname
tab(many(psep))
}
$ifdef _MS_WINDOWS_NT
# On windows, if we are doing a system PATH search with ; try to append .exe
# Might want to append other path-responding executables.
if ( psep == ";") & (*fname<=4 |
( map(fname[-4:0]) ~== ".exe" & (not find(".", fname[-4:0])))) then
return pathfind(fname || ".exe", path, psep)
$endif
fail
end
procedure pathload(fname, entry) #: load C function from $FPATH
local path, found, psep, sfeat
$ifdef _UNIX
psep := ":"
$else
$ifdef _MS_WINDOWS_NT
psep := ";"
$else
psep := " "
$endif
$endif
path := getenv("FPATH") | "."
if match("Binaries at ", sfeat := &features) then {
path || " " || sfeat[13:0]
}
# ^ Binaries at can be patched by:
# patchstr iconx newpath
found := pathfind(fname, path, " ") # pathfind prepends "."
if /found then
stop ("cannot find \"", fname, "\" on path \". ", path, "\"")
return loadfunc(found, entry) # aborts if unsuccessful
end
procedure readline(file) #: assemble backslash-continued lines
local line
line := read(file) | fail
while line[-1] == "\\" do
line := line[1:-1] || read(file) | break
return line
end
procedure splitline(file,line,limit) #: split line into pieces
local i, j
if *line = 0 then { # don't fail to write empty line
write(file,line)
return
}
while *line > limit do {
line ?:= {
i := j := 0
every i := find(" ") do { # find a point to split
if i >= limit then break
else j := i
}
if j = 0 then { # can't split
write(file,line)
return
}
write(file,tab(j + 1),"\\")
tab(0) # update line
}
}
if *line > 0 then write(file,line) # the rest
return
end
procedure suffix(s,separator) #: find suffix of file name
local i
/separator := "."
i := *s + 1
every i := find(separator,s)
return [s[1:i],s[(*s >= i) + 1:0] | &null]
end
procedure tail(s,separator) #: find tail of file name
local i
/separator := "/"
i := 0
every i := find(separator,s)
return [s[1:i + (i <= 1 | 0)],"" ~== s[i + 1:0] | &null]
end
procedure tempfile( #: get temporary file
prefix, suffix, path, len
)
local name
name := tempname(prefix, suffix, path, len)
return open(name, "w") | fail
end
procedure tempname( #: get temporary file name
prefix, suffix, path, len
)
local name, file
/prefix := ""
/suffix := ""
exists(\path) |
(path := getenv("TEMP") | (if exists("/tmp") then "/tmp") | ".")
if find("MS-DOS"|"MS Windows NT", &features) then
prefix := path || "\\" || prefix
else
prefix := path || "/" || prefix
/len := 8
randomize()
repeat {
?1 # change &random
name := prefix || left(&random, 8, "0") || suffix
if not exists(name) then return name
}
end
This page produced by UniDoc on 2021/04/15 @ 23:59:43.