B/glut: Glulx Template. @Purpose: To start up the Glk interface for the Glulx virtual machine, and provide Glulx-specific printing functions. @------------------------------------------------------------------------------- @p 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. @p Variables and Arrays. @c Array gg_event --> 4; Array gg_arguments buffer 28; Global gg_mainwin = 0; Global gg_statuswin = 0; Global gg_quotewin = 0; Global gg_scriptfref = 0; Global gg_scriptstr = 0; Global gg_savestr = 0; Global gg_commandstr = 0; Global gg_command_reading = 0; ! true if gg_commandstr is being replayed Global gg_foregroundchan = 0; Global gg_backgroundchan = 0; Constant GLK_NULL 0; Constant INPUT_BUFFER_LEN = 260; ! No extra byte necessary Constant MAX_BUFFER_WORDS = 20; Constant PARSE_BUFFER_LEN = 61; Array buffer buffer INPUT_BUFFER_LEN; Array buffer2 buffer INPUT_BUFFER_LEN; Array buffer3 buffer INPUT_BUFFER_LEN; Array parse --> PARSE_BUFFER_LEN; Array parse2 --> PARSE_BUFFER_LEN; @p 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. @c ! infglk.h -- auto-generated by parse_dispatch.py. ! Generated for Glk API version 0.7.4 Constant evtype_Arrange = 5; Constant evtype_CharInput = 2; Constant evtype_Hyperlink = 8; Constant evtype_LineInput = 3; Constant evtype_MouseInput = 4; Constant evtype_None = 0; Constant evtype_Redraw = 6; Constant evtype_SoundNotify = 7; Constant evtype_Timer = 1; Constant evtype_VolumeNotify = 9; Constant filemode_Read = 2; Constant filemode_ReadWrite = 3; Constant filemode_Write = 1; Constant filemode_WriteAppend = 5; Constant fileusage_BinaryMode = 0; Constant fileusage_Data = 0; Constant fileusage_InputRecord = 3; Constant fileusage_SavedGame = 1; Constant fileusage_TextMode = 256; Constant fileusage_Transcript = 2; Constant fileusage_TypeMask = 15; Constant gestalt_CharInput = 1; Constant gestalt_CharOutput = 3; Constant gestalt_CharOutput_ApproxPrint = 1; Constant gestalt_CharOutput_CannotPrint = 0; Constant gestalt_CharOutput_ExactPrint = 2; Constant gestalt_DateTime = 20; Constant gestalt_DrawImage = 7; Constant gestalt_Graphics = 6; Constant gestalt_GraphicsCharInput = 23; Constant gestalt_GraphicsTransparency = 14; Constant gestalt_HyperlinkInput = 12; Constant gestalt_Hyperlinks = 11; Constant gestalt_LineInput = 2; Constant gestalt_LineInputEcho = 17; Constant gestalt_LineTerminatorKey = 19; Constant gestalt_LineTerminators = 18; Constant gestalt_MouseInput = 4; Constant gestalt_ResourceStream = 22; Constant gestalt_Sound = 8; Constant gestalt_Sound2 = 21; Constant gestalt_SoundMusic = 13; Constant gestalt_SoundNotify = 10; Constant gestalt_SoundVolume = 9; Constant gestalt_Timer = 5; Constant gestalt_Unicode = 15; Constant gestalt_UnicodeNorm = 16; Constant gestalt_Version = 0; Constant imagealign_InlineCenter = 3; Constant imagealign_InlineDown = 2; Constant imagealign_MarginLeft = 4; Constant imagealign_MarginRight = 5; Constant imagealign_InlineUp = 1; Constant keycode_Delete = 4294967289; Constant keycode_Down = 4294967291; Constant keycode_End = 4294967283; Constant keycode_Escape = 4294967288; Constant keycode_Func1 = 4294967279; Constant keycode_Func10 = 4294967270; Constant keycode_Func11 = 4294967269; Constant keycode_Func12 = 4294967268; Constant keycode_Func2 = 4294967278; Constant keycode_Func3 = 4294967277; Constant keycode_Func4 = 4294967276; Constant keycode_Func5 = 4294967275; Constant keycode_Func6 = 4294967274; Constant keycode_Func7 = 4294967273; Constant keycode_Func8 = 4294967272; Constant keycode_Func9 = 4294967271; Constant keycode_Home = 4294967284; Constant keycode_Left = 4294967294; Constant keycode_MAXVAL = 28; Constant keycode_PageDown = 4294967285; Constant keycode_PageUp = 4294967286; Constant keycode_Return = 4294967290; Constant keycode_Right = 4294967293; Constant keycode_Tab = 4294967287; Constant keycode_Unknown = 4294967295; Constant keycode_Up = 4294967292; Constant seekmode_Current = 1; Constant seekmode_End = 2; Constant seekmode_Start = 0; Constant style_Alert = 5; Constant style_BlockQuote = 7; Constant style_Emphasized = 1; Constant style_Header = 3; Constant style_Input = 8; Constant style_NUMSTYLES = 11; Constant style_Normal = 0; Constant style_Note = 6; Constant style_Preformatted = 2; Constant style_Subheader = 4; Constant style_User1 = 9; Constant style_User2 = 10; Constant stylehint_BackColor = 8; Constant stylehint_Indentation = 0; Constant stylehint_Justification = 2; Constant stylehint_NUMHINTS = 10; Constant stylehint_Oblique = 5; Constant stylehint_ParaIndentation = 1; Constant stylehint_Proportional = 6; Constant stylehint_ReverseColor = 9; Constant stylehint_Size = 3; Constant stylehint_TextColor = 7; Constant stylehint_Weight = 4; Constant stylehint_just_Centered = 2; Constant stylehint_just_LeftFlush = 0; Constant stylehint_just_LeftRight = 1; Constant stylehint_just_RightFlush = 3; Constant winmethod_Above = 2; Constant winmethod_Below = 3; Constant winmethod_Border = 0; Constant winmethod_BorderMask = 256; Constant winmethod_DirMask = 15; Constant winmethod_DivisionMask = 240; Constant winmethod_Fixed = 16; Constant winmethod_Left = 0; Constant winmethod_NoBorder = 256; Constant winmethod_Proportional = 32; Constant winmethod_Right = 1; Constant wintype_AllTypes = 0; Constant wintype_Blank = 2; Constant wintype_Graphics = 5; Constant wintype_Pair = 1; Constant wintype_TextBuffer = 3; Constant wintype_TextGrid = 4; [ glk_exit _vararg_count; ! glk_exit() @glk 1 _vararg_count 0; return 0; ]; [ glk_tick _vararg_count; ! glk_tick() @glk 3 _vararg_count 0; return 0; ]; [ glk_gestalt _vararg_count ret; ! glk_gestalt(uint, uint) => uint @glk 4 _vararg_count ret; return ret; ]; [ glk_gestalt_ext _vararg_count ret; ! glk_gestalt_ext(uint, uint, uintarray, arraylen) => uint @glk 5 _vararg_count ret; return ret; ]; [ glk_window_iterate _vararg_count ret; ! glk_window_iterate(window, &uint) => window @glk 32 _vararg_count ret; return ret; ]; [ glk_window_get_rock _vararg_count ret; ! glk_window_get_rock(window) => uint @glk 33 _vararg_count ret; return ret; ]; [ glk_window_get_root _vararg_count ret; ! glk_window_get_root() => window @glk 34 _vararg_count ret; return ret; ]; [ glk_window_open _vararg_count ret; ! glk_window_open(window, uint, uint, uint, uint) => window @glk 35 _vararg_count ret; return ret; ]; [ glk_window_close _vararg_count; ! glk_window_close(window, &{uint, uint}) @glk 36 _vararg_count 0; return 0; ]; [ glk_window_get_size _vararg_count; ! glk_window_get_size(window, &uint, &uint) @glk 37 _vararg_count 0; return 0; ]; [ glk_window_set_arrangement _vararg_count; ! glk_window_set_arrangement(window, uint, uint, window) @glk 38 _vararg_count 0; return 0; ]; [ glk_window_get_arrangement _vararg_count; ! glk_window_get_arrangement(window, &uint, &uint, &window) @glk 39 _vararg_count 0; return 0; ]; [ glk_window_get_type _vararg_count ret; ! glk_window_get_type(window) => uint @glk 40 _vararg_count ret; return ret; ]; [ glk_window_get_parent _vararg_count ret; ! glk_window_get_parent(window) => window @glk 41 _vararg_count ret; return ret; ]; [ glk_window_clear _vararg_count; ! glk_window_clear(window) @glk 42 _vararg_count 0; return 0; ]; [ glk_window_move_cursor _vararg_count; ! glk_window_move_cursor(window, uint, uint) @glk 43 _vararg_count 0; return 0; ]; [ glk_window_get_stream _vararg_count ret; ! glk_window_get_stream(window) => stream @glk 44 _vararg_count ret; return ret; ]; [ glk_window_set_echo_stream _vararg_count; ! glk_window_set_echo_stream(window, stream) @glk 45 _vararg_count 0; return 0; ]; [ glk_window_get_echo_stream _vararg_count ret; ! glk_window_get_echo_stream(window) => stream @glk 46 _vararg_count ret; return ret; ]; [ glk_set_window _vararg_count; ! glk_set_window(window) @glk 47 _vararg_count 0; return 0; ]; [ glk_window_get_sibling _vararg_count ret; ! glk_window_get_sibling(window) => window @glk 48 _vararg_count ret; return ret; ]; [ glk_stream_iterate _vararg_count ret; ! glk_stream_iterate(stream, &uint) => stream @glk 64 _vararg_count ret; return ret; ]; [ glk_stream_get_rock _vararg_count ret; ! glk_stream_get_rock(stream) => uint @glk 65 _vararg_count ret; return ret; ]; [ glk_stream_open_file _vararg_count ret; ! glk_stream_open_file(fileref, uint, uint) => stream @glk 66 _vararg_count ret; return ret; ]; [ glk_stream_open_memory _vararg_count ret; ! glk_stream_open_memory(nativechararray, arraylen, uint, uint) => stream @glk 67 _vararg_count ret; return ret; ]; [ glk_stream_close _vararg_count; ! glk_stream_close(stream, &{uint, uint}) @glk 68 _vararg_count 0; return 0; ]; [ glk_stream_set_position _vararg_count; ! glk_stream_set_position(stream, int, uint) @glk 69 _vararg_count 0; return 0; ]; [ glk_stream_get_position _vararg_count ret; ! glk_stream_get_position(stream) => uint @glk 70 _vararg_count ret; return ret; ]; [ glk_stream_set_current _vararg_count; ! glk_stream_set_current(stream) @glk 71 _vararg_count 0; return 0; ]; [ glk_stream_get_current _vararg_count ret; ! glk_stream_get_current() => stream @glk 72 _vararg_count ret; return ret; ]; [ glk_stream_open_resource _vararg_count ret; ! glk_stream_open_resource(uint, uint) => stream @glk 73 _vararg_count ret; return ret; ]; [ glk_fileref_create_temp _vararg_count ret; ! glk_fileref_create_temp(uint, uint) => fileref @glk 96 _vararg_count ret; return ret; ]; [ glk_fileref_create_by_name _vararg_count ret; ! glk_fileref_create_by_name(uint, string, uint) => fileref @glk 97 _vararg_count ret; return ret; ]; [ glk_fileref_create_by_prompt _vararg_count ret; ! glk_fileref_create_by_prompt(uint, uint, uint) => fileref @glk 98 _vararg_count ret; return ret; ]; [ glk_fileref_destroy _vararg_count; ! glk_fileref_destroy(fileref) @glk 99 _vararg_count 0; return 0; ]; [ glk_fileref_iterate _vararg_count ret; ! glk_fileref_iterate(fileref, &uint) => fileref @glk 100 _vararg_count ret; return ret; ]; [ glk_fileref_get_rock _vararg_count ret; ! glk_fileref_get_rock(fileref) => uint @glk 101 _vararg_count ret; return ret; ]; [ glk_fileref_delete_file _vararg_count; ! glk_fileref_delete_file(fileref) @glk 102 _vararg_count 0; return 0; ]; [ glk_fileref_does_file_exist _vararg_count ret; ! glk_fileref_does_file_exist(fileref) => uint @glk 103 _vararg_count ret; return ret; ]; [ glk_fileref_create_from_fileref _vararg_count ret; ! glk_fileref_create_from_fileref(uint, fileref, uint) => fileref @glk 104 _vararg_count ret; return ret; ]; [ glk_put_char _vararg_count; ! glk_put_char(uchar) @glk 128 _vararg_count 0; return 0; ]; [ glk_put_char_stream _vararg_count; ! glk_put_char_stream(stream, uchar) @glk 129 _vararg_count 0; return 0; ]; [ glk_put_string _vararg_count; ! glk_put_string(string) @glk 130 _vararg_count 0; return 0; ]; [ glk_put_string_stream _vararg_count; ! glk_put_string_stream(stream, string) @glk 131 _vararg_count 0; return 0; ]; [ glk_put_buffer _vararg_count; ! glk_put_buffer(nativechararray, arraylen) @glk 132 _vararg_count 0; return 0; ]; [ glk_put_buffer_stream _vararg_count; ! glk_put_buffer_stream(stream, nativechararray, arraylen) @glk 133 _vararg_count 0; return 0; ]; [ glk_set_style _vararg_count; ! glk_set_style(uint) @glk 134 _vararg_count 0; return 0; ]; [ glk_set_style_stream _vararg_count; ! glk_set_style_stream(stream, uint) @glk 135 _vararg_count 0; return 0; ]; [ glk_get_char_stream _vararg_count ret; ! glk_get_char_stream(stream) => int @glk 144 _vararg_count ret; return ret; ]; [ glk_get_line_stream _vararg_count ret; ! glk_get_line_stream(stream, nativechararray, arraylen) => uint @glk 145 _vararg_count ret; return ret; ]; [ glk_get_buffer_stream _vararg_count ret; ! glk_get_buffer_stream(stream, nativechararray, arraylen) => uint @glk 146 _vararg_count ret; return ret; ]; [ glk_char_to_lower _vararg_count ret; ! glk_char_to_lower(uchar) => uchar @glk 160 _vararg_count ret; return ret; ]; [ glk_char_to_upper _vararg_count ret; ! glk_char_to_upper(uchar) => uchar @glk 161 _vararg_count ret; return ret; ]; [ glk_stylehint_set _vararg_count; ! glk_stylehint_set(uint, uint, uint, int) @glk 176 _vararg_count 0; return 0; ]; [ glk_stylehint_clear _vararg_count; ! glk_stylehint_clear(uint, uint, uint) @glk 177 _vararg_count 0; return 0; ]; [ glk_style_distinguish _vararg_count ret; ! glk_style_distinguish(window, uint, uint) => uint @glk 178 _vararg_count ret; return ret; ]; [ glk_style_measure _vararg_count ret; ! glk_style_measure(window, uint, uint, &uint) => uint @glk 179 _vararg_count ret; return ret; ]; [ glk_select _vararg_count; ! glk_select(&{uint, window, uint, uint}) @glk 192 _vararg_count 0; return 0; ]; [ glk_select_poll _vararg_count; ! glk_select_poll(&{uint, window, uint, uint}) @glk 193 _vararg_count 0; return 0; ]; [ glk_request_line_event _vararg_count; ! glk_request_line_event(window, nativechararray, arraylen, uint) @glk 208 _vararg_count 0; return 0; ]; [ glk_cancel_line_event _vararg_count; ! glk_cancel_line_event(window, &{uint, window, uint, uint}) @glk 209 _vararg_count 0; return 0; ]; [ glk_request_char_event _vararg_count; ! glk_request_char_event(window) @glk 210 _vararg_count 0; return 0; ]; [ glk_cancel_char_event _vararg_count; ! glk_cancel_char_event(window) @glk 211 _vararg_count 0; return 0; ]; [ glk_request_mouse_event _vararg_count; ! glk_request_mouse_event(window) @glk 212 _vararg_count 0; return 0; ]; [ glk_cancel_mouse_event _vararg_count; ! glk_cancel_mouse_event(window) @glk 213 _vararg_count 0; return 0; ]; [ glk_request_timer_events _vararg_count; ! glk_request_timer_events(uint) @glk 214 _vararg_count 0; return 0; ]; [ glk_image_get_info _vararg_count ret; ! glk_image_get_info(uint, &uint, &uint) => uint @glk 224 _vararg_count ret; return ret; ]; [ glk_image_draw _vararg_count ret; ! glk_image_draw(window, uint, int, int) => uint @glk 225 _vararg_count ret; return ret; ]; [ glk_image_draw_scaled _vararg_count ret; ! glk_image_draw_scaled(window, uint, int, int, uint, uint) => uint @glk 226 _vararg_count ret; return ret; ]; [ glk_window_flow_break _vararg_count; ! glk_window_flow_break(window) @glk 232 _vararg_count 0; return 0; ]; [ glk_window_erase_rect _vararg_count; ! glk_window_erase_rect(window, int, int, uint, uint) @glk 233 _vararg_count 0; return 0; ]; [ glk_window_fill_rect _vararg_count; ! glk_window_fill_rect(window, uint, int, int, uint, uint) @glk 234 _vararg_count 0; return 0; ]; [ glk_window_set_background_color _vararg_count; ! glk_window_set_background_color(window, uint) @glk 235 _vararg_count 0; return 0; ]; [ glk_schannel_iterate _vararg_count ret; ! glk_schannel_iterate(schannel, &uint) => schannel @glk 240 _vararg_count ret; return ret; ]; [ glk_schannel_get_rock _vararg_count ret; ! glk_schannel_get_rock(schannel) => uint @glk 241 _vararg_count ret; return ret; ]; [ glk_schannel_create _vararg_count ret; ! glk_schannel_create(uint) => schannel @glk 242 _vararg_count ret; return ret; ]; [ glk_schannel_destroy _vararg_count; ! glk_schannel_destroy(schannel) @glk 243 _vararg_count 0; return 0; ]; [ glk_schannel_create_ext _vararg_count ret; ! glk_schannel_create_ext(uint, uint) => schannel @glk 244 _vararg_count ret; return ret; ]; [ glk_schannel_play_multi _vararg_count ret; ! glk_schannel_play_multi(schannelarray, arraylen, uintarray, arraylen, uint) => uint @glk 247 _vararg_count ret; return ret; ]; [ glk_schannel_play _vararg_count ret; ! glk_schannel_play(schannel, uint) => uint @glk 248 _vararg_count ret; return ret; ]; [ glk_schannel_play_ext _vararg_count ret; ! glk_schannel_play_ext(schannel, uint, uint, uint) => uint @glk 249 _vararg_count ret; return ret; ]; [ glk_schannel_stop _vararg_count; ! glk_schannel_stop(schannel) @glk 250 _vararg_count 0; return 0; ]; [ glk_schannel_set_volume _vararg_count; ! glk_schannel_set_volume(schannel, uint) @glk 251 _vararg_count 0; return 0; ]; [ glk_sound_load_hint _vararg_count; ! glk_sound_load_hint(uint, uint) @glk 252 _vararg_count 0; return 0; ]; [ glk_schannel_set_volume_ext _vararg_count; ! glk_schannel_set_volume_ext(schannel, uint, uint, uint) @glk 253 _vararg_count 0; return 0; ]; [ glk_schannel_pause _vararg_count; ! glk_schannel_pause(schannel) @glk 254 _vararg_count 0; return 0; ]; [ glk_schannel_unpause _vararg_count; ! glk_schannel_unpause(schannel) @glk 255 _vararg_count 0; return 0; ]; [ glk_set_hyperlink _vararg_count; ! glk_set_hyperlink(uint) @glk 256 _vararg_count 0; return 0; ]; [ glk_set_hyperlink_stream _vararg_count; ! glk_set_hyperlink_stream(stream, uint) @glk 257 _vararg_count 0; return 0; ]; [ glk_request_hyperlink_event _vararg_count; ! glk_request_hyperlink_event(window) @glk 258 _vararg_count 0; return 0; ]; [ glk_cancel_hyperlink_event _vararg_count; ! glk_cancel_hyperlink_event(window) @glk 259 _vararg_count 0; return 0; ]; [ glk_buffer_to_lower_case_uni _vararg_count ret; ! glk_buffer_to_lower_case_uni(uintarray, arraylen, uint) => uint @glk 288 _vararg_count ret; return ret; ]; [ glk_buffer_to_upper_case_uni _vararg_count ret; ! glk_buffer_to_upper_case_uni(uintarray, arraylen, uint) => uint @glk 289 _vararg_count ret; return ret; ]; [ glk_buffer_to_title_case_uni _vararg_count ret; ! glk_buffer_to_title_case_uni(uintarray, arraylen, uint, uint) => uint @glk 290 _vararg_count ret; return ret; ]; [ glk_buffer_canon_decompose_uni _vararg_count ret; ! glk_buffer_canon_decompose_uni(uintarray, arraylen, uint) => uint @glk 291 _vararg_count ret; return ret; ]; [ glk_buffer_canon_normalize_uni _vararg_count ret; ! glk_buffer_canon_normalize_uni(uintarray, arraylen, uint) => uint @glk 292 _vararg_count ret; return ret; ]; [ glk_put_char_uni _vararg_count; ! glk_put_char_uni(uint) @glk 296 _vararg_count 0; return 0; ]; [ glk_put_string_uni _vararg_count; ! glk_put_string_uni(unicode) @glk 297 _vararg_count 0; return 0; ]; [ glk_put_buffer_uni _vararg_count; ! glk_put_buffer_uni(uintarray, arraylen) @glk 298 _vararg_count 0; return 0; ]; [ glk_put_char_stream_uni _vararg_count; ! glk_put_char_stream_uni(stream, uint) @glk 299 _vararg_count 0; return 0; ]; [ glk_put_string_stream_uni _vararg_count; ! glk_put_string_stream_uni(stream, unicode) @glk 300 _vararg_count 0; return 0; ]; [ glk_put_buffer_stream_uni _vararg_count; ! glk_put_buffer_stream_uni(stream, uintarray, arraylen) @glk 301 _vararg_count 0; return 0; ]; [ glk_get_char_stream_uni _vararg_count ret; ! glk_get_char_stream_uni(stream) => int @glk 304 _vararg_count ret; return ret; ]; [ glk_get_buffer_stream_uni _vararg_count ret; ! glk_get_buffer_stream_uni(stream, uintarray, arraylen) => uint @glk 305 _vararg_count ret; return ret; ]; [ glk_get_line_stream_uni _vararg_count ret; ! glk_get_line_stream_uni(stream, uintarray, arraylen) => uint @glk 306 _vararg_count ret; return ret; ]; [ glk_stream_open_file_uni _vararg_count ret; ! glk_stream_open_file_uni(fileref, uint, uint) => stream @glk 312 _vararg_count ret; return ret; ]; [ glk_stream_open_memory_uni _vararg_count ret; ! glk_stream_open_memory_uni(uintarray, arraylen, uint, uint) => stream @glk 313 _vararg_count ret; return ret; ]; [ glk_stream_open_resource_uni _vararg_count ret; ! glk_stream_open_resource_uni(uint, uint) => stream @glk 314 _vararg_count ret; return ret; ]; [ glk_request_char_event_uni _vararg_count; ! glk_request_char_event_uni(window) @glk 320 _vararg_count 0; return 0; ]; [ glk_request_line_event_uni _vararg_count; ! glk_request_line_event_uni(window, uintarray, arraylen, uint) @glk 321 _vararg_count 0; return 0; ]; [ glk_set_echo_line_event _vararg_count; ! glk_set_echo_line_event(window, uint) @glk 336 _vararg_count 0; return 0; ]; [ glk_set_terminators_line_event _vararg_count; ! glk_set_terminators_line_event(window, uintarray, arraylen) @glk 337 _vararg_count 0; return 0; ]; [ glk_current_time _vararg_count; ! glk_current_time(&{int, uint, int}) @glk 352 _vararg_count 0; return 0; ]; [ glk_current_simple_time _vararg_count ret; ! glk_current_simple_time(uint) => int @glk 353 _vararg_count ret; return ret; ]; [ glk_time_to_date_utc _vararg_count; ! glk_time_to_date_utc(&{int, uint, int}, &{int, int, int, int, int, int, int, int}) @glk 360 _vararg_count 0; return 0; ]; [ glk_time_to_date_local _vararg_count; ! glk_time_to_date_local(&{int, uint, int}, &{int, int, int, int, int, int, int, int}) @glk 361 _vararg_count 0; return 0; ]; [ glk_simple_time_to_date_utc _vararg_count; ! glk_simple_time_to_date_utc(int, uint, &{int, int, int, int, int, int, int, int}) @glk 362 _vararg_count 0; return 0; ]; [ glk_simple_time_to_date_local _vararg_count; ! glk_simple_time_to_date_local(int, uint, &{int, int, int, int, int, int, int, int}) @glk 363 _vararg_count 0; return 0; ]; [ glk_date_to_time_utc _vararg_count; ! glk_date_to_time_utc(&{int, int, int, int, int, int, int, int}, &{int, uint, int}) @glk 364 _vararg_count 0; return 0; ]; [ glk_date_to_time_local _vararg_count; ! glk_date_to_time_local(&{int, int, int, int, int, int, int, int}, &{int, uint, int}) @glk 365 _vararg_count 0; return 0; ]; [ glk_date_to_simple_time_utc _vararg_count ret; ! glk_date_to_simple_time_utc(&{int, int, int, int, int, int, int, int}, uint) => int @glk 366 _vararg_count ret; return ret; ]; [ glk_date_to_simple_time_local _vararg_count ret; ! glk_date_to_simple_time_local(&{int, int, int, int, int, int, int, int}, uint) => int @glk 367 _vararg_count ret; return ret; ]; @p Rocks. These are unique ID codes used to mark resources; think of them as inedible cookies. @c Constant GG_MAINWIN_ROCK 201; Constant GG_STATUSWIN_ROCK 202; Constant GG_QUOTEWIN_ROCK 203; Constant GG_SAVESTR_ROCK 301; Constant GG_SCRIPTSTR_ROCK 302; Constant GG_COMMANDWSTR_ROCK 303; Constant GG_COMMANDRSTR_ROCK 304; Constant GG_SCRIPTFREF_ROCK 401; Constant GG_FOREGROUNDCHAN_ROCK 410; Constant GG_BACKGROUNDCHAN_ROCK 411; @p 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. @c #Stub HandleGlkEvent 2; #Stub IdentifyGlkObject 4; #Stub InitGlkWindow 1; @p 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.") @c [ VM_PreInitialise res; @gestalt 4 2 res; ! Test if this interpreter has Glk... if (res == 0) quit; ! ...without which there would be nothing we could do unicode_gestalt_ok = false; if (glk_gestalt(gestalt_Unicode, 0)) unicode_gestalt_ok = true; ! Set the VM's I/O system to be Glk. @setiosys 2 0; ]; [ VM_Initialise res sty i; @gestalt 4 2 res; ! Test if this interpreter has Glk... if (res == 0) quit; ! ...without which there would be nothing we could do ! First, we must go through all the Glk objects that exist, and see ! if we created any of them. One might think this strange, since the ! program has just started running, but remember that the player might ! have just typed "restart". GGRecoverObjects(); ! Sound channel initialisation, and RNG fixing, must be done now rather ! than later in case InitGlkWindow() returns a non-zero value. if (glk_gestalt(gestalt_Sound, 0)) { if (gg_foregroundchan == 0) gg_foregroundchan = glk_schannel_create(GG_FOREGROUNDCHAN_ROCK); if (gg_backgroundchan == 0) gg_backgroundchan = glk_schannel_create(GG_BACKGROUNDCHAN_ROCK); } #ifdef FIX_RNG; @random 10000 i; i = -i-2000; print "[Random number generator seed is ", i, "]^"; @setrandom i; #endif; ! FIX_RNG res = InitGlkWindow(0); if (res ~= 0) return; ! Now, gg_mainwin and gg_storywin might already be set. If not, set them. if (gg_mainwin == 0) { ! Open the story window. res = InitGlkWindow(GG_MAINWIN_ROCK); if (res == 0) { ! Left-justify the header style glk_stylehint_set(wintype_TextBuffer, style_Header, stylehint_Justification, 0); ! Try to make emphasized type in italics and not boldface glk_stylehint_set(wintype_TextBuffer, style_Emphasized, stylehint_Weight, 0); glk_stylehint_set(wintype_TextBuffer, style_Emphasized, stylehint_Oblique, 1); gg_mainwin = glk_window_open(0, 0, 0, wintype_TextBuffer, GG_MAINWIN_ROCK); } if (gg_mainwin == 0) quit; ! If we can't even open one window, give in } else { ! There was already a story window. We should erase it. glk_window_clear(gg_mainwin); } if (gg_statuswin == 0) { res = InitGlkWindow(GG_STATUSWIN_ROCK); if (res == 0) { statuswin_cursize = statuswin_size; for (sty=0: sty0) { GG_SAVESTR_ROCK: gg_savestr = id; GG_SCRIPTSTR_ROCK: gg_scriptstr = id; #Ifdef DEBUG; GG_COMMANDWSTR_ROCK: gg_commandstr = id; gg_command_reading = false; GG_COMMANDRSTR_ROCK: gg_commandstr = id; gg_command_reading = true; #Endif; ! DEBUG default: IdentifyGlkObject(1, 1, id, gg_arguments-->0); } id = glk_stream_iterate(id, gg_arguments); } id = glk_window_iterate(0, gg_arguments); while (id) { switch (gg_arguments-->0) { GG_MAINWIN_ROCK: gg_mainwin = id; GG_STATUSWIN_ROCK: gg_statuswin = id; GG_QUOTEWIN_ROCK: gg_quotewin = id; default: IdentifyGlkObject(1, 0, id, gg_arguments-->0); } id = glk_window_iterate(id, gg_arguments); } id = glk_fileref_iterate(0, gg_arguments); while (id) { switch (gg_arguments-->0) { GG_SCRIPTFREF_ROCK: gg_scriptfref = id; default: IdentifyGlkObject(1, 2, id, gg_arguments-->0); } id = glk_fileref_iterate(id, gg_arguments); } if (glk_gestalt(gestalt_Sound, 0)) { id = glk_schannel_iterate(0, gg_arguments); while (id) { switch (gg_arguments-->0) { GG_FOREGROUNDCHAN_ROCK: gg_foregroundchan = id; GG_BACKGROUNDCHAN_ROCK: gg_backgroundchan = id; } id = glk_schannel_iterate(id, gg_arguments); } if (gg_foregroundchan ~= 0) { glk_schannel_stop(gg_foregroundchan); } if (gg_backgroundchan ~= 0) { glk_schannel_stop(gg_backgroundchan); } } ! Tell the game to tie up any loose ends. IdentifyGlkObject(2); ]; @p 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. @c [ ENABLE_GLULX_ACCEL_R addr res; @gestalt 9 0 res; if (res == 0) return; addr = #classes_table; @accelparam 0 addr; @accelparam 1 INDIV_PROP_START; @accelparam 2 Class; @accelparam 3 Object; @accelparam 4 Routine; @accelparam 5 String; addr = #globals_array + WORDSIZE * #g$self; @accelparam 6 addr; @accelparam 7 NUM_ATTR_BYTES; addr = #cpv__start; @accelparam 8 addr; @accelfunc 1 Z__Region; @accelfunc 2 CP__Tab; @accelfunc 3 RA__Pr; @accelfunc 4 RL__Pr; @accelfunc 5 OC__Cl; @accelfunc 6 RV__Pr; @accelfunc 7 OP__Pr; rfalse; ]; @p 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. @c [ VM_Describe_Release i; print "Release "; @aloads ROM_GAMERELEASE 0 i; print i; print " / Serial number "; for (i=0 : i<6 : i++) print (char) ROM_GAMESERIAL->i; ]; @p Keyboard Input. 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. @c [ VM_KeyChar win nostat done res ix jx ch; jx = ch; ! squash compiler warnings if (win == 0) win = gg_mainwin; if (gg_commandstr ~= 0 && gg_command_reading ~= false) { done = glk_get_line_stream(gg_commandstr, gg_arguments, 31); if (done == 0) { glk_stream_close(gg_commandstr, 0); gg_commandstr = 0; gg_command_reading = false; ! fall through to normal user input. } else { ! Trim the trailing newline if (gg_arguments->(done-1) == 10) done = done-1; res = gg_arguments->0; if (res == '\') { res = 0; for (ix=1 : ixix; if (ch >= '0' && ch <= '9') { @shiftl res 4 res; res = res + (ch-'0'); } else if (ch >= 'a' && ch <= 'f') { @shiftl res 4 res; res = res + (ch+10-'a'); } else if (ch >= 'A' && ch <= 'F') { @shiftl res 4 res; res = res + (ch+10-'A'); } } } jump KCPContinue; } } done = false; glk_request_char_event(win); while (~~done) { glk_select(gg_event); switch (gg_event-->0) { 5: ! evtype_Arrange if (nostat) { glk_cancel_char_event(win); res = $80000000; done = true; break; } DrawStatusLine(); 2: ! evtype_CharInput if (gg_event-->1 == win) { res = gg_event-->2; done = true; } } ix = HandleGlkEvent(gg_event, 1, gg_arguments); if (ix == 2) { res = gg_arguments-->0; done = true; } else if (ix == -1) done = false; } if (gg_commandstr ~= 0 && gg_command_reading == false) { if (res < 32 || res >= 256 || (res == '\' or ' ')) { glk_put_char_stream(gg_commandstr, '\'); done = 0; jx = res; for (ix=0 : ix<8 : ix++) { @ushiftr jx 28 ch; @shiftl jx 4 jx; ch = ch & $0F; if (ch ~= 0 || ix == 7) done = 1; if (done) { if (ch >= 0 && ch <= 9) ch = ch + '0'; else ch = (ch - 10) + 'A'; glk_put_char_stream(gg_commandstr, ch); } } } else { glk_put_char_stream(gg_commandstr, res); } glk_put_char_stream(gg_commandstr, 10); ! newline } .KCPContinue; return res; ]; [ VM_KeyDelay tenths key done ix; glk_request_char_event(gg_mainwin); glk_request_timer_events(tenths*100); while (~~done) { glk_select(gg_event); ix = HandleGlkEvent(gg_event, 1, gg_arguments); if (ix == 2) { key = gg_arguments-->0; done = true; } else if (ix >= 0 && gg_event-->0 == 1 or 2) { key = gg_event-->2; done = true; } } glk_cancel_char_event(gg_mainwin); glk_request_timer_events(0); return key; ]; [ VM_ReadKeyboard a_buffer a_table done ix; if (gg_commandstr ~= 0 && gg_command_reading ~= false) { done = glk_get_line_stream(gg_commandstr, a_buffer+WORDSIZE, (INPUT_BUFFER_LEN-WORDSIZE)-1); if (done == 0) { glk_stream_close(gg_commandstr, 0); gg_commandstr = 0; gg_command_reading = false; } else { ! Trim the trailing newline if ((a_buffer+WORDSIZE)->(done-1) == 10) done = done-1; a_buffer-->0 = done; VM_Style(INPUT_VMSTY); glk_put_buffer(a_buffer+WORDSIZE, done); VM_Style(NORMAL_VMSTY); print "^"; jump KPContinue; } } done = false; glk_request_line_event(gg_mainwin, a_buffer+WORDSIZE, INPUT_BUFFER_LEN-WORDSIZE, 0); while (~~done) { glk_select(gg_event); switch (gg_event-->0) { 5: ! evtype_Arrange DrawStatusLine(); 3: ! evtype_LineInput if (gg_event-->1 == gg_mainwin) { a_buffer-->0 = gg_event-->2; done = true; } } ix = HandleGlkEvent(gg_event, 0, a_buffer); if (ix == 2) done = true; else if (ix == -1) done = false; } if (gg_commandstr ~= 0 && gg_command_reading == false) { glk_put_buffer_stream(gg_commandstr, a_buffer+WORDSIZE, a_buffer-->0); glk_put_char_stream(gg_commandstr, 10); ! newline } .KPContinue; VM_Tokenise(a_buffer,a_table); ! It's time to close any quote window we've got going. if (gg_quotewin) { glk_window_close(gg_quotewin, 0); gg_quotewin = 0; } #ifdef ECHO_COMMANDS; print "** "; for (ix=WORDSIZE: ix<(a_buffer-->0)+WORDSIZE: ix++) print (char) a_buffer->ix; print "^"; #endif; ! ECHO_COMMANDS ]; @p 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. @c [ VM_CopyBuffer bto bfrom i; for (i=0: ii = bfrom->i; ]; [ VM_PrintToBuffer buf len a b c; if (b) { if (metaclass(a) == Object && a.#b == WORDSIZE && metaclass(a.b) == String) buf-->0 = Glulx_PrintAnyToArray(buf+WORDSIZE, len, a.b); else if (metaclass(a) == Routine) buf-->0 = Glulx_PrintAnyToArray(buf+WORDSIZE, len, a, b, c); else buf-->0 = Glulx_PrintAnyToArray(buf+WORDSIZE, len, a, b); } else if (metaclass(a) == Routine) buf-->0 = Glulx_PrintAnyToArray(buf+WORDSIZE, len, a, b, c); else buf-->0 = Glulx_PrintAnyToArray(buf+WORDSIZE, len, a); if (buf-->0 > len) buf-->0 = len; return buf-->0; ]; [ VM_Tokenise buf tab cx numwords len bx ix wx wpos wlen val res dictlen entrylen; len = buf-->0; buf = buf+WORDSIZE; ! First, split the buffer up into words. We use the standard Infocom ! list of word separators (comma, period, double-quote). cx = 0; numwords = 0; while (cx < len) { while (cx < len && buf->cx == ' ') cx++; if (cx >= len) break; bx = cx; if (buf->cx == '.' or ',' or '"') cx++; else { while (cx < len && buf->cx ~= ' ' or '.' or ',' or '"') cx++; } tab-->(numwords*3+2) = (cx-bx); tab-->(numwords*3+3) = WORDSIZE+bx; numwords++; if (numwords >= MAX_BUFFER_WORDS) break; } tab-->0 = numwords; ! Now we look each word up in the dictionary. dictlen = #dictionary_table-->0; entrylen = DICT_WORD_SIZE + 7; for (wx=0 : wx(wx*3+2); wpos = tab-->(wx*3+3); ! Copy the word into the gg_tokenbuf array, clipping to DICT_WORD_SIZE ! characters and lower case. if (wlen > DICT_WORD_SIZE) wlen = DICT_WORD_SIZE; cx = wpos - WORDSIZE; for (ix=0 : ixix = VM_UpperToLowerCase(buf->(cx+ix)); for (: ixix = 0; val = #dictionary_table + WORDSIZE; @binarysearch gg_tokenbuf DICT_WORD_SIZE val entrylen dictlen 1 1 res; tab-->(wx*3+1) = res; } ]; [ LTI_Insert i ch b y; ! Protect us from strict mode, as this isn't an array in quite the ! sense it expects b = buffer; ! Insert character ch into buffer at point i. ! Being careful not to let the buffer possibly overflow: y = b-->0; if (y > INPUT_BUFFER_LEN) y = INPUT_BUFFER_LEN; ! Move the subsequent text along one character: for (y=y+WORDSIZE : y>i : y--) b->y = b->(y-1); b->i = ch; ! And the text is now one character longer: if (b-->0 < INPUT_BUFFER_LEN) (b-->0)++; ]; @p 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. @c [ VM_InvalidDictionaryAddress addr; if (addr < 0) rtrue; rfalse; ]; [ VM_DictionaryAddressToNumber w; return w; ]; [ VM_NumberToDictionaryAddress n; return n; ]; Array gg_tokenbuf -> DICT_WORD_SIZE; [ GGWordCompare str1 str2 ix jx; for (ix=0 : ixix) - (str2->ix); if (jx ~= 0) return jx; } return 0; ]; @p SHOWVERB support. Further VM-specific tables cover actions and attributes, and these are used by the SHOWVERB testing command. @c #Ifdef DEBUG; [ DebugAction a str; if (a >= 4096) { print ""; return; } if (a < 0 || a >= #identifiers_table-->7) print ""; else { str = #identifiers_table-->6; str = str-->a; if (str) print (string) str; else print ""; } ]; [ DebugAttribute a str; if (a < 0 || a >= NUM_ATTR_BYTES*8) print ""; else { str = #identifiers_table-->4; str = str-->a; if (str) print (string) str; else print ""; } ]; #Endif; @p 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. @c [ VM_CommandTableAddress i; return (#grammar_table)-->(i+1); ]; [ VM_PrintCommandWords i wd j dictlen entrylen; dictlen = #dictionary_table-->0; entrylen = DICT_WORD_SIZE + 7; for (j=0 : j ! Glulx_PrintAnything(0) ! Glulx_PrintAnything("string"); print (string) "string"; ! Glulx_PrintAnything('word') print (address) 'word'; ! Glulx_PrintAnything(obj) print (name) obj; ! Glulx_PrintAnything(obj, prop) obj.prop(); ! Glulx_PrintAnything(obj, prop, args...) obj.prop(args...); ! Glulx_PrintAnything(func) func(); ! Glulx_PrintAnything(func, args...) func(args...); [ Glulx_PrintAnything _vararg_count obj mclass; if (_vararg_count == 0) return; @copy sp obj; _vararg_count--; if (obj == 0) return; if (obj->0 == $60) { ! Dictionary word. Metaclass() can't catch this case, so we do it manually print (address) obj; return; } mclass = metaclass(obj); switch (mclass) { nothing: return; String: print (string) obj; return; Routine: ! Call the function with all the arguments which are already ! on the stack. @call obj _vararg_count 0; return; Object: if (_vararg_count == 0) { print (name) obj; } else { ! Push the object back onto the stack, and call the ! veneer routine that handles obj.prop() calls. @copy obj sp; _vararg_count++; @call CA__Pr _vararg_count 0; } return; } ]; [ Glulx_PrintAnyToArray _vararg_count arr arrlen str oldstr len; @copy sp arr; @copy sp arrlen; _vararg_count = _vararg_count - 2; oldstr = glk_stream_get_current(); str = glk_stream_open_memory(arr, arrlen, 1, 0); if (str == 0) return 0; glk_stream_set_current(str); @call Glulx_PrintAnything _vararg_count 0; glk_stream_set_current(oldstr); @copy $ffffffff sp; @copy str sp; @glk $0044 2 0; ! stream_close @copy sp len; @copy sp 0; return len; ]; Constant GG_ANYTOSTRING_LEN 66; Array AnyToStrArr -> GG_ANYTOSTRING_LEN+1; [ Glulx_ChangeAnyToCString _vararg_count ix len; ix = GG_ANYTOSTRING_LEN-2; @copy ix sp; ix = AnyToStrArr+1; @copy ix sp; ix = _vararg_count+2; @call Glulx_PrintAnyToArray ix len; AnyToStrArr->0 = $E0; if (len >= GG_ANYTOSTRING_LEN) len = GG_ANYTOSTRING_LEN-1; AnyToStrArr->(len+1) = 0; return AnyToStrArr; ]; @p 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. @c [ VM_ClearScreen window; if (window == WIN_ALL or WIN_MAIN) { glk_window_clear(gg_mainwin); if (gg_quotewin) { glk_window_close(gg_quotewin, 0); gg_quotewin = 0; } } if (gg_statuswin && window == WIN_ALL or WIN_STATUS) glk_window_clear(gg_statuswin); ]; [ VM_ScreenWidth id; id=gg_mainwin; if (gg_statuswin && statuswin_current) id = gg_statuswin; glk_window_get_size(id, gg_arguments, 0); return gg_arguments-->0; ]; [ VM_ScreenHeight; glk_window_get_size(gg_mainwin, 0, gg_arguments); return gg_arguments-->0; ]; @p 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). @c [ VM_SetWindowColours f b window doclear i fwd bwd swin; if (clr_on && f && b) { if (window) swin = 5-window; ! 4 for TextGrid, 3 for TextBuffer fwd = MakeColourWord(f); bwd = MakeColourWord(b); for (i=0 : i 9) return c; c = c-2; return $ff0000*(c&1) + $ff00*(c&2 ~= 0) + $ff*(c&4 ~= 0); ]; @p 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. @c [ VM_MainWindow; glk_set_window(gg_mainwin); ! set_window statuswin_current=0; ]; @p 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. @c [ VM_StatusLineHeight hgt; if (gg_statuswin == 0) return; if (hgt == statuswin_cursize) return; glk_window_set_arrangement(glk_window_get_parent(gg_statuswin), $12, hgt, 0); statuswin_cursize = hgt; ]; [ VM_MoveCursorInStatusLine line column; if (gg_statuswin == 0) return; glk_set_window(gg_statuswin); if (line == 0) { line = 1; column = 1; } glk_window_move_cursor(gg_statuswin, column-1, line-1); statuswin_current=1; ]; @p 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. @c [ Box__Routine maxwid arr ix lines lastnl parwin; maxwid = 0; ! squash compiler warning lines = arr-->0; if (gg_quotewin == 0) { gg_arguments-->0 = lines; ix = InitGlkWindow(GG_QUOTEWIN_ROCK); if (ix == 0) gg_quotewin = glk_window_open(gg_mainwin, winmethod_Fixed + winmethod_Above, lines, wintype_TextBuffer, GG_QUOTEWIN_ROCK); } else { parwin = glk_window_get_parent(gg_quotewin); glk_window_set_arrangement(parwin, $12, lines, 0); } lastnl = true; if (gg_quotewin) { glk_window_clear(gg_quotewin); glk_set_window(gg_quotewin); lastnl = false; } VM_Style(BLOCKQUOTE_VMSTY); for (ix=0 : ix(ix+1); if (ix < lines-1 || lastnl) new_line; } VM_Style(NORMAL_VMSTY); if (gg_quotewin) glk_set_window(gg_mainwin); ]; @p 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. @c #Ifdef DEBUG; [ GlkListSub id val; id = glk_window_iterate(0, gg_arguments); while (id) { print "Window ", id, " (", gg_arguments-->0, "): "; val = glk_window_get_type(id); switch (val) { 1: print "pair"; 2: print "blank"; 3: print "textbuffer"; 4: print "textgrid"; 5: print "graphics"; default: print "unknown"; } val = glk_window_get_parent(id); if (val) print ", parent is window ", val; else print ", no parent (root)"; val = glk_window_get_stream(id); print ", stream ", val; val = glk_window_get_echo_stream(id); if (val) print ", echo stream ", val; print "^"; id = glk_window_iterate(id, gg_arguments); } id = glk_stream_iterate(0, gg_arguments); while (id) { print "Stream ", id, " (", gg_arguments-->0, ")^"; id = glk_stream_iterate(id, gg_arguments); } id = glk_fileref_iterate(0, gg_arguments); while (id) { print "Fileref ", id, " (", gg_arguments-->0, ")^"; id = glk_fileref_iterate(id, gg_arguments); } if (glk_gestalt(gestalt_Sound, 0)) { id = glk_schannel_iterate(0, gg_arguments); while (id) { print "Soundchannel ", id, " (", gg_arguments-->0, ")^"; id = glk_schannel_iterate(id, gg_arguments); } } ]; {-testing-command:glklist} * -> Glklist; #Endif; @p 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. @c [ VM_Undo result_code; @restoreundo result_code; return (~~result_code); ]; [ VM_Save_Undo result_code; @saveundo result_code; if (result_code == -1) { GGRecoverObjects(); return 2; } return (~~result_code); ]; @p Quit The Game Rule. @c [ QUIT_THE_GAME_R; if (actor ~= player) rfalse; if ((actor == player) && (untouchable_silence == false)) QUIT_THE_GAME_RM('A'); if (YesOrNo()~=0) quit; ]; @p Restart The Game Rule. @c [ RESTART_THE_GAME_R; if (actor ~= player) rfalse; RESTART_THE_GAME_RM('A'); if (YesOrNo()~=0) { @restart; RESTART_THE_GAME_RM('B'); new_line; } ]; @p Restore The Game Rule. @c [ RESTORE_THE_GAME_R res fref; if (actor ~= player) rfalse; fref = glk_fileref_create_by_prompt($01, $02, 0); if (fref == 0) jump RFailed; gg_savestr = glk_stream_open_file(fref, $02, GG_SAVESTR_ROCK); glk_fileref_destroy(fref); if (gg_savestr == 0) jump RFailed; @restore gg_savestr res; glk_stream_close(gg_savestr, 0); gg_savestr = 0; .RFailed; RESTORE_THE_GAME_RM('A'); new_line; ]; @p Save The Game Rule. @c [ SAVE_THE_GAME_R res fref; if (actor ~= player) rfalse; fref = glk_fileref_create_by_prompt($01, $01, 0); if (fref == 0) jump SFailed; gg_savestr = glk_stream_open_file(fref, $01, GG_SAVESTR_ROCK); glk_fileref_destroy(fref); if (gg_savestr == 0) jump SFailed; @save gg_savestr res; if (res == -1) { ! The player actually just typed "restore". We first have to recover ! all the Glk objects; the values in our global variables are all wrong. GGRecoverObjects(); glk_stream_close(gg_savestr, 0); ! stream_close gg_savestr = 0; RESTORE_THE_GAME_RM('B'); new_line; rtrue; } glk_stream_close(gg_savestr, 0); ! stream_close gg_savestr = 0; if (res == 0) { SAVE_THE_GAME_RM('B'); new_line; rtrue; } .SFailed; SAVE_THE_GAME_RM('A'); new_line; ]; @p 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. @c [ VERIFY_THE_STORY_FILE_R res; if (actor ~= player) rfalse; @verify res; if (res == 0) { VERIFY_THE_STORY_FILE_RM('A'); new_line; rtrue; } VERIFY_THE_STORY_FILE_RM('B'); new_line; ]; @p Switch Transcript On Rule. @c [ SWITCH_TRANSCRIPT_ON_R; if (actor ~= player) rfalse; if (gg_scriptstr ~= 0) { SWITCH_TRANSCRIPT_ON_RM('A'); new_line; rtrue; } if (gg_scriptfref == 0) { gg_scriptfref = glk_fileref_create_by_prompt($102, $05, GG_SCRIPTFREF_ROCK); if (gg_scriptfref == 0) jump S1Failed; } ! stream_open_file gg_scriptstr = glk_stream_open_file(gg_scriptfref, $05, GG_SCRIPTSTR_ROCK); if (gg_scriptstr == 0) jump S1Failed; glk_window_set_echo_stream(gg_mainwin, gg_scriptstr); SWITCH_TRANSCRIPT_ON_RM('B'); new_line; VersionSub(); return; .S1Failed; SWITCH_TRANSCRIPT_ON_RM('C'); new_line; ]; @p Switch Transcript Off Rule. @c [ SWITCH_TRANSCRIPT_OFF_R; if (actor ~= player) rfalse; if (gg_scriptstr == 0) { SWITCH_TRANSCRIPT_OFF_RM('A'); new_line; rtrue; } SWITCH_TRANSCRIPT_OFF_RM('B'); new_line; glk_stream_close(gg_scriptstr, 0); ! stream_close gg_scriptstr = 0; ]; @p Announce Story File Version Rule. @c [ ANNOUNCE_STORY_FILE_VERSION_R ix; if (actor ~= player) rfalse; Banner(); print "Identification number: "; for (ix=6: ix <= UUID_ARRAY->0: ix++) print (char) UUID_ARRAY->ix; print "^"; @gestalt 1 0 ix; print "Interpreter version ", ix / $10000, ".", (ix & $FF00) / $100, ".", ix & $FF, " / "; @gestalt 0 0 ix; print "VM ", ix / $10000, ".", (ix & $FF00) / $100, ".", ix & $FF, " / "; print "Library serial number ", (string) LibSerial, "^"; #Ifdef LanguageVersion; print (string) LanguageVersion, "^"; #Endif; ! LanguageVersion ShowExtensionVersions(); say__p = 1; ]; @p 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|. @c [ DESCEND_TO_SPECIFIC_ACTION_R; indirect(#actions_table-->(action+1)); rtrue; ]; @p Veneer. @c [ Unsigned__Compare x y; @jleu x y ?lesseq; return 1; .lesseq; @jeq x y ?equal; return -1; .equal; return 0; ]; [ RT__ChLDW x y; @aload x y sp; @return sp; ]; [ RT__ChLDB x y; @aloadb x y sp; @return sp; ];