Just enough of the Glk input/output interface to allow simple console text in and out, and no more.


§1. Glk - an apology. The code below is in no way a proper implementation of the Glk input/output system, which was developed as an interactive fiction standard by Andrew Plotkin, and which has served us well and will continue to do so. It is not even a full implementation of basic console I/O via Glk, for which see the cheapglk C library.

Instead, our aim is to do the absolute minimum possible in simple self-contained C code, and to impose as few restrictions as possible beyond that. The flip side of Glk's gilt-edged engineering quality is that it can be a gilded cage: for some imaginable uses of Inform 7-via-C, say based on Unity or in an iOS app, strict use of Glk would be constraining.

In an attempt to have the best of both worlds, the code below is only the default Glk implementation for an Inform 7-via-C project, and the user can duck out of it by providing an implementation of her own. (Indeed, this could even be cheapglk, as mentioned above.)

This section of code therefore defines just two functions, i7_default_stylist and i7_default_glk, plus their supporting code — which turns out to be quite a lot, but there are only those two points of entry.

§2. Miniglk. Each process needs to keep track of its own files, streams, windows and events, which are wrapped up in a miniglk_data structure as follows:

typedef struct i7_mg_file_t {
    i7word_t usage;
    i7word_t name;
    i7word_t rock;
    char leafname[128];
    FILE *handle;
} i7_mg_file_t;

typedef struct i7_mg_stream_t {
    FILE *to_file;
    i7word_t to_file_id;
    wchar_t *to_memory;
    size_t memory_used;
    size_t memory_capacity;
    i7word_t previous_id;
    i7word_t write_here_on_closure;
    size_t write_limit;
    int active;
    int encode_UTF8;
    int char_size;
    int chars_read;
    int read_position;
    int end_position;
    int owned_by_window_id;
    int fixed_pitch;
    char style[128];
    char composite_style[300];
} i7_mg_stream_t;

typedef struct i7_mg_window_t {
    i7word_t type;
    i7word_t stream_id;
    i7word_t rock;
} i7_mg_window_t;

typedef struct i7_mg_event_t {
    i7word_t type;
    i7word_t win_id;
    i7word_t val1;
    i7word_t val2;
} i7_mg_event_t;

#define I7_MINIGLK_MAX_STREAMS 128
#define I7_MINIGLK_MAX_WINDOWS 128
#define I7_MINIGLK_RING_BUFFER_SIZE 32

typedef struct miniglk_data {
    /* streams */
    i7_mg_stream_t memory_streams[I7_MINIGLK_MAX_STREAMS];
    i7word_t stdout_stream_id, stderr_stream_id;
    /* files */
    i7_mg_file_t files[128 + 32];
    int no_files;
    /* windows */
    i7_mg_window_t windows[I7_MINIGLK_MAX_WINDOWS];
    int no_windows;
    /* events */
    i7_mg_event_t events_ring_buffer[I7_MINIGLK_RING_BUFFER_SIZE];
    int rb_back, rb_front;
    int no_lr;
} miniglk_data;

void i7_initialise_miniglk_data(i7process_t *proc);
void i7_initialise_miniglk_data(i7process_t *proc) {
    proc->miniglk = malloc(sizeof(miniglk_data));
    if (proc->miniglk == NULL) {
        printf("Memory allocation failed\n");
        exit(1);
    }
    proc->miniglk->no_files = 0;
    proc->miniglk->stdout_stream_id = 0;
    proc->miniglk->stderr_stream_id = 1;
    proc->miniglk->no_windows = 1;
    proc->miniglk->rb_back = 0;
    proc->miniglk->rb_front = 0;
    proc->miniglk->no_lr = 0;
}

§3.

i7word_t i7_miniglk_fileref_create_by_name(i7process_t *proc, i7word_t usage, i7word_t name, i7word_t rock);
int i7_fseek(i7process_t *proc, int id, int pos, int origin);
int i7_ftell(i7process_t *proc, int id);
int i7_fopen(i7process_t *proc, int id, int mode);
void i7_fclose(i7process_t *proc, int id);
i7word_t i7_miniglk_fileref_does_file_exist(i7process_t *proc, i7word_t id);
void i7_fputc(i7process_t *proc, int c, int id);
int i7_fgetc(i7process_t *proc, int id);
i7word_t i7_miniglk_stream_get_current(i7process_t *proc);
i7_mg_stream_t i7_new_stream(i7process_t *proc, FILE *F, int win_id);
i7word_t fn_i7_mgl_TEXT_TY_CharacterLength(i7process_t *proc, i7word_t i7_mgl_local_txt, i7word_t i7_mgl_local_ch, i7word_t i7_mgl_local_i, i7word_t i7_mgl_local_dsize, i7word_t i7_mgl_local_p, i7word_t i7_mgl_local_cp, i7word_t i7_mgl_local_r);
i7word_t fn_i7_mgl_BlkValueRead(i7process_t *proc, i7word_t i7_mgl_local_from, i7word_t i7_mgl_local_pos, i7word_t i7_mgl_local_do_not_indirect, i7word_t i7_mgl_local_long_block, i7word_t i7_mgl_local_chunk_size_in_bytes, i7word_t i7_mgl_local_header_size_in_bytes, i7word_t i7_mgl_local_flags, i7word_t i7_mgl_local_entry_size_in_bytes, i7word_t i7_mgl_local_seek_byte_position);
void i7_default_stylist(i7process_t *proc, i7word_t which, i7word_t what) {
    if (which == 1) {
        i7_mg_stream_t *S = &(proc->miniglk->memory_streams[proc->state.current_output_stream_ID]);
        S->fixed_pitch = what;
        sprintf(S->composite_style, "%s", S->style);
        if (S->fixed_pitch) {
            if (strlen(S->style) > 0) sprintf(S->composite_style + strlen(S->composite_style), ",");
            sprintf(S->composite_style + strlen(S->composite_style), "fixedpitch");
        }
    } else {
        i7_mg_stream_t *S = &(proc->miniglk->memory_streams[proc->state.current_output_stream_ID]);
        S->style[0] = 0;
        switch (what) {
            case 0: break;
            case 1: sprintf(S->style, "bold"); break;
            case 2: sprintf(S->style, "italic"); break;
            case 3: sprintf(S->style, "reverse"); break;
            default: {
                int L = fn_i7_mgl_TEXT_TY_CharacterLength(proc, what, 0, 0, 0, 0, 0, 0);
                if (L > 127) L = 127;
                for (int i=0; i<L; i++) S->style[i] = fn_i7_mgl_BlkValueRead(proc, what, i, 0, 0, 0, 0, 0, 0, 0);
                S->style[L] = 0;
            }
        }
        sprintf(S->composite_style, "%s", S->style);
        if (S->fixed_pitch) {
            if (strlen(S->style) > 0) sprintf(S->composite_style + strlen(S->composite_style), ",");
            sprintf(S->composite_style + strlen(S->composite_style), "fixedpitch");
        }
    }
}

i7word_t i7_miniglk_fileref_create_by_name(i7process_t *proc, i7word_t usage, i7word_t name, i7word_t rock) {
    if (proc->miniglk->no_files >= 128) {
        fprintf(stderr, "Out of streams\n"); i7_fatal_exit(proc);
    }
    int id = proc->miniglk->no_files++;
    proc->miniglk->files[id].usage = usage;
    proc->miniglk->files[id].name = name;
    proc->miniglk->files[id].rock = rock;
    proc->miniglk->files[id].handle = NULL;
    for (int i=0; i<128; i++) {
        i7byte_t c = i7_read_byte(proc, name+1+i);
        proc->miniglk->files[id].leafname[i] = c;
        if (c == 0) break;
    }
    proc->miniglk->files[id].leafname[127] = 0;
    sprintf(proc->miniglk->files[id].leafname + strlen(proc->miniglk->files[id].leafname), ".glkdata");
    return id;
}

int i7_fseek(i7process_t *proc, int id, int pos, int origin) {
    if ((id < 0) || (id >= 128)) { fprintf(stderr, "Too many files\n"); i7_fatal_exit(proc); }
    if (proc->miniglk->files[id].handle == NULL) { fprintf(stderr, "File not open\n"); i7_fatal_exit(proc); }
    return fseek(proc->miniglk->files[id].handle, pos, origin);
}

int i7_ftell(i7process_t *proc, int id) {
    if ((id < 0) || (id >= 128)) { fprintf(stderr, "Too many files\n"); i7_fatal_exit(proc); }
    if (proc->miniglk->files[id].handle == NULL) { fprintf(stderr, "File not open\n"); i7_fatal_exit(proc); }
    int t = ftell(proc->miniglk->files[id].handle);
    return t;
}

int i7_fopen(i7process_t *proc, int id, int mode) {
    if ((id < 0) || (id >= 128)) { fprintf(stderr, "Too many files\n"); i7_fatal_exit(proc); }
    if (proc->miniglk->files[id].handle) { fprintf(stderr, "File already open\n"); i7_fatal_exit(proc); }
    char *c_mode = "r";
    switch (mode) {
        case i7_filemode_Write: c_mode = "w"; break;
        case i7_filemode_Read: c_mode = "r"; break;
        case i7_filemode_ReadWrite: c_mode = "r+"; break;
        case i7_filemode_WriteAppend: c_mode = "r+"; break;
    }
    FILE *h = fopen(proc->miniglk->files[id].leafname, c_mode);
    if (h == NULL) return 0;
    proc->miniglk->files[id].handle = h;
    if (mode == i7_filemode_WriteAppend) i7_fseek(proc, id, 0, SEEK_END);
    return 1;
}

void i7_fclose(i7process_t *proc, int id) {
    if ((id < 0) || (id >= 128)) { fprintf(stderr, "Too many files\n"); i7_fatal_exit(proc); }
    if (proc->miniglk->files[id].handle == NULL) { fprintf(stderr, "File not open\n"); i7_fatal_exit(proc); }
    fclose(proc->miniglk->files[id].handle);
    proc->miniglk->files[id].handle = NULL;
}


i7word_t i7_miniglk_fileref_does_file_exist(i7process_t *proc, i7word_t id) {
    if ((id < 0) || (id >= 128)) { fprintf(stderr, "Too many files\n"); i7_fatal_exit(proc); }
    if (proc->miniglk->files[id].handle) return 1;
    if (i7_fopen(proc, id, i7_filemode_Read)) {
        i7_fclose(proc, id); return 1;
    }
    return 0;
}

void i7_fputc(i7process_t *proc, int c, int id) {
    if ((id < 0) || (id >= 128)) { fprintf(stderr, "Too many files\n"); i7_fatal_exit(proc); }
    if (proc->miniglk->files[id].handle == NULL) { fprintf(stderr, "File not open\n"); i7_fatal_exit(proc); }
    fputc(c, proc->miniglk->files[id].handle);
}

int i7_fgetc(i7process_t *proc, int id) {
    if ((id < 0) || (id >= 128)) { fprintf(stderr, "Too many files\n"); i7_fatal_exit(proc); }
    if (proc->miniglk->files[id].handle == NULL) { fprintf(stderr, "File not open\n"); i7_fatal_exit(proc); }
    int c = fgetc(proc->miniglk->files[id].handle);
    return c;
}


i7word_t i7_miniglk_stream_get_current(i7process_t *proc) {
    return proc->state.current_output_stream_ID;
}

void i7_miniglk_stream_set_current(i7process_t *proc, i7word_t id) {
    if ((id < 0) || (id >= I7_MINIGLK_MAX_STREAMS)) { fprintf(stderr, "Stream ID %d out of range\n", id); i7_fatal_exit(proc); }
    proc->state.current_output_stream_ID = id;
}

i7_mg_stream_t i7_new_stream(i7process_t *proc, FILE *F, int win_id) {
    i7_mg_stream_t S;
    S.to_file = F;
    S.to_file_id = -1;
    S.to_memory = NULL;
    S.memory_used = 0;
    S.memory_capacity = 0;
    S.write_here_on_closure = 0;
    S.write_limit = 0;
    S.previous_id = 0;
    S.active = 0;
    S.encode_UTF8 = 0;
    S.char_size = 4;
    S.chars_read = 0;
    S.read_position = 0;
    S.end_position = 0;
    S.owned_by_window_id = win_id;
    S.style[0] = 0;
    S.fixed_pitch = 0;
    S.composite_style[0] = 0;
    return S;
}

§4.

void i7_initialise_streams(i7process_t *proc);
i7word_t i7_open_stream(i7process_t *proc, FILE *F, int win_id);
i7word_t i7_miniglk_stream_open_memory(i7process_t *proc, i7word_t buffer, i7word_t len, i7word_t fmode, i7word_t rock);
i7word_t i7_miniglk_stream_open_memory_uni(i7process_t *proc, i7word_t buffer, i7word_t len, i7word_t fmode, i7word_t rock);
i7word_t i7_miniglk_stream_open_file(i7process_t *proc, i7word_t fileref, i7word_t usage, i7word_t rock);
void i7_miniglk_stream_set_position(i7process_t *proc, i7word_t id, i7word_t pos, i7word_t seekmode);
i7word_t i7_miniglk_stream_get_position(i7process_t *proc, i7word_t id);
void i7_miniglk_stream_close(i7process_t *proc, i7word_t id, i7word_t result);
i7word_t i7_miniglk_window_open(i7process_t *proc, i7word_t split, i7word_t method, i7word_t size, i7word_t wintype, i7word_t rock);
i7word_t i7_stream_of_window(i7process_t *proc, i7word_t id);
i7word_t i7_rock_of_window(i7process_t *proc, i7word_t id);
void i7_to_receiver(i7process_t *proc, i7word_t rock, wchar_t c);
void i7_miniglk_put_char_stream(i7process_t *proc, i7word_t stream_id, i7word_t x);
i7word_t i7_miniglk_get_char_stream(i7process_t *proc, i7word_t stream_id);
void i7_initialise_streams(i7process_t *proc) {
    for (int i=0; i<I7_MINIGLK_MAX_STREAMS; i++) proc->miniglk->memory_streams[i] = i7_new_stream(proc, NULL, 0);
    proc->miniglk->memory_streams[proc->miniglk->stdout_stream_id] = i7_new_stream(proc, stdout, 0);
    proc->miniglk->memory_streams[proc->miniglk->stdout_stream_id].active = 1;
    proc->miniglk->memory_streams[proc->miniglk->stdout_stream_id].encode_UTF8 = 1;
    proc->miniglk->memory_streams[proc->miniglk->stderr_stream_id] = i7_new_stream(proc, stderr, 0);
    proc->miniglk->memory_streams[proc->miniglk->stderr_stream_id].active = 1;
    proc->miniglk->memory_streams[proc->miniglk->stderr_stream_id].encode_UTF8 = 1;
    i7_miniglk_stream_set_current(proc, proc->miniglk->stdout_stream_id);
}

i7word_t i7_open_stream(i7process_t *proc, FILE *F, int win_id) {
    for (int i=0; i<I7_MINIGLK_MAX_STREAMS; i++)
        if (proc->miniglk->memory_streams[i].active == 0) {
            proc->miniglk->memory_streams[i] = i7_new_stream(proc, F, win_id);
            proc->miniglk->memory_streams[i].active = 1;
            proc->miniglk->memory_streams[i].previous_id = proc->state.current_output_stream_ID;
            return i;
        }
    fprintf(stderr, "Out of streams\n"); i7_fatal_exit(proc);
    return 0;
}

i7word_t i7_miniglk_stream_open_memory(i7process_t *proc, i7word_t buffer, i7word_t len, i7word_t fmode, i7word_t rock) {
    if (fmode != 1) { fprintf(stderr, "Only file mode 1 supported, not %d\n", fmode); i7_fatal_exit(proc); }
    i7word_t id = i7_open_stream(proc, NULL, 0);
    proc->miniglk->memory_streams[id].write_here_on_closure = buffer;
    proc->miniglk->memory_streams[id].write_limit = (size_t) len;
    proc->miniglk->memory_streams[id].char_size = 1;
    proc->state.current_output_stream_ID = id;
    return id;
}

i7word_t i7_miniglk_stream_open_memory_uni(i7process_t *proc, i7word_t buffer, i7word_t len, i7word_t fmode, i7word_t rock) {
    if (fmode != 1) { fprintf(stderr, "Only file mode 1 supported, not %d\n", fmode); i7_fatal_exit(proc); }
    i7word_t id = i7_open_stream(proc, NULL, 0);
    proc->miniglk->memory_streams[id].write_here_on_closure = buffer;
    proc->miniglk->memory_streams[id].write_limit = (size_t) len;
    proc->miniglk->memory_streams[id].char_size = 4;
    proc->state.current_output_stream_ID = id;
    return id;
}

i7word_t i7_miniglk_stream_open_file(i7process_t *proc, i7word_t fileref, i7word_t usage, i7word_t rock) {
    i7word_t id = i7_open_stream(proc, NULL, 0);
    proc->miniglk->memory_streams[id].to_file_id = fileref;
    if (i7_fopen(proc, fileref, usage) == 0) return 0;
    return id;
}

void i7_miniglk_stream_set_position(i7process_t *proc, i7word_t id, i7word_t pos, i7word_t seekmode) {
    if ((id < 0) || (id >= I7_MINIGLK_MAX_STREAMS)) { fprintf(stderr, "Stream ID %d out of range\n", id); i7_fatal_exit(proc); }
    i7_mg_stream_t *S = &(proc->miniglk->memory_streams[id]);
    if (S->to_file_id >= 0) {
        int origin;
        switch (seekmode) {
            case seekmode_Start: origin = SEEK_SET; break;
            case seekmode_Current: origin = SEEK_CUR; break;
            case seekmode_End: origin = SEEK_END; break;
            default: fprintf(stderr, "Unknown seekmode\n"); i7_fatal_exit(proc);
        }
        i7_fseek(proc, S->to_file_id, pos, origin);
    } else {
        fprintf(stderr, "glk_stream_set_position supported only for file streams\n"); i7_fatal_exit(proc);
    }
}

i7word_t i7_miniglk_stream_get_position(i7process_t *proc, i7word_t id) {
    if ((id < 0) || (id >= I7_MINIGLK_MAX_STREAMS)) { fprintf(stderr, "Stream ID %d out of range\n", id); i7_fatal_exit(proc); }
    i7_mg_stream_t *S = &(proc->miniglk->memory_streams[id]);
    if (S->to_file_id >= 0) {
        return (i7word_t) i7_ftell(proc, S->to_file_id);
    }
    return (i7word_t) S->memory_used;
}

void i7_miniglk_stream_close(i7process_t *proc, i7word_t id, i7word_t result) {
    if ((id < 0) || (id >= I7_MINIGLK_MAX_STREAMS)) { fprintf(stderr, "Stream ID %d out of range\n", id); i7_fatal_exit(proc); }
    if (id == 0) { fprintf(stderr, "Cannot close stdout\n"); i7_fatal_exit(proc); }
    if (id == 1) { fprintf(stderr, "Cannot close stderr\n"); i7_fatal_exit(proc); }
    i7_mg_stream_t *S = &(proc->miniglk->memory_streams[id]);
    if (S->active == 0) { fprintf(stderr, "Stream %d already closed\n", id); i7_fatal_exit(proc); }
    if (proc->state.current_output_stream_ID == id) proc->state.current_output_stream_ID = S->previous_id;
    if (S->write_here_on_closure != 0) {
        if (S->char_size == 4) {
            for (size_t i = 0; i < S->write_limit; i++)
                if (i < S->memory_used)
                    i7_write_word(proc, S->write_here_on_closure, i, S->to_memory[i]);
                else
                    i7_write_word(proc, S->write_here_on_closure, i, 0);
        } else {
            for (size_t i = 0; i < S->write_limit; i++)
                if (i < S->memory_used)
                    i7_write_byte(proc, S->write_here_on_closure + i, S->to_memory[i]);
                else
                    i7_write_byte(proc, S->write_here_on_closure + i, 0);
        }
    }
    if (result == -1) {
        i7_push(proc, S->chars_read);
        i7_push(proc, S->memory_used);
    } else if (result != 0) {
        i7_write_word(proc, result, 0, S->chars_read);
        i7_write_word(proc, result, 1, S->memory_used);
    }
    if (S->to_file_id >= 0) i7_fclose(proc, S->to_file_id);
    S->active = 0;
    S->memory_used = 0;
}

i7word_t i7_miniglk_window_open(i7process_t *proc, i7word_t split, i7word_t method, i7word_t size, i7word_t wintype, i7word_t rock) {
    if (proc->miniglk->no_windows >= 128) {
        fprintf(stderr, "Out of windows\n"); i7_fatal_exit(proc);
    }
    int id = proc->miniglk->no_windows++;
    proc->miniglk->windows[id].type = wintype;
    proc->miniglk->windows[id].stream_id = i7_open_stream(proc, stdout, id);
    proc->miniglk->windows[id].rock = rock;
    return id;
}

i7word_t i7_stream_of_window(i7process_t *proc, i7word_t id) {
    if ((id < 0) || (id >= proc->miniglk->no_windows)) { fprintf(stderr, "Window ID %d out of range\n", id); i7_fatal_exit(proc); }
    return proc->miniglk->windows[id].stream_id;
}

i7word_t i7_rock_of_window(i7process_t *proc, i7word_t id) {
    if ((id < 0) || (id >= proc->miniglk->no_windows)) { fprintf(stderr, "Window ID %d out of range\n", id); i7_fatal_exit(proc); }
    return proc->miniglk->windows[id].rock;
}

void i7_to_receiver(i7process_t *proc, i7word_t rock, wchar_t c) {
    i7_mg_stream_t *S = &(proc->miniglk->memory_streams[proc->state.current_output_stream_ID]);
    if (proc->receiver == NULL) fputc(c, stdout);
    (proc->receiver)(rock, c, S->composite_style);
}

void i7_miniglk_put_char_stream(i7process_t *proc, i7word_t stream_id, i7word_t x) {
    i7_mg_stream_t *S = &(proc->miniglk->memory_streams[stream_id]);
    if (S->to_file) {
        int win_id = S->owned_by_window_id;
        int rock = -1;
        if (win_id >= 1) rock = i7_rock_of_window(proc, win_id);
        unsigned int c = (unsigned int) x;
        if (proc->use_UTF8) {
            if (c >= 0x800) {
                i7_to_receiver(proc, rock, 0xE0 + (c >> 12));
                i7_to_receiver(proc, rock, 0x80 + ((c >> 6) & 0x3f));
                i7_to_receiver(proc, rock, 0x80 + (c & 0x3f));
            } else if (c >= 0x80) {
                i7_to_receiver(proc, rock, 0xC0 + (c >> 6));
                i7_to_receiver(proc, rock, 0x80 + (c & 0x3f));
            } else i7_to_receiver(proc, rock, (int) c);
        } else {
            i7_to_receiver(proc, rock, (int) c);
        }
    } else if (S->to_file_id >= 0) {
        i7_fputc(proc, (int) x, S->to_file_id);
        S->end_position++;
    } else {
        if (S->memory_used >= S->memory_capacity) {
            size_t needed = 4*S->memory_capacity;
            if (needed == 0) needed = 1024;
            wchar_t *new_data = (wchar_t *) calloc(needed, sizeof(wchar_t));
            if (new_data == NULL) { fprintf(stderr, "Out of memory\n"); i7_fatal_exit(proc); }
            for (size_t i=0; i<S->memory_used; i++) new_data[i] = S->to_memory[i];
            free(S->to_memory);
            S->to_memory = new_data;
        }
        S->to_memory[S->memory_used++] = (wchar_t) x;
    }
}

i7word_t i7_miniglk_get_char_stream(i7process_t *proc, i7word_t stream_id) {
    i7_mg_stream_t *S = &(proc->miniglk->memory_streams[stream_id]);
    if (S->to_file_id >= 0) {
        S->chars_read++;
        return i7_fgetc(proc, S->to_file_id);
    }
    return 0;
}
i7_mg_event_t *i7_next_event(i7process_t *proc);
void i7_make_event(i7process_t *proc, i7_mg_event_t e);
i7word_t i7_miniglk_select(i7process_t *proc, i7word_t structure);
i7word_t i7_miniglk_request_line_event(i7process_t *proc, i7word_t window_id, i7word_t buffer, i7word_t max_len, i7word_t init_len);
i7word_t fn_i7_mgl_IndefArt(i7process_t *proc, i7word_t i7_mgl_local_obj, i7word_t i7_mgl_local_i);
i7word_t fn_i7_mgl_DefArt(i7process_t *proc, i7word_t i7_mgl_local_obj, i7word_t i7_mgl_local_i);
i7word_t fn_i7_mgl_CIndefArt(i7process_t *proc, i7word_t i7_mgl_local_obj, i7word_t i7_mgl_local_i);
i7word_t fn_i7_mgl_CDefArt(i7process_t *proc, i7word_t i7_mgl_local_obj, i7word_t i7_mgl_local_i);
i7word_t fn_i7_mgl_PrintShortName(i7process_t *proc, i7word_t i7_mgl_local_obj, i7word_t i7_mgl_local_i);
void i7_print_name(i7process_t *proc, i7word_t x);
void i7_read(i7process_t *proc, i7word_t x);
i7_mg_event_t *i7_next_event(i7process_t *proc) {
    if (proc->miniglk->rb_front == proc->miniglk->rb_back) return NULL;
    i7_mg_event_t *e = &(proc->miniglk->events_ring_buffer[proc->miniglk->rb_back]);
    proc->miniglk->rb_back++; if (proc->miniglk->rb_back == I7_MINIGLK_RING_BUFFER_SIZE) proc->miniglk->rb_back = 0;
    return e;
}

void i7_make_event(i7process_t *proc, i7_mg_event_t e) {
    proc->miniglk->events_ring_buffer[proc->miniglk->rb_front] = e;
    proc->miniglk->rb_front++; if (proc->miniglk->rb_front == I7_MINIGLK_RING_BUFFER_SIZE) proc->miniglk->rb_front = 0;
}

i7word_t i7_miniglk_select(i7process_t *proc, i7word_t structure) {
    i7_mg_event_t *e = i7_next_event(proc);
    if (e == NULL) {
        fprintf(stderr, "No events available to select\n"); i7_fatal_exit(proc);
    }
    if (structure == -1) {
        i7_push(proc, e->type);
        i7_push(proc, e->win_id);
        i7_push(proc, e->val1);
        i7_push(proc, e->val2);
    } else {
        if (structure) {
            i7_write_word(proc, structure, 0, e->type);
            i7_write_word(proc, structure, 1, e->win_id);
            i7_write_word(proc, structure, 2, e->val1);
            i7_write_word(proc, structure, 3, e->val2);
        }
    }
    return 0;
}

i7word_t i7_miniglk_request_line_event(i7process_t *proc, i7word_t window_id, i7word_t buffer, i7word_t max_len, i7word_t init_len) {
    i7_mg_event_t e;
    e.type = i7_evtype_LineInput;
    e.win_id = window_id;
    e.val1 = 1;
    e.val2 = 0;
    wchar_t c; int pos = init_len;
    if (proc->sender == NULL) i7_benign_exit(proc);
    char *s = (proc->sender)(proc->send_count++);
    int i = 0;
    while (1) {
        c = s[i++];
        if ((c == EOF) || (c == 0) || (c == '\n') || (c == '\r')) break;
        if (pos < max_len) i7_write_byte(proc, buffer + pos++, c);
    }
    if (pos < max_len) i7_write_byte(proc, buffer + pos, 0); else i7_write_byte(proc, buffer + max_len-1, 0);
    e.val1 = pos;
    i7_make_event(proc, e);
    if (proc->miniglk->no_lr++ == 1000) {
        fprintf(stdout, "[Too many line events: terminating to prevent hang]\n"); exit(0);
    }
    return 0;
}


void i7_default_glk(i7process_t *proc, i7word_t glk_api_selector, i7word_t varargc, i7word_t *z) {
    i7_debug_stack("i7_opcode_glk");
    i7word_t args[5] = { 0, 0, 0, 0, 0 }, argc = 0;
    while (varargc > 0) {
        i7word_t v = i7_pull(proc);
        if (argc < 5) args[argc++] = v;
        varargc--;
    }

    int rv = 0;
    switch (glk_api_selector) {
        case i7_glk_gestalt:
            rv = 1; break;
        case i7_glk_window_iterate:
            rv = 0; break;
        case i7_glk_window_open:
            rv = i7_miniglk_window_open(proc, args[0], args[1], args[2], args[3], args[4]); break;
        case i7_glk_set_window:
            i7_miniglk_stream_set_current(proc, i7_stream_of_window(proc, args[0])); break;
        case i7_glk_stream_iterate:
            rv = 0; break;
        case i7_glk_fileref_iterate:
            rv = 0; break;
        case i7_glk_stylehint_set:
            rv = 0; break;
        case i7_glk_schannel_iterate:
            rv = 0; break;
        case i7_glk_schannel_create:
            rv = 0; break;
        case i7_glk_set_style:
            rv = 0; break;
        case i7_glk_window_move_cursor:
            rv = 0; break;
        case i7_glk_stream_get_position:
            rv = i7_miniglk_stream_get_position(proc, args[0]); break;
        case i7_glk_window_get_size:
            if (args[0]) i7_write_word(proc, args[0], 0, 80);
            if (args[1]) i7_write_word(proc, args[1], 0, 8);
            rv = 0; break;
        case i7_glk_request_line_event:
            rv = i7_miniglk_request_line_event(proc, args[0], args[1], args[2], args[3]); break;
        case i7_glk_select:
            rv = i7_miniglk_select(proc, args[0]); break;
        case i7_glk_stream_close:
            i7_miniglk_stream_close(proc, args[0], args[1]); break;
        case i7_glk_stream_set_current:
            i7_miniglk_stream_set_current(proc, args[0]); break;
        case i7_glk_stream_get_current:
            rv = i7_miniglk_stream_get_current(proc); break;
        case i7_glk_stream_open_memory:
            rv = i7_miniglk_stream_open_memory(proc, args[0], args[1], args[2], args[3]); break;
        case i7_glk_stream_open_memory_uni:
            rv = i7_miniglk_stream_open_memory_uni(proc, args[0], args[1], args[2], args[3]); break;
        case i7_glk_fileref_create_by_name:
            rv = i7_miniglk_fileref_create_by_name(proc, args[0], args[1], args[2]); break;
        case i7_glk_fileref_does_file_exist:
            rv = i7_miniglk_fileref_does_file_exist(proc, args[0]); break;
        case i7_glk_stream_open_file:
            rv = i7_miniglk_stream_open_file(proc, args[0], args[1], args[2]); break;
        case i7_glk_fileref_destroy:
            rv = 0; break;
        case i7_glk_char_to_lower:
            rv = args[0];
            if (((rv >= 0x41) && (rv <= 0x5A)) ||
                ((rv >= 0xC0) && (rv <= 0xD6)) ||
                ((rv >= 0xD8) && (rv <= 0xDE))) rv += 32;
            break;
        case i7_glk_char_to_upper:
            rv = args[0];
            if (((rv >= 0x61) && (rv <= 0x7A)) ||
                ((rv >= 0xE0) && (rv <= 0xF6)) ||
                ((rv >= 0xF8) && (rv <= 0xFE))) rv -= 32;
            break;
        case i7_glk_stream_set_position:
            i7_miniglk_stream_set_position(proc, args[0], args[1], args[2]); break;
        case i7_glk_put_char_stream:
            i7_miniglk_put_char_stream(proc, args[0], args[1]); break;
        case i7_glk_get_char_stream:
            rv = i7_miniglk_get_char_stream(proc, args[0]); break;
        default:
            printf("Unimplemented: i7_opcode_glk %d.\n", glk_api_selector); i7_fatal_exit(proc);
            break;
    }
    if (z) *z = rv;
}

void i7_print_name(i7process_t *proc, i7word_t x) {
    fn_i7_mgl_PrintShortName(proc, x, 0);
}

i7word_t fn_i7_mgl_pending_boxed_quotation(i7process_t *proc) {
    return 0;
}