Glulx contents
Summary.
This segment closely parallels ZMachine.i6t, which provides exactly equivalent functionality (indeed, usually the same-named functions and in the same order) for the Z-machine VM. This is intended to make the rest of the template code independent of the choice of VM, although that is more of an ideal than a reality, because there are so many fiddly differences in some of the grammar and dictionary tables that it is not really practical for the parser (for instance) to call VM-neutral routines to get the data it wants out of these arrays.
Variables and Arrays.
21Array gg_event --> 4;
22Array gg_arguments buffer 28;
23Global gg_mainwin = 0;
24Global gg_statuswin = 0;
25Global gg_quotewin = 0;
26Global gg_scriptfref = 0;
27Global gg_scriptstr = 0;
28Global gg_savestr = 0;
29Global gg_commandstr = 0;
30Global gg_command_reading = 0;
31Global gg_foregroundchan = 0;
32Global gg_backgroundchan = 0;
33
34Constant GLK_NULL 0;
35
36Constant INPUT_BUFFER_LEN = 260;
37Constant MAX_BUFFER_WORDS = 20;
38Constant PARSE_BUFFER_LEN = 61;
39
40Array buffer buffer INPUT_BUFFER_LEN;
41Array buffer2 buffer INPUT_BUFFER_LEN;
42Array buffer3 buffer INPUT_BUFFER_LEN;
43Array parse --> PARSE_BUFFER_LEN;
44Array parse2 --> PARSE_BUFFER_LEN;
Infglk.
This section is a verbatim copy of an invaluable I6 header file originally put together by John Cater but now maintained by Andrew Plotkin. The routines are convenient to have on hand, and also provide a canonical set of I6 names for the many gestalt and other codes.
53
54
55
56Constant evtype_Arrange = 5;
57Constant evtype_CharInput = 2;
58Constant evtype_Hyperlink = 8;
59Constant evtype_LineInput = 3;
60Constant evtype_MouseInput = 4;
61Constant evtype_None = 0;
62Constant evtype_Redraw = 6;
63Constant evtype_SoundNotify = 7;
64Constant evtype_Timer = 1;
65Constant evtype_VolumeNotify = 9;
66Constant filemode_Read = 2;
67Constant filemode_ReadWrite = 3;
68Constant filemode_Write = 1;
69Constant filemode_WriteAppend = 5;
70Constant fileusage_BinaryMode = 0;
71Constant fileusage_Data = 0;
72Constant fileusage_InputRecord = 3;
73Constant fileusage_SavedGame = 1;
74Constant fileusage_TextMode = 256;
75Constant fileusage_Transcript = 2;
76Constant fileusage_TypeMask = 15;
77Constant gestalt_CharInput = 1;
78Constant gestalt_CharOutput = 3;
79Constant gestalt_CharOutput_ApproxPrint = 1;
80Constant gestalt_CharOutput_CannotPrint = 0;
81Constant gestalt_CharOutput_ExactPrint = 2;
82Constant gestalt_DateTime = 20;
83Constant gestalt_DrawImage = 7;
84Constant gestalt_Graphics = 6;
85Constant gestalt_GraphicsCharInput = 23;
86Constant gestalt_GraphicsTransparency = 14;
87Constant gestalt_HyperlinkInput = 12;
88Constant gestalt_Hyperlinks = 11;
89Constant gestalt_LineInput = 2;
90Constant gestalt_LineInputEcho = 17;
91Constant gestalt_LineTerminatorKey = 19;
92Constant gestalt_LineTerminators = 18;
93Constant gestalt_MouseInput = 4;
94Constant gestalt_ResourceStream = 22;
95Constant gestalt_Sound = 8;
96Constant gestalt_Sound2 = 21;
97Constant gestalt_SoundMusic = 13;
98Constant gestalt_SoundNotify = 10;
99Constant gestalt_SoundVolume = 9;
100Constant gestalt_Timer = 5;
101Constant gestalt_Unicode = 15;
102Constant gestalt_UnicodeNorm = 16;
103Constant gestalt_Version = 0;
104Constant imagealign_InlineCenter = 3;
105Constant imagealign_InlineDown = 2;
106Constant imagealign_MarginLeft = 4;
107Constant imagealign_MarginRight = 5;
108Constant imagealign_InlineUp = 1;
109Constant keycode_Delete = 4294967289;
110Constant keycode_Down = 4294967291;
111Constant keycode_End = 4294967283;
112Constant keycode_Escape = 4294967288;
113Constant keycode_Func1 = 4294967279;
114Constant keycode_Func10 = 4294967270;
115Constant keycode_Func11 = 4294967269;
116Constant keycode_Func12 = 4294967268;
117Constant keycode_Func2 = 4294967278;
118Constant keycode_Func3 = 4294967277;
119Constant keycode_Func4 = 4294967276;
120Constant keycode_Func5 = 4294967275;
121Constant keycode_Func6 = 4294967274;
122Constant keycode_Func7 = 4294967273;
123Constant keycode_Func8 = 4294967272;
124Constant keycode_Func9 = 4294967271;
125Constant keycode_Home = 4294967284;
126Constant keycode_Left = 4294967294;
127Constant keycode_MAXVAL = 28;
128Constant keycode_PageDown = 4294967285;
129Constant keycode_PageUp = 4294967286;
130Constant keycode_Return = 4294967290;
131Constant keycode_Right = 4294967293;
132Constant keycode_Tab = 4294967287;
133Constant keycode_Unknown = 4294967295;
134Constant keycode_Up = 4294967292;
135Constant seekmode_Current = 1;
136Constant seekmode_End = 2;
137Constant seekmode_Start = 0;
138Constant style_Alert = 5;
139Constant style_BlockQuote = 7;
140Constant style_Emphasized = 1;
141Constant style_Header = 3;
142Constant style_Input = 8;
143Constant style_NUMSTYLES = 11;
144Constant style_Normal = 0;
145Constant style_Note = 6;
146Constant style_Preformatted = 2;
147Constant style_Subheader = 4;
148Constant style_User1 = 9;
149Constant style_User2 = 10;
150Constant stylehint_BackColor = 8;
151Constant stylehint_Indentation = 0;
152Constant stylehint_Justification = 2;
153Constant stylehint_NUMHINTS = 10;
154Constant stylehint_Oblique = 5;
155Constant stylehint_ParaIndentation = 1;
156Constant stylehint_Proportional = 6;
157Constant stylehint_ReverseColor = 9;
158Constant stylehint_Size = 3;
159Constant stylehint_TextColor = 7;
160Constant stylehint_Weight = 4;
161Constant stylehint_just_Centered = 2;
162Constant stylehint_just_LeftFlush = 0;
163Constant stylehint_just_LeftRight = 1;
164Constant stylehint_just_RightFlush = 3;
165Constant winmethod_Above = 2;
166Constant winmethod_Below = 3;
167Constant winmethod_Border = 0;
168Constant winmethod_BorderMask = 256;
169Constant winmethod_DirMask = 15;
170Constant winmethod_DivisionMask = 240;
171Constant winmethod_Fixed = 16;
172Constant winmethod_Left = 0;
173Constant winmethod_NoBorder = 256;
174Constant winmethod_Proportional = 32;
175Constant winmethod_Right = 1;
176Constant wintype_AllTypes = 0;
177Constant wintype_Blank = 2;
178Constant wintype_Graphics = 5;
179Constant wintype_Pair = 1;
180Constant wintype_TextBuffer = 3;
181Constant wintype_TextGrid = 4;
182
183[ glk_exit _vararg_count;
184
185 @glk 1 _vararg_count 0;
186 return 0;
187];
188
189[ glk_tick _vararg_count;
190
191 @glk 3 _vararg_count 0;
192 return 0;
193];
194
195[ glk_gestalt _vararg_count ret;
196
197 @glk 4 _vararg_count ret;
198 return ret;
199];
200
201[ glk_gestalt_ext _vararg_count ret;
202
203 @glk 5 _vararg_count ret;
204 return ret;
205];
206
207[ glk_window_iterate _vararg_count ret;
208
209 @glk 32 _vararg_count ret;
210 return ret;
211];
212
213[ glk_window_get_rock _vararg_count ret;
214
215 @glk 33 _vararg_count ret;
216 return ret;
217];
218
219[ glk_window_get_root _vararg_count ret;
220
221 @glk 34 _vararg_count ret;
222 return ret;
223];
224
225[ glk_window_open _vararg_count ret;
226
227 @glk 35 _vararg_count ret;
228 return ret;
229];
230
231[ glk_window_close _vararg_count;
232
233 @glk 36 _vararg_count 0;
234 return 0;
235];
236
237[ glk_window_get_size _vararg_count;
238
239 @glk 37 _vararg_count 0;
240 return 0;
241];
242
243[ glk_window_set_arrangement _vararg_count;
244
245 @glk 38 _vararg_count 0;
246 return 0;
247];
248
249[ glk_window_get_arrangement _vararg_count;
250
251 @glk 39 _vararg_count 0;
252 return 0;
253];
254
255[ glk_window_get_type _vararg_count ret;
256
257 @glk 40 _vararg_count ret;
258 return ret;
259];
260
261[ glk_window_get_parent _vararg_count ret;
262
263 @glk 41 _vararg_count ret;
264 return ret;
265];
266
267[ glk_window_clear _vararg_count;
268
269 @glk 42 _vararg_count 0;
270 return 0;
271];
272
273[ glk_window_move_cursor _vararg_count;
274
275 @glk 43 _vararg_count 0;
276 return 0;
277];
278
279[ glk_window_get_stream _vararg_count ret;
280
281 @glk 44 _vararg_count ret;
282 return ret;
283];
284
285[ glk_window_set_echo_stream _vararg_count;
286
287 @glk 45 _vararg_count 0;
288 return 0;
289];
290
291[ glk_window_get_echo_stream _vararg_count ret;
292
293 @glk 46 _vararg_count ret;
294 return ret;
295];
296
297[ glk_set_window _vararg_count;
298
299 @glk 47 _vararg_count 0;
300 return 0;
301];
302
303[ glk_window_get_sibling _vararg_count ret;
304
305 @glk 48 _vararg_count ret;
306 return ret;
307];
308
309[ glk_stream_iterate _vararg_count ret;
310
311 @glk 64 _vararg_count ret;
312 return ret;
313];
314
315[ glk_stream_get_rock _vararg_count ret;
316
317 @glk 65 _vararg_count ret;
318 return ret;
319];
320
321[ glk_stream_open_file _vararg_count ret;
322
323 @glk 66 _vararg_count ret;
324 return ret;
325];
326
327[ glk_stream_open_memory _vararg_count ret;
328
329 @glk 67 _vararg_count ret;
330 return ret;
331];
332
333[ glk_stream_close _vararg_count;
334
335 @glk 68 _vararg_count 0;
336 return 0;
337];
338
339[ glk_stream_set_position _vararg_count;
340
341 @glk 69 _vararg_count 0;
342 return 0;
343];
344
345[ glk_stream_get_position _vararg_count ret;
346
347 @glk 70 _vararg_count ret;
348 return ret;
349];
350
351[ glk_stream_set_current _vararg_count;
352
353 @glk 71 _vararg_count 0;
354 return 0;
355];
356
357[ glk_stream_get_current _vararg_count ret;
358
359 @glk 72 _vararg_count ret;
360 return ret;
361];
362
363[ glk_stream_open_resource _vararg_count ret;
364
365 @glk 73 _vararg_count ret;
366 return ret;
367];
368
369[ glk_fileref_create_temp _vararg_count ret;
370
371 @glk 96 _vararg_count ret;
372 return ret;
373];
374
375[ glk_fileref_create_by_name _vararg_count ret;
376
377 @glk 97 _vararg_count ret;
378 return ret;
379];
380
381[ glk_fileref_create_by_prompt _vararg_count ret;
382
383 @glk 98 _vararg_count ret;
384 return ret;
385];
386
387[ glk_fileref_destroy _vararg_count;
388
389 @glk 99 _vararg_count 0;
390 return 0;
391];
392
393[ glk_fileref_iterate _vararg_count ret;
394
395 @glk 100 _vararg_count ret;
396 return ret;
397];
398
399[ glk_fileref_get_rock _vararg_count ret;
400
401 @glk 101 _vararg_count ret;
402 return ret;
403];
404
405[ glk_fileref_delete_file _vararg_count;
406
407 @glk 102 _vararg_count 0;
408 return 0;
409];
410
411[ glk_fileref_does_file_exist _vararg_count ret;
412
413 @glk 103 _vararg_count ret;
414 return ret;
415];
416
417[ glk_fileref_create_from_fileref _vararg_count ret;
418
419 @glk 104 _vararg_count ret;
420 return ret;
421];
422
423[ glk_put_char _vararg_count;
424
425 @glk 128 _vararg_count 0;
426 return 0;
427];
428
429[ glk_put_char_stream _vararg_count;
430
431 @glk 129 _vararg_count 0;
432 return 0;
433];
434
435[ glk_put_string _vararg_count;
436
437 @glk 130 _vararg_count 0;
438 return 0;
439];
440
441[ glk_put_string_stream _vararg_count;
442
443 @glk 131 _vararg_count 0;
444 return 0;
445];
446
447[ glk_put_buffer _vararg_count;
448
449 @glk 132 _vararg_count 0;
450 return 0;
451];
452
453[ glk_put_buffer_stream _vararg_count;
454
455 @glk 133 _vararg_count 0;
456 return 0;
457];
458
459[ glk_set_style _vararg_count;
460
461 @glk 134 _vararg_count 0;
462 return 0;
463];
464
465[ glk_set_style_stream _vararg_count;
466
467 @glk 135 _vararg_count 0;
468 return 0;
469];
470
471[ glk_get_char_stream _vararg_count ret;
472
473 @glk 144 _vararg_count ret;
474 return ret;
475];
476
477[ glk_get_line_stream _vararg_count ret;
478
479 @glk 145 _vararg_count ret;
480 return ret;
481];
482
483[ glk_get_buffer_stream _vararg_count ret;
484
485 @glk 146 _vararg_count ret;
486 return ret;
487];
488
489[ glk_char_to_lower _vararg_count ret;
490
491 @glk 160 _vararg_count ret;
492 return ret;
493];
494
495[ glk_char_to_upper _vararg_count ret;
496
497 @glk 161 _vararg_count ret;
498 return ret;
499];
500
501[ glk_stylehint_set _vararg_count;
502
503 @glk 176 _vararg_count 0;
504 return 0;
505];
506
507[ glk_stylehint_clear _vararg_count;
508
509 @glk 177 _vararg_count 0;
510 return 0;
511];
512
513[ glk_style_distinguish _vararg_count ret;
514
515 @glk 178 _vararg_count ret;
516 return ret;
517];
518
519[ glk_style_measure _vararg_count ret;
520
521 @glk 179 _vararg_count ret;
522 return ret;
523];
524
525[ glk_select _vararg_count;
526
527 @glk 192 _vararg_count 0;
528 return 0;
529];
530
531[ glk_select_poll _vararg_count;
532
533 @glk 193 _vararg_count 0;
534 return 0;
535];
536
537[ glk_request_line_event _vararg_count;
538
539 @glk 208 _vararg_count 0;
540 return 0;
541];
542
543[ glk_cancel_line_event _vararg_count;
544
545 @glk 209 _vararg_count 0;
546 return 0;
547];
548
549[ glk_request_char_event _vararg_count;
550
551 @glk 210 _vararg_count 0;
552 return 0;
553];
554
555[ glk_cancel_char_event _vararg_count;
556
557 @glk 211 _vararg_count 0;
558 return 0;
559];
560
561[ glk_request_mouse_event _vararg_count;
562
563 @glk 212 _vararg_count 0;
564 return 0;
565];
566
567[ glk_cancel_mouse_event _vararg_count;
568
569 @glk 213 _vararg_count 0;
570 return 0;
571];
572
573[ glk_request_timer_events _vararg_count;
574
575 @glk 214 _vararg_count 0;
576 return 0;
577];
578
579[ glk_image_get_info _vararg_count ret;
580
581 @glk 224 _vararg_count ret;
582 return ret;
583];
584
585[ glk_image_draw _vararg_count ret;
586
587 @glk 225 _vararg_count ret;
588 return ret;
589];
590
591[ glk_image_draw_scaled _vararg_count ret;
592
593 @glk 226 _vararg_count ret;
594 return ret;
595];
596
597[ glk_window_flow_break _vararg_count;
598
599 @glk 232 _vararg_count 0;
600 return 0;
601];
602
603[ glk_window_erase_rect _vararg_count;
604
605 @glk 233 _vararg_count 0;
606 return 0;
607];
608
609[ glk_window_fill_rect _vararg_count;
610
611 @glk 234 _vararg_count 0;
612 return 0;
613];
614
615[ glk_window_set_background_color _vararg_count;
616
617 @glk 235 _vararg_count 0;
618 return 0;
619];
620
621[ glk_schannel_iterate _vararg_count ret;
622
623 @glk 240 _vararg_count ret;
624 return ret;
625];
626
627[ glk_schannel_get_rock _vararg_count ret;
628
629 @glk 241 _vararg_count ret;
630 return ret;
631];
632
633[ glk_schannel_create _vararg_count ret;
634
635 @glk 242 _vararg_count ret;
636 return ret;
637];
638
639[ glk_schannel_destroy _vararg_count;
640
641 @glk 243 _vararg_count 0;
642 return 0;
643];
644
645[ glk_schannel_create_ext _vararg_count ret;
646
647 @glk 244 _vararg_count ret;
648 return ret;
649];
650
651[ glk_schannel_play_multi _vararg_count ret;
652
653 @glk 247 _vararg_count ret;
654 return ret;
655];
656
657[ glk_schannel_play _vararg_count ret;
658
659 @glk 248 _vararg_count ret;
660 return ret;
661];
662
663[ glk_schannel_play_ext _vararg_count ret;
664
665 @glk 249 _vararg_count ret;
666 return ret;
667];
668
669[ glk_schannel_stop _vararg_count;
670
671 @glk 250 _vararg_count 0;
672 return 0;
673];
674
675[ glk_schannel_set_volume _vararg_count;
676
677 @glk 251 _vararg_count 0;
678 return 0;
679];
680
681[ glk_sound_load_hint _vararg_count;
682
683 @glk 252 _vararg_count 0;
684 return 0;
685];
686
687[ glk_schannel_set_volume_ext _vararg_count;
688
689 @glk 253 _vararg_count 0;
690 return 0;
691];
692
693[ glk_schannel_pause _vararg_count;
694
695 @glk 254 _vararg_count 0;
696 return 0;
697];
698
699[ glk_schannel_unpause _vararg_count;
700
701 @glk 255 _vararg_count 0;
702 return 0;
703];
704
705[ glk_set_hyperlink _vararg_count;
706
707 @glk 256 _vararg_count 0;
708 return 0;
709];
710
711[ glk_set_hyperlink_stream _vararg_count;
712
713 @glk 257 _vararg_count 0;
714 return 0;
715];
716
717[ glk_request_hyperlink_event _vararg_count;
718
719 @glk 258 _vararg_count 0;
720 return 0;
721];
722
723[ glk_cancel_hyperlink_event _vararg_count;
724
725 @glk 259 _vararg_count 0;
726 return 0;
727];
728
729[ glk_buffer_to_lower_case_uni _vararg_count ret;
730
731 @glk 288 _vararg_count ret;
732 return ret;
733];
734
735[ glk_buffer_to_upper_case_uni _vararg_count ret;
736
737 @glk 289 _vararg_count ret;
738 return ret;
739];
740
741[ glk_buffer_to_title_case_uni _vararg_count ret;
742
743 @glk 290 _vararg_count ret;
744 return ret;
745];
746
747[ glk_buffer_canon_decompose_uni _vararg_count ret;
748
749 @glk 291 _vararg_count ret;
750 return ret;
751];
752
753[ glk_buffer_canon_normalize_uni _vararg_count ret;
754
755 @glk 292 _vararg_count ret;
756 return ret;
757];
758
759[ glk_put_char_uni _vararg_count;
760
761 @glk 296 _vararg_count 0;
762 return 0;
763];
764
765[ glk_put_string_uni _vararg_count;
766
767 @glk 297 _vararg_count 0;
768 return 0;
769];
770
771[ glk_put_buffer_uni _vararg_count;
772
773 @glk 298 _vararg_count 0;
774 return 0;
775];
776
777[ glk_put_char_stream_uni _vararg_count;
778
779 @glk 299 _vararg_count 0;
780 return 0;
781];
782
783[ glk_put_string_stream_uni _vararg_count;
784
785 @glk 300 _vararg_count 0;
786 return 0;
787];
788
789[ glk_put_buffer_stream_uni _vararg_count;
790
791 @glk 301 _vararg_count 0;
792 return 0;
793];
794
795[ glk_get_char_stream_uni _vararg_count ret;
796
797 @glk 304 _vararg_count ret;
798 return ret;
799];
800
801[ glk_get_buffer_stream_uni _vararg_count ret;
802
803 @glk 305 _vararg_count ret;
804 return ret;
805];
806
807[ glk_get_line_stream_uni _vararg_count ret;
808
809 @glk 306 _vararg_count ret;
810 return ret;
811];
812
813[ glk_stream_open_file_uni _vararg_count ret;
814
815 @glk 312 _vararg_count ret;
816 return ret;
817];
818
819[ glk_stream_open_memory_uni _vararg_count ret;
820
821 @glk 313 _vararg_count ret;
822 return ret;
823];
824
825[ glk_stream_open_resource_uni _vararg_count ret;
826
827 @glk 314 _vararg_count ret;
828 return ret;
829];
830
831[ glk_request_char_event_uni _vararg_count;
832
833 @glk 320 _vararg_count 0;
834 return 0;
835];
836
837[ glk_request_line_event_uni _vararg_count;
838
839 @glk 321 _vararg_count 0;
840 return 0;
841];
842
843[ glk_set_echo_line_event _vararg_count;
844
845 @glk 336 _vararg_count 0;
846 return 0;
847];
848
849[ glk_set_terminators_line_event _vararg_count;
850
851 @glk 337 _vararg_count 0;
852 return 0;
853];
854
855[ glk_current_time _vararg_count;
856
857 @glk 352 _vararg_count 0;
858 return 0;
859];
860
861[ glk_current_simple_time _vararg_count ret;
862
863 @glk 353 _vararg_count ret;
864 return ret;
865];
866
867[ glk_time_to_date_utc _vararg_count;
868
869 @glk 360 _vararg_count 0;
870 return 0;
871];
872
873[ glk_time_to_date_local _vararg_count;
874
875 @glk 361 _vararg_count 0;
876 return 0;
877];
878
879[ glk_simple_time_to_date_utc _vararg_count;
880
881 @glk 362 _vararg_count 0;
882 return 0;
883];
884
885[ glk_simple_time_to_date_local _vararg_count;
886
887 @glk 363 _vararg_count 0;
888 return 0;
889];
890
891[ glk_date_to_time_utc _vararg_count;
892
893 @glk 364 _vararg_count 0;
894 return 0;
895];
896
897[ glk_date_to_time_local _vararg_count;
898
899 @glk 365 _vararg_count 0;
900 return 0;
901];
902
903[ glk_date_to_simple_time_utc _vararg_count ret;
904
905 @glk 366 _vararg_count ret;
906 return ret;
907];
908
909[ glk_date_to_simple_time_local _vararg_count ret;
910
911 @glk 367 _vararg_count ret;
912 return ret;
913];
Rocks.
These are unique ID codes used to mark resources; think of them as inedible cookies.
920Constant GG_MAINWIN_ROCK 201;
921Constant GG_STATUSWIN_ROCK 202;
922Constant GG_QUOTEWIN_ROCK 203;
923Constant GG_SAVESTR_ROCK 301;
924Constant GG_SCRIPTSTR_ROCK 302;
925Constant GG_COMMANDWSTR_ROCK 303;
926Constant GG_COMMANDRSTR_ROCK 304;
927Constant GG_SCRIPTFREF_ROCK 401;
928Constant GG_FOREGROUNDCHAN_ROCK 410;
929Constant GG_BACKGROUNDCHAN_ROCK 411;
Stubs.
These are I6 library-style entry point routines, not used by I7, but retained in case I7 extensions want to do interesting things with Glulx.
936#Stub HandleGlkEvent 2;
937#Stub IdentifyGlkObject 4;
938#Stub InitGlkWindow 1;
Starting Up.
VM_Initialise() is almost the first routine called, except that the "starting the virtual machine" activity is allowed to go first; and, come to think of it, memory allocation has to be set up before even that, and that in turn calls VM_PreInitialise() to do the absolute minimum.
Arrangements are a little different here from on the Z-machine, because some data is retained in the case of a restart.
(Many thanks are due to Eliuk Blau, who found several tricky timing errors here and elsewhere in the Glulx-specific code. Frankly, I feel like hanging a sign on the following routines which reads "Congratulations on bringing light to the Dark Room.")
955[ VM_PreInitialise res;
956 @gestalt 4 2 res;
957 if (res == 0) quit;
958
959 unicode_gestalt_ok = false;
960 if (glk_gestalt(gestalt_Unicode, 0))
961 unicode_gestalt_ok = true;
962
963
964 @setiosys 2 0;
965];
966
967[ VM_Initialise res sty i;
968 @gestalt 4 2 res;
969 if (res == 0) quit;
970
971
972
973
974
975
976 GGRecoverObjects();
977
978
979
980
981 if (glk_gestalt(gestalt_Sound, 0)) {
982 if (gg_foregroundchan == 0)
983 gg_foregroundchan = glk_schannel_create(GG_FOREGROUNDCHAN_ROCK);
984 if (gg_backgroundchan == 0)
985 gg_backgroundchan = glk_schannel_create(GG_BACKGROUNDCHAN_ROCK);
986 }
987
988 #ifdef FIX_RNG;
989 @random 10000 i;
990 i = -i-2000;
991 print "[Random number generator seed is ", i, "]^";
992 @setrandom i;
993 #endif;
994
995 res = InitGlkWindow(0);
996 if (res ~= 0) return;
997
998
999
1000 if (gg_mainwin == 0) {
1001
1002 res = InitGlkWindow(GG_MAINWIN_ROCK);
1003 if (res == 0) {
1004
1005 glk_stylehint_set(wintype_TextBuffer, style_Header, stylehint_Justification, 0);
1006
1007 glk_stylehint_set(wintype_TextBuffer, style_Emphasized, stylehint_Weight, 0);
1008 glk_stylehint_set(wintype_TextBuffer, style_Emphasized, stylehint_Oblique, 1);
1009 gg_mainwin = glk_window_open(0, 0, 0, wintype_TextBuffer, GG_MAINWIN_ROCK);
1010 }
1011 if (gg_mainwin == 0) quit;
1012 } else {
1013
1014 glk_window_clear(gg_mainwin);
1015 }
1016
1017 if (gg_statuswin == 0) {
1018 res = InitGlkWindow(GG_STATUSWIN_ROCK);
1019 if (res == 0) {
1020 statuswin_cursize = statuswin_size;
1021 for (sty=0: sty<style_NUMSTYLES: sty++)
1022 glk_stylehint_set(wintype_TextGrid, sty, stylehint_ReverseColor, 1);
1023 gg_statuswin =
1024 glk_window_open(gg_mainwin, winmethod_Fixed + winmethod_Above,
1025 statuswin_cursize, wintype_TextGrid, GG_STATUSWIN_ROCK);
1026 }
1027 }
1028
1029
1030
1031 glk_set_window(gg_mainwin);
1032
1033 InitGlkWindow(1);
1034
1035
1036 buffer3-->0 = 0;
1037];
1038
1039[ GGRecoverObjects id;
1040
1041
1042
1043
1044 gg_mainwin = 0;
1045 gg_statuswin = 0;
1046 gg_quotewin = 0;
1047 gg_scriptfref = 0;
1048 gg_scriptstr = 0;
1049 gg_savestr = 0;
1050 statuswin_cursize = 0;
1051 gg_foregroundchan = 0;
1052 gg_backgroundchan = 0;
1053 #Ifdef DEBUG;
1054 gg_commandstr = 0;
1055 gg_command_reading = false;
1056 #Endif;
1057
1058 IdentifyGlkObject(0);
1059
1060 id = glk_stream_iterate(0, gg_arguments);
1061 while (id) {
1062 switch (gg_arguments-->0) {
1063 GG_SAVESTR_ROCK: gg_savestr = id;
1064 GG_SCRIPTSTR_ROCK: gg_scriptstr = id;
1065 #Ifdef DEBUG;
1066 GG_COMMANDWSTR_ROCK: gg_commandstr = id;
1067 gg_command_reading = false;
1068 GG_COMMANDRSTR_ROCK: gg_commandstr = id;
1069 gg_command_reading = true;
1070 #Endif;
1071 default: IdentifyGlkObject(1, 1, id, gg_arguments-->0);
1072 }
1073 id = glk_stream_iterate(id, gg_arguments);
1074 }
1075
1076 id = glk_window_iterate(0, gg_arguments);
1077 while (id) {
1078 switch (gg_arguments-->0) {
1079 GG_MAINWIN_ROCK: gg_mainwin = id;
1080 GG_STATUSWIN_ROCK: gg_statuswin = id;
1081 GG_QUOTEWIN_ROCK: gg_quotewin = id;
1082 default: IdentifyGlkObject(1, 0, id, gg_arguments-->0);
1083 }
1084 id = glk_window_iterate(id, gg_arguments);
1085 }
1086
1087 id = glk_fileref_iterate(0, gg_arguments);
1088 while (id) {
1089 switch (gg_arguments-->0) {
1090 GG_SCRIPTFREF_ROCK: gg_scriptfref = id;
1091 default: IdentifyGlkObject(1, 2, id, gg_arguments-->0);
1092 }
1093 id = glk_fileref_iterate(id, gg_arguments);
1094 }
1095
1096 if (glk_gestalt(gestalt_Sound, 0)) {
1097 id = glk_schannel_iterate(0, gg_arguments);
1098 while (id) {
1099 switch (gg_arguments-->0) {
1100 GG_FOREGROUNDCHAN_ROCK: gg_foregroundchan = id;
1101 GG_BACKGROUNDCHAN_ROCK: gg_backgroundchan = id;
1102 default: IdentifyGlkObject(1, 3, id, gg_arguments-->0);
1103 }
1104 id = glk_schannel_iterate(id, gg_arguments);
1105 }
1106 if (gg_foregroundchan ~= 0) { glk_schannel_stop(gg_foregroundchan); }
1107 if (gg_backgroundchan ~= 0) { glk_schannel_stop(gg_backgroundchan); }
1108 }
1109
1110
1111 IdentifyGlkObject(2);
1112];
Enable Acceleration.
This enables use of March 2009 extension to Glulx which optimises the speed of Inform-compiled story files by moving the work of I6 veneer routines into the interpreter itself. It should have no effect on earlier versions of the Glulx VM, which will lack the gestalt for this feature, but nor should it do any harm.
1122[ ENABLE_GLULX_ACCEL_R addr res;
1123 @gestalt 9 0 res;
1124 if (res == 0) return;
1125 addr = #classes_table;
1126 @accelparam 0 addr;
1127 @accelparam 1 INDIV_PROP_START;
1128 @accelparam 2 Class;
1129 @accelparam 3 Object;
1130 @accelparam 4 Routine;
1131 @accelparam 5 String;
1132 addr = #globals_array + WORDSIZE * #g$self;
1133 @accelparam 6 addr;
1134 @accelparam 7 NUM_ATTR_BYTES;
1135 addr = #cpv__start;
1136 @accelparam 8 addr;
1137 @accelfunc 1 Z__Region;
1138 @accelfunc 2 CP__Tab;
1139 @accelfunc 3 RA__Pr;
1140 @accelfunc 4 RL__Pr;
1141 @accelfunc 5 OC__Cl;
1142 @accelfunc 6 RV__Pr;
1143 @accelfunc 7 OP__Pr;
1144 rfalse;
1145];
Release Number.
Like all software, IF story files have release numbers to mark revised versions being circulated: unlike most software, and partly for traditional reasons, the version number is recorded not in some print statement or variable but is branded on, so to speak, in a specific memory location of the story file header.
VM_Describe_Release() describes the release and is used as part of the "banner", IF's equivalent to a title page.
1158[ VM_Describe_Release i;
1159 print "Release ";
1160 @aloads ROM_GAMERELEASE 0 i;
1161 print i;
1162 print " / Serial number ";
1163 for (i=0 : i<6 : i++) print (char) ROM_GAMESERIAL->i;
1164];
The VM must provide three routines for keyboard input:
(a) VM_KeyChar() waits for a key to be pressed and then returns the character chosen as a ZSCII character. (b) VM_KeyDelay(N) waits up to N/10 seconds for a key to be pressed, returning the ZSCII character if so, or 0 if not. (c) VM_ReadKeyboard(b, t) reads a whole newline-terminated command into the buffer b, then parses it into a word stream in the table t.
There are elaborations to due with mouse clicks, but this isn't the place to document all of that.
1180[ VM_KeyChar win nostat done res ix jx ch;
1181 jx = ch;
1182 if (win == 0) win = gg_mainwin;
1183 if (gg_commandstr ~= 0 && gg_command_reading ~= false) {
1184 done = glk_get_line_stream(gg_commandstr, gg_arguments, 31);
1185 if (done == 0) {
1186 glk_stream_close(gg_commandstr, 0);
1187 gg_commandstr = 0;
1188 gg_command_reading = false;
1189
1190 } else {
1191
1192 if (gg_arguments->(done-1) == 10) done = done-1;
1193 res = gg_arguments->0;
1194 if (res == '\') {
1195 res = 0;
1196 for (ix=1 : ix<done : ix++) {
1197 ch = gg_arguments->ix;
1198 if (ch >= '0' && ch <= '9') {
1199 @shiftl res 4 res;
1200 res = res + (ch-'0');
1201 } else if (ch >= 'a' && ch <= 'f') {
1202 @shiftl res 4 res;
1203 res = res + (ch+10-'a');
1204 } else if (ch >= 'A' && ch <= 'F') {
1205 @shiftl res 4 res;
1206 res = res + (ch+10-'A');
1207 }
1208 }
1209 }
1210 jump KCPContinue;
1211 }
1212 }
1213 done = false;
1214 glk_request_char_event(win);
1215 while (~~done) {
1216 glk_select(gg_event);
1217 switch (gg_event-->0) {
1218 5:
1219 if (nostat) {
1220 glk_cancel_char_event(win);
1221 res = $80000000;
1222 done = true;
1223 break;
1224 }
1225 DrawStatusLine();
1226 2:
1227 if (gg_event-->1 == win) {
1228 res = gg_event-->2;
1229 done = true;
1230 }
1231 }
1232 ix = HandleGlkEvent(gg_event, 1, gg_arguments);
1233 if (ix == 2) {
1234 res = gg_arguments-->0;
1235 done = true;
1236 } else if (ix == -1) done = false;
1237 }
1238 if (gg_commandstr ~= 0 && gg_command_reading == false) {
1239 if (res < 32 || res >= 256 || (res == '\' or ' ')) {
1240 glk_put_char_stream(gg_commandstr, '\');
1241 done = 0;
1242 jx = res;
1243 for (ix=0 : ix<8 : ix++) {
1244 @ushiftr jx 28 ch;
1245 @shiftl jx 4 jx;
1246 ch = ch & $0F;
1247 if (ch ~= 0 || ix == 7) done = 1;
1248 if (done) {
1249 if (ch >= 0 && ch <= 9) ch = ch + '0';
1250 else ch = (ch - 10) + 'A';
1251 glk_put_char_stream(gg_commandstr, ch);
1252 }
1253 }
1254 } else {
1255 glk_put_char_stream(gg_commandstr, res);
1256 }
1257 glk_put_char_stream(gg_commandstr, 10);
1258 }
1259 .KCPContinue;
1260 return res;
1261];
1262
1263[ VM_KeyDelay tenths key done ix;
1264 glk_request_char_event(gg_mainwin);
1265 glk_request_timer_events(tenths*100);
1266 while (~~done) {
1267 glk_select(gg_event);
1268 ix = HandleGlkEvent(gg_event, 1, gg_arguments);
1269 if (ix == 2) {
1270 key = gg_arguments-->0;
1271 done = true;
1272 }
1273 else if (ix >= 0 && gg_event-->0 == 1 or 2) {
1274 key = gg_event-->2;
1275 done = true;
1276 }
1277 }
1278 glk_cancel_char_event(gg_mainwin);
1279 glk_request_timer_events(0);
1280 return key;
1281];
1282
1283[ VM_ReadKeyboard a_buffer a_table done ix;
1284 if (gg_commandstr ~= 0 && gg_command_reading ~= false) {
1285 done = glk_get_line_stream(gg_commandstr, a_buffer+WORDSIZE,
1286 (INPUT_BUFFER_LEN-WORDSIZE)-1);
1287 if (done == 0) {
1288 glk_stream_close(gg_commandstr, 0);
1289 gg_commandstr = 0;
1290 gg_command_reading = false;
1291 }
1292 else {
1293
1294 if ((a_buffer+WORDSIZE)->(done-1) == 10) done = done-1;
1295 a_buffer-->0 = done;
1296 VM_Style(INPUT_VMSTY);
1297 glk_put_buffer(a_buffer+WORDSIZE, done);
1298 VM_Style(NORMAL_VMSTY);
1299 print "^";
1300 jump KPContinue;
1301 }
1302 }
1303 done = false;
1304 glk_request_line_event(gg_mainwin, a_buffer+WORDSIZE, INPUT_BUFFER_LEN-WORDSIZE, 0);
1305 while (~~done) {
1306 glk_select(gg_event);
1307 switch (gg_event-->0) {
1308 5:
1309 DrawStatusLine();
1310 3:
1311 if (gg_event-->1 == gg_mainwin) {
1312 a_buffer-->0 = gg_event-->2;
1313 done = true;
1314 }
1315 }
1316 ix = HandleGlkEvent(gg_event, 0, a_buffer);
1317 if (ix == 2) done = true;
1318 else if (ix == -1) done = false;
1319 }
1320 if (gg_commandstr ~= 0 && gg_command_reading == false) {
1321 glk_put_buffer_stream(gg_commandstr, a_buffer+WORDSIZE, a_buffer-->0);
1322 glk_put_char_stream(gg_commandstr, 10);
1323 }
1324 .KPContinue;
1325 VM_Tokenise(a_buffer,a_table);
1326
1327 if (gg_quotewin) {
1328 glk_window_close(gg_quotewin, 0);
1329 gg_quotewin = 0;
1330 }
1331 #ifdef ECHO_COMMANDS;
1332 print "** ";
1333 for (ix=WORDSIZE: ix<(a_buffer-->0)+WORDSIZE: ix++) print (char) a_buffer->ix;
1334 print "^";
1335 #endif;
1336];
Buffer Functions.
A "buffer", in this sense, is an array containing a stream of characters typed from the keyboard; a "parse buffer" is an array which resolves this into individual words, pointing to the relevant entries in the dictionary structure. Because each VM has its own format for each of these arrays (not to mention the dictionary), we have to provide some standard operations needed by the rest of the template as routines for each VM.
VM_CopyBuffer(to, from) copies one buffer into another.
VM_Tokenise(buff, parse_buff) takes the text in the buffer buff and produces the corresponding data in the parse buffer parse_buff – this is called tokenisation since the characters are divided into words: in traditional computing jargon, such clumps of characters treated syntactically as units are called tokens.
LTI_Insert is documented in the DM4 and the LTI prefix stands for "Language To Informese": it's used only by translations into non-English languages of play, and is not called in the template.
1359[ VM_CopyBuffer bto bfrom i;
1360 for (i=0: i<INPUT_BUFFER_LEN: i++) bto->i = bfrom->i;
1361];
1362
1363[ VM_PrintToBuffer buf len a b c;
1364 if (b) {
1365 if (metaclass(a) == Object && a.#b == WORDSIZE
1366 && metaclass(a.b) == String)
1367 buf-->0 = Glulx_PrintAnyToArray(buf+WORDSIZE, len, a.b);
1368 else if (metaclass(a) == Routine)
1369 buf-->0 = Glulx_PrintAnyToArray(buf+WORDSIZE, len, a, b, c);
1370 else
1371 buf-->0 = Glulx_PrintAnyToArray(buf+WORDSIZE, len, a, b);
1372 }
1373 else if (metaclass(a) == Routine)
1374 buf-->0 = Glulx_PrintAnyToArray(buf+WORDSIZE, len, a, b, c);
1375 else
1376 buf-->0 = Glulx_PrintAnyToArray(buf+WORDSIZE, len, a);
1377 if (buf-->0 > len) buf-->0 = len;
1378 return buf-->0;
1379];
1380
1381[ VM_Tokenise buf tab
1382 cx numwords len bx ix wx wpos wlen val res dictlen entrylen;
1383 len = buf-->0;
1384 buf = buf+WORDSIZE;
1385
1386
1387
1388 cx = 0;
1389 numwords = 0;
1390 while (cx < len) {
1391 while (cx < len && buf->cx == ' ') cx++;
1392 if (cx >= len) break;
1393 bx = cx;
1394 if (buf->cx == '.' or ',' or '') cx++;
1395 else {
1396 while (cx < len && buf->cx ~= ' ' or '.' or ',' or '') cx++;
1397 }
1398 tab-->(numwords*3+2) = (cx-bx);
1399 tab-->(numwords*3+3) = WORDSIZE+bx;
1400 numwords++;
1401 if (numwords >= MAX_BUFFER_WORDS) break;
1402 }
1403 tab-->0 = numwords;
1404
1405
1406 dictlen = #dictionary_table-->0;
1407 entrylen = DICT_WORD_SIZE + 7;
1408
1409 for (wx=0 : wx<numwords : wx++) {
1410 wlen = tab-->(wx*3+2);
1411 wpos = tab-->(wx*3+3);
1412
1413
1414
1415 if (wlen > DICT_WORD_SIZE) wlen = DICT_WORD_SIZE;
1416 cx = wpos - WORDSIZE;
1417 for (ix=0 : ix<wlen : ix++) gg_tokenbuf->ix = VM_UpperToLowerCase(buf->(cx+ix));
1418 for (: ix<DICT_WORD_SIZE : ix++) gg_tokenbuf->ix = 0;
1419
1420 val = #dictionary_table + WORDSIZE;
1421 @binarysearch gg_tokenbuf DICT_WORD_SIZE val entrylen dictlen 1 1 res;
1422 tab-->(wx*3+1) = res;
1423 }
1424];
1425
1426[ LTI_Insert i ch b y;
1427
1428
1429
1430 b = buffer;
1431
1432
1433
1434 y = b-->0;
1435 if (y > INPUT_BUFFER_LEN) y = INPUT_BUFFER_LEN;
1436
1437
1438 for (y=y+WORDSIZE : y>i : y--) b->y = b->(y-1);
1439 b->i = ch;
1440
1441
1442 if (b-->0 < INPUT_BUFFER_LEN) (b-->0)++;
1443];
Dictionary Functions.
Again, the dictionary structure is differently arranged on the different VMs. This is a data structure containing, in compressed form, the text of all the words to be recognised by tokenisation (above). In I6 for Glulx, a dictionary word is represented at run-time by its record's address in the dictionary.
VM_InvalidDictionaryAddress(A) tests whether A is a valid record address in the dictionary data structure. In Glulx, dictionary records might in theory be anywhere in the 2 GB or so of possible memory, but we can rule out negative addresses. (This allows -1, say, to be used as a value meaning "not a valid dictionary word".)
VM_DictionaryAddressToNumber(A) and VM_NumberToDictionaryAddress(N) convert between word addresses and their run-time representations: since, on Glulx, they are the same, these are each the identity function.
1462[ VM_InvalidDictionaryAddress addr;
1463 if (addr < 0) rtrue;
1464 rfalse;
1465];
1466
1467[ VM_DictionaryAddressToNumber w; return w; ];
1468[ VM_NumberToDictionaryAddress n; return n; ];
1469
1470Array gg_tokenbuf -> DICT_WORD_SIZE;
1471
1472[ GGWordCompare str1 str2 ix jx;
1473 for (ix=0 : ix<DICT_WORD_SIZE : ix++) {
1474 jx = (str1->ix) - (str2->ix);
1475 if (jx ~= 0) return jx;
1476 }
1477 return 0;
1478];
SHOWVERB support.
Further VM-specific tables cover actions and attributes, and these are used by the SHOWVERB testing command.
1485#Ifdef DEBUG;
1486[ DebugAction a str;
1487 if (a >= 4096) { print "<fake action ", a-4096, ">"; return; }
1488 if (a < 0 || a >= #identifiers_table-->7) print "<invalid action ", a, ">";
1489 else {
1490 str = #identifiers_table-->6;
1491 str = str-->a;
1492 if (str) print (string) str; else print "<unnamed action ", a, ">";
1493 }
1494];
1495
1496[ DebugAttribute a str;
1497 if (a < 0 || a >= NUM_ATTR_BYTES*8) print "<invalid attribute ", a, ">";
1498 else {
1499 str = #identifiers_table-->4;
1500 str = str-->a;
1501 if (str) print (string) str; else print "<unnamed attribute ", a, ">";
1502 }
1503];
1504#Endif;
Command Tables.
The VM is also generated containing a data structure for the grammar produced by I6's Verb and Extend directives: this is essentially a list of command verbs such as DROP or PUSH, together with a list of synonyms, and then the grammar for the subsequent commands to be recognised by the parser.
1514[ VM_CommandTableAddress i;
1515 return (#grammar_table)-->(i+1);
1516];
1517
1518[ VM_PrintCommandWords i wd j dictlen entrylen;
1519 dictlen = #dictionary_table-->0;
1520 entrylen = DICT_WORD_SIZE + 7;
1521 for (j=0 : j<dictlen : j++) {
1522 wd = #dictionary_table + WORDSIZE + entrylen*j;
1523 if (DictionaryWordToVerbNum(wd) == i)
1524 print "", (address) wd, " ";
1525 }
1526];
Random Number Generator.
No routine is needed for extracting a random number, since I6's built-in random function does that, but it's useful to abstract the process of seeding the RNG so that it produces a repeatable sequence of "random" numbers from here on: the necessary opcodes are different for the two VMs.
1535[ VM_Seed_RNG n;
1536 @setrandom n;
1537];
Memory Allocation.
This is dynamic memory allocation: something which is never practicable in the Z-machine, because the whole address range is already claimed, but which is viable on recent revisions of Glulx.
1545[ VM_AllocateMemory amount i;
1546 @gestalt 7 0 i;
1547 if (i == 0) return i;
1548 @malloc amount i;
1549 return i;
1550];
1551
1552[ VM_FreeMemory address i;
1553 @gestalt 7 0 i;
1554 if (i == 0) return;
1555 @mfree address;
1556];
Audiovisual Resources.
The Z-machine only barely supports figures and sound effects, so Glulx is the preferred VM to choose if they are wanted. Properly speaking, it's not Glulx which supports these, but its I/O layer Glk, and implementations of Glk are free to support them or not as they please: "cheapglk", a dumb terminal version, does not, for instance. We therefore have to investigate the "gestalt" to find out.
1567[ VM_Picture resource_ID;
1568 if (glk_gestalt(gestalt_Graphics, 0)) {
1569 glk_image_draw(gg_mainwin, resource_ID, imagealign_InlineCenter, 0);
1570 } else {
1571 print "[Picture number ", resource_ID, " here.]^";
1572 }
1573];
1574
1575[ VM_SoundEffect resource_ID;
1576 if (glk_gestalt(gestalt_Sound, 0)) {
1577 glk_schannel_play(gg_foregroundchan, resource_ID);
1578 } else {
1579 print "[Sound effect number ", resource_ID, " here.]^";
1580 }
1581];
Typography.
Glk makes an attempt to present typographic styles as being a matter of semantic markup rather than controlling the actual appearance of text: the idea is that the story file should want to print something in a heading kind of way, and then the interpreter – guided by the player's reading preferences – might set that in bold, or larger type, or red ink, or any combination of the three, or with other effects entirely. This is not the place to discuss whether that was a wise decision for Glk to take (it really, really, really wasn't): we can only play along.
1594[ VM_Style sty;
1595 switch (sty) {
1596 NORMAL_VMSTY: glk_set_style(style_Normal);
1597 HEADER_VMSTY: glk_set_style(style_Header);
1598 SUBHEADER_VMSTY: glk_set_style(style_Subheader);
1599 NOTE_VMSTY: glk_set_style(style_Note);
1600 ALERT_VMSTY: glk_set_style(style_Alert);
1601 BLOCKQUOTE_VMSTY: glk_set_style(style_BlockQuote);
1602 INPUT_VMSTY: glk_set_style(style_Input);
1603 }
1604];
Character Casing.
The following are the equivalent of tolower and toupper, the traditional C library functions for forcing letters into lower and upper case form, for the ZSCII character set. Note that Glulx can also use Unicode characters for some purposes (Unicode was a relatively late addition to the Glulx standard), and we make good use of this when storing text.
1614[ VM_UpperToLowerCase c; return glk_char_to_lower(c); ];
1615[ VM_LowerToUpperCase c; return glk_char_to_upper(c); ];
Glulx-Only Printing Routines.
Partly because of the smallness of the range of representable values in the Z-machine, there is little run-time type-checking that can be done: for instance a dictionary address cannot be distinguished from a function address because they are encoded differently, so that a function address (which is packed) could well coincide with that of a dictionary word (which is not). On Glulx these restrictions are somewhat lifted, so that it's possible to write a routine which can look at a value, work out what it must mean, and print it suitably. This is only possible up to a point – for instance, it can't distinguish an integer from a function address – and in I7 the use of this sort of trick is much less important because type-checking in the NI compiler handles the problem much better. Still, we retain some Glulx-only features because they are convenient for writing external files to disc, for instance, something which the Z-machine can't do in any case.
Glulx_PrintAnything handles strings, functions (with optional arguments), objects, object properties (with optional arguments), and dictionary words.
Glulx_PrintAnyToArray does the same, but the output is sent to a byte array in memory. The first two arguments must be the array address and length; subsequent arguments are as for Glulx_PrintAnything. The return value is the number of characters output. If the output is longer than the array length given, the extra characters are discarded, so the array does not overflow. (However, the return value is the total length of the output, including discarded characters.) The character set stored here is ZSCII, not Unicode.
Glulx_ChangeAnyToCString calls Glulx_PrintAnyToArray on a particular array, then amends the result to make it a C-style string – that is, a sequence of byte-sized characters which are null terminated. The character set stored here is once again ZSCII, not Unicode.
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661[ Glulx_PrintAnything _vararg_count obj mclass;
1662 if (_vararg_count == 0) return;
1663 @copy sp obj;
1664 _vararg_count--;
1665 if (obj == 0) return;
1666
1667 if (obj->0 == $60) {
1668
1669 print (address) obj;
1670 return;
1671 }
1672
1673 mclass = metaclass(obj);
1674 switch (mclass) {
1675 nothing:
1676 return;
1677 String:
1678 print (string) obj;
1679 return;
1680 Routine:
1681
1682
1683 @call obj _vararg_count 0;
1684 return;
1685 Object:
1686 if (_vararg_count == 0) {
1687 print (name) obj;
1688 }
1689 else {
1690
1691
1692 @copy obj sp;
1693 _vararg_count++;
1694 @call CA__Pr _vararg_count 0;
1695 }
1696 return;
1697 }
1698];
1699
1700[ Glulx_PrintAnyToArray _vararg_count arr arrlen str oldstr len;
1701 @copy sp arr;
1702 @copy sp arrlen;
1703 _vararg_count = _vararg_count - 2;
1704
1705 oldstr = glk_stream_get_current();
1706 str = glk_stream_open_memory(arr, arrlen, 1, 0);
1707 if (str == 0) return 0;
1708
1709 glk_stream_set_current(str);
1710
1711 @call Glulx_PrintAnything _vararg_count 0;
1712
1713 glk_stream_set_current(oldstr);
1714 @copy $ffffffff sp;
1715 @copy str sp;
1716 @glk $0044 2 0;
1717 @copy sp len;
1718 @copy sp 0;
1719 return len;
1720];
1721
1722Constant GG_ANYTOSTRING_LEN 66;
1723Array AnyToStrArr -> GG_ANYTOSTRING_LEN+1;
1724
1725[ Glulx_ChangeAnyToCString _vararg_count ix len;
1726 ix = GG_ANYTOSTRING_LEN-2;
1727 @copy ix sp;
1728 ix = AnyToStrArr+1;
1729 @copy ix sp;
1730 ix = _vararg_count+2;
1731 @call Glulx_PrintAnyToArray ix len;
1732 AnyToStrArr->0 = $E0;
1733 if (len >= GG_ANYTOSTRING_LEN)
1734 len = GG_ANYTOSTRING_LEN-1;
1735 AnyToStrArr->(len+1) = 0;
1736 return AnyToStrArr;
1737];
The Screen.
Our generic screen model is that the screen is made up of windows: we tend to refer only to two of these, the main window and the status line, but others may also exist from time to time. Windows have unique ID numbers: the special window ID -1 means "all windows" or "the entire screen", which usually amounts to the same thing.
Screen height and width are measured in characters, with respect to the fixed-pitch font used for the status line. The main window normally contains variable-pitch text which may even have been kerned, and character dimensions make little sense there.
1752[ VM_ClearScreen window;
1753 if (window == WIN_ALL or WIN_MAIN) {
1754 glk_window_clear(gg_mainwin);
1755 if (gg_quotewin) {
1756 glk_window_close(gg_quotewin, 0);
1757 gg_quotewin = 0;
1758 }
1759 }
1760 if (gg_statuswin && window == WIN_ALL or WIN_STATUS) glk_window_clear(gg_statuswin);
1761];
1762
1763[ VM_ScreenWidth id;
1764 id=gg_mainwin;
1765 if (gg_statuswin && statuswin_current) id = gg_statuswin;
1766 glk_window_get_size(id, gg_arguments, 0);
1767 return gg_arguments-->0;
1768];
1769
1770[ VM_ScreenHeight;
1771 glk_window_get_size(gg_mainwin, 0, gg_arguments);
1772 return gg_arguments-->0;
1773];
Window Colours.
Our generic screen model is that the screen is made up of windows, each of which can have its own foreground and background colours.
The colour of individual letters or words of type is not controllable in Glulx, to the frustration of many, and so the template layer of I7 has no framework for handling this (even though it is controllable on the Z-machine, which is greatly superior in this respect).
1785[ VM_SetWindowColours f b window doclear i fwd bwd swin;
1786 if (clr_on && f && b) {
1787 if (window) swin = 5-window;
1788
1789 fwd = MakeColourWord(f);
1790 bwd = MakeColourWord(b);
1791 for (i=0 : i<style_NUMSTYLES: i++) {
1792 if (f == CLR_DEFAULT || b == CLR_DEFAULT) {
1793 glk_stylehint_clear(swin, i, stylehint_TextColor);
1794 glk_stylehint_clear(swin, i, stylehint_BackColor);
1795 } else {
1796 glk_stylehint_set(swin, i, stylehint_TextColor, fwd);
1797 glk_stylehint_set(swin, i, stylehint_BackColor, bwd);
1798 }
1799 }
1800
1801
1802 if (gg_statuswin) glk_window_close(gg_statuswin, 0);
1803 gg_statuswin = 0;
1804
1805 if (doclear || ( window ~= 1 && (clr_fg ~= f || clr_bg ~= b) ) ) {
1806 glk_window_close(gg_mainwin, 0);
1807 gg_mainwin = glk_window_open(0, 0, 0, wintype_TextBuffer, GG_MAINWIN_ROCK);
1808 if (gg_scriptstr ~= 0)
1809 glk_window_set_echo_stream(gg_mainwin, gg_scriptstr);
1810 }
1811
1812 gg_statuswin =
1813 glk_window_open(gg_mainwin, winmethod_Fixed + winmethod_Above,
1814 statuswin_cursize, wintype_TextGrid, GG_STATUSWIN_ROCK);
1815 if (statuswin_current && gg_statuswin) VM_MoveCursorInStatusLine(); else VM_MainWindow();
1816
1817 if (window ~= 2) {
1818 clr_fgstatus = f;
1819 clr_bgstatus = b;
1820 }
1821 if (window ~= 1) {
1822 clr_fg = f;
1823 clr_bg = b;
1824 }
1825 }
1826];
1827
1828[ VM_RestoreWindowColours;
1829 if (clr_on) {
1830 VM_SetWindowColours(clr_fg, clr_bg, 2);
1831 VM_SetWindowColours(clr_fgstatus, clr_bgstatus, 1, true);
1832 VM_ClearScreen();
1833 }
1834];
1835
1836[ MakeColourWord c;
1837 if (c > 9) return c;
1838 c = c-2;
1839 return $ff0000*(c&1) + $ff00*(c&2 ~= 0) + $ff*(c&4 ~= 0);
1840];
Main Window.
The part of the screen on which commands and responses are printed, which ordinarily occupies almost all of the screen area.
VM_MainWindow() switches printing back from another window, usually the status line, to the main window.
1850[ VM_MainWindow;
1851 glk_set_window(gg_mainwin);
1852 statuswin_current=0;
1853];
Status Line.
Despite the name, the status line need not be a single line at the top of the screen: that's only the conventional default arrangement. It can expand to become the equivalent of an old-fashioned VT220 terminal, with menus and grids and mazes displayed lovingly in character graphics, or it can close up to invisibility.
VM_StatusLineHeight(n) sets the status line to have a height of n lines of type. (The width of the status line is always the width of the whole screen, and the position is always at the top, so the height is the only controllable aspect.) The n=0 case makes the status line disappear.
VM_MoveCursorInStatusLine(x, y) switches printing to the status line, positioning the "cursor" – the position at which printing will begin – at the given character grid position (x, y). Line 1 represents the top line; line 2 is underneath, and so on; columns are similarly numbered from 1 at the left.
1874[ VM_StatusLineHeight hgt;
1875 if (gg_statuswin == 0) return;
1876 if (hgt == statuswin_cursize) return;
1877 glk_window_set_arrangement(glk_window_get_parent(gg_statuswin), $12, hgt, 0);
1878 statuswin_cursize = hgt;
1879];
1880
1881[ VM_MoveCursorInStatusLine line column;
1882 if (gg_statuswin == 0) return;
1883 glk_set_window(gg_statuswin);
1884 if (line == 0) { line = 1; column = 1; }
1885 glk_window_move_cursor(gg_statuswin, column-1, line-1);
1886 statuswin_current=1;
1887];
Quotation Boxes.
On the Z-machine, quotation boxes are produced by stretching the status line, but on Glulx they usually occupy windows of their own. If it isn't possible to create such a window, so that gg_quotewin is zero below, the quotation text just appears in the main window.
1896[ Box__Routine maxwid arr ix lines lastnl parwin;
1897 maxwid = 0;
1898 lines = arr-->0;
1899
1900 if (gg_quotewin == 0) {
1901 gg_arguments-->0 = lines;
1902 ix = InitGlkWindow(GG_QUOTEWIN_ROCK);
1903 if (ix == 0)
1904 gg_quotewin =
1905 glk_window_open(gg_mainwin, winmethod_Fixed + winmethod_Above,
1906 lines, wintype_TextBuffer, GG_QUOTEWIN_ROCK);
1907 } else {
1908 parwin = glk_window_get_parent(gg_quotewin);
1909 glk_window_set_arrangement(parwin, $12, lines, 0);
1910 }
1911
1912 lastnl = true;
1913 if (gg_quotewin) {
1914 glk_window_clear(gg_quotewin);
1915 glk_set_window(gg_quotewin);
1916 lastnl = false;
1917 }
1918
1919 VM_Style(BLOCKQUOTE_VMSTY);
1920 for (ix=0 : ix<lines : ix++) {
1921 print (string) arr-->(ix+1);
1922 if (ix < lines-1 || lastnl) new_line;
1923 }
1924 VM_Style(NORMAL_VMSTY);
1925
1926 if (gg_quotewin) glk_set_window(gg_mainwin);
1927];
GlkList Command.
GLKLIST is a testing command best used by those who understand Glulx and its ways: it isn't documented in the I7 manual, because it is pretty inscrutable for "real" users, but it's probably worth keeping just the same.
1935#Ifdef DEBUG;
1936[ GlkListSub id val;
1937 id = glk_window_iterate(0, gg_arguments);
1938 while (id) {
1939 print "Window ", id, " (", gg_arguments-->0, "): ";
1940 val = glk_window_get_type(id);
1941 switch (val) {
1942 1: print "pair";
1943 2: print "blank";
1944 3: print "textbuffer";
1945 4: print "textgrid";
1946 5: print "graphics";
1947 default: print "unknown";
1948 }
1949 val = glk_window_get_parent(id);
1950 if (val) print ", parent is window ", val;
1951 else print ", no parent (root)";
1952 val = glk_window_get_stream(id);
1953 print ", stream ", val;
1954 val = glk_window_get_echo_stream(id);
1955 if (val) print ", echo stream ", val;
1956 print "^";
1957 id = glk_window_iterate(id, gg_arguments);
1958 }
1959 id = glk_stream_iterate(0, gg_arguments);
1960 while (id) {
1961 print "Stream ", id, " (", gg_arguments-->0, ")^";
1962 id = glk_stream_iterate(id, gg_arguments);
1963 }
1964 id = glk_fileref_iterate(0, gg_arguments);
1965 while (id) {
1966 print "Fileref ", id, " (", gg_arguments-->0, ")^";
1967 id = glk_fileref_iterate(id, gg_arguments);
1968 }
1969 if (glk_gestalt(gestalt_Sound, 0)) {
1970 id = glk_schannel_iterate(0, gg_arguments);
1971 while (id) {
1972 print "Soundchannel ", id, " (", gg_arguments-->0, ")^";
1973 id = glk_schannel_iterate(id, gg_arguments);
1974 }
1975 }
1976];
1977
1978{-testing-command:glklist}
1979 * -> Glklist;
1980#Endif;
Undo.
These are really emulations of the Z-machine's conventions on UNDO: Glulx's undo opcodes used different result codes while providing essentially the same functionality, for reasons which are opaque, but no trouble is caused thereby.
1988[ VM_Undo result_code;
1989 @restoreundo result_code;
1990 return (~~result_code);
1991];
1992
1993[ VM_Save_Undo result_code;
1994 @saveundo result_code;
1995 if (result_code == -1) { GGRecoverObjects(); return 2; }
1996 return (~~result_code);
1997];
2002[ QUIT_THE_GAME_R;
2003 if (actor ~= player) rfalse;
2004 if ((actor == player) && (untouchable_silence == false))
2005 QUIT_THE_GAME_RM('A');
2006 if (YesOrNo()~=0) quit;
2007];
2012[ RESTART_THE_GAME_R;
2013 if (actor ~= player) rfalse;
2014 RESTART_THE_GAME_RM('A');
2015 if (YesOrNo()~=0) {
2016 @restart;
2017 RESTART_THE_GAME_RM('B'); new_line;
2018 }
2019];
2024[ RESTORE_THE_GAME_R res fref;
2025 if (actor ~= player) rfalse;
2026 fref = glk_fileref_create_by_prompt($01, $02, 0);
2027 if (fref == 0) jump RFailed;
2028 gg_savestr = glk_stream_open_file(fref, $02, GG_SAVESTR_ROCK);
2029 glk_fileref_destroy(fref);
2030 if (gg_savestr == 0) jump RFailed;
2031 @restore gg_savestr res;
2032 glk_stream_close(gg_savestr, 0);
2033 gg_savestr = 0;
2034 .RFailed;
2035 RESTORE_THE_GAME_RM('A'); new_line;
2036];
2041[ SAVE_THE_GAME_R res fref;
2042 if (actor ~= player) rfalse;
2043 fref = glk_fileref_create_by_prompt($01, $01, 0);
2044 if (fref == 0) jump SFailed;
2045 gg_savestr = glk_stream_open_file(fref, $01, GG_SAVESTR_ROCK);
2046 glk_fileref_destroy(fref);
2047 if (gg_savestr == 0) jump SFailed;
2048 @save gg_savestr res;
2049 if (res == -1) {
2050
2051
2052 GGRecoverObjects();
2053 glk_stream_close(gg_savestr, 0);
2054 gg_savestr = 0;
2055 RESTORE_THE_GAME_RM('B'); new_line;
2056 rtrue;
2057 }
2058 glk_stream_close(gg_savestr, 0);
2059 gg_savestr = 0;
2060 if (res == 0) { SAVE_THE_GAME_RM('B'); new_line; rtrue; }
2061 .SFailed;
2062 SAVE_THE_GAME_RM('A'); new_line;
2063];
Verify The Story File Rule.
This is a fossil now, really, but in the days of Infocom, the 110K story file occupying an entire disc was a huge data set: floppy discs were by no means a reliable medium, and cheap hardware often used hit-and-miss components, as on the notorious Commodore 64 disc controller. If somebody experienced an apparent bug in play, it could easily be that he had a corrupt disc or was unable to read data of that density. So the VERIFY command, which took up to ten minutes on some early computers, would chug through the entire story file and compute a checksum, compare it against a known result in the header, and determine that the story file could or could not properly be read. The Z-machine provided this service as an opcode, and so Glulx followed suit.
2079[ VERIFY_THE_STORY_FILE_R res;
2080 if (actor ~= player) rfalse;
2081 @verify res;
2082 if (res == 0) { VERIFY_THE_STORY_FILE_RM('A'); new_line; rtrue; }
2083 VERIFY_THE_STORY_FILE_RM('B'); new_line;
2084];
Switch Transcript On Rule.
2089[ SWITCH_TRANSCRIPT_ON_R;
2090 if (actor ~= player) rfalse;
2091 if (gg_scriptstr ~= 0) { SWITCH_TRANSCRIPT_ON_RM('A'); new_line; rtrue; }
2092 if (gg_scriptfref == 0) {
2093 gg_scriptfref = glk_fileref_create_by_prompt($102, $05, GG_SCRIPTFREF_ROCK);
2094 if (gg_scriptfref == 0) jump S1Failed;
2095 }
2096
2097 gg_scriptstr = glk_stream_open_file(gg_scriptfref, $05, GG_SCRIPTSTR_ROCK);
2098 if (gg_scriptstr == 0) jump S1Failed;
2099 glk_window_set_echo_stream(gg_mainwin, gg_scriptstr);
2100 SWITCH_TRANSCRIPT_ON_RM('B'); new_line;
2101 VersionSub();
2102 return;
2103 .S1Failed;
2104 SWITCH_TRANSCRIPT_ON_RM('C'); new_line;
2105];
Switch Transcript Off Rule.
2110[ SWITCH_TRANSCRIPT_OFF_R;
2111 if (actor ~= player) rfalse;
2112 if (gg_scriptstr == 0) { SWITCH_TRANSCRIPT_OFF_RM('A'); new_line; rtrue; }
2113 SWITCH_TRANSCRIPT_OFF_RM('B'); new_line;
2114 glk_stream_close(gg_scriptstr, 0);
2115 gg_scriptstr = 0;
2116];
Announce Story File Version Rule.
2121[ ANNOUNCE_STORY_FILE_VERSION_R ix;
2122 if (actor ~= player) rfalse;
2123 Banner();
2124 print "Identification number: ";
2125 for (ix=6: ix <= UUID_ARRAY->0: ix++) print (char) UUID_ARRAY->ix;
2126 print "^";
2127 @gestalt 1 0 ix;
2128 print "Interpreter version ", ix / $10000, ".", (ix & $FF00) / $100,
2129 ".", ix & $FF, " / ";
2130 @gestalt 0 0 ix;
2131 print "VM ", ix / $10000, ".", (ix & $FF00) / $100, ".", ix & $FF, " / ";
2132 print "Library serial number ", (string) LibSerial, "^";
2133 #Ifdef LanguageVersion;
2134 print (string) LanguageVersion, "^";
2135 #Endif;
2136 ShowExtensionVersions();
2137 say__p = 1;
2138];
Descend To Specific Action Rule.
There are 100 or so actions, typically, and this rule is for efficiency's sake: rather than perform 100 or so comparisons to see which routine to call, we indirect through a jump table. The routines called are the -Sub routines: thus, for instance, if action is ##Wait then WaitSub is called. It is essential that this routine not be called for fake actions: in I7 use this is guaranteed, since fake actions are not allowed into the action machinery at all.
Strangely, Glulx's action routines table is numbered in an off-by-one way compared to the Z-machine's: hence the +1.
2153[ DESCEND_TO_SPECIFIC_ACTION_R;
2154 indirect(#actions_table-->(action+1));
2155 rtrue;
2156];
2161[ Unsigned__Compare x y;
2162 @jleu x y ?lesseq;
2163 return 1;
2164 .lesseq;
2165 @jeq x y ?equal;
2166 return -1;
2167 .equal;
2168 return 0;
2169];
2170
2171[ RT__ChLDW x y;
2172 @aload x y sp;
2173 @return sp;
2174];
2175
2176[ RT__ChLDB x y;
2177 @aloadb x y sp;
2178 @return sp;
2179];