Update Dispatch layer
[projects/chimara/chimara.git] / interpreters / nitfol / z_io.c
1 /*  Nitfol - z-machine interpreter using Glk for output.
2     Copyright (C) 1999  Evin Robertson
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
17
18     The author can be reached at nitfol@deja.com
19 */
20 #include "nitfol.h"
21
22 #define UPPER_WINDOW 1
23 #define LOWER_WINDOW 0
24
25 static zwinid lower_win, upper_win;
26 static zwinid current_window;
27
28
29 #define STREAM1 1
30 #define STREAM2 2
31 #define STREAM3 4
32 #define STREAM4 8
33
34 static strid_t stream2, stream4;
35
36 static int output_stream;
37 static zword stream3_table_starts[16];
38 static zword stream3_table_locations[16];
39 static int stream3_nesting_depth;
40
41
42 static int  font = 1;
43
44 static BOOL abort_output = FALSE; /* quickly stop outputting */
45
46
47 BOOL is_transcripting(void)
48 {
49   return (output_stream & STREAM2) != 0;
50 }
51
52 void set_transcript(strid_t stream)
53 {
54   if(stream) {
55     if(z_memory) {
56       zword flags2 = LOWORD(HD_FLAGS2) | b00000001;
57       LOWORDwrite(HD_FLAGS2, flags2);
58     }
59     stream2 = stream;
60     output_stream |= STREAM2;
61     if(lower_win)
62       z_set_transcript(lower_win, stream2);
63   } else {
64     if(z_memory) {
65       zword flags2 = LOWORD(HD_FLAGS2) & b11111110;
66       LOWORDwrite(HD_FLAGS2, flags2);
67     }
68     output_stream &= ~STREAM2;
69     if(lower_win)
70       z_set_transcript(lower_win, 0);
71   }
72 }
73
74
75 /* initialize the windowing environment */
76 void init_windows(BOOL dofixed, glui32 maxwidth, glui32 maxheight)
77 {
78   z_init_windows(dofixed, draw_upper_callback, upper_mouse_callback,
79                  maxwidth, maxheight, &upper_win, &lower_win);
80
81   current_window = lower_win;
82   output_stream = STREAM1 | (output_stream & STREAM2);
83   stream3_nesting_depth = 0;
84   font = 1;
85
86   if(output_stream & STREAM2) {
87     set_transcript(stream2);
88   } else {
89     set_transcript(0);
90   }
91
92   if(zversion == 6) {
93     v6_main_window_is(lower_win);
94   }
95 }
96
97
98 static int upper_roomname_length;
99
100 static void counting_glk_put_char(int ch)
101 {
102   upper_roomname_length++;
103   glk_put_char(ch);
104 }
105
106 glui32 draw_upper_callback(winid_t win, glui32 width, glui32 height)
107 {
108   glui32 curx = 0, cury = 0;
109   
110   glui32 numlines = 0;
111
112   if(win == NULL || height == 0) {
113     if(zversion <= 3)
114       numlines++;
115     return numlines;
116   }
117   
118   if(zversion <= 3) {
119     zword location = get_var(16);
120     offset short_name_off = object_name(location);
121
122     glk_window_move_cursor(win, 0, cury);
123     
124     if(location && short_name_off) {
125       glk_put_char(' '); curx++;
126       upper_roomname_length = 0;
127       decodezscii(short_name_off, counting_glk_put_char);
128       curx += upper_roomname_length;
129     }
130
131     glk_window_move_cursor(win, width - 8, cury);
132     if((zversion <= 2) || ((LOBYTE(HD_FLAGS1) & 2) == 0)) {
133       if(width > curx + 26) {
134         glk_window_move_cursor(win, width - 24, cury);
135         w_glk_put_string("Score: ");
136         g_print_znumber(get_var(17));
137         
138         glk_window_move_cursor(win, width - 12, cury);
139         w_glk_put_string("Moves: ");
140         g_print_znumber(get_var(18));
141       } else {
142         g_print_znumber(get_var(17)); /* score */
143         glk_put_char('/');
144         g_print_znumber(get_var(18)); /* turns */
145       }
146     } else {
147       const char *ampmstr[8] = { " AM", " PM" };
148       int ampm = 0;
149       zword hours = get_var(17);
150       zword minutes = get_var(18);
151       while(hours >= 12) {
152         hours-=12;
153         ampm ^= 1;
154       }
155       if(hours == 0)
156         hours = 12;
157       if(hours < 10)
158         glk_put_char(' ');
159       g_print_number(hours);
160       glk_put_char(':');
161       if(minutes < 10)
162         glk_put_char('0');
163       g_print_number(minutes);
164       w_glk_put_string(ampmstr[ampm]);
165     }
166     numlines++;
167     cury++;
168     glk_window_move_cursor(win, 0, cury);
169   }
170
171   return numlines;
172 }
173
174 void output_string(const char *s)
175 {
176   while(*s)
177     output_char(*s++);
178 }
179
180 void output_char(int c)
181 {
182   static int starlength = 0;
183
184   if(output_stream & STREAM3) {  /* Table output */
185     zword num_chars = LOWORD(stream3_table_starts[stream3_nesting_depth-1]) +1;
186     if((c < 32 && c !=  13) || (c >= 127 && c <= 159) || (c > 255))
187       c = '?';     /* Section 7.5.3 */
188     LOBYTEwrite(stream3_table_locations[stream3_nesting_depth-1], c);
189     stream3_table_locations[stream3_nesting_depth-1] += 1;
190     LOWORDwrite(stream3_table_starts[stream3_nesting_depth-1], num_chars);
191   } else {
192     if(output_stream & STREAM1) {  /* Normal screen output */
193       if(c >= 155 && c <= 251) {  /* "extra characters" */
194         zword game_unicode_table = header_extension_read(3);
195         if(game_unicode_table && LOBYTE(game_unicode_table) >= (zbyte) (c - 155)) {
196           zword address = game_unicode_table + 1 + (c - 155) * 2;
197           c = LOWORD(address);
198         } else {
199           const unsigned default_unicode_translation[] = {
200             0xe4, 0xf6, 0xfc, 0xc4, 0xd6, 0xdc, 0xdf, 0xbb,
201             0xab, 0xeb, 0xef, 0xff, 0xcb, 0xcf, 0xe1, 0xe9,
202             0xed, 0xf3, 0xfa, 0xfd, 0xc1, 0xc9, 0xcd, 0xd3,
203             0xda, 0xdd, 0xe0, 0xe8, 0xec, 0xf2, 0xf9, 0xc0,
204             0xc8, 0xcc, 0xd2, 0xd9, 0xe2, 0xea, 0xee, 0xf4,
205             0xfb, 0xc2, 0xca, 0xce, 0xd4, 0xdb, 0xe5, 0xc5,
206             0xf8, 0xd8, 0xe3, 0xf1, 0xf5, 0xc3, 0xd1, 0xd5,
207             0xe6, 0xc6, 0xe7, 0xc7, 0xfe, 0xf0, 0xde, 0xd0,
208             0xa3, 0x153, 0x152, 0xa1, 0xbf
209           };
210           c = default_unicode_translation[c - 155];
211         }
212       }
213       if(c == '*') {
214         if(++starlength == 3) /* Three asterisks usually means win or death */
215           if(automap_unexplore())
216             abort_output = TRUE;
217       } else {
218         starlength = 0;
219       }
220
221       if(font == 3) {
222         const char font3trans[] =
223           " <>/"    "\\ --"   "||||"  "--\\/"   /* 32-47 */
224           "\\//\\/\\@ "   "   |"  "|-- "        /* 48-63 */
225           "   /"    "\\/\\ "  "    "  "    "    /* 64-79 */
226           "    "    "####"    "  X+"  "udb*"    /* 80-95 */
227           "?abc"    "defg"    "hijk"  "lmno"    /* 96-111 */
228           "pqrs"    "tuvw"    "xyzU"  "DB?";    /* 112-126 */
229         if(c >= 32 && c <= 126)
230           c = font3trans[c - 32];
231       }
232
233       if(allow_output)
234         z_put_char(current_window, c);
235     }
236   }
237 }
238
239 void n_print_number(unsigned n)
240 {
241   int i;
242   char buffer[12];
243   int length = n_to_decimal(buffer, n);
244   
245   for(i = length - 1; i >= 0; i--)
246     output_char(buffer[i]);
247 }
248
249
250 void g_print_number(unsigned n)
251 {
252   int i;
253   char buffer[12];
254   int length = n_to_decimal(buffer, n);
255
256   for(i = length - 1; i >= 0; i--)
257     glk_put_char(buffer[i]);
258 }
259
260 void g_print_snumber(int n)
261 {
262   if(n < 0) {
263     glk_put_char('-');
264     n = -n;
265   }
266   g_print_number(n);
267 }
268
269 void g_print_znumber(zword n)
270 {
271   if(is_neg(n)) {
272     glk_put_char('-');
273     g_print_number(neg(n));
274   } else {
275     g_print_number(n);
276   }
277 }
278
279 void n_print_znumber(zword n)
280 {
281   if(is_neg(n)) {
282     output_char('-');
283     n_print_number(neg(n));
284   } else {
285     n_print_number(n);
286   }
287 }
288
289
290 void stream4number(unsigned c)
291 {
292   if(output_stream & STREAM4) {
293     glk_stream_set_current(stream4);
294     glk_put_char('[');
295     g_print_number(c);
296     glk_put_char(']');
297     glk_put_char(10);
298   }
299 }
300
301
302 void op_buffer_mode(void)
303 {
304   /* FIXME: Glk can't really do this.
305    * I could rely on the Plotkin Bug to do it, but that's ugly and would
306    * break 20 years from now when somebody fixes it.  I could also print
307    * spaces between each letter, which isn't the intended effect
308    *
309    * For now, do nothing.  Doubt this opcode is used often anyway...
310    */
311 }
312
313
314 void op_check_unicode(void)
315 {
316   unsigned result = 0;
317   if(operand[0] <= 255 &&
318      (glk_gestalt(gestalt_CharOutput, (unsigned char) operand[0]) !=
319       gestalt_CharOutput_CannotPrint))
320     result |= 1;
321   if(operand[0] <= 255 &&
322      (glk_gestalt(gestalt_LineInput, (unsigned char) operand[0]) !=
323       FALSE))
324     result |= 2;
325   mop_store_result(result);
326 }
327
328
329 void op_erase_line(void)
330 {
331   if(!allow_output)
332     return;
333
334   if(operand[0] == 1 && current_window == upper_win) {
335     z_erase_line(current_window);
336   }
337 }
338
339
340 void op_erase_window(void)
341 {
342   if(!allow_output)
343     return;
344
345 #ifdef DEBUG_IO
346   n_show_debug(E_OUTPUT, "erase_window", operand[0]);
347   return;
348 #endif
349
350   switch(operand[0]) {
351   case neg(1):
352     operand[0] = 0; op_split_window();
353     current_window = lower_win;
354   case neg(2):
355   case UPPER_WINDOW:
356     z_clear_window(upper_win);
357     if(operand[0] == UPPER_WINDOW) break; /* Ok, this is evil, but it works. */
358   case LOWER_WINDOW:
359     z_clear_window(lower_win);
360     break;
361   }
362 }
363
364
365 void op_get_cursor(void)
366 {
367   zword x, y;
368   z_getxy(upper_win, &x, &y);
369   LOWORDwrite(operand[0], x);
370   LOWORDwrite(operand[0] + ZWORD_SIZE, y);
371 }
372
373
374 void op_new_line(void)
375 {
376   output_char(13);
377 }
378
379
380 void op_output_stream(void)
381 {
382   if(operand[0] == 0)
383     return;
384   if(is_neg(operand[0])) {
385     switch(neg(operand[0])) {
386     case 1:
387       if(!allow_output)
388         return;
389       output_stream &= ~STREAM1;
390       break;
391
392     case 2:
393       if(!allow_output)
394         return;
395       set_transcript(0);
396       break;
397
398     case 3:
399       if(stream3_nesting_depth)
400         stream3_nesting_depth--;
401       else
402         n_show_error(E_OUTPUT, "stream3 unnested too many times", 0);
403       if(!stream3_nesting_depth)
404         output_stream &= ~STREAM3;
405       break;
406
407     case 4:
408       if(!allow_output)
409         return;
410       glk_stream_close(stream4, NULL);
411       stream4 = 0;
412       output_stream &= ~STREAM4;
413       break;
414
415     default:
416       n_show_error(E_OUTPUT, "unknown stream deselected", neg(operand[0]));
417     }
418   } else {
419     switch(operand[0]) {
420     case 1:
421       if(!allow_output)
422         return;
423       output_stream |= STREAM1;
424       break;
425
426     case 2:
427       if(!allow_output)
428         return;
429       if(!stream2) {
430         stream2 = n_file_prompt(fileusage_Transcript | fileusage_TextMode,
431                                 filemode_WriteAppend);
432       }
433       if(stream2)
434         set_transcript(stream2);
435       break;
436
437     case 3:
438       if(stream3_nesting_depth >= 16) {
439         n_show_error(E_OUTPUT, "nesting stream 3 too deeply",
440                    stream3_nesting_depth);
441         return;
442       }
443       LOWORDwrite(operand[1], 0);
444       stream3_table_starts[stream3_nesting_depth] = operand[1];
445       stream3_table_locations[stream3_nesting_depth] = operand[1] + 2;
446       
447       output_stream |= STREAM3;
448       stream3_nesting_depth++;
449       break;
450         
451     case 4:
452       if(!allow_output)
453         return;
454       stream4 = n_file_prompt(fileusage_InputRecord | fileusage_TextMode,
455                               filemode_WriteAppend);
456       if(stream4)
457         output_stream |= STREAM4;
458       break;
459     default:
460       n_show_error(E_OUTPUT, "unknown stream selected", operand[0]);
461     }
462   }
463 }
464
465
466 void op_print(void)
467 {
468   int length;
469   abort_output = FALSE;
470   length = decodezscii(PC, output_char);
471   if(!abort_output)
472     PC += length;
473 }
474
475
476 void op_print_ret(void)
477 {
478   int length;
479   abort_output = FALSE;
480   length = decodezscii(PC, output_char);
481   if(abort_output)
482     return;
483   PC += length;
484   output_char(13);
485   if(abort_output)
486     return;
487   mop_func_return(1);
488 }
489
490
491 void op_print_addr(void)
492 {
493   decodezscii(operand[0], output_char);
494 }
495
496
497 void op_print_paddr(void)
498 {
499   getstring(operand[0]);
500 }
501
502
503 void op_print_char(void)
504 {
505   if(operand[0] > 1023) {
506     n_show_error(E_INSTR, "attempt to print character > 1023", operand[0]);
507     return;
508   }
509   output_char(operand[0]);
510 }
511
512
513 void op_print_num(void)
514 {
515   n_print_znumber(operand[0]);
516 }
517
518
519 void op_print_table(void)
520 {
521   unsigned x, y;
522   zword text = operand[0];
523   zword width = operand[1];
524   zword height = operand[2];
525   zword skips = operand[3];
526
527   zword startx, starty;
528   unsigned win_width, win_height;
529
530   z_getxy(current_window, &startx, &starty);
531   z_getsize(current_window, &win_width, &win_height);
532
533   if(numoperands < 4)
534     skips = 0;
535   if(numoperands < 3)
536     height = 1;
537
538   if(current_window == upper_win) {
539     if(startx + width - 1 > win_width) {
540       int diff;
541       n_show_warn(E_OUTPUT, "table too wide; trimming", width);
542       diff = startx + width - 1 - win_width;
543       width -= diff;
544       skips += diff;
545     }
546     if(starty + height - 1 > win_height) {
547       n_show_warn(E_OUTPUT, "table too tall; trimming", height);
548       height = win_height - starty + 1;
549     }
550   }
551
552   for(y = 0; y < height; y++) {
553     if(current_window == upper_win && allow_output)
554       z_setxy(upper_win, startx, y+starty);
555
556     for(x = 0; x < width; x++) {
557       output_char(LOBYTE(text));
558       text++;
559     }
560     text += skips;
561
562     if(current_window != upper_win && y+1 < height)
563       output_char(13);
564   }
565 }
566
567
568 void op_set_colour(void)
569 {
570   if(!allow_output)
571     return;
572
573   z_set_color(current_window, operand[0], operand[1]);
574 }
575
576
577 void op_set_cursor(void)
578 {
579   unsigned width, height;
580   zword x = operand[1];
581   zword y = operand[0];
582   
583   if(!allow_output)
584     return;
585   
586 #ifdef DEBUG_IO
587   n_show_debug(E_OUTPUT, "set_cursor y=", operand[0]);
588   n_show_debug(E_OUTPUT, "set_cursor x=", operand[1]);
589 #endif
590
591   if(current_window != upper_win) {
592     return;
593   }
594
595   z_getsize(current_window, &width, &height);
596
597   if(y == 0 || y > height) {    /* section 8.7.2.3 */
598     n_show_error(E_OUTPUT, "illegal line for set_cursor", y);
599     if(y == 0 || y > 512)
600       return;
601     z_set_height(upper_win, y); /* Resize to allow broken games to work */
602   }
603   if(x == 0 || x > width) {
604     n_show_error(E_OUTPUT, "illegal column for set_cursor", x);
605     return;
606   }
607
608   z_setxy(current_window, x, y);
609 }
610
611
612 void op_set_text_style(void)
613 {
614   if(!allow_output)
615     return;
616
617   z_set_style(current_window, operand[0]);
618 }
619
620
621 void op_set_window(void)
622 {
623   if(!allow_output)
624     return;
625
626 #ifdef DEBUG_IO
627   n_show_debug(E_OUTPUT, "set_window", operand[0]);
628   return;
629 #endif
630
631   switch(operand[0]) {
632   case UPPER_WINDOW:
633     current_window = upper_win;
634     z_setxy(upper_win, 1, 1);
635     break;
636   case LOWER_WINDOW:
637     current_window = lower_win;
638     break;
639   default:
640     n_show_error(E_OUTPUT, "invalid window selected", operand[0]);
641   }
642 }
643
644
645 void op_split_window(void)
646 {
647   if(!allow_output)
648     return;
649
650 #ifdef DEBUG_IO
651   n_show_debug(E_OUTPUT, "split_window", operand[0]);
652 #endif
653
654   if(zversion == 6)
655     return;
656
657
658   if(operand[0] > 512) {
659     n_show_error(E_OUTPUT, "game is being ridiculous", operand[0]);
660     return;
661   }
662
663   if(zversion == 3)
664     z_set_height(upper_win, 0);  /* clear the whole upper window first */
665
666   z_set_height(upper_win, operand[0]);
667 }
668
669
670 static BOOL timer_callback(zword routine)
671 {
672   zword dummylocals[16];
673   in_timer = TRUE;
674   mop_call(routine, 0, dummylocals, -2); /* add a special stack frame */
675   decode();                              /* start interpreting the routine */
676   in_timer = FALSE;
677   exit_decoder = FALSE;
678   return time_ret;
679 }
680
681
682 BOOL upper_mouse_callback(BOOL is_char_event, winid_t win, glui32 x, glui32 y)
683 {
684   int i;
685   
686   if(!(LOBYTE(HD_FLAGS2) & b00100000))
687     return FALSE;
688
689   header_extension_write(1, x + 1);
690   header_extension_write(2, y + 1);
691
692   stream4number(254);
693   stream4number(x + 1);
694   stream4number(y + 1);
695
696   if(is_char_event)
697     return TRUE;
698
699   for(i = z_terminators; LOBYTE(i) != 0; i++)
700     if(LOBYTE(i) == 255 || LOBYTE(i) == 254)
701       return TRUE;
702
703   /* @read will not receive mouse input inputs if they're not
704    * terminating characters, but I'm not sure how to reasonably do
705    * that and it shouldn't matter to most things */
706   
707   return FALSE;
708 }
709
710
711 typedef struct alias_entry alias_entry;
712
713 struct alias_entry
714 {
715   alias_entry *next;
716   char *from;
717   char *to;
718   BOOL in_use, is_recursive;
719 };
720
721 static alias_entry *alias_list = NULL;
722
723
724 void parse_new_alias(const char *aliascommand, BOOL is_recursive)
725 {
726   char *from, *to;
727   char *stringcopy = n_strdup(aliascommand);
728   char *pcommand = stringcopy;
729   while(isspace(*pcommand))
730     pcommand++;
731   from = pcommand;
732   while(isgraph(*pcommand))
733     pcommand++;
734   if(*pcommand) {
735     *pcommand = 0;
736     pcommand++;
737   }
738   while(isspace(*pcommand))
739     pcommand++;
740   to = pcommand;
741
742   while(*to == ' ')
743     to++;
744   
745   if(*to == 0) /* Expand blank aliases to a single space */
746     add_alias(from, " ", is_recursive);
747   else
748     add_alias(from, to, is_recursive);
749   free(stringcopy);
750 }
751
752 void add_alias(const char *from, const char *to, BOOL is_recursive)
753 {
754   alias_entry newalias;
755   remove_alias(from);
756   newalias.next = NULL;
757   newalias.from = n_strdup(from);
758   newalias.to = n_strdup(to);
759   newalias.in_use = FALSE;
760   newalias.is_recursive = is_recursive;
761   LEadd(alias_list, newalias);
762 }
763
764
765 BOOL remove_alias(const char *from)
766 {
767   alias_entry *p, *t;
768   while(*from == ' ')
769     from++;
770   LEsearchremove(alias_list, p, t, n_strcmp(p->from, from) == 0, (n_free(p->from), n_free(p->to)));
771   return t != NULL;
772 }
773
774
775 static alias_entry *find_alias(const char *text, int length)
776 {
777   alias_entry *p;
778   LEsearch(alias_list, p, n_strmatch(p->from, text, length));
779   return p;
780 }
781
782
783 int search_for_aliases(char *text, int length, int maxlen)
784 {
785   int word_start = 0;
786   int i;
787   if(!length)
788     return length;
789   for(i = 0; i <= length; i++) {
790     if(i == length || isspace(text[i]) || ispunct(text[i])) {
791       int word_length = i - word_start;
792       if(word_length) {
793         alias_entry *p = find_alias(text + word_start, word_length);
794         if(p && !(p->in_use)) {
795           int newlen = strlen(p->to);
796           if(length - word_length + newlen > maxlen)
797             newlen = maxlen - length + word_length;
798           n_memmove(text + word_start + newlen,
799                     text + word_start + word_length,
800                     maxlen - word_start - MAX(newlen, word_length));
801           n_memcpy(text + word_start, p->to, newlen);
802
803           if(p->is_recursive) {
804             p->in_use = TRUE;
805             newlen = search_for_aliases(text + word_start, newlen, maxlen - word_start);
806             p->in_use = FALSE;
807           }
808           
809           length += newlen - word_length;
810           i = word_start + newlen;
811         }
812       }
813       word_start = i+1;
814     }
815   }
816   return length;
817 }
818
819
820 int n_read(zword dest, unsigned maxlen, zword parse, unsigned initlen,
821            zword timer, zword routine, unsigned char *terminator)
822 {
823   unsigned length;
824   unsigned i;
825   char *buffer = (char *) n_malloc(maxlen + 1);
826   
827 #ifdef SMART_TOKENISER
828   forget_corrections();
829 #endif
830
831   if(false_undo)
832     initlen = 0;
833   false_undo = FALSE;
834
835   if(maxlen < 3)
836     n_show_warn(E_OUTPUT, "small text buffer", maxlen);
837
838   if(dest > dynamic_size || dest < 64) {
839     n_show_error(E_OUTPUT, "input buffer in invalid location", dest);
840     return 0;
841   }
842
843   if(dest + maxlen > dynamic_size) {
844     n_show_error(E_OUTPUT, "input buffer exceeds dynamic memory", dest + maxlen);
845     maxlen = dynamic_size - dest;
846   }
847   
848   if(parse >= dest && dest + maxlen > parse) {
849     n_show_warn(E_OUTPUT, "input buffer overlaps parse", dest + maxlen - parse);
850     maxlen = parse - dest;
851   }
852
853   for(i = 0; i < maxlen; i++)
854     buffer[i] = LOBYTE(dest + i);
855
856   length = z_read(current_window, buffer, maxlen, initlen, timer, timer_callback, routine, terminator);
857     
858   if(read_abort) {
859     n_free(buffer);
860     return 0;
861   }
862
863   length = search_for_aliases(buffer, length, maxlen);
864   
865   for(i = 0; i < length; i++) {
866     buffer[i] = glk_char_to_lower(buffer[i]);
867     LOBYTEwrite(dest + i, buffer[i]);
868   }
869
870   if(parse)
871     z_tokenise(buffer, length, parse, z_dictionary, TRUE);
872
873   n_free(buffer);
874   return length;
875 }
876
877
878 void stream4line(const char *buffer, int length, char terminator)
879 {
880   if(output_stream & STREAM4) {
881     w_glk_put_buffer_stream(stream4, buffer, length);
882     if(terminator != 10)
883       stream4number(terminator);
884     glk_put_char_stream(stream4, 10);
885   }
886 }
887
888
889 void op_sread(void)
890 {
891   unsigned maxlen, length;
892   unsigned char term;
893   zword text = operand[0];
894
895   maxlen = LOBYTE(text) - 1;
896   if(numoperands < 3)
897     operand[2] = 0;
898   if(numoperands < 4)
899     operand[3] = 0;
900
901   length = n_read(text + 1, maxlen, operand[1], 0,
902                   operand[2], operand[3], &term);
903   if(!read_abort) {
904     LOBYTEwrite(text + 1 + length, 0);  /* zero terminator */
905
906     if(allow_saveundo) {
907       if(!has_done_save_undo && auto_save_undo)
908         saveundo(FALSE);
909       has_done_save_undo = FALSE;
910     }
911   }
912 }
913
914 void op_aread(void)
915 {
916   int maxlen, length, initlen;
917   unsigned char term;
918   zword text = operand[0];
919
920   maxlen = LOBYTE(text);
921   initlen = LOBYTE(text + 1);
922   if(numoperands < 3)
923     operand[2] = 0;
924   if(numoperands < 4)
925     operand[3] = 0;
926
927   length = n_read(text + 2, maxlen, operand[1], initlen,
928                 operand[2], operand[3], &term);
929   if(!read_abort) {
930     LOBYTEwrite(text + 1, length);
931     mop_store_result(term);
932   
933     if(allow_saveundo) {
934       if(!has_done_save_undo && auto_save_undo)
935         saveundo(FALSE);
936       has_done_save_undo = FALSE;
937     }
938   }
939 }
940
941 void op_read_char(void)
942 {
943   zword validch = 0;
944
945   if(in_timer) {
946     n_show_error(E_OUTPUT, "input attempted during time routine", 0);
947     mop_store_result(0);
948     return;
949   }
950
951   if(operand[0] != 1) {
952     n_show_warn(E_OUTPUT, "read_char with non-one first operand", operand[0]);
953     mop_store_result(0);
954     return;
955   }
956
957   if(numoperands < 2)
958     operand[1] = 0;
959   if(numoperands < 3)
960     operand[2] = 0;
961
962   validch = z_read_char(current_window, operand[1], timer_callback, operand[2]);
963
964   if(read_abort)
965     return;
966
967   mop_store_result(validch);
968
969   /*
970   if(!has_done_save_undo && auto_save_undo_char) {
971     saveundo(FALSE);
972     has_done_save_undo = FALSE;
973   }
974   */
975   
976   stream4number(validch);
977 }
978
979
980 void op_show_status(void)
981 {
982   if(!in_timer)
983     z_flush_fixed(upper_win);
984 }
985
986 /* Returns a character, or returns 0 if it found a number, which it stores
987    in *num */
988 unsigned char transcript_getchar(unsigned *num)
989 {
990   glsi32 c;
991   *num = 0;
992   if(!input_stream1)
993     return 0;
994
995   c = glk_get_char_stream(input_stream1);
996
997   if(c == GLK_EOF) {
998       glk_stream_close(input_stream1, NULL);
999       input_stream1 = 0;
1000       return 0;
1001   }
1002     
1003   if(c == '[') {
1004     while((c = glk_get_char_stream(input_stream1)) != GLK_EOF) {
1005       if(c == ']')
1006         break;
1007       if(c >= '0' && c <= '9') {
1008         *num = (*num * 10) + (c - '0');
1009       }
1010     }
1011     c = glk_get_char_stream(input_stream1);
1012     if(c != 10)
1013       n_show_error(E_OUTPUT, "input script not understood", c);
1014
1015     return 0;
1016   }
1017   return c;
1018 }
1019
1020 /* Returns line terminator.  Writes up to *len bytes to dest, writing in the
1021    actual number of characters read in *len. */
1022 unsigned char transcript_getline(char *dest, glui32 *length)
1023 {
1024   unsigned char term = 10;
1025   unsigned char c;
1026   unsigned num;
1027   glui32 len;
1028   if(!input_stream1) {
1029     *length = 0;
1030     return 0;
1031   }
1032   for(len = 0; len < *length; len++) {
1033     c = transcript_getchar(&num);
1034     if(!c) {
1035       term = num;
1036       break;
1037     }
1038     if(c == 10)
1039       break;
1040
1041     dest[len] = c;
1042   }
1043   *length = len;
1044   return term;
1045 }
1046
1047
1048 void op_input_stream(void)
1049 {
1050   /* *FIXME*: 10.2.4 says we shouldn't wait for [MORE]. Glk won't allow this */
1051   if(input_stream1)
1052     glk_stream_close(input_stream1, NULL);
1053   input_stream1 = 0;
1054
1055   switch(operand[0]) {
1056   case 0:
1057     break;
1058   case 1:
1059     input_stream1 = n_file_prompt(fileusage_InputRecord | fileusage_TextMode,
1060                                   filemode_Read);
1061     break;
1062   default:
1063     n_show_error(E_OUTPUT, "unknown input stream selected", operand[0]);
1064   }
1065 }
1066
1067
1068 void op_set_font(void)
1069 {
1070   int lastfont;
1071
1072   if(!allow_output) {
1073     mop_store_result(0);
1074     return;
1075   }
1076
1077   lastfont = font;
1078   
1079 #ifdef DEBUG_IO
1080   n_show_debug(E_OUTPUT, "set_font", operand[0]);
1081   return;
1082 #endif
1083
1084   switch(operand[0]) {
1085   case 1: font = 1; break;
1086   case 4: font = 4; break;
1087   case 3: if(enablefont3) font = 3;
1088   default: mop_store_result(0); return;
1089   }
1090   set_fixed(font == 4);
1091
1092   mop_store_result(lastfont);
1093 }
1094
1095
1096 void op_print_unicode(void)
1097 {
1098   if(!allow_output)
1099     return;
1100   if(operand[0] >= 256 || (operand[0] > 127 && operand[0] < 160)) {
1101     output_char('?');
1102     return;
1103   }
1104   if(output_stream & STREAM3) {
1105     if(operand[0] >= 160) {
1106       const unsigned char default_unicode_zscii_translation[] = {
1107         0x00, 0xde, 0x00, 0xdb, 0x00, 0x00, 0x00, 0x00, 
1108         0x00, 0x00, 0x00, 0xa3, 0x00, 0x00, 0x00, 0x00, 
1109         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
1110         0x00, 0x00, 0x00, 0xa2, 0x00, 0x00, 0x00, 0xdf, 
1111         0xba, 0xaf, 0xc4, 0xd0, 0x9e, 0xca, 0xd4, 0xd6, 
1112         0xbb, 0xb0, 0xc5, 0xa7, 0xbc, 0xb1, 0xc6, 0xa8, 
1113         0xda, 0xd1, 0xbd, 0xb2, 0xc7, 0xd2, 0x9f, 0x00, 
1114         0xcc, 0xbe, 0xb3, 0xc8, 0xa0, 0xb4, 0xd9, 0xa1, 
1115         0xb5, 0xa9, 0xbf, 0xcd, 0x9b, 0xc9, 0xd3, 0xd5, 
1116         0xb6, 0xaa, 0xc0, 0xa4, 0xb7, 0xab, 0xc1, 0xa5, 
1117         0xd8, 0xce, 0xb8, 0xac, 0xc2, 0xcf, 0x9c, 0x00, 
1118         0xcb, 0xb9, 0xad, 0xc3, 0x9d, 0xae, 0xd7, 0xa6
1119       };
1120       unsigned char c = default_unicode_zscii_translation[operand[0] - 160];
1121       output_char(c == 0 ? '?' : c);
1122     } else if(operand[0] == 10) {
1123       output_char(13);
1124     } else {
1125       output_char(operand[0]);
1126     }
1127   } else {
1128     if(output_stream & STREAM1) {
1129       z_put_char(current_window, operand[0]);
1130     }
1131   }
1132 }