Handles

We want to read from and write to files (and file-like objects) and we do that through handles. In particular, the reader will use a handle to read input from.

From Scheme we also get string handles a very neat abstraction that is right up our shell-street and also means that the reader is equally at home with string handles.

To make all that work there is a high-level interface, the handle, and two low-level implementations, file handle and string handle.

The high-level interface maintains:

  • a pointer to the low-level stream data – which is file-/string-specific

  • a lookahead char – think LL(1) grammars

  • a line number within the stream, starting at 1

    Essentially, as bytes are consumed from the stream, any U+000A (LINE FEED) character causes a line number increment.

    The line number cannot be maintained and will be set to 0 (zero) if you seek somewhere in the stream other than position 0 (zero) which resets the line number to 1.

  • a position (offset) within the stream – always maintained though less human-friendly

  • the name the user used to access the file

    For a string handle the name is a construction from input and/or output (with a / if necessary), string-handle # and a monotonically increasing number.

  • the actual pathname of the file

    For a string handle it is the name, as above.

  • a table of methods to manipulate the stream

The methods are:

  • free - used when garbage collecting the handle

  • readyp - a check that a character is available to be read without blocking

    Note it will return #t for interactive handles at end-of-file.

  • getc - analogous to fgetc(3)

  • eofp - analogous to feof(3)

  • close - analogous to close(2)

  • putc - analogous to fputc(3)

  • puts - analogous to fputs(3)

  • flush - analogous to fflush(3)

  • seek - analogous to fseek(3)

    There isn’t a tell low-level method as all the other methods studiously update the stream’s position in the high-level interface and handle-pos handle will return it

  • print - a mechanism to convert an object to a string and puts that to the stream

    To note, here, is that the object is considered to be displayed – in other words, strings are not double-quoted.

We can now define a handle abstraction that almost everything else will use and two specific implementations.

That’s the basic Scheme model but we can (and must!) extend it for our own nefarious shell-like purposes. Our obvious first need is to be able to write into or read from commands and the obvious implementation is a pipe(2).

Hence we get pipe handles which are a variation on a file handle re-using most methods as, ultimately, they are simple wrappers around standard operating system file descriptors. The only useful difference is that you cannot seek on a pipe handle.

As a handy generic form, there are fd handle primitives, ie. file descriptor handle primitives, which are useful generic tests for either a file or a pipe handle.

You can imagine other file descriptor types will be available in due course.

Implementation

Following the above description the two sets of interfaces and accessors are reasonably straightforward:

gc.h
typedef struct idio_handle_methods_s {
    void (*free)      (struct idio_s *h);
    int (*readyp)     (struct idio_s *h);
    int (*getc)       (struct idio_s *h);
    int (*eofp)       (struct idio_s *h);
    int (*close)      (struct idio_s *h);
    int (*putc)       (struct idio_s *h, int c);
    ptrdiff_t (*puts) (struct idio_s *h, char *s, size_t slen);
    int (*flush)      (struct idio_s *h);
    off_t (*seek)     (struct idio_s *h, off_t offset, int whence);
    void (*print)     (struct idio_s *h, struct idio_s *o);
} idio_handle_methods_t;

#define IDIO_HANDLE_FLAG_NONE                0
#define IDIO_HANDLE_FLAG_READ                (1<<0)
#define IDIO_HANDLE_FLAG_WRITE               (1<<1)
#define IDIO_HANDLE_FLAG_CLOSED              (1<<2)
#define IDIO_HANDLE_FLAG_FILE                (1<<3)
#define IDIO_HANDLE_FLAG_PIPE                (1<<4)
#define IDIO_HANDLE_FLAG_STRING              (1<<5)

typedef struct idio_handle_s {
    struct idio_s *grey;
    void *stream;                    /* file/string specific stream data */
    idio_handle_methods_t *methods; /* file/string specific methods */
    int lc;                          /* lookahead char */
    off_t line;                      /* 1+ */
    off_t pos;                       /* position in file: 0+ */
    struct idio_s *filename;         /* filename the user used */
    struct idio_s *pathname;         /* pathname or some other identifying data */
} idio_handle_t;

#define IDIO_HANDLE_GREY(H)          ((H)->u.handle->grey)
#define IDIO_HANDLE_STREAM(H)        ((H)->u.handle->stream)
#define IDIO_HANDLE_METHODS(H)       ((H)->u.handle->methods)
#define IDIO_HANDLE_LC(H)            ((H)->u.handle->lc)
#define IDIO_HANDLE_LINE(H)          ((H)->u.handle->line)
#define IDIO_HANDLE_POS(H)           ((H)->u.handle->pos)
#define IDIO_HANDLE_FILENAME(H)      ((H)->u.handle->filename)
#define IDIO_HANDLE_PATHNAME(H)      ((H)->u.handle->pathname)
#define IDIO_HANDLE_FLAGS(H)         ((H)->tflags)

#define IDIO_INPUTP_HANDLE(H)        (IDIO_HANDLE_FLAGS(H) & IDIO_HANDLE_FLAG_READ)
#define IDIO_OUTPUTP_HANDLE(H)       (IDIO_HANDLE_FLAGS(H) & IDIO_HANDLE_FLAG_WRITE)
#define IDIO_CLOSEDP_HANDLE(H)       (IDIO_HANDLE_FLAGS(H) & IDIO_HANDLE_FLAG_CLOSED)
#define IDIO_FILEP_HANDLE(H)         (IDIO_HANDLE_FLAGS(H) & IDIO_HANDLE_FLAG_FILE)
#define IDIO_PIPEP_HANDLE(H)         (IDIO_HANDLE_FLAGS(H) & IDIO_HANDLE_FLAG_PIPE)
#define IDIO_STRINGP_HANDLE(H)       (IDIO_HANDLE_FLAGS(H) & IDIO_HANDLE_FLAG_STRING)

#define IDIO_HANDLE_M_FREE(H)        (IDIO_HANDLE_METHODS (H)->free)
#define IDIO_HANDLE_M_READYP(H)      (IDIO_HANDLE_METHODS (H)->readyp)
#define IDIO_HANDLE_M_GETC(H)        (IDIO_HANDLE_METHODS (H)->getc)
#define IDIO_HANDLE_M_EOFP(H)        (IDIO_HANDLE_METHODS (H)->eofp)
#define IDIO_HANDLE_M_CLOSE(H)       (IDIO_HANDLE_METHODS (H)->close)
#define IDIO_HANDLE_M_PUTC(H)        (IDIO_HANDLE_METHODS (H)->putc)
#define IDIO_HANDLE_M_PUTS(H)        (IDIO_HANDLE_METHODS (H)->puts)
#define IDIO_HANDLE_M_FLUSH(H)       (IDIO_HANDLE_METHODS (H)->flush)
#define IDIO_HANDLE_M_SEEK(H)        (IDIO_HANDLE_METHODS (H)->seek)
#define IDIO_HANDLE_M_PRINT(H)       (IDIO_HANDLE_METHODS (H)->print)

Lookahead Char

When I first implemented this in C-mode, I could note that:

fgetc(3) returns either an unsigned char cast to an int or the sentinel value EOF. We require a sentinel value for our lookahead char and EOF is as handy for us as it was for fgetc(3). So, if the lookahead char is EOF it actually means call fgetc(3) to get the next char.

However, UTF-8 laughs at our simple plan. Or my C laughs at me. Either way, 0xff – which will be recast to -1, aka, EOF – is invalid UTF-8 but it is explicitly used because it is invalid in UTF-8 test cases. So we have to be able to handle -1/EOF as a viable, if invalid, char.

The upshot of which is, we can’t have a sentinel value in the lookahead char but have to explicitly call the eofp method instead. Hardly the end of the world! (But close, right?)

Handles

Nothing creates a handle directly. Instead file-handles, pipe handles or string-handles are created which, in turn, create a handle.

So you open-input-file name (where name is a string) and get in return a handle of the file handle variety. Similarly, open-output-string returns a handle of the string handle variety.

You can now run any of the plethora of handle-oriented operations on either.

Reading

There is no reader input form for a handle.

Writing

As there is no reader input form then if we print a handle out it’ll take a #<...> (invalid reader) form.

For a handle we can print out some useful info like:

  • the type: H for handle

  • open or closed: o/c

  • file, pipe or string: f/p/S

    S is used for string as in readiness for a socket(2) based handle then f, p and s match up nicely with the f?, p? and s? predicates.

  • open for reading and/or writing: r and/or w

  • some fd handle flags:

    • O_CLOEXEC or not: e/!

      e here matching the e mode flag passed to the open-* commands

    • i if the handle is interactive

    • F if the handle is an original stdio handle (stdin, stdout or stderr) with the F a mnemonic for the C FILE* object

    • E if the handle is registered as EOF

    • the file descriptor

  • some handle details

    • the name (the user used – or we concocted)

    • the line number

    • the position

Which might give us:

Idio> (current-input-handle)
#<H ofr!iF   0:"*stdin*":2:23>

Idio> osh := (open-output-string)
#<H oSw:"output string-handle #1889":1:0>

In the first instance, #<H ofr!iF   0:"*stdin*":2:23> we can see the value is:

  • a handle

  • open

  • a file

  • open for read

  • does not have O_CLOEXEC set (by us, anyway)

  • is interactive

  • is constructed from the original STDIO set

  • the file descriptor is 0

  • the handle’s name is *stdin*

  • we are on line 2

  • position 23

In the second case, #<H oSw:"output string-handle #1889":1:0> we can infer:

  • it’s a handle

  • open

  • a string

  • writeable

  • it’s the 1889th string handle this process has created

  • we’re on line 1

  • position 0

File Handles

File handles were implemented using libc’s FILE type and you could imagine that all the implementation methods were essentially the libc equivalents.

However, the introduction of pipe handles (and some recurring “issues” with what was, in effect, double buffered input and output) has had them rewritten as native read(2), write(2) and friends.

The file handle stream data is:

#define IDIO_FILE_HANDLE_FLAG_NONE           0
#define IDIO_FILE_HANDLE_FLAG_EOF            (1<<0)
#define IDIO_FILE_HANDLE_FLAG_INTERACTIVE    (1<<1)
#define IDIO_FILE_HANDLE_FLAG_STDIO          (1<<2)
#define IDIO_FILE_HANDLE_FLAG_CLOEXEC        (1<<3)

typedef struct idio_file_handle_stream_s {
    int fd;
    IDIO_FLAGS_T flags;              /* IDIO_FILE_HANDLE_FLAG_* */
    char *buf;                       /* buffer */
    int bufsiz;
    char *ptr;                       /* ptr into buffer */
    int count;                       /* bytes in buffer */
} idio_file_handle_stream_t;

Of note here is that we buffer data to and from the stream – essentially we’ve re-implemented STDIO!

There is a generic file opening method:

static IDIO idio_open_file_handle (IDIO filename,
                                   char *pathname,
                                   int fd,
                                   int h_type,
                                   int h_flags,
                                   int s_flags);

with:

  • filename the name the user supplied

  • pathname the “real” name of the opened file – this might be *stdin*, for example

  • fd is the file descriptor

  • h_type is IDIO_HANDLE_FLAG_FILE, IDIO_HANDLE_FLAG_PIPE etc..

  • h_flags are handle flags as per the struct idio_handle_s structure definition

  • s_flags are the (file handle) stream-specific flags as seen above, such as a flag if the file handle is interactive.

Of interest, static IDIO idio_open_std_file_handle (FILE *filep) is used to alternately wrapper the usual three STDIO variables, stdin, stdout and stderr, with a Idio handle. The handle’s name is set to be *stdin*, *stdout* or *stderr*.

File handles need a finalizer which will close(2) the contained file descriptor when the GC is trying to free the corresponding handle.

file-handle.c

src/file-handle.c contains a bundle of functions relating to the business of finding Idio library files.

We will have the usual sort of library search list environment variable, cleverly called IDIOLIB. When a user loads a library file we need to find it.

Note

Before I read of an issue with systemd I had some preconceived notions about PATH_MAX.

I still do, but hopefully fewer incorrect ones. If I understand things correctly then PATH_MAX is a (glibc?) constraint on user-supplied pathnames (as distinct from filenames in directory entries) but is not a constraint on the pathnames retrievable from the filesystem.

Causing a bit more fun, PATH_MAX is probably, in reality, filesystem-dependent, see pathconf(3), and may return a very large number indeed with the suggestion being there is no limit in the general case.

As to how to open(2) a file that has a pathname more than PATH_MAX - 1 bytes you’ll need to refactor the original pathname into leading directory segments, each up up to PATH_MAX - 1 bytes, and make repeated calls to openat(2).

There is a similar note in “APPLICATION USAGE” in pwd(1p).

Most of that work is delegated to idio_libfile_find_C(char *file) which potters about worrying about:

  • splitting IDIOLIB into directory entries

  • PATH_MAX when appending name elements

  • various library file extensions

    The obvious extension is .idio but we might have other forms, for example, a compiled format which may require a different reader. Consequently there is, elsewhere, a table of (extension, reader, evaluator) tuples.

There were two file loading mechanisms which derived from me changing my mind.

Consider how we read and evaluate files (technically, handles but we’re all used to thinking about files). We could:

while read expression
    evaluate expression
    run expression

or

read all expressions
evaluate all expressions
run all expressions

I liked the latter as it solved a recurring problem I have when developing scripts, the sort of scripts that take a minute or twenty to chunter through yet you’ve spotted a bugfeature just after it’s started and edit the script. Of course you save the script with a satisfying kerthunk!…while the script is still running. Oops. The next read from the file by Bash will get a garbled string. It’s all gone pear-shaped.

I ran, for a long time, with the latter, “all in one,” then changed my mind (and deleted it). Why? What’s the difference?

The problem is subtle and primarily affects templates, indeed, anything that has a meta-effect on the program, like operators.

The problem is that we say “evaluate” but what we really mean is:

  • determine the meaning of the expression

  • implement any template use

  • prep the code for the VM

But running the code in the VM may or may not happen now.

If I define a template then I cannot use it until the code for the definition is run – which will add to the list of “expander” terms that the evaluator will recognise. That means I cannot use the template until the byte code is run by the VM.

That might not sound like a big problem but it does prevent you defining and using a macro within a module: for the latter variant, the definition isn’t run until we’ve read and evaluated everything. In other words the evaluator will not have been informed that this new template exists until we’ve hit the end of the file and can run all the statements.

Part of this might arise from me not having written enough gnarly code to require a template to be defined and used in the same module but when porting Scheme code you quickly discover that other people are altogether more with it.

*

I did keep the “expression by expression” and “all in one” variants around for a while but eventually ditched the “all in one” when documenting module and realising it was banjaxed for similar reasons.

So, the normal C file loading function is:

IDIO idio_load_file_name (IDIO filename, IDIO cs)

and its Idio equivalent load filename will load “expression by expression.”

Pipe Handles

We would like to read from and write to external commands for which we need to pipe their output from them or pipe our output to them. Maintaining the handle-style is important, hence pipe handles.

The implementation of pipe handles will hold no surprises, open-input-pipe is essentially the open-input-file-from-fd style of construction.

Note

There is obvious naming confusion with pipes in that the output from a command will be an input handle as we are reading from that input stream. Similarly the input to a pipe will be an output handle.

So pipe-into returns an output handle and pipe-from returns an input handle.

A more intriguing question is how do we do this for the user?

We’re a shell and, despite our use of reader operators, have a leaning towards the cmd args style. Hence, if we want to filter the output of a command then it makes sense for us to declare that intention as a cmd, hence:

pipe-from zcat file.tgz | tar tf -

or, maybe:

pipe-from tar tzf file.tgz

ie. note that this works for simple commands or pipelines.

Although what we clearly want pipe-from to do is return us a pipe handle hence what we should have written is:

ih := pipe-from zcat file.tgz | tar tf -

ih is now a regular (input) handle that we can manipulate is regular handle-like ways, read-line springs to mind.

close-handle should also spring to mind otherwise, depending on the scope of ih you’ll be accumulating both unclosed pipe(2)s and a number of zombie processes.

String Handles

String handles are in some sense just the buffer part of a file handle. For an input string handle we will have initialised the buffer with whatever the string argument was and with an output string handle we just keep extending the buffer.

We can zip about inside a string handle, just like a file handle – with the same effects on line number and position.

The string handle stream data looks like:

typedef struct idio_string_handle_stream_s {
    char *buf;                       /* buffer */
    size_t blen;
    char *ptr;                       /* ptr into buffer */
    char *end;                       /* end of buffer */
    char eof;                        /* EOF flag */
} idio_string_handle_stream_t;

We don’t get an end-of-file chime as we do with file handles so we need to fake one up by maintaining and end of buffer marker and “raise” end of file when we get there.

It turns out that string handles are really handy for generating error messages within the code base from, say, a bit of C string and a bit of Idio object. Take, for example, the calls to fdopen(3) in src/file-handle.c, above.

If the system call fails we want to raise an error condition indicating the system call and the arguments:

file-handle.c
idio_error_system_errno ("fdopen",
                         IDIO_LIST2 (idio_string_C (name),
                                     idio_string_C (mode)),
                         IDIO_C_FUNC_LOCATION ());

We’re passing

  • a C string with the problem’s contextual message

  • an Idio list of the name associated with the file descriptor (defaulting to /dev/fd/X) and the mode (defaulting to r/w).

  • IDIO_C_FUNC_LOCATION () is a macro which, when compiled with DEBUG, brings together __FILE__, __LINE__ and GNU’s __func__ for some handy diagnosis.

idio_error_system_errno() is a wrapper to idio_error_system() with errno added as an argument and we get:

error.c
void idio_error_system (char *msg, IDIO args, int err, IDIO c_location)
{
    ...

    IDIO msh = idio_open_output_string_handle_C ();
    idio_display_C (msg, msh);
    if (idio_S_nil != args) {
        idio_display_C (": ", msh);
        idio_display (args, msh);
    }
    idio_display_C (": ", msh);
    idio_display_C (strerror (err), msh);

    IDIO location = idio_vm_source_location ();

    IDIO dsh = idio_open_output_string_handle_C ();
#ifdef IDIO_DEBUG
    idio_display (c_location, dsh);
#endif

    IDIO c = idio_struct_instance (idio_condition_system_error_type,
                                   IDIO_LIST4 (idio_get_output_string (msh),
                                               location,
                                               idio_get_output_string (dsh),
                                               idio_C_int (err)));

    idio_raise_condition (idio_S_true, c);
}

All conditions derived from ^idio-error take three standard parameters: message, location and detail.

We can use two helper functions, idio_display() for displaying Idio values and idio_display_C() for displaying C strings. Of course, at the end of some tumultuous computation and constructing a (huge?) string in C, idio_display() will also be calling idio_display_C().

Here we can first create a “message” string handle, msh, into which we print (display!) the C string msg (fdopen in this case) then, if some args were passed, add a C : string then whatever the printed representation of the args are.

Add on another : then the operating system’s description of the system call error for a bit of normalised guidance. Secondly, recover the user-land source code location (from the EXPR register in the thread). Finally, we can create a “detail” string handle and add the C source location if we’re running under debug for extra clarification. The fourth parameter for a ^system-error is, in effect, errno.

Finally, we can construct a condition, of the “system error” type which is expecting a further four arguments including the re-constituted strings from the two output string handles.

handle.c

src/handle.c is the front-end for manipulating handles and its methods are largely just calls to the underlying file handle or string handle methods.

Many of them differ in that they do not require a handle to be passed to them, it is an optional argument. The premise, here, is that the default should be the current input handle or current output handle. Entities maintained by the current thread.

idio_getc_handle() (and idio_ungetc_handle()) maintain the line and position as U+000A (LINE FEED) characters pass through.

read

While we’re here we can muse on an example of naming hell.

read is a heavily overloaded name. At our lowest level, in libc, the primitive read is read(2).

However, in Idio, the primitive read is an invocation of the reader, an instruction to return a complete Idio “line” of input (which might be multiple lines if we have some unmatched parentheses or braces or line continuations or …).

We can complicate the issue with read-expr which will have the reader consume a single expression (and not consume expressions to the end of the line).

As a user of Idio I’m unlikely to call either of those and am more likely interested in reading a line of text from a file, hence, read-line and its greedier sibling, read-lines.

As a user I might also want to read-char to get back the next (UTF-8 encoded) Unicode code point. There is no (exposed) read-byte function.

read (and read-expr) isn’t really used other than for testing so I suspect they could be re-worked into a more reader-oriented name or, indeed, namespace clearing the way for “normal” usage of the names by users.

load

There is a handle-variant of load: load-handle.

More interesting is the C-only idio_load_handle_interactive() which is the REPL.

Previously, I’ve mentioned that I don’t want to spend much time here as I’d rather target scripting, however it does get used.

I’ve noted above that the REPL is C-only in the sense that there isn’t an Idio entry-point. Like most things it could probably do with being re-arranged slightly so that there is an Idio primitive which calls the existing idio_load_handle_interactive() but one we could replace with a pure-Idio function.

In the meanwhile, the only useful difference between the REPL and load-handle-ebe is that:

  • a prompt is printed to the current error handle

  • and the value returned by the expression is printed to the current output handle

Notably, then

  1. the prompt is currently fixed to being the name of the current module followed by > , so Idio> by default. That could easily be passed off to some Idio function which might use whatever to construct a prompt.

    Bash, obviously, uses PS1 with its myriad of baskslash-escaped special characters.

  2. there is no readline-style interactive editing

    (which is annoying – although you can fall back on rlwrap)

Operations

File Handles

See src/file-handle.c.

Note that when manipulating a file descriptor (rather than a file handle) you are manipulating a C-int, not a fixnum (and certainly not a bignum). Whilst you can construct an arbitrary C-int and therefore “file descriptor”, like regular systems programming, it behooves the user to choose values wisely. Normally, you would receive such a value from a call known to provide one and pass it around opaquely.

function open-file-from-fd fd [name [mode]]

construct a file handle from fd using the optional name instead of the default /dev/fd/fd and the optional mode mode instead of re

function open-input-file-from-fd fd [name]

construct a file handle from fd using the optional name instead of the default /dev/fd/fd and the optional mode mode instead of re

function open-output-file-from-fd fd [name]

construct a file handle from fd using the optional name instead of the default /dev/fd/fd and the optional mode mode instead of we

function open-input-pipe fd [name]

construct a pipe handle from fd using the optional name instead of the default /dev/fd/fd and the optional mode mode instead of re

function open-output-pipe fd [name]

construct a pipe handle from fd using the optional name instead of the default /dev/fd/fd and the optional mode mode instead of we

function open-file name mode

construct a file handle by opening the file name and the mode mode

open-file has to handle the resource contention issue mentioned previously.

If the fopen(3) call fails with EMFILE (a process limit) or ENFILE (a system-wide limit) indicating the lack of available file descriptors then it has to forcibly invoke the garbage collector and try again.

For reasons that escape me, it tries that twice….

function open-input-file name

construct a file handle by opening the file name with the mode re

function open-output-file name

construct a file handle by opening the file name with the mode we

function file-handle? value

is value a file handle

function input-file-handle? value

is value a file handle capable of being read from

Obviously this is input file handles but also files opened for writing with the “+” mode flag: “w+”, “a+”.

function output-file-handle? value

is value a file handle capable of being written to

Obviously this is output file handles but also files opened for reading with the “+” mode flag: “r+”.

function file-handle-fd fh

return the file descriptor associated with file handle fh

function fd-handle? value

is value a fd handle

function input-fd-handle? value

is value a fd handle capable of being read from

Obviously this is input fd handles but also fds opened for writing with the “+” mode flag: “w+”, “a+”.

function output-fd-handle? value

is value a fd handle capable of being written to

Obviously this is output fd handles but also fds opened for reading with the “+” mode flag: “r+”.

function fd-handle-fd fh

return the file descriptor associated with fd handle fh

function pipe-handle? value

is value a pipe handle

function input-pipe-handle? value

is value a pipe handle capable of being read from

The “+” mode flag is ignored for a pipe.

function output-pipe-handle? value

is value a pipe handle capable of being written to

The “+” mode flag is ignored for a pipe.

function pipe-handle-fd ph

return the file descriptor associated with pipe handle ph

function close-fd-handle-on-exec fh

call fcntl(3) on the underlying C file descriptor in fd handle fh with F_SETFD and FD_CLOEXEC arguments.

function find-lib filename

search IDIOLIB for filename using a set of possible filename extensions

function load filename

search IDIOLIB for filename using a set of possible filename extensions and then load it in “expression by expression.”

function file-exists? filename

does filename exist

Technically, the test is a call to access(2) with the R_OK flag.

function delete-file filename

remove(3) filename

String Handles

See src/string-handle.c.

function open-input-string string

construct an input string handle from the string string

function open-output-string

construct an output string handle

function string-handle? value

is value a string handle

function input-string-handle? value

is value an input string handle

function output-string-handle? value

is value an output string handle

function get-output-string sh

return a string constructed from the contents of the output string handle sh

Handles

See src/handle.c.

function handle? value

is value a handle

function input-handle? value

is value an input handle

function output-handle? value

is value an output handle

function ready? handle

is handle handle ready, ie. not at end-of-file

function eof? handle

has handle handle seen end-of-file

function peek-char handle

return the Unicode code point of the next character in handle handle without moving the position in the handle forward

function puts value [handle]

invoke the puts method associated with handle handle, if supplied or the current output handle otherwise, with value

puts will use the printed conversion of value rather than the displayed version

function flush-handle handle

invoke the flush method associated with handle handle

function seek-handle handle pos [whence]

invoke the seek method associated with handle handle with pos and whence, if supplied or 'set otherwise

whence can be one of the symbols set, end or cur.

See handle-pos for the equivalent of a tell-handle.

Invoking seek-handle on a pipe handle will generate a ^rt-parameter-value-error.

function rewind-handle handle

invoke the seek method associated with handle handle with a position of zero and whence of set.

function close-handle handle

invoke the close method associated with handle handle

function close-input-handle handle

invoke the close method associated with input handle handle

function close-output-handle handle

invoke the close method associated with output handle handle

function closed-handle? handle

return #t if the handle handle has been closed and #f otherwise

function eof-object? value

return #t if the value value is the end-of-file value

function handle-line [handle]

return the current line number in handle handle if supplied otherwise the current input handle

The line number can be invalidated by a seek-handle other than to position zero.

function handle-pos [handle]

return the current position in handle handle if supplied otherwise the current input handle

function handle-location handle

return a description of the location handle handle consisting of the handle’s name, line number and position

function load-handle handle

load from handle handle “expression by expression.”

function current-input-handle

return the current input handle

function current-output-handle

return the current output handle

function current-error-handle

return the current error handle

function set-input-handle! handle

set the current input handle to handle handle

function set-output-handle! handle

set the current output handle to handle handle

function set-error-handle! handle

set the current error handle to handle handle

function read [handle]

invoke the reader with handle handle if supplied otherwise the current input handle

function read-expr [handle]

invoke the expression reader with handle handle if supplied otherwise the current input handle

function read-line [handle]

return the next canonical line of text (up to a newline) as a string from handle handle if supplied otherwise the current input handle

function read-lines [handle]

return the remaining lines of text as a string from handle handle if supplied otherwise the current input handle

function read-char [handle]

return the UTF-8 character as a Unicode code point from handle handle if supplied otherwise the current input handle

function write value [handle]

invoke the puts method associated with handle handle if supplied otherwise the current input handle with value

write will use the printed conversion of value rather than the displayed version. See display below.

function write-char cp [handle]

invoke the putc method associated with handle handle if supplied otherwise the current input handle with Unicode code point cp

Error

putc doesn’t generate UTF-8

function newline [handle]

invoke the putc method associated with handle handle if supplied otherwise the current input handle with Unicode code point U+000A (LINE FEED)

function display value [handle]

invoke the puts method associated with handle handle if supplied otherwise the current input handle with value

display will use the displayed conversion of value rather than the printed version. See write above.

A function display* (and sibling functions edisplay and edisplay*) have been written to display multiple values separated by a space and with a trailing newline (to the current error handle). These are largely deprecated in favour of printf (and eprintf)

function %printf handle format [args]

[deprecated in favour of printf]

rudimentary support for printf(3) and can only handle %[flags][width][.prec]s for strings (with all Idio values being converted to strings).

In lib/common.idio there are some extra utility functions.

function %format type format [args]

This %format function (in lib/common.idio) makes a much better attempt at the vagaries of printf(3) by utilising some dynamic variables to convey the print conversion format and precision to other parts of the system

You would not normally invoke %format directly but rather use format or one of the printf variants, below.

type is one of the symbols:

  • 'args in which case a % character in the format string starts an escape sequence which has the general form %[flags][width][.prec]K where K is a printf(3)-ish format character with arguments in the parameter list args

    So, like a normal printf. The idea being that we can print, say, Idio integers as decimal (fixnums or bignums) or hexadecimal, octal and binary (fixnums).

    %s should work for any Idio type.

  • 'keyed in which case a % character in the format string starts an escape sequence which has the general form %[flags][width][.prec]K where K is a single Unicode code point (satisfying Alphabetic?) which is expected to be a key in the optional hash table – unless it is another % character. The value associated with the key will be printed according to the specification.

  • 'timeformat which is essentially the same as 'keyed except we avoid a double application of any precision

    This is to support the time function’s TIMEFORMAT format string which is of the form: "Real %.3R\nUser %.3U\nSyst %.3S\n" where %R, %U and %S are now the consumed real time, user time and system time.

    If K is a % character then a % is printed according to the specification.

    The possible flags are:

    %format supported flags

    -

    U+002D (HYPHEN-MINUS)

    left align the output within width if applicable

    U+0020 (SPACE)

    use #\{space} as the left padding character

    0

    U+0030 (DIGIT ZERO)

    use #\0 (zero) as the left padding character

    The default padding character is #\{space}.

    *

    As the start of some work to make the printer more dynamic, you can redefine how a struct instance or a C pointer is printed. By default, the format is #<SI typename fields> where fields is a space-separated list of fieldname:value.

    As an alternative, you can register a “printer” against a struct type and it will be called when an instance of that type is printed. The printer should return a string.

    By way of a simple example:

    define-struct point x y
    
    P := make-point 1 2
    
    printf "%s\n" P ; #<SI point x:1 y:2>
    
    define (point-as-string p seen)
      if (point? p) {
        r := (open-output-string)
        hprintf r "#<SI point"
        hprintf r " x is %d" p.x
        hprintf r " and y is %d" p.y
        hprintf r ">"
        get-output-string r
       #n
    }
    
    %%add-as-string point point-as-string
    
    printf "%s\n" P ; #<SI point x is 1 and y is 2>
    

    The seen parameter to the printer function can be used for any purpose. The obvious use is to record values as they are seen and pass seen onto similar printer thus preventing circular loops.

    In this case, suppose x should be another point or #n (rather than an integer as we seem to be using it as). Instead of calling p.x we might call (point-as-string x updated-seen). To avoid loops we can add some checks and updates:

    define (point-as-string p seen)
      if (point? p) {
        if (assq p seen) {
          "@"
    
          r := (open-output-string)
          hprintf r "#<SI point"
          hprintf r " x is %s" (point-as-string x (pair (list p) seen))
          hprintf r " and y is %d" p.y
          hprintf r ">"
          get-output-string r
    
      } #n
    }
    

    An example is in state-as-string in lib/SRFI-115.idio where the struct has an ID which can be used to identify where the circular loop starts (or ends?) rather than just returning "@" as we do.

function format format [args]

An invocation of %format ‘args format [args].

function hprintf handle format [args]

For the h in hprintf think of the leading f in fprintf in C – this is the generic “print to handle” variant.

In practice it calls display with the result of format format [args].

function printf format [args]

A call to hprintf with the current output handle.

function eprintf format [args]

A call to hprintf with the current error handle.

function sprintf format [args]

A call to hprintf with an output string handle. The result is a call to get-output-string on that output string handle.

Last built at 2024-10-18T06:11:25Z+0000 from 463152b (dev)