1
0
Fork 0
mirror of https://github.com/ganelson/inform.git synced 2024-07-05 16:44:21 +03:00
inform7/retrospective/6L02/I6T/Glulx.i6t
2019-02-05 00:44:07 +00:00

2178 lines
63 KiB
Plaintext

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: sty<style_NUMSTYLES: sty++)
glk_stylehint_set(wintype_TextGrid, sty, stylehint_ReverseColor, 1);
gg_statuswin =
glk_window_open(gg_mainwin, winmethod_Fixed + winmethod_Above,
statuswin_cursize, wintype_TextGrid, GG_STATUSWIN_ROCK);
}
}
! It's possible that the status window couldn't be opened, in which case
! gg_statuswin is now zero. We must allow for that later on.
glk_set_window(gg_mainwin);
InitGlkWindow(1);
];
[ GGRecoverObjects id;
! If GGRecoverObjects() has been called, all these stored IDs are
! invalid, so we start by clearing them all out.
! (In fact, after a restoreundo, some of them may still be good.
! For simplicity, though, we assume the general case.)
gg_mainwin = 0;
gg_statuswin = 0;
gg_quotewin = 0;
gg_scriptfref = 0;
gg_scriptstr = 0;
gg_savestr = 0;
statuswin_cursize = 0;
gg_foregroundchan = 0;
gg_backgroundchan = 0;
#Ifdef DEBUG;
gg_commandstr = 0;
gg_command_reading = false;
#Endif; ! DEBUG
! Also tell the game to clear its object references.
IdentifyGlkObject(0);
id = glk_stream_iterate(0, gg_arguments);
while (id) {
switch (gg_arguments-->0) {
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 : ix<done : ix++) {
ch = gg_arguments->ix;
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: i<INPUT_BUFFER_LEN: i++) bto->i = 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<numwords : wx++) {
wlen = tab-->(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 : ix<wlen : ix++) gg_tokenbuf->ix = VM_UpperToLowerCase(buf->(cx+ix));
for (: ix<DICT_WORD_SIZE : ix++) gg_tokenbuf->ix = 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 : ix<DICT_WORD_SIZE : ix++) {
jx = (str1->ix) - (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 "<fake action ", a-4096, ">"; return; }
if (a < 0 || a >= #identifiers_table-->7) print "<invalid action ", a, ">";
else {
str = #identifiers_table-->6;
str = str-->a;
if (str) print (string) str; else print "<unnamed action ", a, ">";
}
];
[ DebugAttribute a str;
if (a < 0 || a >= NUM_ATTR_BYTES*8) print "<invalid attribute ", a, ">";
else {
str = #identifiers_table-->4;
str = str-->a;
if (str) print (string) str; else print "<unnamed attribute ", a, ">";
}
];
#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<dictlen : j++) {
wd = #dictionary_table + WORDSIZE + entrylen*j;
if (DictionaryWordToVerbNum(wd) == i)
print "'", (address) wd, "' ";
}
];
@p 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.
@c
[ VM_Seed_RNG n;
@setrandom n;
];
@p 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.
@c
[ VM_AllocateMemory amount i;
@gestalt 7 0 i;
if (i == 0) return i;
@malloc amount i;
return i;
];
[ VM_FreeMemory address i;
@gestalt 7 0 i;
if (i == 0) return;
@mfree address;
];
@p 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.
@c
[ VM_Picture resource_ID;
if (glk_gestalt(gestalt_Graphics, 0)) {
glk_image_draw(gg_mainwin, resource_ID, imagealign_InlineCenter, 0);
} else {
print "[Picture number ", resource_ID, " here.]^";
}
];
[ VM_SoundEffect resource_ID;
if (glk_gestalt(gestalt_Sound, 0)) {
glk_schannel_play(gg_foregroundchan, resource_ID);
} else {
print "[Sound effect number ", resource_ID, " here.]^";
}
];
@p 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, {\it really} wasn't): we can only play along.
@c
[ VM_Style sty;
switch (sty) {
NORMAL_VMSTY: glk_set_style(style_Normal);
HEADER_VMSTY: glk_set_style(style_Header);
SUBHEADER_VMSTY: glk_set_style(style_Subheader);
NOTE_VMSTY: glk_set_style(style_Note);
ALERT_VMSTY: glk_set_style(style_Alert);
BLOCKQUOTE_VMSTY: glk_set_style(style_BlockQuote);
INPUT_VMSTY: glk_set_style(style_Input);
}
];
@p 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.
@c
[ VM_UpperToLowerCase c; return glk_char_to_lower(c); ];
[ VM_LowerToUpperCase c; return glk_char_to_upper(c); ];
@p 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.
@c
! Glulx_PrintAnything() <nothing printed>
! Glulx_PrintAnything(0) <nothing printed>
! 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<style_NUMSTYLES: i++) {
if (f == CLR_DEFAULT || b == CLR_DEFAULT) { ! remove style hints
glk_stylehint_clear(swin, i, stylehint_TextColor);
glk_stylehint_clear(swin, i, stylehint_BackColor);
} else {
glk_stylehint_set(swin, i, stylehint_TextColor, fwd);
glk_stylehint_set(swin, i, stylehint_BackColor, bwd);
}
}
! Now re-open the windows to apply the hints
if (gg_statuswin) glk_window_close(gg_statuswin, 0);
gg_statuswin = 0;
if (doclear || ( window ~= 1 && (clr_fg ~= f || clr_bg ~= b) ) ) {
glk_window_close(gg_mainwin, 0);
gg_mainwin = glk_window_open(0, 0, 0, wintype_TextBuffer, GG_MAINWIN_ROCK);
if (gg_scriptstr ~= 0)
glk_window_set_echo_stream(gg_mainwin, gg_scriptstr);
}
gg_statuswin =
glk_window_open(gg_mainwin, winmethod_Fixed + winmethod_Above,
statuswin_cursize, wintype_TextGrid, GG_STATUSWIN_ROCK);
if (statuswin_current && gg_statuswin) VM_MoveCursorInStatusLine(); else VM_MainWindow();
if (window ~= 2) {
clr_fgstatus = f;
clr_bgstatus = b;
}
if (window ~= 1) {
clr_fg = f;
clr_bg = b;
}
}
];
[ VM_RestoreWindowColours; ! used after UNDO: compare I6 patch L61007
if (clr_on) { ! check colour has been used
VM_SetWindowColours(clr_fg, clr_bg, 2); ! make sure both sets of variables are restored
VM_SetWindowColours(clr_fgstatus, clr_bgstatus, 1, true);
VM_ClearScreen();
}
];
[ MakeColourWord c;
if (c > 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<lines : ix++) {
print (string) arr-->(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;
];