1
0
Fork 0
inform6_examination/library-glulx/parserm.h

6680 lines
224 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

! ==============================================================================
! PARSERM: Core of parser.
!
! Supplied for use with Inform 6 -- Release 6/11 -- Serial number 040227
!
! Copyright Graham Nelson 1993-2004 but freely usable (see manuals)
!
! This file is automatically Included in your game file by "Parser".
! ------------------------------------------------------------------------------
! Inclusion of "linklpa" (which defines properties and attributes)
! Global variables, constants and arrays
! 1: outside of the parser
! 2: used within the parser
! Inclusion of natural language definition file
! (which creates a compass and direction-objects)
! Darkness and player objects
! Definition of grammar token numbering system used by Inform
!
! The InformParser object
! keyboard reading
! level 0: outer shell, conversation, errors
! 1: grammar lines
! 2: tokens
! 3: object lists
! 4: scope and ambiguity resolving
! 5: object comparisons
! 6: word comparisons
! 7: reading words and moving tables about
! pronoun management
!
! The InformLibrary object
! main game loop
! action processing
! end of turn sequence
! scope looping, before/after sequence, sending messages out
! timers, daemons, time of day, score notification
! light and darkness
! changing player personality
! tracing code (only present if DEBUG is set)
!
! Status line printing, menu display
! Printing object names with articles
! Miscellaneous utility routines
! Game banner, "version" verb, run-time errors
! ==============================================================================
System_file;
Include "infglk";
#Ifdef MODULE_MODE;
Constant DEBUG;
Constant Grammar__Version 2;
Include "linklpa";
#Endif; ! MODULE_MODE
! ------------------------------------------------------------------------------
! Global variables and their associated Constant and Array declarations
! ------------------------------------------------------------------------------
Global location = InformLibrary; ! Must be first global defined
Global sline1; ! Must be second
Global sline2; ! Must be third
! (for status line display)
! ------------------------------------------------------------------------------
! Z-Machine and interpreter issues
! ------------------------------------------------------------------------------
#Ifdef TARGET_ZCODE;
Global top_object; ! Largest valid number of any tree object
! ### these globals are not meaningful... well, maybe standard_interpreter,
! but I'll decide that later (AP).
Constant INDIV_PROP_START 64; ! Equivalent of a Glulx constant
#Endif; ! TARGET_ZCODE
Global standard_interpreter; ! The version number of the Z-Machine Standard which the
! interpreter claims to support, in form (upper byte).(lower)
Global undo_flag; ! Can the interpreter provide "undo"?
Global just_undone; ! Can't have two successive UNDOs
Global transcript_mode; ! true when game scripting is on
#Ifdef TARGET_ZCODE;
Global xcommsdir; ! true if command recording is on
#Endif; ! TARGET_ZCODE
#Ifdef TARGET_GLULX;
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;
Array gg_event --> 4;
#Ifdef VN_1630;
Array gg_arguments buffer 28;
#Ifnot;
Array gg_arguments --> 8;
#Endif; ! VN_
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
#Endif; ! TARGET_GLULX
Global gg_statuswin_cursize = 0;
Global gg_statuswin_size = 1;
! ------------------------------------------------------------------------------
! Time and score
! (for linkage reasons, the task_* arrays are created not here but in verblib.h)
! ------------------------------------------------------------------------------
#Ifndef sys_statusline_flag;
Global sys_statusline_flag = 0; ! non-zero if status line displays time
#Endif;
#Ifndef START_MOVE;
Constant START_MOVE 0; ! Traditionally 0 for Infocom, 1 for Inform
#Endif;
Global turns = START_MOVE; ! Number of turns of play so far
Global the_time = NULL; ! Current time (in minutes since midnight)
Global time_rate = 1; ! How often time is updated
Global time_step; ! By how much
#Ifndef MAX_TIMERS;
Constant MAX_TIMERS 32; ! Max number timers/daemons active at once
#Endif; ! MAX_TIMERS
Array the_timers --> MAX_TIMERS;
Global active_timers; ! Number of timers/daemons actives
Global score; ! The current score
Global last_score; ! Score last turn (for testing for changes)
Global notify_mode = true; ! Score notification
Global places_score; ! Contribution to score made by visiting
Global things_score; ! Contribution made by acquisition
! ------------------------------------------------------------------------------
! The player
! ------------------------------------------------------------------------------
Global player; ! Which object the human is playing through
Global deadflag; ! Normally 0, or false; 1 for dead; 2 for victorious, and
! higher numbers represent exotic forms of death
! ------------------------------------------------------------------------------
! Light and room descriptions
! ------------------------------------------------------------------------------
Global lightflag = true; ! Is there currently light to see by?
Global real_location; ! When in darkness, location = thedark
! and this holds the real location
Global visibility_ceiling; ! Highest object in tree visible from the player's point of view
! (usually the room, sometimes darkness, sometimes a closed
! non-transparent container).
Global lookmode = 1; ! 1=standard, 2=verbose, 3=brief room descs
Global print_player_flag; ! If set, print something like "(as Fred)" in room descriptions,
! to reveal whom the human is playing through
Global lastdesc; ! Value of location at time of most recent room description
! printed out
! ------------------------------------------------------------------------------
! List writing (style bits are defined as Constants in "verblibm.h")
! ------------------------------------------------------------------------------
Global c_style; ! Current list-writer style
Global lt_value; ! Common value of list_together
Global listing_together; ! Object number of one member of a group being listed together
Global listing_size; ! Size of such a group
Global wlf_indent; ! Current level of indentation printed by WriteListFrom()
Global inventory_stage = 1; ! 1 or 2 according to the context in which "invent" routines
! of objects are called
Global inventory_style; ! List-writer style currently used while printing inventories
! ------------------------------------------------------------------------------
! Menus and printing
! ------------------------------------------------------------------------------
Global pretty_flag = true; ! Use character graphics, or plain text?
Global menu_nesting; ! Level of nesting (0 = root menu)
Global menu_item; ! These are used in communicating
Global item_width = 8; ! with the menu-creating routines
Global item_name = "---";
Global lm_n; ! Parameters used by LibraryMessages
Global lm_o; ! mechanism
#Ifdef DEBUG;
Global debug_flag; ! Bitmap of flags for tracing actions,
! calls to object routines, etc.
Global x_scope_count; ! Used in printing a list of everything
#Endif; ! DEBUG ! in scope
! five for colour control
! see http://www.inform-fiction.org/patches/L61007.html
Global clr_fg = 1; ! foreground colour
Global clr_bg = 1; ! background colour
Global clr_fgstatus = 1; ! foreground colour of statusline
Global clr_bgstatus = 1; ! background colour of statusline
Global clr_on; ! has colour been enabled by the player?
Global statuswin_current; ! if writing to top window
Constant CLR_DEFAULT 1;
Constant CLR_BLACK 2;
Constant CLR_RED 3;
Constant CLR_GREEN 4;
Constant CLR_YELLOW 5;
Constant CLR_BLUE 6;
Constant CLR_MAGENTA 7;
Constant CLR_CYAN 8;
Constant CLR_WHITE 9;
Constant CLR_PURPLE 7;
Constant CLR_AZURE 8;
Constant WIN_ALL 0;
Constant WIN_STATUS 1;
Constant WIN_MAIN 2;
! ------------------------------------------------------------------------------
! Action processing
! ------------------------------------------------------------------------------
Global action; ! Action currently being asked to perform
Global inp1; ! 0 (nothing), 1 (number) or first noun
Global inp2; ! 0 (nothing), 1 (number) or second noun
Global noun; ! First noun or numerical value
Global second; ! Second noun or numerical value
Global keep_silent; ! If true, attempt to perform the action silently (e.g. for
! implicit takes, implicit opening of unlocked doors)
Global reason_code; ! Reason for calling a "life" rule
! (an action or fake such as ##Kiss)
Global receive_action; ! Either ##PutOn or ##Insert, whichever is action being tried
! when an object's "before" rule is checking "Receive"
! ==============================================================================
! Parser variables: first, for communication to the parser
! ------------------------------------------------------------------------------
Global parser_trace = 0; ! Set this to 1 to make the parser trace tokens and lines
Global parser_action; ! For the use of the parser when calling
Global parser_one; ! user-supplied routines
Global parser_two; !
Array inputobjs --> 16; ! For parser to write its results in
Global parser_inflection; ! A property (usually "name") to find object names in
! ------------------------------------------------------------------------------
! Parser output
! ------------------------------------------------------------------------------
Global actor; ! Person asked to do something
Global actors_location; ! Like location, but for the actor
Global meta; ! Verb is a meta-command (such as "save")
Array multiple_object --> 64; ! List of multiple parameters
Global multiflag; ! Multiple-object flag passed to actions
! Also used to prevent misleading MULTI_PE
Global toomany_flag; ! Flag for "multiple match too large"
! (e.g. if "take all" took over 100 things)
Global special_word; ! Dictionary address for "special" token
Global special_number; ! Number typed for "special" token
Global parsed_number; ! For user-supplied parsing routines
Global consult_from; ! Word that a "consult" topic starts on
Global consult_words; ! ...and number of words in topic
! ------------------------------------------------------------------------------
! Implicit taking
! ------------------------------------------------------------------------------
Global notheld_mode; ! To do with implicit taking
Global onotheld_mode; ! "old copy of notheld_mode", ditto
Global not_holding; ! Object to be automatically taken as an
! implicit command
Array kept_results --> 16; ! Delayed command (while the take happens)
! ------------------------------------------------------------------------------
! Error numbers when parsing a grammar line
! ------------------------------------------------------------------------------
Global etype; ! Error number on current line
Global best_etype; ! Preferred error number so far
Global nextbest_etype; ! Preferred one, if ASKSCOPE_PE disallowed
Constant STUCK_PE = 1;
Constant UPTO_PE = 2;
Constant NUMBER_PE = 3;
Constant CANTSEE_PE = 4;
Constant TOOLIT_PE = 5;
Constant NOTHELD_PE = 6;
Constant MULTI_PE = 7;
Constant MMULTI_PE = 8;
Constant VAGUE_PE = 9;
Constant EXCEPT_PE = 10;
Constant ANIMA_PE = 11;
Constant VERB_PE = 12;
Constant SCENERY_PE = 13;
Constant ITGONE_PE = 14;
Constant JUNKAFTER_PE = 15;
Constant TOOFEW_PE = 16;
Constant NOTHING_PE = 17;
Constant ASKSCOPE_PE = 18;
! ------------------------------------------------------------------------------
! Pattern-matching against a single grammar line
! ------------------------------------------------------------------------------
Array pattern --> 32; ! For the current pattern match
Global pcount; ! and a marker within it
Array pattern2 --> 32; ! And another, which stores the best match
Global pcount2; ! so far
Constant PATTERN_NULL = $ffff; ! Entry for a token producing no text
Array line_ttype-->32; ! For storing an analysed grammar line
Array line_tdata-->32;
Array line_token-->32;
Global parameters; ! Parameters (objects) entered so far
Global nsns; ! Number of special_numbers entered so far
Global special_number1; ! First number, if one was typed
Global special_number2; ! Second number, if two were typed
! ------------------------------------------------------------------------------
! Inferences and looking ahead
! ------------------------------------------------------------------------------
Global params_wanted; ! Number of parameters needed (which may change in parsing)
Global inferfrom; ! The point from which the rest of the command must be inferred
Global inferword; ! And the preposition inferred
Global dont_infer; ! Another dull flag
Global action_to_be; ! (If the current line were accepted.)
Global action_reversed; ! (Parameters would be reversed in order.)
Global advance_warning; ! What a later-named thing will be
! ------------------------------------------------------------------------------
! At the level of individual tokens now
! ------------------------------------------------------------------------------
Global found_ttype; ! Used to break up tokens into type
Global found_tdata; ! and data (by AnalyseToken)
Global token_filter; ! For noun filtering by user routines
Global length_of_noun; ! Set by NounDomain to no of words in noun
#Ifdef TARGET_ZCODE;
Constant REPARSE_CODE = 10000; ! Signals "reparse the text" as a reply from NounDomain
#Ifnot; ! TARGET_GLULX
Constant REPARSE_CODE = $40000000; ! The parser rather gunkily adds addresses to REPARSE_CODE for
! some purposes. And expects the result to be greater than
! REPARSE_CODE (signed comparison). So Glulx Inform is limited
! to a single gigabyte of storage, for the moment.
#Endif; ! TARGET_
Global lookahead; ! The token after the one now being matched
Global multi_mode; ! Multiple mode
Global multi_wanted; ! Number of things needed in multitude
Global multi_had; ! Number of things actually found
Global multi_context; ! What token the multi-obj was accepted for
Global indef_mode; ! "Indefinite" mode - ie, "take a brick"
! is in this mode
Global indef_type; ! Bit-map holding types of specification
Global indef_wanted; ! Number of items wanted (100 for all)
Global indef_guess_p; ! Plural-guessing flag
Global indef_owner; ! Object which must hold these items
Global indef_cases; ! Possible gender and numbers of them
Global indef_possambig; ! Has a possibly dangerous assumption
! been made about meaning of a descriptor?
Global indef_nspec_at; ! Word at which a number like "two" was parsed
! (for backtracking)
Global allow_plurals; ! Whether plurals presently allowed or not
Global take_all_rule; ! Slightly different rules apply to "take all" than other uses
! of multiple objects, to make adjudication produce more
! pragmatically useful results
! (Not a flag: possible values 0, 1, 2)
Global dict_flags_of_noun; ! Of the noun currently being parsed
! (a bitmap in #dict_par1 format)
Global pronoun_word; ! Records which pronoun ("it", "them", ...) caused an error
Global pronoun_obj; ! And what obj it was thought to refer to
Global pronoun__word; ! Saved value
Global pronoun__obj; ! Saved value
! ------------------------------------------------------------------------------
! Searching through scope and parsing "scope=Routine" grammar tokens
! ------------------------------------------------------------------------------
Constant PARSING_REASON = 0; ! Possible reasons for searching scope
Constant TALKING_REASON = 1;
Constant EACH_TURN_REASON = 2;
Constant REACT_BEFORE_REASON = 3;
Constant REACT_AFTER_REASON = 4;
Constant LOOPOVERSCOPE_REASON = 5;
Constant TESTSCOPE_REASON = 6;
Global scope_reason = PARSING_REASON; ! Current reason for searching scope
Global scope_token; ! For "scope=Routine" grammar tokens
Global scope_error;
Global scope_stage; ! 1, 2 then 3
Global ats_flag = 0; ! For AddToScope routines
Global ats_hls; !
Global placed_in_flag; ! To do with PlaceInScope
! ------------------------------------------------------------------------------
! The match list of candidate objects for a given token
! ------------------------------------------------------------------------------
Constant MATCH_LIST_SIZE = 128;
Array match_list --> 64; ! An array of matched objects so far
Array match_classes --> 64; ! An array of equivalence classes for them
Array match_scores --> 64; ! An array of match scores for them
Global number_matched; ! How many items in it? (0 means none)
Global number_of_classes; ! How many equivalence classes?
Global match_length; ! How many words long are these matches?
Global match_from; ! At what word of the input do they begin?
Global bestguess_score; ! What did the best-guess object score?
! ------------------------------------------------------------------------------
! Low level textual manipulation
! ------------------------------------------------------------------------------
#Ifdef TARGET_ZCODE;
Constant INPUT_BUFFER_LEN = 120; ! Length of buffer array (although we leave an extra byte
! to allow for interpreter bugs)
Array buffer -> 123; ! Buffer for parsing main line of input
#Ifdef VN_1630;
Array parse buffer 63; ! Parse table mirroring it
Array parse2 buffer 63; !
#Ifnot;
Array parse -> 65; ! Parse table mirroring it
Array parse2 -> 65; !
#Endif; ! VN_
Array buffer2 -> 123; ! Buffers for supplementary questions
Array buffer3 -> 123; ! Buffer retaining input for "again"
#Ifnot; ! TARGET_GLULX
Constant INPUT_BUFFER_LEN = 260; ! No extra byte necessary
Constant MAX_BUFFER_WORDS = 20;
Constant PARSE_BUFFER_LEN = 61;
Array buffer --> INPUT_BUFFER_LEN+1;
Array buffer2 --> INPUT_BUFFER_LEN+1;
Array buffer3 --> INPUT_BUFFER_LEN+1;
Array parse --> PARSE_BUFFER_LEN;
Array parse2 --> PARSE_BUFFER_LEN;
#Endif; ! TARGET_
Constant comma_word = 'comma,'; ! An "untypeable word" used to substitute
! for commas in parse buffers
Global wn; ! Word number within "parse" (from 1)
Global num_words; ! Number of words typed
Global verb_word; ! Verb word (eg, take in "take all" or
! "dwarf, take all") - address in dict
Global verb_wordnum; ! its number in typing order (eg, 1 or 3)
Global usual_grammar_after; ! Point from which usual grammar is parsed (it may vary from the
! above if user's routines match multi-word verbs)
Global oops_from; ! The "first mistake" word number
Global saved_oops; ! Used in working this out
Array oops_workspace -> 64; ! Used temporarily by "oops" routine
Global held_back_mode; ! Flag: is there some input from last time
Global hb_wn; ! left over? (And a save value for wn.)
! (Used for full stops and "then".)
! ----------------------------------------------------------------------------
Array PowersOfTwo_TB ! Used in converting case numbers to case bitmaps
--> $$100000000000
$$010000000000
$$001000000000
$$000100000000
$$000010000000
$$000001000000
$$000000100000
$$000000010000
$$000000001000
$$000000000100
$$000000000010
$$000000000001;
! ============================================================================
! Constants, and one variable, needed for the language definition file
! ----------------------------------------------------------------------------
Constant POSSESS_PK = $100;
Constant DEFART_PK = $101;
Constant INDEFART_PK = $102;
Global short_name_case;
Global dict_start;
Global dict_entry_size;
Global dict_end;
! ----------------------------------------------------------------------------
Include "language__"; ! The natural language definition, whose filename is taken from
! the ICL language_name variable
! ----------------------------------------------------------------------------
#Ifndef LanguageCases;
Constant LanguageCases = 1;
#Endif; ! LanguageCases
! ------------------------------------------------------------------------------
! Pronouns support for the cruder (library 6/2 and earlier) version:
! only needed in English
! ------------------------------------------------------------------------------
#Ifdef EnglishNaturalLanguage;
Global itobj = NULL; ! The object which is currently "it"
Global himobj = NULL; ! The object which is currently "him"
Global herobj = NULL; ! The object which is currently "her"
Global old_itobj = NULL; ! The object which is currently "it"
Global old_himobj = NULL; ! The object which is currently "him"
Global old_herobj = NULL; ! The object which is currently "her"
#Endif; ! EnglishNaturalLanguage
! ============================================================================
! "Darkness" is not really a place: but it has to be an object so that the
! location-name on the status line can be "Darkness".
! ----------------------------------------------------------------------------
Object thedark "(darkness object)"
with initial 0,
short_name DARKNESS__TX,
description [; return L__M(##Miscellany, 17); ];
Object selfobj "(self object)"
with short_name [; return L__M(##Miscellany, 18); ],
description [; return L__M(##Miscellany, 19); ],
before NULL,
after NULL,
life NULL,
each_turn NULL,
time_out NULL,
describe NULL,
add_to_scope 0,
capacity 100,
parse_name 0,
orders 0,
number 0,
before_implicit NULL,
has concealed animate proper transparent;
! ============================================================================
! The definition of the token-numbering system used by Inform.
! ----------------------------------------------------------------------------
Constant ILLEGAL_TT = 0; ! Types of grammar token: illegal
Constant ELEMENTARY_TT = 1; ! (one of those below)
Constant PREPOSITION_TT = 2; ! e.g. 'into'
Constant ROUTINE_FILTER_TT = 3; ! e.g. noun=CagedCreature
Constant ATTR_FILTER_TT = 4; ! e.g. edible
Constant SCOPE_TT = 5; ! e.g. scope=Spells
Constant GPR_TT = 6; ! a general parsing routine
Constant NOUN_TOKEN = 0; ! The elementary grammar tokens, and
Constant HELD_TOKEN = 1; ! the numbers compiled by Inform to
Constant MULTI_TOKEN = 2; ! encode them
Constant MULTIHELD_TOKEN = 3;
Constant MULTIEXCEPT_TOKEN = 4;
Constant MULTIINSIDE_TOKEN = 5;
Constant CREATURE_TOKEN = 6;
Constant SPECIAL_TOKEN = 7;
Constant NUMBER_TOKEN = 8;
Constant TOPIC_TOKEN = 9;
Constant GPR_FAIL = -1; ! Return values from General Parsing
Constant GPR_PREPOSITION = 0; ! Routines
Constant GPR_NUMBER = 1;
Constant GPR_MULTIPLE = 2;
Constant GPR_REPARSE = REPARSE_CODE;
Constant GPR_NOUN = $ff00;
Constant GPR_HELD = $ff01;
Constant GPR_MULTI = $ff02;
Constant GPR_MULTIHELD = $ff03;
Constant GPR_MULTIEXCEPT = $ff04;
Constant GPR_MULTIINSIDE = $ff05;
Constant GPR_CREATURE = $ff06;
Constant ENDIT_TOKEN = 15; ! Value used to mean "end of grammar line"
#Iftrue (Grammar__Version == 1);
[ AnalyseToken token m;
found_tdata = token;
if (token < 0) { found_ttype = ILLEGAL_TT; return; }
if (token <= 8) { found_ttype = ELEMENTARY_TT; return; }
if (token < 15) { found_ttype = ILLEGAL_TT; return; }
if (token == 15) { found_ttype = ELEMENTARY_TT; return; }
if (token < 48) { found_ttype = ROUTINE_FILTER_TT;
found_tdata = token - 16;
return;
}
if (token < 80) { found_ttype = GPR_TT;
found_tdata = #preactions_table-->(token-48);
return;
}
if (token < 128) { found_ttype = SCOPE_TT;
found_tdata = #preactions_table-->(token-80);
return;
}
if (token < 180) { found_ttype = ATTR_FILTER_TT;
found_tdata = token - 128;
return;
}
found_ttype = PREPOSITION_TT;
m = #adjectives_table;
for (::) {
if (token == m-->1) { found_tdata = m-->0; return; }
m = m+4;
}
m = #adjectives_table; RunTimeError(1);
found_tdata = m;
];
[ UnpackGrammarLine line_address i m;
for (i=0 : i<32 : i++) {
line_token-->i = ENDIT_TOKEN;
line_ttype-->i = ELEMENTARY_TT;
line_tdata-->i = ENDIT_TOKEN;
}
for (i=0 : i<=5 : i++) {
line_token-->i = line_address->(i+1);
AnalyseToken(line_token-->i);
if ((found_ttype == ELEMENTARY_TT) && (found_tdata == NOUN_TOKEN)
&& (m == line_address->0)) {
line_token-->i = ENDIT_TOKEN;
break;
}
line_ttype-->i = found_ttype;
line_tdata-->i = found_tdata;
if (found_ttype ~= PREPOSITION_TT) m++;
}
action_to_be = line_address->7;
action_reversed = false;
params_wanted = line_address->0;
return line_address + 8;
];
#Ifnot; ! Grammar__Version == 2
[ AnalyseToken token;
if (token == ENDIT_TOKEN) {
found_ttype = ELEMENTARY_TT;
found_tdata = ENDIT_TOKEN;
return;
}
found_ttype = (token->0) & $$1111;
found_tdata = (token+1)-->0;
];
#Ifdef TARGET_ZCODE;
[ UnpackGrammarLine line_address i;
for (i=0 : i<32 : i++) {
line_token-->i = ENDIT_TOKEN;
line_ttype-->i = ELEMENTARY_TT;
line_tdata-->i = ENDIT_TOKEN;
}
action_to_be = 256*(line_address->0) + line_address->1;
action_reversed = ((action_to_be & $400) ~= 0);
action_to_be = action_to_be & $3ff;
line_address--;
params_wanted = 0;
for (i=0 : : i++) {
line_address = line_address + 3;
if (line_address->0 == ENDIT_TOKEN) break;
line_token-->i = line_address;
AnalyseToken(line_address);
if (found_ttype ~= PREPOSITION_TT) params_wanted++;
line_ttype-->i = found_ttype;
line_tdata-->i = found_tdata;
}
return line_address + 1;
];
#Ifnot; ! TARGET_GLULX
[ UnpackGrammarLine line_address i;
for (i=0 : i<32 : i++) {
line_token-->i = ENDIT_TOKEN;
line_ttype-->i = ELEMENTARY_TT;
line_tdata-->i = ENDIT_TOKEN;
}
@aloads line_address 0 action_to_be;
action_reversed = (((line_address->2) & 1) ~= 0);
line_address = line_address - 2;
params_wanted = 0;
for (i=0 : : i++) {
line_address = line_address + 5;
if (line_address->0 == ENDIT_TOKEN) break;
line_token-->i = line_address;
AnalyseToken(line_address);
if (found_ttype ~= PREPOSITION_TT) params_wanted++;
line_ttype-->i = found_ttype;
line_tdata-->i = found_tdata;
}
return line_address + 1;
];
#Endif; ! TARGET_
#Endif; ! Grammar__Version
! To protect against a bug in early versions of the "Zip" interpreter:
! Of course, in Glulx, this routine actually performs work.
#Ifdef TARGET_ZCODE;
[ Tokenise__ b p; b->(2 + b->1) = 0; @tokenise b p; ];
#Ifnot; ! TARGET_GLULX
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;
];
[ 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 = glk($00A0, 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;
}
];
Constant LOWERCASE_BUF_SIZE = 2*DICT_WORD_SIZE;
Array gg_lowercasebuf --> LOWERCASE_BUF_SIZE;
[ VM_CopyBuffer bto bfrom i;
for (i=0: i<INPUT_BUFFER_LEN: i++) bto-->i = bfrom-->i;
];
[ VM_Tokenise buf tab
cx numwords len bx ix wx wpos wlen val res dictlen ch bytesperword uninormavail;
!print "parse: ^";
!for (i=0: i <= 20: i++) {
! print parse->i, (char) ' ';
!}
!print "^";
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) = 1+bx;
numwords++;
if (numwords >= MAX_BUFFER_WORDS) break;
}
tab-->0 = numwords;
! Now we look each word up in the dictionary.
dictlen = #dictionary_table-->0;
bytesperword = DICT_WORD_SIZE * DICT_CHAR_SIZE;
uninormavail = glk($0004, 16, 0);
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. We'll do this in two steps, because
! lowercasing might (theoretically) condense characters and allow more
! to fit into gg_tokenbuf.
if (wlen > LOWERCASE_BUF_SIZE) wlen = LOWERCASE_BUF_SIZE;
cx = wpos - 1;
for (ix=0 : ix<wlen : ix++) {
ch = buf-->(cx+ix);
gg_lowercasebuf-->ix = ch;
}
wlen = glk_buffer_to_lower_case_uni(gg_lowercasebuf, LOWERCASE_BUF_SIZE, wlen);
if (uninormavail) {
! Also normalize the Unicode -- combine accent marks with letters
! where possible.
wlen = glk($0124, gg_lowercasebuf, LOWERCASE_BUF_SIZE, wlen); ! buffer_canon_normalize_uni
}
if (wlen > DICT_WORD_SIZE) wlen = DICT_WORD_SIZE;
for (ix=0 : ix<wlen : ix++) {
gg_tokenbuf-->ix = gg_lowercasebuf-->ix;
}
for (: ix<DICT_WORD_SIZE : ix++) gg_tokenbuf-->ix = 0;
val = #dictionary_table + WORDSIZE;
@binarysearch gg_tokenbuf bytesperword val DICT_ENTRY_BYTES dictlen 4 1 res;
tab-->(wx*3+1) = res;
}
!print "parse after Tokenise__: ^";
!for (i=0: i <= 40: i++) {
! print tab->i, (char) ' ';
!}
!print "^";
];
#Endif; ! TARGET_
! ============================================================================
! The InformParser object abstracts the front end of the parser.
!
! InformParser.parse_input(results)
! returns only when a sensible request has been made, and puts into the
! "results" buffer:
!
! --> 0 = The action number
! --> 1 = Number of parameters
! --> 2, 3, ... = The parameters (object numbers), but
! 0 means "put the multiple object list here"
! 1 means "put one of the special numbers here"
!
! ----------------------------------------------------------------------------
Object InformParser "(Inform Parser)"
with parse_input [ results; Parser__parse(results); ],
has proper;
! ----------------------------------------------------------------------------
! The Keyboard routine actually receives the player's words,
! putting the words in "a_buffer" and their dictionary addresses in
! "a_table". It is assumed that the table is the same one on each
! (standard) call.
!
! It can also be used by miscellaneous routines in the game to ask
! yes-no questions and the like, without invoking the rest of the parser.
!
! Return the number of words typed
! ----------------------------------------------------------------------------
#Ifdef TARGET_ZCODE;
[ KeyboardPrimitive a_buffer a_table;
read a_buffer a_table;
#Iftrue (#version_number == 6);
@output_stream -1;
@loadb a_buffer 1 -> sp;
@add a_buffer 2 -> sp;
@print_table sp sp;
new_line;
@output_stream 1;
#Endif;
];
[ KeyCharPrimitive win key;
if (win) @set_window win;
@read_char 1 -> key;
return key;
];
[ KeyTimerInterrupt;
rtrue;
];
[ KeyDelay tenths key;
@read_char 1 tenths KeyTimerInterrupt -> key;
return key;
];
#Ifnot; ! TARGET_GLULX
[ KeyCharPrimitive 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) {
! get_line_stream
done = glk($0132, gg_commandstr, gg_arguments, 31);
if (done == 0) {
glk($0044, gg_commandstr, 0); ! stream_close
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($00D2, win); ! request_char_event
while (~~done) {
glk($00C0, gg_event); ! select
switch (gg_event-->0) {
5: ! evtype_Arrange
if (nostat) {
glk($00D3, win); ! cancel_char_event
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($012B, gg_commandstr, '\'); ! put_buffer_char
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($012B, gg_commandstr, ch); ! put_buffer_char
}
}
}
else {
glk($012B, gg_commandstr, res); ! put_buffer_char
}
glk($012B, gg_commandstr, 10); ! put_char_stream (newline)
}
.KCPContinue;
return res;
];
[ KeyDelay tenths key done ix;
glk($00D2, gg_mainwin); ! request_char_event
glk($00D6, tenths*100); ! request_timer_events
while (~~done) {
glk($00C0, gg_event); ! select
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($00D3, gg_mainwin); ! cancel_char_event
glk($00D6, 0); ! request_timer_events
return key;
];
[ KeyboardPrimitive a_buffer a_table done ix;
if (gg_commandstr ~= 0 && gg_command_reading ~= false) {
! get_line_stream
done = glk($0132, gg_commandstr, a_buffer+WORDSIZE, (INPUT_BUFFER_LEN-WORDSIZE)-1);
if (done == 0) {
glk($0044, gg_commandstr, 0); ! stream_close
gg_commandstr = 0;
gg_command_reading = false;
! L__M(##CommandsRead, 5); would come after prompt
! fall through to normal user input.
}
else {
! Trim the trailing newline
if ((a_buffer+WORDSIZE)-->(done-1) == 10) done = done-1; ! LF
if ((a_buffer+WORDSIZE)-->(done-1) == 13) done = done-1; ! CR
a_buffer-->0 = done;
glk($0086, 8); ! set input style
glk($012A, a_buffer+WORDSIZE, done); ! put_buffer
glk($0086, 0); ! set normal style
print "^";
jump KPContinue;
}
}
done = false;
glk($0141, gg_mainwin, a_buffer+WORDSIZE, INPUT_BUFFER_LEN-WORDSIZE, 0); ! request_line_event
while (~~done) {
glk($00C0, gg_event); ! select
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) {
! put_buffer_stream
glk($012D, gg_commandstr, a_buffer+WORDSIZE, a_buffer-->0);
glk($012B, gg_commandstr, 10); ! put_char_stream (newline)
}
.KPContinue;
VM_Tokenise(a_buffer,a_table);
! It's time to close any quote window we've got going.
if (gg_quotewin) {
glk($0024, gg_quotewin, 0); ! close_window
gg_quotewin = 0;
}
];
#Endif; ! TARGET_
[ Keyboard a_buffer a_table nw i w w2 x1 x2;
DisplayStatus();
.FreshInput;
! Save the start of the buffer, in case "oops" needs to restore it
! to the previous time's buffer
for (i=0 : i<64 : i++) oops_workspace->i = a_buffer->i;
! In case of an array entry corruption that shouldn't happen, but would be
! disastrous if it did:
#Ifdef TARGET_ZCODE;
a_buffer->0 = INPUT_BUFFER_LEN;
a_table->0 = 15; ! Allow to split input into this many words
#Endif; ! TARGET_
! Print the prompt, and read in the words and dictionary addresses
L__M(##Prompt);
AfterPrompt();
#IfV5;
DrawStatusLine();
#Endif; ! V5
KeyboardPrimitive(a_buffer, a_table);
#Ifdef TARGET_ZCODE;
nw = a_table->1;
#Ifnot; ! TARGET_GLULX
nw = a_table-->0;
#Endif; ! TARGET_
! If the line was blank, get a fresh line
if (nw == 0) {
L__M(##Miscellany, 10);
jump FreshInput;
}
! Unless the opening word was "oops", return
! Conveniently, a_table-->1 is the first word in both ZCODE and GLULX.
w = a_table-->1;
if (w == OOPS1__WD or OOPS2__WD or OOPS3__WD) jump DoOops;
if (a_buffer-->1 == COMMENT_CHARACTER) {
#Ifdef TARGET_ZCODE;
if ((HDR_GAMEFLAGS-->0) & 1 || xcommsdir)
L__M(##Miscellany, 54);
else L__M(##Miscellany, 55);
#Ifnot; ! TARGET_GLULX
if (gg_scriptstr || gg_commandstr) L__M(##Miscellany, 54);
else L__M(##Miscellany, 55);
#Endif; ! TARGET_
jump FreshInput;
}
#IfV5;
! Undo handling
if ((w == UNDO1__WD or UNDO2__WD or UNDO3__WD) && (nw==1)) {
if (turns == START_MOVE) {
L__M(##Miscellany, 11);
jump FreshInput;
}
if (undo_flag == 0) {
L__M(##Miscellany, 6);
jump FreshInput;
}
if (undo_flag == 1) jump UndoFailed;
#Ifdef TARGET_ZCODE;
@restore_undo i;
#Ifnot; ! TARGET_GLULX
@restoreundo i;
i = (~~i);
#Endif; ! TARGET_
if (i == 0) {
.UndoFailed;
L__M(##Miscellany, 7);
}
jump FreshInput;
}
#Ifdef TARGET_ZCODE;
@save_undo i;
#Ifnot; ! TARGET_GLULX
@saveundo i;
if (i == -1) {
GGRecoverObjects();
i = 2;
}
else i = (~~i);
#Endif; ! TARGET_
just_undone = 0;
undo_flag = 2;
if (i == -1) undo_flag = 0;
if (i == 0) undo_flag = 1;
if (i == 2) {
RestoreColours();
#Ifdef TARGET_ZCODE;
style bold;
#Ifnot; ! TARGET_GLULX
glk($0086, 4); ! set subheader style
#Endif; ! TARGET_
print (name) location, "^";
#Ifdef TARGET_ZCODE;
style roman;
#Ifnot; ! TARGET_GLULX
glk($0086, 0); ! set normal style
#Endif; ! TARGET_
L__M(##Miscellany, 13);
just_undone = 1;
jump FreshInput;
}
#Endif; ! V5
return nw;
.DoOops;
if (oops_from == 0) {
L__M(##Miscellany, 14);
jump FreshInput;
}
if (nw == 1) {
L__M(##Miscellany, 15);
jump FreshInput;
}
if (nw > 2) {
L__M(##Miscellany, 16);
jump FreshInput;
}
! So now we know: there was a previous mistake, and the player has
! attempted to correct a single word of it.
for (i=0 : i<INPUT_BUFFER_LEN : i++) buffer2->i = a_buffer->i;
#Ifdef TARGET_ZCODE;
x1 = a_table->9; ! Start of word following "oops"
x2 = a_table->8; ! Length of word following "oops"
#Ifnot; ! TARGET_GLULX
x1 = a_table-->6; ! Start of word following "oops"
x2 = a_table-->5; ! Length of word following "oops"
#Endif; ! TARGET_
! Repair the buffer to the text that was in it before the "oops"
! was typed:
for (i=0 : i<64 : i++) a_buffer->i = oops_workspace->i;
VM_Tokenise(a_buffer,a_table);
! Work out the position in the buffer of the word to be corrected:
#Ifdef TARGET_ZCODE;
w = a_table->(4*oops_from + 1); ! Start of word to go
w2 = a_table->(4*oops_from); ! Length of word to go
#Ifnot; ! TARGET_GLULX
w = a_table-->(3*oops_from); ! Start of word to go
w2 = a_table-->(3*oops_from - 1); ! Length of word to go
#Endif; ! TARGET_
! Write spaces over the word to be corrected:
for (i=0 : i<w2 : i++) a_buffer->(i+w) = ' ';
if (w2 < x2) {
! If the replacement is longer than the original, move up...
for (i=INPUT_BUFFER_LEN-1 : i>=w+x2 : i--)
a_buffer->i = a_buffer->(i-x2+w2);
! ...increasing buffer size accordingly.
#Ifdef TARGET_ZCODE;
a_buffer->1 = (a_buffer->1) + (x2-w2);
#Ifnot; ! TARGET_GLULX
a_buffer-->0 = (a_buffer-->0) + (x2-w2);
#Endif; ! TARGET_
}
! Write the correction in:
for (i=0 : i<x2 : i++) a_buffer->(i+w) = buffer2->(i+x1);
VM_Tokenise(a_buffer, a_table);
#Ifdef TARGET_ZCODE;
nw = a_table->1;
#Ifnot; ! TARGET_GLULX
nw = a_table-->0;
#Endif; ! TARGET_
return nw;
]; ! end of Keyboard
! ----------------------------------------------------------------------------
! To simplify the picture a little, a rough map of the main routine:
!
! (A) Get the input, do "oops" and "again"
! (B) Is it a direction, and so an implicit "go"? If so go to (K)
! (C) Is anyone being addressed?
! (D) Get the verb: try all the syntax lines for that verb
! (E) Break down a syntax line into analysed tokens
! (F) Look ahead for advance warning for multiexcept/multiinside
! (G) Parse each token in turn (calling ParseToken to do most of the work)
! (H) Cheaply parse otherwise unrecognised conversation and return
! (I) Print best possible error message
! (J) Retry the whole lot
! (K) Last thing: check for "then" and further instructions(s), return.
!
! The strategic points (A) to (K) are marked in the commentary.
!
! Note that there are three different places where a return can happen.
! ----------------------------------------------------------------------------
[ Parser__parse results syntax line num_lines line_address i j k
token l m;
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
! A: Get the input, do "oops" and "again"
!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Firstly, in "not held" mode, we still have a command left over from last
! time (eg, the user typed "eat biscuit", which was parsed as "take biscuit"
! last time, with "eat biscuit" tucked away until now). So we return that.
if (notheld_mode == 1) {
for (i=0 : i<8 : i++) results-->i = kept_results-->i;
notheld_mode = 0;
rtrue;
}
if (held_back_mode == 1) {
held_back_mode = 0;
VM_Tokenise(buffer, parse);
jump ReParse;
}
.ReType;
Keyboard(buffer,parse);
.ReParse;
parser_inflection = name;
! Initially assume the command is aimed at the player, and the verb
! is the first word
#Ifdef TARGET_ZCODE;
num_words = parse->1;
#Ifnot; ! TARGET_GLULX
num_words = parse-->0;
#Endif; ! TARGET_
wn = 1;
#Ifdef LanguageToInformese;
LanguageToInformese();
#IfV5;
! Re-tokenise:
VM_Tokenise(buffer,parse);
#Endif; ! V5
#Endif; ! LanguageToInformese
BeforeParsing();
#Ifdef TARGET_ZCODE;
num_words = parse->1;
#Ifnot; ! TARGET_GLULX
num_words = parse-->0;
#Endif; ! TARGET_
k=0;
#Ifdef DEBUG;
if (parser_trace >= 2) {
print "[ ";
for (i=0 : i<num_words : i++) {
#Ifdef TARGET_ZCODE;
j = parse-->(i*2 + 1);
#Ifnot; ! TARGET_GLULX
j = parse-->(i*3 + 1);
#Endif; ! TARGET_
k = WordAddress(i+1);
l = WordLength(i+1);
print "~"; for (m=0 : m<l : m++) print (char) k-->m; print "~ ";
if (j == 0) print "?";
else {
#Ifdef TARGET_ZCODE;
if (UnsignedCompare(j, HDR_DICTIONARY-->0) >= 0 &&
UnsignedCompare(j, HDR_HIGHMEMORY-->0) < 0)
print (address) j;
else print j;
#Ifnot; ! TARGET_GLULX
if (j->0 == $60) print (address) j;
else print j;
#Endif; ! TARGET_
}
if (i ~= num_words-1) print " / ";
}
print " ]^";
}
#Endif; ! DEBUG
verb_wordnum = 1;
actor = player;
actors_location = ScopeCeiling(player);
usual_grammar_after = 0;
.AlmostReParse;
scope_token = 0;
action_to_be = NULL;
! Begin from what we currently think is the verb word
.BeginCommand;
wn = verb_wordnum;
verb_word = NextWordStopped();
! If there's no input here, we must have something like "person,".
if (verb_word == -1) {
best_etype = STUCK_PE;
jump GiveError;
}
! Now try for "again" or "g", which are special cases: don't allow "again" if nothing
! has previously been typed; simply copy the previous text across
if (verb_word == AGAIN2__WD or AGAIN3__WD) verb_word = AGAIN1__WD;
if (verb_word == AGAIN1__WD) {
if (actor ~= player) {
L__M(##Miscellany, 20);
jump ReType;
}
#Ifdef TARGET_ZCODE;
if (buffer3->1 == 0) {
L__M(##Miscellany, 21);
jump ReType;
}
#Ifnot; ! TARGET_GLULX
if (buffer3-->0 == 0) {
L__M(##Miscellany, 21);
jump ReType;
}
#Endif; ! TARGET_
for (i=0 : i<INPUT_BUFFER_LEN : i++) buffer-->i = buffer3-->i;
jump ReParse;
}
! Save the present input in case of an "again" next time
if (verb_word ~= AGAIN1__WD)
for (i=0 : i<INPUT_BUFFER_LEN : i++) buffer3-->i = buffer-->i;
if (usual_grammar_after == 0) {
j = verb_wordnum;
i = RunRoutines(actor, grammar);
#Ifdef DEBUG;
if (parser_trace >= 2 && actor.grammar ~= 0 or NULL)
print " [Grammar property returned ", i, "]^";
#Endif; ! DEBUG
#Ifdef TARGET_ZCODE;
if ((i ~= 0 or 1) &&
(UnsignedCompare(i, dict_start) < 0 ||
UnsignedCompare(i, dict_end) >= 0 ||
(i - dict_start) % dict_entry_size ~= 0)) {
usual_grammar_after = j;
i=-i;
}
#Ifnot; ! TARGET_GLULX
if (i < 0) { usual_grammar_after = verb_wordnum; i=-i; }
#Endif;
if (i == 1) {
results-->0 = action;
results-->1 = noun;
results-->2 = second;
rtrue;
}
if (i ~= 0) { verb_word = i; wn--; verb_wordnum--; }
else { wn = verb_wordnum; verb_word = NextWord(); }
}
else usual_grammar_after = 0;
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
! B: Is it a direction, and so an implicit "go"? If so go to (K)
!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
#Ifdef LanguageIsVerb;
if (verb_word == 0) {
i = wn; verb_word = LanguageIsVerb(buffer, parse, verb_wordnum);
wn = i;
}
#Endif; ! LanguageIsVerb
! If the first word is not listed as a verb, it must be a direction
! or the name of someone to talk to
if (verb_word == 0 || ((verb_word->#dict_par1) & 1) == 0) {
! So is the first word an object contained in the special object "compass"
! (i.e., a direction)? This needs use of NounDomain, a routine which
! does the object matching, returning the object number, or 0 if none found,
! or REPARSE_CODE if it has restructured the parse table so the whole parse
! must be begun again...
wn = verb_wordnum; indef_mode = false; token_filter = 0;
l = NounDomain(compass, 0, 0);
if (l == REPARSE_CODE) jump ReParse;
! If it is a direction, send back the results:
! action=GoSub, no of arguments=1, argument 1=the direction.
if (l ~= 0) {
results-->0 = ##Go;
action_to_be = ##Go;
results-->1 = 1;
results-->2 = l;
jump LookForMore;
}
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
! C: Is anyone being addressed?
!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Only check for a comma (a "someone, do something" command) if we are
! not already in the middle of one. (This simplification stops us from
! worrying about "robot, wizard, you are an idiot", telling the robot to
! tell the wizard that she is an idiot.)
if (actor == player) {
for (j=2 : j<=num_words : j++) {
i=NextWord();
if (i == comma_word) jump Conversation;
}
verb_word = UnknownVerb(verb_word);
if (verb_word ~= 0) jump VerbAccepted;
}
best_etype = VERB_PE;
jump GiveError;
! NextWord nudges the word number wn on by one each time, so we've now
! advanced past a comma. (A comma is a word all on its own in the table.)
.Conversation;
j = wn - 1;
if (j == 1) {
L__M(##Miscellany, 22);
jump ReType;
}
! Use NounDomain (in the context of "animate creature") to see if the
! words make sense as the name of someone held or nearby
wn = 1; lookahead = HELD_TOKEN;
scope_reason = TALKING_REASON;
l = NounDomain(player,actors_location,6);
scope_reason = PARSING_REASON;
if (l == REPARSE_CODE) jump ReParse;
if (l == 0) {
L__M(##Miscellany, 23);
jump ReType;
}
.Conversation2;
! The object addressed must at least be "talkable" if not actually "animate"
! (the distinction allows, for instance, a microphone to be spoken to,
! without the parser thinking that the microphone is human).
if (l hasnt animate && l hasnt talkable) {
L__M(##Miscellany, 24, l);
jump ReType;
}
! Check that there aren't any mystery words between the end of the person's
! name and the comma (eg, throw out "dwarf sdfgsdgs, go north").
if (wn ~= j) {
L__M(##Miscellany, 25);
jump ReType;
}
! The player has now successfully named someone. Adjust "him", "her", "it":
PronounNotice(l);
! Set the global variable "actor", adjust the number of the first word,
! and begin parsing again from there.
verb_wordnum = j + 1;
! Stop things like "me, again":
if (l == player) {
wn = verb_wordnum;
if (NextWordStopped() == AGAIN1__WD or AGAIN2__WD or AGAIN3__WD) {
L__M(##Miscellany, 20);
jump ReType;
}
}
actor = l;
actors_location = ScopeCeiling(l);
#Ifdef DEBUG;
if (parser_trace >= 1)
print "[Actor is ", (the) actor, " in ", (name) actors_location, "]^";
#Endif; ! DEBUG
jump BeginCommand;
} ! end of first-word-not-a-verb
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
! D: Get the verb: try all the syntax lines for that verb
!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.VerbAccepted;
! We now definitely have a verb, not a direction, whether we got here by the
! "take ..." or "person, take ..." method. Get the meta flag for this verb:
meta = ((verb_word->#dict_par1) & 2)/2;
! You can't order other people to "full score" for you, and so on...
if (meta == 1 && actor ~= player) {
best_etype = VERB_PE;
meta = 0;
jump GiveError;
}
! Now let i be the corresponding verb number, stored in the dictionary entry
! (in a peculiar 255-n fashion for traditional Infocom reasons)...
i = $ff-(verb_word->#dict_par2);
! ...then look up the i-th entry in the verb table, whose address is at word
! 7 in the Z-machine (in the header), so as to get the address of the syntax
! table for the given verb...
#Ifdef TARGET_ZCODE;
syntax = (HDR_STATICMEMORY-->0)-->i;
#Ifnot; ! TARGET_GLULX
syntax = (#grammar_table)-->(i+1);
#Endif; ! TARGET_
! ...and then see how many lines (ie, different patterns corresponding to the
! same verb) are stored in the parse table...
num_lines = (syntax->0) - 1;
! ...and now go through them all, one by one.
! To prevent pronoun_word 0 being misunderstood,
pronoun_word = NULL; pronoun_obj = NULL;
#Ifdef DEBUG;
if (parser_trace >= 1) print "[Parsing for the verb '", (address) verb_word, "' (", num_lines+1, " lines)]^";
#Endif; ! DEBUG
best_etype = STUCK_PE; nextbest_etype = STUCK_PE;
multiflag = false;
! "best_etype" is the current failure-to-match error - it is by default
! the least informative one, "don't understand that sentence".
! "nextbest_etype" remembers the best alternative to having to ask a
! scope token for an error message (i.e., the best not counting ASKSCOPE_PE).
! multiflag is used here to prevent inappropriate MULTI_PE errors
! in addition to its unrelated duties passing information to action routines
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
! E: Break down a syntax line into analysed tokens
!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
line_address = syntax + 1;
for (line=0 : line<=num_lines : line++) {
for (i=0 : i<32 : i++) {
line_token-->i = ENDIT_TOKEN;
line_ttype-->i = ELEMENTARY_TT;
line_tdata-->i = ENDIT_TOKEN;
}
! Unpack the syntax line from Inform format into three arrays; ensure that
! the sequence of tokens ends in an ENDIT_TOKEN.
line_address = UnpackGrammarLine(line_address);
#Ifdef DEBUG;
if (parser_trace >= 1) {
if (parser_trace >= 2) new_line;
print "[line ", line; DebugGrammarLine();
print "]^";
}
#Endif; ! DEBUG
! We aren't in "not holding" or inferring modes, and haven't entered
! any parameters on the line yet, or any special numbers; the multiple
! object is still empty.
not_holding = 0;
inferfrom = 0;
parameters = 0;
nsns = 0; special_word = 0; special_number = 0;
multiple_object-->0 = 0;
multi_context = 0;
etype = STUCK_PE;
! Put the word marker back to just after the verb
wn = verb_wordnum+1;
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
! F: Look ahead for advance warning for multiexcept/multiinside
!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! There are two special cases where parsing a token now has to be
! affected by the result of parsing another token later, and these
! two cases (multiexcept and multiinside tokens) are helped by a quick
! look ahead, to work out the future token now. We can only carry this
! out in the simple (but by far the most common) case:
!
! multiexcept <one or more prepositions> noun
!
! and similarly for multiinside.
advance_warning = NULL; indef_mode = false;
for (i=0,m=false,pcount=0 : line_token-->pcount ~= ENDIT_TOKEN : pcount++) {
scope_token = 0;
if (line_ttype-->pcount ~= PREPOSITION_TT) i++;
if (line_ttype-->pcount == ELEMENTARY_TT) {
if (line_tdata-->pcount == MULTI_TOKEN) m = true;
if (line_tdata-->pcount == MULTIEXCEPT_TOKEN or MULTIINSIDE_TOKEN && i == 1) {
! First non-preposition is "multiexcept" or
! "multiinside", so look ahead.
#Ifdef DEBUG;
if (parser_trace >= 2) print " [Trying look-ahead]^";
#Endif; ! DEBUG
! We need this to be followed by 1 or more prepositions.
pcount++;
if (line_ttype-->pcount == PREPOSITION_TT) {
while (line_ttype-->pcount == PREPOSITION_TT) pcount++;
if ((line_ttype-->pcount == ELEMENTARY_TT) && (line_tdata-->pcount == NOUN_TOKEN)) {
! Advance past the last preposition
while (wn < num_words) {
l=NextWord();
if ( l && (l->#dict_par1) &8 ) { ! if preposition
l = Descriptors(); ! skip past THE etc
if (l~=0) etype=l; ! don't allow multiple objects
l = NounDomain(actors_location, actor, NOUN_TOKEN);
#Ifdef DEBUG;
if (parser_trace >= 2) {
print " [Advanced to ~noun~ token: ";
if (l == REPARSE_CODE) print "re-parse request]^";
if (l == 1) print "but multiple found]^";
if (l == 0) print "error ", etype, "]^";
if (l >= 2) print (the) l, "]^";
}
#Endif; ! DEBUG
if (l == REPARSE_CODE) jump ReParse;
if (l >= 2) advance_warning = l;
}
}
}
}
break;
}
}
}
! Slightly different line-parsing rules will apply to "take multi", to
! prevent "take all" behaving correctly but misleadingly when there's
! nothing to take.
take_all_rule = 0;
if (m && params_wanted == 1 && action_to_be == ##Take)
take_all_rule = 1;
! And now start again, properly, forearmed or not as the case may be.
! As a precaution, we clear all the variables again (they may have been
! disturbed by the call to NounDomain, which may have called outside
! code, which may have done anything!).
not_holding = 0;
inferfrom = 0;
parameters = 0;
nsns = 0; special_word = 0; special_number = 0;
multiple_object-->0 = 0;
etype = STUCK_PE;
wn = verb_wordnum+1;
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
! G: Parse each token in turn (calling ParseToken to do most of the work)
!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! "Pattern" gradually accumulates what has been recognised so far,
! so that it may be reprinted by the parser later on
for (pcount=1 : : pcount++) {
pattern-->pcount = PATTERN_NULL; scope_token = 0;
token = line_token-->(pcount-1);
lookahead = line_token-->pcount;
#Ifdef DEBUG;
if (parser_trace >= 2)
print " [line ", line, " token ", pcount, " word ", wn, " : ", (DebugToken) token,
"]^";
#Endif; ! DEBUG
if (token ~= ENDIT_TOKEN) {
scope_reason = PARSING_REASON;
parser_inflection = name;
AnalyseToken(token);
if (action_to_be == ##AskTo && found_ttype == ELEMENTARY_TT &&
found_tdata == TOPIC_TOKEN)
{
l=inputobjs-->2;
wn--;
j = wn;
jump Conversation2;
}
l = ParseToken__(found_ttype, found_tdata, pcount-1, token);
while (l<-200) l = ParseToken__(ELEMENTARY_TT, l + 256);
scope_reason = PARSING_REASON;
if (l == GPR_PREPOSITION) {
if (found_ttype~=PREPOSITION_TT && (found_ttype~=ELEMENTARY_TT ||
found_tdata~=TOPIC_TOKEN)) params_wanted--;
l = true;
}
else
if (l < 0) l = false;
else
if (l ~= GPR_REPARSE) {
if (l == GPR_NUMBER) {
if (nsns == 0) special_number1 = parsed_number;
else special_number2 = parsed_number;
nsns++; l = 1;
}
if (l == GPR_MULTIPLE) l = 0;
results-->(parameters+2) = l;
parameters++;
pattern-->pcount = l;
l = true;
}
#Ifdef DEBUG;
if (parser_trace >= 3) {
print " [token resulted in ";
if (l == REPARSE_CODE) print "re-parse request]^";
if (l == 0) print "failure with error type ", etype, "]^";
if (l == 1) print "success]^";
}
#Endif; ! DEBUG
if (l == REPARSE_CODE) jump ReParse;
if (l == false) break;
}
else {
! If the player has entered enough already but there's still
! text to wade through: store the pattern away so as to be able to produce
! a decent error message if this turns out to be the best we ever manage,
! and in the mean time give up on this line
! However, if the superfluous text begins with a comma or "then" then
! take that to be the start of another instruction
if (wn <= num_words) {
l = NextWord();
if (l == THEN1__WD or THEN2__WD or THEN3__WD or comma_word) {
held_back_mode = 1; hb_wn = wn-1;
}
else {
for (m=0 : m<32 : m++) pattern2-->m = pattern-->m;
pcount2 = pcount;
etype = UPTO_PE;
break;
}
}
! Now, we may need to revise the multiple object because of the single one
! we now know (but didn't when the list was drawn up).
if (parameters >= 1 && results-->2 == 0) {
l = ReviseMulti(results-->3);
if (l ~= 0) { etype = l; results-->0 = action_to_be; break; }
}
if (parameters >= 2 && results-->3 == 0) {
l = ReviseMulti(results-->2);
if (l ~= 0) { etype = l; break; }
}
! To trap the case of "take all" inferring only "yourself" when absolutely
! nothing else is in the vicinity...
if (take_all_rule == 2 && results-->2 == actor) {
best_etype = NOTHING_PE;
jump GiveError;
}
#Ifdef DEBUG;
if (parser_trace >= 1) print "[Line successfully parsed]^";
#Endif; ! DEBUG
! The line has successfully matched the text. Declare the input error-free...
oops_from = 0;
! ...explain any inferences made (using the pattern)...
if (inferfrom ~= 0) {
print "("; PrintCommand(inferfrom); print ")^";
}
! ...copy the action number, and the number of parameters...
results-->0 = action_to_be;
results-->1 = parameters;
! ...reverse first and second parameters if need be...
if (action_reversed && parameters == 2) {
i = results-->2; results-->2 = results-->3;
results-->3 = i;
if (nsns == 2) {
i = special_number1; special_number1 = special_number2;
special_number2 = i;
}
}
! ...and to reset "it"-style objects to the first of these parameters, if
! there is one (and it really is an object)...
if (parameters > 0 && results-->2 >= 2)
PronounNotice(results-->2);
! ...and worry about the case where an object was allowed as a parameter
! even though the player wasn't holding it and should have been: in this
! event, keep the results for next time round, go into "not holding" mode,
! and for now tell the player what's happening and return a "take" request
! instead...
if (not_holding ~= 0 && actor == player) {
action = ##Take;
i = RunRoutines(not_holding, before_implicit);
! i = 0: Take the object, tell the player (default)
! i = 1: Take the object, don't tell the player
! i = 2: don't Take the object, continue
! i = 3: don't Take the object, don't continue
if (i > 2) { best_etype = NOTHELD_PE; jump GiveError; }
if (i < 2) { ! perform the implicit Take
if (i ~= 1) ! and tell the player
L__M(##Miscellany, 26, not_holding);
notheld_mode = 1;
for (i=0 : i<8 : i++) kept_results-->i = results-->i;
results-->0 = ##Take;
results-->1 = 1;
results-->2 = not_holding;
}
}
! (Notice that implicit takes are only generated for the player, and not
! for other actors. This avoids entirely logical, but misleading, text
! being printed.)
! ...and return from the parser altogether, having successfully matched
! a line.
if (held_back_mode == 1) {
wn=hb_wn;
jump LookForMore;
}
rtrue;
} ! end of if(token ~= ENDIT_TOKEN) else
} ! end of for(pcount++)
! The line has failed to match.
! We continue the outer "for" loop, trying the next line in the grammar.
if (etype > best_etype) best_etype = etype;
if (etype ~= ASKSCOPE_PE && etype > nextbest_etype) nextbest_etype = etype;
! ...unless the line was something like "take all" which failed because
! nothing matched the "all", in which case we stop and give an error now.
if (take_all_rule == 2 && etype==NOTHING_PE) break;
} ! end of for(line++)
! The grammar is exhausted: every line has failed to match.
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
! H: Cheaply parse otherwise unrecognised conversation and return
!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.GiveError;
etype = best_etype;
! Errors are handled differently depending on who was talking.
! If the command was addressed to somebody else (eg, "dwarf, sfgh") then
! it is taken as conversation which the parser has no business in disallowing.
if (actor ~= player) {
if (usual_grammar_after ~= 0) {
verb_wordnum = usual_grammar_after;
jump AlmostReParse;
}
wn = verb_wordnum;
special_word = NextWord();
if (special_word == comma_word) {
special_word = NextWord();
verb_wordnum++;
}
special_number = TryNumber(verb_wordnum);
results-->0 = ##NotUnderstood;
results-->1 = 2;
results-->2 = 1; special_number1 = special_word;
results-->3 = actor;
consult_from = verb_wordnum; consult_words = num_words-consult_from+1;
rtrue;
}
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
! I: Print best possible error message
!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! If the player was the actor (eg, in "take dfghh") the error must be printed,
! and fresh input called for. In three cases the oops word must be jiggled.
if (ParserError(etype) ~= 0) jump ReType;
pronoun_word = pronoun__word; pronoun_obj = pronoun__obj;
if (etype == STUCK_PE) { L__M(##Miscellany, 27); oops_from = 1; }
if (etype == UPTO_PE) { L__M(##Miscellany, 28);
for (m=0 : m<32 : m++) pattern-->m = pattern2-->m;
pcount = pcount2; PrintCommand(0); L__M(##Miscellany, 56);
}
if (etype == NUMBER_PE) L__M(##Miscellany, 29);
if (etype == CANTSEE_PE) { L__M(##Miscellany, 30); oops_from=saved_oops; }
if (etype == TOOLIT_PE) L__M(##Miscellany, 31);
if (etype == NOTHELD_PE) { L__M(##Miscellany, 32); oops_from=saved_oops; }
if (etype == MULTI_PE) L__M(##Miscellany, 33);
if (etype == MMULTI_PE) L__M(##Miscellany, 34);
if (etype == VAGUE_PE) L__M(##Miscellany, 35);
if (etype == EXCEPT_PE) L__M(##Miscellany, 36);
if (etype == ANIMA_PE) L__M(##Miscellany, 37);
if (etype == VERB_PE) L__M(##Miscellany, 38);
if (etype == SCENERY_PE) L__M(##Miscellany, 39);
if (etype == ITGONE_PE) {
if (pronoun_obj == NULL)
L__M(##Miscellany, 35);
else L__M(##Miscellany, 40);
}
if (etype == JUNKAFTER_PE) L__M(##Miscellany, 41);
if (etype == TOOFEW_PE) L__M(##Miscellany, 42, multi_had);
if (etype == NOTHING_PE) {
if (results-->0 == ##Remove && results-->3 ofclass Object) {
noun = results-->3; ! ensure valid for messages
if (noun has animate) L__M(##Take, 6, noun);
else if (noun hasnt container or supporter) L__M(##Insert, 2, noun);
else if (noun has container && noun hasnt open) L__M(##Take, 9, noun);
else if (children(noun)==0) L__M(##Search, 6, noun);
else results-->0 = 0;
}
if (results-->0 ~= ##Remove) {
if (multi_wanted==100) L__M(##Miscellany, 43);
else L__M(##Miscellany, 44);
}
}
if (etype == ASKSCOPE_PE) {
scope_stage = 3;
if (indirect(scope_error) == -1) {
best_etype = nextbest_etype;
jump GiveError;
}
}
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
! J: Retry the whole lot
!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! And go (almost) right back to square one...
jump ReType;
! ...being careful not to go all the way back, to avoid infinite repetition
! of a deferred command causing an error.
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
! K: Last thing: check for "then" and further instructions(s), return.
!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! At this point, the return value is all prepared, and we are only looking
! to see if there is a "then" followed by subsequent instruction(s).
.LookForMore;
if (wn > num_words) rtrue;
i = NextWord();
if (i == THEN1__WD or THEN2__WD or THEN3__WD or comma_word) {
if (wn > num_words) {
held_back_mode = false;
return;
}
i = WordAddress(verb_wordnum);
j = WordAddress(wn);
for (: i<j : i++) i-->0 = ' ';
i = NextWord();
if (i == AGAIN1__WD or AGAIN2__WD or AGAIN3__WD) {
! Delete the words "then again" from the again buffer,
! in which we have just realised that it must occur:
! prevents an infinite loop on "i. again"
i = (WordAddress(wn-2)-buffer) / WORDSIZE;
if (wn > num_words) j = INPUT_BUFFER_LEN-1;
else j = (WordAddress(wn)-buffer) / WORDSIZE;
for (: i<j : i++) buffer3-->i = ' ';
}
VM_Tokenise(buffer,parse);
held_back_mode = true;
return;
}
best_etype = UPTO_PE;
jump GiveError;
]; ! end of Parser__parse
[ ScopeCeiling person act;
act = parent(person);
if (act == 0) return person;
if (person == player && location == thedark) return thedark;
while (parent(act)~=0 && (act has transparent || act has supporter ||
(act has container && act has open)))
act = parent(act);
return act;
];
! ----------------------------------------------------------------------------
! Descriptors()
!
! Handles descriptive words like "my", "his", "another" and so on.
! Skips "the", and leaves wn pointing to the first misunderstood word.
!
! Allowed to set up for a plural only if allow_p is set
!
! Returns error number, or 0 if no error occurred
! ----------------------------------------------------------------------------
Constant OTHER_BIT = 1; ! These will be used in Adjudicate()
Constant MY_BIT = 2; ! to disambiguate choices
Constant THAT_BIT = 4;
Constant PLURAL_BIT = 8;
Constant LIT_BIT = 16;
Constant UNLIT_BIT = 32;
[ ResetDescriptors;
indef_mode = 0; indef_type = 0; indef_wanted = 0; indef_guess_p = 0;
indef_possambig = false;
indef_owner = nothing;
indef_cases = $$111111111111;
indef_nspec_at = 0;
];
[ Descriptors o x flag cto type n;
ResetDescriptors();
if (wn > num_words) return 0;
for (flag=true : flag :) {
o = NextWordStopped(); flag = false;
for (x=1 : x<=LanguageDescriptors-->0 : x=x+4)
if (o == LanguageDescriptors-->x) {
flag = true;
type = LanguageDescriptors-->(x+2);
if (type ~= DEFART_PK) indef_mode = true;
indef_possambig = true;
indef_cases = indef_cases & (LanguageDescriptors-->(x+1));
if (type == POSSESS_PK) {
cto = LanguageDescriptors-->(x+3);
switch (cto) {
0: indef_type = indef_type | MY_BIT;
1: indef_type = indef_type | THAT_BIT;
default:
indef_owner = PronounValue(cto);
if (indef_owner == NULL) indef_owner = InformParser;
}
}
if (type == light) indef_type = indef_type | LIT_BIT;
if (type == -light) indef_type = indef_type | UNLIT_BIT;
}
if (o == OTHER1__WD or OTHER2__WD or OTHER3__WD) {
indef_mode = 1; flag = 1;
indef_type = indef_type | OTHER_BIT;
}
if (o == ALL1__WD or ALL2__WD or ALL3__WD or ALL4__WD or ALL5__WD) {
indef_mode = 1; flag = 1; indef_wanted = 100;
if (take_all_rule == 1) take_all_rule = 2;
indef_type = indef_type | PLURAL_BIT;
}
if (allow_plurals) {
n = TryNumber(wn-1);
if (n == 1) { indef_mode = 1; flag = 1; }
if (n > 1) {
indef_guess_p = 1;
indef_mode = 1; flag = 1; indef_wanted = n;
indef_nspec_at = wn-1;
indef_type = indef_type | PLURAL_BIT;
}
}
if (flag == 1 && NextWordStopped() ~= OF1__WD or OF2__WD or OF3__WD or OF4__WD)
wn--; ! Skip 'of' after these
}
wn--;
return 0;
];
! ----------------------------------------------------------------------------
! CreatureTest: Will this person do for a "creature" token?
! ----------------------------------------------------------------------------
[ CreatureTest obj;
if (obj has animate) rtrue;
if (obj hasnt talkable) rfalse;
if (action_to_be == ##Ask or ##Answer or ##Tell or ##AskFor) rtrue;
rfalse;
];
[ PrepositionChain wd index;
if (line_tdata-->index == wd) return wd;
if ((line_token-->index)->0 & $20 == 0) return -1;
do {
if (line_tdata-->index == wd) return wd;
index++;
} until ((line_token-->index == ENDIT_TOKEN) || (((line_token-->index)->0 & $10) == 0));
return -1;
];
! ----------------------------------------------------------------------------
! ParseToken(type, data):
! Parses the given token, from the current word number wn, with exactly
! the specification of a general parsing routine.
! (Except that for "topic" tokens and prepositions, you need to supply
! a position in a valid grammar line as third argument.)
!
! Returns:
! GPR_REPARSE for "reconstructed input, please re-parse from scratch"
! GPR_PREPOSITION for "token accepted with no result"
! $ff00 + x for "please parse ParseToken(ELEMENTARY_TT, x) instead"
! 0 for "token accepted, result is the multiple object list"
! 1 for "token accepted, result is the number in parsed_number"
! object num for "token accepted with this object as result"
! -1 for "token rejected"
!
! (A) Analyse the token; handle all tokens not involving
! object lists and break down others into elementary tokens
! (B) Begin parsing an object list
! (C) Parse descriptors (articles, pronouns, etc.) in the list
! (D) Parse an object name
! (E) Parse connectives ("and", "but", etc.) and go back to (C)
! (F) Return the conclusion of parsing an object list
! ----------------------------------------------------------------------------
[ ParseToken given_ttype given_tdata token_n x y;
x = lookahead; lookahead = NOUN_TOKEN;
y = ParseToken__(given_ttype,given_tdata,token_n);
if (y == GPR_REPARSE) VM_Tokenise(buffer,parse);
lookahead = x; return y;
];
[ ParseToken__ given_ttype given_tdata token_n
token l o i j k and_parity single_object desc_wn many_flag
token_allows_multiple prev_indef_wanted;
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
! A: Analyse token; handle all not involving object lists, break down others
!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
token_filter = 0;
switch (given_ttype) {
ELEMENTARY_TT:
switch (given_tdata) {
SPECIAL_TOKEN:
l = TryNumber(wn);
special_word = NextWord();
#Ifdef DEBUG;
if (l ~= -1000)
if (parser_trace >= 3) print " [Read special as the number ", l, "]^";
#Endif; ! DEBUG
if (l == -1000) {
#Ifdef DEBUG;
if (parser_trace >= 3) print " [Read special word at word number ", wn, "]^";
#Endif; ! DEBUG
l = special_word;
}
parsed_number = l; return GPR_NUMBER;
NUMBER_TOKEN:
l=TryNumber(wn++);
if (l == -1000) { etype = NUMBER_PE; return GPR_FAIL; }
#Ifdef DEBUG;
if (parser_trace>=3) print " [Read number as ", l, "]^";
#Endif; ! DEBUG
parsed_number = l; return GPR_NUMBER;
CREATURE_TOKEN:
if (action_to_be == ##Answer or ##Ask or ##AskFor or ##Tell)
scope_reason = TALKING_REASON;
TOPIC_TOKEN:
consult_from = wn;
if ((line_ttype-->(token_n+1) ~= PREPOSITION_TT) &&
(line_token-->(token_n+1) ~= ENDIT_TOKEN))
RunTimeError(13);
do o = NextWordStopped();
until (o == -1 || PrepositionChain(o, token_n+1) ~= -1);
wn--;
consult_words = wn-consult_from;
if (consult_words == 0) return GPR_FAIL;
if (action_to_be == ##Ask or ##Answer or ##Tell) {
o = wn; wn = consult_from; parsed_number = NextWord();
#Ifdef EnglishNaturalLanguage;
if (parsed_number == 'the' && consult_words > 1) parsed_number=NextWord();
#Endif; ! EnglishNaturalLanguage
wn = o; return 1;
}
if (o==-1 && (line_ttype-->(token_n+1) == PREPOSITION_TT))
return GPR_FAIL; ! don't infer if required preposition is absent
return GPR_PREPOSITION;
}
PREPOSITION_TT:
#Iffalse (Grammar__Version == 1);
! Is it an unnecessary alternative preposition, when a previous choice
! has already been matched?
if ((token->0) & $10) return GPR_PREPOSITION;
#Endif; ! Grammar__Version
! If we've run out of the player's input, but still have parameters to
! specify, we go into "infer" mode, remembering where we are and the
! preposition we are inferring...
if (wn > num_words) {
if (inferfrom==0 && parameters<params_wanted) {
inferfrom = pcount; inferword = token;
pattern-->pcount = REPARSE_CODE + Dword__No(given_tdata);
}
! If we are not inferring, then the line is wrong...
if (inferfrom == 0) return -1;
! If not, then the line is right but we mark in the preposition...
pattern-->pcount = REPARSE_CODE + Dword__No(given_tdata);
return GPR_PREPOSITION;
}
o = NextWord();
pattern-->pcount = REPARSE_CODE + Dword__No(o);
! Whereas, if the player has typed something here, see if it is the
! required preposition... if it's wrong, the line must be wrong,
! but if it's right, the token is passed (jump to finish this token).
if (o == given_tdata) return GPR_PREPOSITION;
#Iffalse (Grammar__Version == 1);
if (PrepositionChain(o, token_n) ~= -1) return GPR_PREPOSITION;
#Endif; ! Grammar__Version
return -1;
GPR_TT:
l = indirect(given_tdata);
#Ifdef DEBUG;
if (parser_trace >= 3) print " [Outside parsing routine returned ", l, "]^";
#Endif; ! DEBUG
return l;
SCOPE_TT:
scope_token = given_tdata;
scope_stage = 1;
l = indirect(scope_token);
#Ifdef DEBUG;
if (parser_trace >= 3) print " [Scope routine returned multiple-flag of ", l, "]^";
#Endif; ! DEBUG
if (l == 1) given_tdata = MULTI_TOKEN; else given_tdata = NOUN_TOKEN;
ATTR_FILTER_TT:
token_filter = 1 + given_tdata;
given_tdata = NOUN_TOKEN;
ROUTINE_FILTER_TT:
token_filter = given_tdata;
given_tdata = NOUN_TOKEN;
} ! end of switch(given_ttype)
token = given_tdata;
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
! B: Begin parsing an object list
!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! There are now three possible ways we can be here:
! parsing an elementary token other than "special" or "number";
! parsing a scope token;
! parsing a noun-filter token (either by routine or attribute).
!
! In each case, token holds the type of elementary parse to
! perform in matching one or more objects, and
! token_filter is 0 (default), an attribute + 1 for an attribute filter
! or a routine address for a routine filter.
token_allows_multiple = false;
if (token == MULTI_TOKEN or MULTIHELD_TOKEN or MULTIEXCEPT_TOKEN or MULTIINSIDE_TOKEN)
token_allows_multiple = true;
many_flag = false; and_parity = true; dont_infer = false;
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
! C: Parse descriptors (articles, pronouns, etc.) in the list
!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! We expect to find a list of objects next in what the player's typed.
.ObjectList;
#Ifdef DEBUG;
if (parser_trace >= 3) print " [Object list from word ", wn, "]^";
#Endif; ! DEBUG
! Take an advance look at the next word: if it's "it" or "them", and these
! are unset, set the appropriate error number and give up on the line
! (if not, these are still parsed in the usual way - it is not assumed
! that they still refer to something in scope)
o = NextWord(); wn--;
pronoun_word = NULL; pronoun_obj = NULL;
l = PronounValue(o);
if (l ~= 0) {
pronoun_word = o; pronoun_obj = l;
if (l == NULL) {
! Don't assume this is a use of an unset pronoun until the
! descriptors have been checked, because it might be an
! article (or some such) instead
for (l=1 : l<=LanguageDescriptors-->0 : l=l+4)
if (o == LanguageDescriptors-->l) jump AssumeDescriptor;
pronoun__word = pronoun_word; pronoun__obj = pronoun_obj;
etype = VAGUE_PE; return GPR_FAIL;
}
}
.AssumeDescriptor;
#ifdef LIBRARY_RUSSIAN;
if (o == ME1__WD or ME2__WD or ME3__WD or ME4__WD or ME5__WD or ME6__WD or ME7__WD)
{ pronoun_word = o; pronoun_obj = player; }
#ifnot;
if (o == ME1__WD or ME2__WD or ME3__WD)
{ pronoun_word = o; pronoun_obj = player; }
#endif; ! LIBRARY_RUSSIAN
allow_plurals = true; desc_wn = wn;
.TryAgain;
! First, we parse any descriptive words (like "the", "five" or "every"):
l = Descriptors();
if (l ~= 0) { etype = l; return GPR_FAIL; }
.TryAgain2;
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
! D: Parse an object name
!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! This is an actual specified object, and is therefore where a typing error
! is most likely to occur, so we set:
oops_from = wn;
! So, two cases. Case 1: token not equal to "held" (so, no implicit takes)
! but we may well be dealing with multiple objects
! In either case below we use NounDomain, giving it the token number as
! context, and two places to look: among the actor's possessions, and in the
! present location. (Note that the order depends on which is likeliest.)
if (token ~= HELD_TOKEN) {
i = multiple_object-->0;
#Ifdef DEBUG;
if (parser_trace >= 3) print " [Calling NounDomain on location and actor]^";
#Endif; ! DEBUG
l = NounDomain(actors_location, actor, token);
if (l == REPARSE_CODE) return l; ! Reparse after Q&A
if (indef_wanted == 100 && l == 0 && number_matched == 0)
l = 1; ! ReviseMulti if TAKE ALL FROM empty container
if (token_allows_multiple && ~~multiflag) {
if (best_etype==MULTI_PE) best_etype=STUCK_PE;
multiflag = true;
}
if (l == 0) {
if (indef_possambig) {
ResetDescriptors();
wn = desc_wn;
jump TryAgain2;
}
if (etype == MULTI_PE or TOOFEW_PE && multiflag) etype = STUCK_PE;
etype=CantSee();
jump FailToken;
} ! Choose best error
#Ifdef DEBUG;
if (parser_trace >= 3) {
if (l > 1) print " [ND returned ", (the) l, "]^";
else {
print " [ND appended to the multiple object list:^";
k = multiple_object-->0;
for (j=i+1 : j<=k : j++)
print " Entry ", j, ": ", (The) multiple_object-->j,
" (", multiple_object-->j, ")^";
print " List now has size ", k, "]^";
}
}
#Endif; ! DEBUG
if (l == 1) {
if (~~many_flag) many_flag = true;
else { ! Merge with earlier ones
k = multiple_object-->0; ! (with either parity)
multiple_object-->0 = i;
for (j=i+1 : j<=k : j++) {
if (and_parity) MultiAdd(multiple_object-->j);
else MultiSub(multiple_object-->j);
}
#Ifdef DEBUG;
if (parser_trace >= 3) print " [Merging ", k-i, " new objects to the ", i, " old ones]^";
#Endif; ! DEBUG
}
}
else {
! A single object was indeed found
if (match_length == 0 && indef_possambig) {
! So the answer had to be inferred from no textual data,
! and we know that there was an ambiguity in the descriptor
! stage (such as a word which could be a pronoun being
! parsed as an article or possessive). It's worth having
! another go.
ResetDescriptors();
wn = desc_wn;
jump TryAgain2;
}
if (token == CREATURE_TOKEN && CreatureTest(l) == 0) {
etype = ANIMA_PE;
jump FailToken;
} ! Animation is required
if (~~many_flag) single_object = l;
else {
if (and_parity) MultiAdd(l); else MultiSub(l);
#Ifdef DEBUG;
if (parser_trace >= 3) print " [Combining ", (the) l, " with list]^";
#Endif; ! DEBUG
}
}
}
else {
! Case 2: token is "held" (which fortunately can't take multiple objects)
! and may generate an implicit take
l = NounDomain(actor,actors_location,token); ! Same as above...
if (l == REPARSE_CODE) return GPR_REPARSE;
if (l == 0) {
if (indef_possambig) {
ResetDescriptors();
wn = desc_wn;
jump TryAgain2;
}
etype = CantSee(); jump FailToken; ! Choose best error
}
! ...until it produces something not held by the actor. Then an implicit
! take must be tried. If this is already happening anyway, things are too
! confused and we have to give up (but saving the oops marker so as to get
! it on the right word afterwards).
! The point of this last rule is that a sequence like
!
! > read newspaper
! (taking the newspaper first)
! The dwarf unexpectedly prevents you from taking the newspaper!
!
! should not be allowed to go into an infinite repeat - read becomes
! take then read, but take has no effect, so read becomes take then read...
! Anyway for now all we do is record the number of the object to take.
o = parent(l);
if (o ~= actor) {
if (notheld_mode == 1) {
saved_oops = oops_from;
etype = NOTHELD_PE;
jump FailToken;
}
not_holding = l;
#Ifdef DEBUG;
if (parser_trace >= 3) print " [Allowing object ", (the) l, " for now]^";
#Endif; ! DEBUG
}
single_object = l;
} ! end of if (token ~= HELD_TOKEN) else
! The following moves the word marker to just past the named object...
wn = oops_from + match_length;
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
! E: Parse connectives ("and", "but", etc.) and go back to (C)
!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Object(s) specified now: is that the end of the list, or have we reached
! "and", "but" and so on? If so, create a multiple-object list if we
! haven't already (and are allowed to).
.NextInList;
o = NextWord();
if (o == AND1__WD or AND2__WD or AND3__WD or BUT1__WD or BUT2__WD or BUT3__WD or comma_word) {
#Ifdef DEBUG;
if (parser_trace >= 3) print " [Read connective '", (address) o, "']^";
#Endif; ! DEBUG
if (~~token_allows_multiple) {
if (multiflag) jump PassToken; ! give UPTO_PE error
etype=MULTI_PE;
jump FailToken;
}
if (o == BUT1__WD or BUT2__WD or BUT3__WD) and_parity = 1-and_parity;
if (~~many_flag) {
multiple_object-->0 = 1;
multiple_object-->1 = single_object;
many_flag = true;
#Ifdef DEBUG;
if (parser_trace >= 3) print " [Making new list from ", (the) single_object, "]^";
#Endif; ! DEBUG
}
dont_infer = true; inferfrom=0; ! Don't print (inferences)
jump ObjectList; ! And back around
}
wn--; ! Word marker back to first not-understood word
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
! F: Return the conclusion of parsing an object list
!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Happy or unhappy endings:
.PassToken;
if (many_flag) {
single_object = GPR_MULTIPLE;
multi_context = token;
}
else {
if (indef_mode == 1 && indef_type & PLURAL_BIT ~= 0) {
if (indef_wanted < 100 && indef_wanted > 1) {
multi_had = 1; multi_wanted = indef_wanted;
etype = TOOFEW_PE;
jump FailToken;
}
}
}
return single_object;
.FailToken;
! If we were only guessing about it being a plural, try again but only
! allowing singulars (so that words like "six" are not swallowed up as
! Descriptors)
if (allow_plurals && indef_guess_p == 1) {
#Ifdef DEBUG;
if (parser_trace >= 4) print " [Retrying singulars after failure ", etype, "]^";
#Endif;
prev_indef_wanted = indef_wanted;
allow_plurals = false;
wn = desc_wn;
jump TryAgain;
}
if ((indef_wanted > 0 || prev_indef_wanted > 0) && (~~multiflag)) etype = MULTI_PE;
return GPR_FAIL;
]; ! end of ParseToken__
! ----------------------------------------------------------------------------
! NounDomain does the most substantial part of parsing an object name.
!
! It is given two "domains" - usually a location and then the actor who is
! looking - and a context (i.e. token type), and returns:
!
! 0 if no match at all could be made,
! 1 if a multiple object was made,
! k if object k was the one decided upon,
! REPARSE_CODE if it asked a question of the player and consequently rewrote
! the player's input, so that the whole parser should start again
! on the rewritten input.
!
! In the case when it returns 1<k<REPARSE_CODE, it also sets the variable
! length_of_noun to the number of words in the input text matched to the
! noun.
! In the case k=1, the multiple objects are added to multiple_object by
! hand (not by MultiAdd, because we want to allow duplicates).
! ----------------------------------------------------------------------------
[ NounDomain domain1 domain2 context first_word i j k l
answer_words marker;
#Ifdef DEBUG;
if (parser_trace >= 4) {
print " [NounDomain called at word ", wn, "^";
print " ";
if (indef_mode) {
print "seeking indefinite object: ";
if (indef_type & OTHER_BIT) print "other ";
if (indef_type & MY_BIT) print "my ";
if (indef_type & THAT_BIT) print "that ";
if (indef_type & PLURAL_BIT) print "plural ";
if (indef_type & LIT_BIT) print "lit ";
if (indef_type & UNLIT_BIT) print "unlit ";
if (indef_owner ~= 0) print "owner:", (name) indef_owner;
new_line;
print " number wanted: ";
if (indef_wanted == 100) print "all"; else print indef_wanted;
new_line;
print " most likely GNAs of names: ", indef_cases, "^";
}
else print "seeking definite object^";
}
#Endif; ! DEBUG
match_length = 0; number_matched = 0; match_from = wn; placed_in_flag = 0;
SearchScope(domain1, domain2, context);
#Ifdef DEBUG;
if (parser_trace >= 4) print " [ND made ", number_matched, " matches]^";
#Endif; ! DEBUG
wn = match_from+match_length;
! If nothing worked at all, leave with the word marker skipped past the
! first unmatched word...
if (number_matched == 0) { wn++; rfalse; }
! Suppose that there really were some words being parsed (i.e., we did
! not just infer). If so, and if there was only one match, it must be
! right and we return it...
if (match_from <= num_words) {
if (number_matched == 1) {
i=match_list-->0;
return i;
}
! ...now suppose that there was more typing to come, i.e. suppose that
! the user entered something beyond this noun. If nothing ought to follow,
! then there must be a mistake, (unless what does follow is just a full
! stop, and or comma)
if (wn <= num_words) {
i = NextWord(); wn--;
if (i ~= AND1__WD or AND2__WD or AND3__WD or comma_word
or THEN1__WD or THEN2__WD or THEN3__WD
or BUT1__WD or BUT2__WD or BUT3__WD) {
if (lookahead == ENDIT_TOKEN) rfalse;
}
}
}
! Now look for a good choice, if there's more than one choice...
number_of_classes = 0;
if (number_matched == 1) i = match_list-->0;
if (number_matched > 1) {
i = Adjudicate(context);
if (i == -1) rfalse;
if (i == 1) rtrue; ! Adjudicate has made a multiple
! object, and we pass it on
}
! If i is non-zero here, one of two things is happening: either
! (a) an inference has been successfully made that object i is
! the intended one from the user's specification, or
! (b) the user finished typing some time ago, but we've decided
! on i because it's the only possible choice.
! In either case we have to keep the pattern up to date,
! note that an inference has been made and return.
! (Except, we don't note which of a pile of identical objects.)
if (i ~= 0) {
if (dont_infer) return i;
if (inferfrom == 0) inferfrom=pcount;
pattern-->pcount = i;
return i;
}
! If we get here, there was no obvious choice of object to make. If in
! fact we've already gone past the end of the player's typing (which
! means the match list must contain every object in scope, regardless
! of its name), then it's foolish to give an enormous list to choose
! from - instead we go and ask a more suitable question...
if (match_from > num_words) jump Incomplete;
! Now we print up the question, using the equivalence classes as worked
! out by Adjudicate() so as not to repeat ourselves on plural objects...
if (context==CREATURE_TOKEN) L__M(##Miscellany, 45);
else L__M(##Miscellany, 46);
j = number_of_classes; marker = 0;
for (i=1 : i<=number_of_classes : i++) {
while (((match_classes-->marker) ~= i) && ((match_classes-->marker) ~= -i)) marker++;
k = match_list-->marker;
#ifdef LIBRARY_RUSSIAN;
if (match_classes-->marker > 0) {
switch (csLRU) {
1: print "@<<", (cNom) k, "@>>";
2: print "@<<", (cGen) k, "@>>";
3: print "@<<", (cDat) k, "@>>";
4: print "@<<", (cAcc) k, "@>>";
5: print "@<<", (cIns) k, "@>>";
6: print "@<<", (cPre) k, "@>>";
default: print (the) k;
}
} else {
print (a) k;
}
#ifnot;
if (match_classes-->marker > 0) print (the) k; else print (a) k;
#endif; ! LIBRARY_RUSSIAN
if (i < j-1) print (string) COMMA__TX;
if (i == j-1) print (string) OR__TX;
}
L__M(##Miscellany, 57);
! ...and get an answer:
.WhichOne;
#Ifdef TARGET_ZCODE;
for (i=2 : i<INPUT_BUFFER_LEN : i++) buffer2->i = ' ';
#Endif; ! TARGET_ZCODE
answer_words=Keyboard(buffer2, parse2);
! Conveniently, parse2-->1 is the first word in both ZCODE and GLULX.
first_word = (parse2-->1);
! Take care of "all", because that does something too clever here to do
! later on:
if (first_word == ALL1__WD or ALL2__WD or ALL3__WD or ALL4__WD or ALL5__WD) {
if (context == MULTI_TOKEN or MULTIHELD_TOKEN or MULTIEXCEPT_TOKEN or MULTIINSIDE_TOKEN) {
l = multiple_object-->0;
for (i=0 : i<number_matched && l+i<63 : i++) {
k = match_list-->i;
multiple_object-->(i+1+l) = k;
}
multiple_object-->0 = i+l;
rtrue;
}
L__M(##Miscellany, 47);
jump WhichOne;
}
! If the first word of the reply can be interpreted as a verb, then
! assume that the player has ignored the question and given a new
! command altogether.
! (This is one time when it's convenient that the directions are
! not themselves verbs - thus, "north" as a reply to "Which, the north
! or south door" is not treated as a fresh command but as an answer.)
#Ifdef LanguageIsVerb;
if (first_word == 0) {
j = wn; first_word = LanguageIsVerb(buffer2, parse2, 1); wn = j;
}
#Endif; ! LanguageIsVerb
if (first_word ~= 0) {
j = first_word->#dict_par1;
if ((0 ~= j&1) && ~~LanguageVerbMayBeName(first_word)) {
CopyBuffer(buffer, buffer2);
return REPARSE_CODE;
}
}
! Now we insert the answer into the original typed command, as
! words additionally describing the same object
! (eg, > take red button
! Which one, ...
! > music
! becomes "take music red button". The parser will thus have three
! words to work from next time, not two.)
#Ifdef TARGET_ZCODE;
k = WordAddress(match_from) - buffer; l=buffer2->1+1;
for (j=buffer + buffer->0 - 1 : j>=buffer+k+l : j--) j->0 = 0->(j-l);
for (i=0 : i<l : i++) buffer->(k+i) = buffer2->(2+i);
buffer->(k+l-1) = ' ';
buffer->1 = buffer->1 + l;
if (buffer->1 >= (buffer->0 - 1)) buffer->1 = buffer->0;
#Ifnot; ! TARGET_GLULX
k = (WordAddress(match_from) - buffer) / WORDSIZE;
l = (buffer2-->0) + 1;
for (j=INPUT_BUFFER_LEN-1 : j>=k+l : j-- ) buffer-->j = buffer-->(j-l);
for (i=0 : i<l : i++) buffer-->(k+i) = buffer2-->(1+i);
buffer-->(k+l-1) = ' ';
buffer-->0 = buffer-->0 + l;
if (buffer-->0 > (INPUT_BUFFER_LEN-WORDSIZE)) buffer-->0 = (INPUT_BUFFER_LEN-WORDSIZE);
#Endif; ! TARGET_
! Having reconstructed the input, we warn the parser accordingly
! and get out.
return REPARSE_CODE;
! Now we come to the question asked when the input has run out
! and can't easily be guessed (eg, the player typed "take" and there
! were plenty of things which might have been meant).
.Incomplete;
if (context == CREATURE_TOKEN) L__M(##Miscellany, 48);
else L__M(##Miscellany, 49);
#Ifdef TARGET_ZCODE;
for (i=2 : i<INPUT_BUFFER_LEN : i++) buffer2->i=' ';
#Endif; ! TARGET_ZCODE
answer_words = Keyboard(buffer2, parse2);
first_word=(parse2-->1);
#Ifdef LanguageIsVerb;
if (first_word==0) {
j = wn; first_word=LanguageIsVerb(buffer2, parse2, 1); wn = j;
}
#Endif; ! LanguageIsVerb
! Once again, if the reply looks like a command, give it to the
! parser to get on with and forget about the question...
if (first_word ~= 0) {
j = first_word->#dict_par1;
if (0 ~= j&1) {
VM_CopyBuffer(buffer, buffer2);
return REPARSE_CODE;
}
}
! ...but if we have a genuine answer, then:
!
! (1) we must glue in text suitable for anything that's been inferred.
if (inferfrom ~= 0) {
for (j=inferfrom : j<pcount : j++) {
if (pattern-->j == PATTERN_NULL) continue;
#Ifdef TARGET_ZCODE;
i = 2+buffer->1; (buffer->1)++; buffer->(i++) = ' ';
#Ifnot; ! TARGET_GLULX
i = WORDSIZE + buffer-->0;
(buffer-->0)++; buffer->(i++) = ' ';
#Endif; ! TARGET_
#Ifdef DEBUG;
if (parser_trace >= 5) print "[Gluing in inference with pattern code ", pattern-->j, "]^";
#Endif; ! DEBUG
! Conveniently, parse2-->1 is the first word in both ZCODE and GLULX.
parse2-->1 = 0;
! An inferred object. Best we can do is glue in a pronoun.
! (This is imperfect, but it's very seldom needed anyway.)
if (pattern-->j >= 2 && pattern-->j < REPARSE_CODE) {
PronounNotice(pattern-->j);
for (k=1 : k<=LanguagePronouns-->0 : k=k+3)
if (pattern-->j == LanguagePronouns-->(k+2)) {
parse2-->1 = LanguagePronouns-->k;
#Ifdef DEBUG;
if (parser_trace >= 5) print "[Using pronoun '", (address) parse2-->1, "']^";
#Endif; ! DEBUG
break;
}
}
else {
! An inferred preposition.
parse2-->1 = No__Dword(pattern-->j - REPARSE_CODE);
#Ifdef DEBUG;
if (parser_trace >= 5) print "[Using preposition '", (address) parse2-->1, "']^";
#Endif; ! DEBUG
}
! parse2-->1 now holds the dictionary address of the word to glue in.
if (parse2-->1 ~= 0) {
k = buffer + i;
#Ifdef TARGET_ZCODE;
@output_stream 3 k;
print (address) parse2-->1;
@output_stream -3;
k = k-->0;
for (l=i : l<i+k : l++) buffer->l = buffer->(l+2);
i = i + k; buffer->1 = i-2;
#Ifnot; ! TARGET_GLULX
k = PrintAnyToArray(buffer+i, INPUT_BUFFER_LEN-i, parse2-->1);
i = i + k; buffer-->0 = i - WORDSIZE;
#Endif; ! TARGET_
}
}
}
! (2) we must glue the newly-typed text onto the end.
#Ifdef TARGET_ZCODE;
i = 2+buffer->1; (buffer->1)++; buffer->(i++) = ' ';
for (j=0 : j<buffer2->1 : i++,j++) {
buffer->i = buffer2->(j+2);
(buffer->1)++;
if (buffer->1 == INPUT_BUFFER_LEN) break;
}
#Ifnot; ! TARGET_GLULX
i = 1 + buffer-->0;
(buffer-->0)++; buffer-->(i++) = ' ';
for (j=0 : j<buffer2-->0 : i++,j++) {
buffer-->i = buffer2-->(j+1);
(buffer-->0)++;
if (buffer-->0 == INPUT_BUFFER_LEN) break;
}
#Endif; ! TARGET_
! (3) we fill up the buffer with spaces, which is unnecessary, but may
! help incorrectly-written interpreters to cope.
#Ifdef TARGET_ZCODE;
for (: i<INPUT_BUFFER_LEN : i++) buffer->i = ' ';
#Endif; ! TARGET_ZCODE
return REPARSE_CODE;
]; ! end of NounDomain
! ----------------------------------------------------------------------------
! The Adjudicate routine tries to see if there is an obvious choice, when
! faced with a list of objects (the match_list) each of which matches the
! player's specification equally well.
!
! To do this it makes use of the context (the token type being worked on).
! It counts up the number of obvious choices for the given context
! (all to do with where a candidate is, except for 6 (animate) which is to
! do with whether it is animate or not);
!
! if only one obvious choice is found, that is returned;
!
! if we are in indefinite mode (don't care which) one of the obvious choices
! is returned, or if there is no obvious choice then an unobvious one is
! made;
!
! at this stage, we work out whether the objects are distinguishable from
! each other or not: if they are all indistinguishable from each other,
! then choose one, it doesn't matter which;
!
! otherwise, 0 (meaning, unable to decide) is returned (but remember that
! the equivalence classes we've just worked out will be needed by other
! routines to clear up this mess, so we can't economise on working them
! out).
!
! Returns -1 if an error occurred
! ----------------------------------------------------------------------------
Constant SCORE__CHOOSEOBJ = 1000;
Constant SCORE__IFGOOD = 500;
Constant SCORE__UNCONCEALED = 100;
Constant SCORE__BESTLOC = 60;
Constant SCORE__NEXTBESTLOC = 40;
Constant SCORE__NOTCOMPASS = 20;
Constant SCORE__NOTSCENERY = 10;
Constant SCORE__NOTACTOR = 5;
Constant SCORE__GNA = 1;
Constant SCORE__DIVISOR = 20;
[ Adjudicate context i j k good_flag good_ones last n flag offset sovert;
#Ifdef DEBUG;
if (parser_trace >= 4) {
print " [Adjudicating match list of size ", number_matched, " in context ", context, "^";
print " ";
if (indef_mode) {
print "indefinite type: ";
if (indef_type & OTHER_BIT) print "other ";
if (indef_type & MY_BIT) print "my ";
if (indef_type & THAT_BIT) print "that ";
if (indef_type & PLURAL_BIT) print "plural ";
if (indef_type & LIT_BIT) print "lit ";
if (indef_type & UNLIT_BIT) print "unlit ";
if (indef_owner ~= 0) print "owner:", (name) indef_owner;
new_line;
print " number wanted: ";
if (indef_wanted == 100) print "all"; else print indef_wanted;
new_line;
print " most likely GNAs of names: ", indef_cases, "^";
}
else print "definite object^";
}
#Endif; ! DEBUG
j = number_matched-1; good_ones = 0; last = match_list-->0;
for (i=0 : i<=j : i++) {
n = match_list-->i;
match_scores-->i = 0;
good_flag = false;
switch (context) {
HELD_TOKEN, MULTIHELD_TOKEN:
if (parent(n) == actor) good_flag = true;
MULTIEXCEPT_TOKEN:
if (advance_warning == -1) {
good_flag = true;
}
else {
if (n ~= advance_warning) good_flag = true;
}
MULTIINSIDE_TOKEN:
if (advance_warning == -1) {
if (parent(n) ~= actor) good_flag = true;
}
else {
if (n in advance_warning) good_flag = true;
}
CREATURE_TOKEN:
if (CreatureTest(n) == 1) good_flag = true;
default:
good_flag = true;
}
if (good_flag) {
match_scores-->i = SCORE__IFGOOD;
good_ones++; last = n;
}
}
if (good_ones == 1) return last;
! If there is ambiguity about what was typed, but it definitely wasn't
! animate as required, then return anything; higher up in the parser
! a suitable error will be given. (This prevents a question being asked.)
if (context == CREATURE_TOKEN && good_ones == 0) return match_list-->0;
if (indef_mode == 0) indef_type=0;
ScoreMatchL(context);
if (number_matched == 0) return -1;
if (indef_mode == 0) {
! Is there now a single highest-scoring object?
i = SingleBestGuess();
if (i >= 0) {
#Ifdef DEBUG;
if (parser_trace >= 4) print " Single best-scoring object returned.]^";
#Endif; ! DEBUG
return i;
}
}
if (indef_mode == 1 && indef_type & PLURAL_BIT ~= 0) {
if (context ~= MULTI_TOKEN or MULTIHELD_TOKEN or MULTIEXCEPT_TOKEN
or MULTIINSIDE_TOKEN) {
etype = MULTI_PE;
return -1;
}
i = 0; offset = multiple_object-->0; sovert = -1;
for (j=BestGuess() : j~=-1 && i<indef_wanted && i+offset<63 : j=BestGuess()) {
flag = 0;
if (j hasnt concealed && j hasnt worn) flag = 1;
if (sovert == -1) sovert = bestguess_score/SCORE__DIVISOR;
else {
if (indef_wanted == 100 && bestguess_score/SCORE__DIVISOR < sovert)
flag = 0;
}
if (context == MULTIHELD_TOKEN or MULTIEXCEPT_TOKEN && parent(j) ~= actor)
flag = 0;
if (action_to_be == ##Take or ##Remove && parent(j) == actor)
flag = 0;
k = ChooseObjects(j, flag);
if (k == 1)
flag = 1;
else {
if (k == 2) flag = 0;
}
if (flag == 1) {
i++; multiple_object-->(i+offset) = j;
#Ifdef DEBUG;
if (parser_trace >= 4) print " Accepting it^";
#Endif; ! DEBUG
}
else {
i = i;
#Ifdef DEBUG;
if (parser_trace >= 4) print " Rejecting it^";
#Endif; ! DEBUG
}
}
if (i < indef_wanted && indef_wanted < 100) {
etype = TOOFEW_PE; multi_wanted = indef_wanted;
multi_had=i;
return -1;
}
multiple_object-->0 = i+offset;
multi_context = context;
#Ifdef DEBUG;
if (parser_trace >= 4)
print " Made multiple object of size ", i, "]^";
#Endif; ! DEBUG
return 1;
}
for (i=0 : i<number_matched : i++) match_classes-->i = 0;
n = 1;
for (i=0 : i<number_matched : i++)
if (match_classes-->i == 0) {
match_classes-->i = n++; flag = 0;
for (j=i+1 : j<number_matched : j++)
if (match_classes-->j == 0 && Identical(match_list-->i, match_list-->j) == 1) {
flag=1;
match_classes-->j = match_classes-->i;
}
if (flag == 1) match_classes-->i = 1-n;
}
n--; number_of_classes = n;
#Ifdef DEBUG;
if (parser_trace >= 4) {
print " Grouped into ", n, " possibilities by name:^";
for (i=0 : i<number_matched : i++)
if (match_classes-->i > 0)
print " ", (The) match_list-->i, " (", match_list-->i, ") --- group ",
match_classes-->i, "^";
}
#Endif; ! DEBUG
if (indef_mode == 0) {
if (n > 1) {
k = -1;
for (i=0 : i<number_matched : i++) {
if (match_scores-->i > k) {
k = match_scores-->i;
j = match_classes-->i; j = j*j;
flag = 0;
}
else
if (match_scores-->i == k) {
if ((match_classes-->i) * (match_classes-->i) ~= j)
flag = 1;
}
}
if (flag) {
#Ifdef DEBUG;
if (parser_trace >= 4) print " Unable to choose best group, so ask player.]^";
#Endif; ! DEBUG
return 0;
}
#Ifdef DEBUG;
if (parser_trace >= 4) print " Best choices are all from the same group.^";
#Endif; ! DEBUG
}
}
! When the player is really vague, or there's a single collection of
! indistinguishable objects to choose from, choose the one the player
! most recently acquired, or if the player has none of them, then
! the one most recently put where it is.
if (n == 1) dont_infer = true;
return BestGuess();
]; ! Adjudicate
! ----------------------------------------------------------------------------
! ReviseMulti revises the multiple object which already exists, in the
! light of information which has come along since then (i.e., the second
! parameter). It returns a parser error number, or else 0 if all is well.
! This only ever throws things out, never adds new ones.
! ----------------------------------------------------------------------------
[ ReviseMulti second_p i low;
#Ifdef DEBUG;
if (parser_trace >= 4) print " Revising multiple object list of size ", multiple_object-->0,
" with 2nd ", (name) second_p, "^";
#Endif; ! DEBUG
if (multi_context == MULTIEXCEPT_TOKEN or MULTIINSIDE_TOKEN) {
for (i=1,low=0 : i<=multiple_object-->0 : i++) {
if ( (multi_context==MULTIEXCEPT_TOKEN && multiple_object-->i ~= second_p) ||
(multi_context==MULTIINSIDE_TOKEN && multiple_object-->i in second_p)) {
low++;
multiple_object-->low = multiple_object-->i;
}
}
multiple_object-->0 = low;
}
if (multi_context == MULTI_TOKEN && action_to_be == ##Take) {
for (i=1,low=0 : i<=multiple_object-->0 : i++)
if (ScopeCeiling(multiple_object-->i)==ScopeCeiling(actor)) low++;
#Ifdef DEBUG;
if (parser_trace >= 4) print " Token 2 plural case: number with actor ", low, "^";
#Endif; ! DEBUG
if (take_all_rule == 2 || low > 0) {
for (i=1,low=0 : i<=multiple_object-->0 : i++) {
if (ScopeCeiling(multiple_object-->i) == ScopeCeiling(actor)) {
low++;
multiple_object-->low = multiple_object-->i;
}
}
multiple_object-->0 = low;
}
}
i = multiple_object-->0;
#Ifdef DEBUG;
if (parser_trace >= 4) print " Done: new size ", i, "^";
#Endif; ! DEBUG
if (i == 0) return NOTHING_PE;
return 0;
];
! ----------------------------------------------------------------------------
! ScoreMatchL scores the match list for quality in terms of what the
! player has vaguely asked for. Points are awarded for conforming with
! requirements like "my", and so on. Remove from the match list any
! entries which fail the basic requirements of the descriptors.
! ----------------------------------------------------------------------------
[ ScoreMatchL context its_owner its_score obj i j threshold met a_s l_s;
! if (indef_type & OTHER_BIT ~= 0) threshold++;
if (indef_type & MY_BIT ~= 0) threshold++;
if (indef_type & THAT_BIT ~= 0) threshold++;
if (indef_type & LIT_BIT ~= 0) threshold++;
if (indef_type & UNLIT_BIT ~= 0) threshold++;
if (indef_owner ~= nothing) threshold++;
#Ifdef DEBUG;
if (parser_trace >= 4) print " Scoring match list: indef mode ", indef_mode, " type ",
indef_type, ", satisfying ", threshold, " requirements:^";
#Endif; ! DEBUG
a_s = SCORE__NEXTBESTLOC; l_s = SCORE__BESTLOC;
if (context == HELD_TOKEN or MULTIHELD_TOKEN or MULTIEXCEPT_TOKEN) {
a_s = SCORE__BESTLOC; l_s = SCORE__NEXTBESTLOC;
}
for (i=0 : i<number_matched : i++) {
obj = match_list-->i; its_owner = parent(obj); its_score=0; met=0;
! if (indef_type & OTHER_BIT ~= 0
! && obj ~= itobj or himobj or herobj) met++;
if (indef_type & MY_BIT ~= 0 && its_owner == actor) met++;
if (indef_type & THAT_BIT ~= 0 && its_owner == actors_location) met++;
if (indef_type & LIT_BIT ~= 0 && obj has light) met++;
if (indef_type & UNLIT_BIT ~= 0 && obj hasnt light) met++;
if (indef_owner ~= 0 && its_owner == indef_owner) met++;
if (met < threshold) {
#Ifdef DEBUG;
if (parser_trace >= 4) print " ", (The) match_list-->i, " (", match_list-->i, ") in ",
(the) its_owner, " is rejected (doesn't match descriptors)^";
#Endif; ! DEBUG
match_list-->i = -1;
}
else {
its_score = 0;
if (obj hasnt concealed) its_score = SCORE__UNCONCEALED;
if (its_owner == actor) its_score = its_score + a_s;
else
if (its_owner == actors_location) its_score = its_score + l_s;
else
if (its_owner ~= compass) its_score = its_score + SCORE__NOTCOMPASS;
its_score = its_score + SCORE__CHOOSEOBJ * ChooseObjects(obj, 2);
if (obj hasnt scenery) its_score = its_score + SCORE__NOTSCENERY;
if (obj ~= actor) its_score = its_score + SCORE__NOTACTOR;
! A small bonus for having the correct GNA,
! for sorting out ambiguous articles and the like.
if (indef_cases & (PowersOfTwo_TB-->(GetGNAOfObject(obj))))
its_score = its_score + SCORE__GNA;
match_scores-->i = match_scores-->i + its_score;
#Ifdef DEBUG;
if (parser_trace >= 4) print " ", (The) match_list-->i, " (", match_list-->i,
") in ", (the) its_owner, " : ", match_scores-->i, " points^";
#Endif; ! DEBUG
}
}
for (i=0 : i<number_matched : i++) {
while (match_list-->i == -1) {
if (i == number_matched-1) { number_matched--; break; }
for (j=i : j<number_matched : j++) {
match_list-->j = match_list-->(j+1);
match_scores-->j = match_scores-->(j+1);
}
number_matched--;
}
}
];
! ----------------------------------------------------------------------------
! BestGuess makes the best guess it can out of the match list, assuming that
! everything in the match list is textually as good as everything else;
! however it ignores items marked as -1, and so marks anything it chooses.
! It returns -1 if there are no possible choices.
! ----------------------------------------------------------------------------
[ BestGuess earliest its_score best i;
earliest = 0; best = -1;
for (i=0 : i<number_matched : i++) {
if (match_list-->i >= 0) {
its_score = match_scores-->i;
if (its_score > best) { best = its_score; earliest = i; }
}
}
#Ifdef DEBUG;
if (parser_trace >= 4)
if (best < 0) print " Best guess ran out of choices^";
else print " Best guess ", (the) match_list-->earliest, " (", match_list-->earliest, ")^";
#Endif; ! DEBUG
if (best < 0) return -1;
i = match_list-->earliest;
match_list-->earliest = -1;
bestguess_score = best;
return i;
];
! ----------------------------------------------------------------------------
! SingleBestGuess returns the highest-scoring object in the match list
! if it is the clear winner, or returns -1 if there is no clear winner
! ----------------------------------------------------------------------------
[ SingleBestGuess earliest its_score best i;
earliest = -1; best = -1000;
for (i=0 : i<number_matched : i++) {
its_score = match_scores-->i;
if (its_score == best) earliest = -1;
if (its_score > best) { best = its_score; earliest = match_list-->i; }
}
bestguess_score = best;
return earliest;
];
! ----------------------------------------------------------------------------
! Identical decides whether or not two objects can be distinguished from
! each other by anything the player can type. If not, it returns true.
! ----------------------------------------------------------------------------
[ Identical o1 o2 p1 p2 n1 n2 i j flag;
if (o1 == o2) rtrue; ! This should never happen, but to be on the safe side
if (o1 == 0 || o2 == 0) rfalse; ! Similarly
if (parent(o1) == compass || parent(o2) == compass) rfalse; ! Saves time
! What complicates things is that o1 or o2 might have a parsing routine,
! so the parser can't know from here whether they are or aren't the same.
! If they have different parsing routines, we simply assume they're
! different. If they have the same routine (which they probably got from
! a class definition) then the decision process is as follows:
!
! the routine is called (with self being o1, not that it matters)
! with noun and second being set to o1 and o2, and action being set
! to the fake action TheSame. If it returns -1, they are found
! identical; if -2, different; and if >=0, then the usual method
! is used instead.
if (o1.parse_name ~= 0 || o2.parse_name ~= 0) {
if (o1.parse_name ~= o2.parse_name) rfalse;
parser_action = ##TheSame; parser_one = o1; parser_two = o2;
j = wn; i = RunRoutines(o1,parse_name); wn = j;
if (i == -1) rtrue;
if (i == -2) rfalse;
}
! This is the default algorithm: do they have the same words in their
! "name" (i.e. property no. 1) properties. (Note that the following allows
! for repeated words and words in different orders.)
p1 = o1.&1; n1 = (o1.#1)/WORDSIZE;
p2 = o2.&1; n2 = (o2.#1)/WORDSIZE;
! for (i=0 : i<n1 : i++) { print (address) p1-->i, " "; } new_line;
! for (i=0 : i<n2 : i++) { print (address) p2-->i, " "; } new_line;
for (i=0 : i<n1 : i++) {
flag = 0;
for (j=0 : j<n2 : j++)
if (p1-->i == p2-->j) flag = 1;
if (flag == 0) rfalse;
}
for (j=0 : j<n2 : j++) {
flag = 0;
for (i=0 : i<n1 : i++)
if (p1-->i == p2-->j) flag = 1;
if (flag == 0) rfalse;
}
! print "Which are identical!^";
rtrue;
];
! ----------------------------------------------------------------------------
! PrintCommand reconstructs the command as it presently reads, from
! the pattern which has been built up
!
! If from is 0, it starts with the verb: then it goes through the pattern.
! The other parameter is "emptyf" - a flag: if 0, it goes up to pcount:
! if 1, it goes up to pcount-1.
!
! Note that verbs and prepositions are printed out of the dictionary:
! and that since the dictionary may only preserve the first six characters
! of a word (in a V3 game), we have to hand-code the longer words needed.
!
! (Recall that pattern entries are 0 for "multiple object", 1 for "special
! word", 2 to REPARSE_CODE-1 are object numbers and REPARSE_CODE+n means the
! preposition n)
! ----------------------------------------------------------------------------
[ PrintCommand from i k spacing_flag;
if (from == 0) {
i = verb_word;
if (LanguageVerb(i) == 0)
if (PrintVerb(i) == 0) print (address) i;
from++; spacing_flag = true;
}
for (k=from : k<pcount : k++) {
i = pattern-->k;
if (i == PATTERN_NULL) continue;
if (spacing_flag) print (char) ' ';
if (i ==0 ) { print (string) THOSET__TX; jump TokenPrinted; }
if (i == 1) { print (string) THAT__TX; jump TokenPrinted; }
if (i >= REPARSE_CODE)
print (address) No__Dword(i-REPARSE_CODE);
else
if (i in compass && LanguageVerbLikesAdverb(verb_word))
LanguageDirection (i.door_dir); ! the direction name as adverb
else
print (the) i;
.TokenPrinted;
spacing_flag = true;
}
];
! ----------------------------------------------------------------------------
! The CantSee routine returns a good error number for the situation where
! the last word looked at didn't seem to refer to any object in context.
!
! The idea is that: if the actor is in a location (but not inside something
! like, for instance, a tank which is in that location) then an attempt to
! refer to one of the words listed as meaningful-but-irrelevant there
! will cause "you don't need to refer to that in this game" rather than
! "no such thing" or "what's 'it'?".
! (The advantage of not having looked at "irrelevant" local nouns until now
! is that it stops them from clogging up the ambiguity-resolving process.
! Thus game objects always triumph over scenery.)
! ----------------------------------------------------------------------------
[ CantSee i w e;
saved_oops=oops_from;
if (scope_token ~= 0) {
scope_error = scope_token;
return ASKSCOPE_PE;
}
wn--; w = NextWord();
e = CANTSEE_PE;
if (w == pronoun_word) {
pronoun__word = pronoun_word; pronoun__obj = pronoun_obj;
e = ITGONE_PE;
}
i = actor; while (parent(i) ~= 0) i = parent(i);
wn--;
if (i has visited && Refers(i,wn) == 1) e = SCENERY_PE;
else {
Descriptors(); ! skip past THE etc
if (i has visited && Refers(i,wn) == 1) e = SCENERY_PE;
}
wn++;
if (etype > e) return etype;
return e;
];
! ----------------------------------------------------------------------------
! The MultiAdd routine adds object "o" to the multiple-object-list.
!
! This is only allowed to hold 63 objects at most, at which point it ignores
! any new entries (and sets a global flag so that a warning may later be
! printed if need be).
! ----------------------------------------------------------------------------
[ MultiAdd o i j;
i = multiple_object-->0;
if (i == 63) { toomany_flag = 1; rtrue; }
for (j=1 : j<=i : j++)
if (o == multiple_object-->j) rtrue;
i++;
multiple_object-->i = o;
multiple_object-->0 = i;
];
! ----------------------------------------------------------------------------
! The MultiSub routine deletes object "o" from the multiple-object-list.
!
! It returns 0 if the object was there in the first place, and 9 (because
! this is the appropriate error number in Parser()) if it wasn't.
! ----------------------------------------------------------------------------
[ MultiSub o i j k et;
i = multiple_object-->0; et = 0;
for (j=1 : j<=i : j++)
if (o == multiple_object-->j) {
for (k=j : k<=i : k++)
multiple_object-->k = multiple_object-->(k+1);
multiple_object-->0 = --i;
return et;
}
et = 9; return et;
];
! ----------------------------------------------------------------------------
! The MultiFilter routine goes through the multiple-object-list and throws
! out anything without the given attribute "attr" set.
! ----------------------------------------------------------------------------
[ MultiFilter attr i j o;
.MFiltl;
i = multiple_object-->0;
for (j=1 : j<=i : j++) {
o = multiple_object-->j;
if (o hasnt attr) {
MultiSub(o);
jump Mfiltl;
}
}
];
! ----------------------------------------------------------------------------
! The UserFilter routine consults the user's filter (or checks on attribute)
! to see what already-accepted nouns are acceptable
! ----------------------------------------------------------------------------
[ UserFilter obj;
if (token_filter > 0 && token_filter < 49) {
if (obj has (token_filter-1)) rtrue;
rfalse;
}
noun = obj;
return indirect(token_filter);
];
! ----------------------------------------------------------------------------
! MoveWord copies word at2 from parse buffer b2 to word at1 in "parse"
! (the main parse buffer)
! ----------------------------------------------------------------------------
#Ifdef TARGET_ZCODE;
[ MoveWord at1 b2 at2 x y;
x = at1*2-1; y = at2*2-1;
parse-->x++ = b2-->y++;
parse-->x = b2-->y;
];
#Ifnot; ! TARGET_GLULX
[ MoveWord at1 b2 at2 x y;
x = at1*3-2; y = at2*3-2;
parse-->x++ = b2-->y++;
parse-->x++ = b2-->y++;
parse-->x = b2-->y;
];
#Endif; ! TARGET_
! ----------------------------------------------------------------------------
! SearchScope domain1 domain2 context
!
! Works out what objects are in scope (possibly asking an outside routine),
! but does not look at anything the player has typed.
! ----------------------------------------------------------------------------
[ SearchScope domain1 domain2 context i;
i = 0;
! Everything is in scope to the debugging commands
#Ifdef DEBUG;
if (scope_reason == PARSING_REASON
&& LanguageVerbIsDebugging(verb_word)) {
#Ifdef TARGET_ZCODE;
for (i=selfobj : i<=top_object : i++)
if (i ofclass Object && (parent(i) == 0 || parent(i) ofclass Object))
PlaceInScope(i);
#Ifnot; ! TARGET_GLULX
objectloop (i)
if (i ofclass Object && (parent(i) == 0 || parent(i) ofclass Object))
PlaceInScope(i);
#Endif; ! TARGET_
rtrue;
}
#Endif; ! DEBUG
! First, a scope token gets priority here:
if (scope_token ~= 0) {
scope_stage = 2;
if (indirect(scope_token) ~= 0) rtrue;
}
! Next, call any user-supplied routine adding things to the scope,
! which may circumvent the usual routines altogether if they return true:
if (actor == domain1 or domain2 && InScope(actor) ~= 0) rtrue;
! Pick up everything in the location except the actor's possessions;
! then go through those. (This ensures the actor's possessions are in
! scope even in Darkness.)
if (context == MULTIINSIDE_TOKEN && advance_warning ~= -1) {
if (IsSeeThrough(advance_warning) == 1)
ScopeWithin(advance_warning, 0, context);
}
else {
if (domain1 ~= 0 && domain1 has supporter or container)
ScopeWithin_O(domain1, domain1, context);
ScopeWithin(domain1, domain2, context);
if (domain2 ~= 0 && domain2 has supporter or container)
ScopeWithin_O(domain2, domain2, context);
ScopeWithin(domain2, 0, context);
}
! A special rule applies:
! in Darkness as in light, the actor is always in scope to himself.
if (thedark == domain1 or domain2) {
ScopeWithin_O(actor, actor, context);
if (parent(actor) has supporter or container)
ScopeWithin_O(parent(actor), parent(actor), context);
}
];
! ----------------------------------------------------------------------------
! IsSeeThrough is used at various places: roughly speaking, it determines
! whether o being in scope means that the contents of o are in scope.
! ----------------------------------------------------------------------------
[ IsSeeThrough o;
if (o has supporter ||
(o has transparent) ||
(o has container && o has open))
rtrue;
rfalse;
];
! ----------------------------------------------------------------------------
! PlaceInScope is provided for routines outside the library, and is not
! called within the parser (except for debugging purposes).
! ----------------------------------------------------------------------------
[ PlaceInScope thing;
if (scope_reason~=PARSING_REASON or TALKING_REASON) {
DoScopeAction(thing); rtrue;
}
wn = match_from; TryGivenObject(thing); placed_in_flag = 1;
];
! ----------------------------------------------------------------------------
! DoScopeAction
! ----------------------------------------------------------------------------
[ DoScopeAction thing s p1;
s = scope_reason; p1 = parser_one;
#Ifdef DEBUG;
if (parser_trace >= 6)
print "[DSA on ", (the) thing, " with reason = ", scope_reason,
" p1 = ", parser_one, " p2 = ", parser_two, "]^";
#Endif; ! DEBUG
switch (scope_reason) {
REACT_BEFORE_REASON:
if (thing.react_before == 0 or NULL) return;
#Ifdef DEBUG;
if (parser_trace >= 2)
print "[Considering react_before for ", (the) thing, "]^";
#Endif; ! DEBUG
if (parser_one == 0) parser_one = RunRoutines(thing, react_before);
REACT_AFTER_REASON:
if (thing.react_after == 0 or NULL) return;
#Ifdef DEBUG;
if (parser_trace >= 2)
print "[Considering react_after for ", (the) thing, "]^";
#Endif; ! DEBUG
if (parser_one == 0) parser_one = RunRoutines(thing, react_after);
EACH_TURN_REASON:
if (thing.each_turn == 0 or NULL) return;
#Ifdef DEBUG;
if (parser_trace >= 2)
print "[Considering each_turn for ", (the) thing, "]^";
#Endif; ! DEBUG
PrintOrRun(thing, each_turn);
TESTSCOPE_REASON:
if (thing == parser_one) parser_two = 1;
LOOPOVERSCOPE_REASON:
indirect(parser_one, thing);
parser_one=p1;
}
scope_reason = s;
];
! ----------------------------------------------------------------------------
! ScopeWithin looks for objects in the domain which make textual sense
! and puts them in the match list. (However, it does not recurse through
! the second argument.)
! ----------------------------------------------------------------------------
[ ScopeWithin domain nosearch context x y;
if (domain == 0) rtrue;
! Special rule: the directions (interpreted as the 12 walls of a room) are
! always in context. (So, e.g., "examine north wall" is always legal.)
! (Unless we're parsing something like "all", because it would just slow
! things down then, or unless the context is "creature".)
if (indef_mode==0 && domain==actors_location
&& scope_reason==PARSING_REASON && context~=CREATURE_TOKEN)
ScopeWithin(compass);
! Look through the objects in the domain, avoiding "objectloop" in case
! movements occur, e.g. when trying each_turn.
x = child(domain);
while (x ~= 0) {
y = sibling(x);
ScopeWithin_O(x, nosearch, context);
x = y;
}
];
[ ScopeWithin_O domain nosearch context i ad n;
! If the scope reason is unusual, don't parse.
if (scope_reason ~= PARSING_REASON or TALKING_REASON) {
DoScopeAction(domain);
jump DontAccept;
}
! "it" or "them" matches to the it-object only. (Note that (1) this means
! that "it" will only be understood if the object in question is still
! in context, and (2) only one match can ever be made in this case.)
if (match_from <= num_words) { ! If there's any text to match, that is
wn = match_from;
i = NounWord();
if (i == 1 && player == domain) MakeMatch(domain, 1);
if (i >= 2 && i < 128 && (LanguagePronouns-->i == domain)) MakeMatch(domain, 1);
}
! Construing the current word as the start of a noun, can it refer to the
! object?
wn = match_from;
if (TryGivenObject(domain) > 0)
if (indef_nspec_at > 0 && match_from ~= indef_nspec_at) {
! This case arises if the player has typed a number in
! which is hypothetically an indefinite descriptor:
! e.g. "take two clubs". We have just checked the object
! against the word "clubs", in the hope of eventually finding
! two such objects. But we also backtrack and check it
! against the words "two clubs", in case it turns out to
! be the 2 of Clubs from a pack of cards, say. If it does
! match against "two clubs", we tear up our original
! assumption about the meaning of "two" and lapse back into
! definite mode.
wn = indef_nspec_at;
if (TryGivenObject(domain) > 0) {
match_from = indef_nspec_at;
ResetDescriptors();
}
wn = match_from;
}
.DontAccept;
! Shall we consider the possessions of the current object, as well?
! Only if it's a container (so, for instance, if a dwarf carries a
! sword, then "drop sword" will not be accepted, but "dwarf, drop sword"
! will).
! Also, only if there are such possessions.
!
! Notice that the parser can see "into" anything flagged as
! transparent - such as a dwarf whose sword you can get at.
if (child(domain) ~= 0 && domain ~= nosearch && IsSeeThrough(domain) == 1)
ScopeWithin(domain,nosearch,context);
! Drag any extras into context
ad = domain.&add_to_scope;
if (ad ~= 0) {
! Test if the property value is not an object.
#Ifdef TARGET_ZCODE;
i = (UnsignedCompare(ad-->0, top_object) > 0);
#Ifnot; ! TARGET_GLULX
i = (((ad-->0)->0) ~= $70);
#Endif; ! TARGET_
if (i) {
ats_flag = 2+context;
RunRoutines(domain, add_to_scope);
ats_flag = 0;
}
else {
n = domain.#add_to_scope;
for (i=0 : (WORDSIZE*i)<n : i++)
if (ad-->i)
ScopeWithin_O(ad-->i, 0, context);
}
}
];
[ AddToScope obj;
if (ats_flag >= 2)
ScopeWithin_O(obj, 0, ats_flag-2);
if (ats_flag == 1) {
if (HasLightSource(obj)==1) ats_hls = 1;
}
];
! ----------------------------------------------------------------------------
! MakeMatch looks at how good a match is. If it's the best so far, then
! wipe out all the previous matches and start a new list with this one.
! If it's only as good as the best so far, add it to the list.
! If it's worse, ignore it altogether.
!
! The idea is that "red panic button" is better than "red button" or "panic".
!
! number_matched (the number of words matched) is set to the current level
! of quality.
!
! We never match anything twice, and keep at most 64 equally good items.
! ----------------------------------------------------------------------------
[ MakeMatch obj quality i;
#Ifdef DEBUG;
if (parser_trace >= 6) print " Match with quality ",quality,"^";
#Endif; ! DEBUG
if (token_filter ~= 0 && UserFilter(obj) == 0) {
#Ifdef DEBUG;
if (parser_trace >= 6) print " Match filtered out: token filter ", token_filter, "^";
#Endif; ! DEBUG
rtrue;
}
if (quality < match_length) rtrue;
if (quality > match_length) { match_length = quality; number_matched = 0; }
else {
if (2*number_matched >= MATCH_LIST_SIZE) rtrue;
for (i=0 : i<number_matched : i++)
if (match_list-->i == obj) rtrue;
}
match_list-->number_matched++ = obj;
#Ifdef DEBUG;
if (parser_trace >= 6) print " Match added to list^";
#Endif; ! DEBUG
];
! ----------------------------------------------------------------------------
! TryGivenObject tries to match as many words as possible in what has been
! typed to the given object, obj. If it manages any words matched at all,
! it calls MakeMatch to say so, then returns the number of words (or 1
! if it was a match because of inadequate input).
! ----------------------------------------------------------------------------
[ TryGivenObject obj threshold k w j;
#Ifdef DEBUG;
if (parser_trace >= 5) print " Trying ", (the) obj, " (", obj, ") at word ", wn, "^";
#Endif; ! DEBUG
dict_flags_of_noun = 0;
! If input has run out then always match, with only quality 0 (this saves
! time).
if (wn > num_words) {
if (indef_mode ~= 0)
dict_flags_of_noun = $$01110000; ! Reject "plural" bit
MakeMatch(obj,0);
#Ifdef DEBUG;
if (parser_trace >= 5) print " Matched (0)^";
#Endif; ! DEBUG
return 1;
}
! Ask the object to parse itself if necessary, sitting up and taking notice
! if it says the plural was used:
if (obj.parse_name~=0) {
parser_action = NULL; j=wn;
k = RunRoutines(obj,parse_name);
if (k > 0) {
wn=j+k;
.MMbyPN;
if (parser_action == ##PluralFound)
! dict_flags_of_noun = dict_flags_of_noun | 4;
! закомментировано, лучше работает парсер, нужно тестирование
if (dict_flags_of_noun & 4) {
if (~~allow_plurals) k = 0;
else {
if (indef_mode == 0) {
indef_mode = 1; indef_type = 0; indef_wanted = 0;
}
indef_type = indef_type | PLURAL_BIT;
if (indef_wanted == 0) indef_wanted = 100;
}
}
#Ifdef DEBUG;
if (parser_trace >= 5) print " Matched (", k, ")^";
#Endif; ! DEBUG
MakeMatch(obj,k);
return k;
}
if (k == 0) jump NoWordsMatch;
}
! The default algorithm is simply to count up how many words pass the
! Refers test:
parser_action = NULL;
w = NounWord();
if (w == 1 && player == obj) { k=1; jump MMbyPN; }
if (w >= 2 && w < 128 && (LanguagePronouns-->w == obj)) { k = 1; jump MMbyPN; }
j=--wn;
threshold = ParseNoun(obj);
#Ifdef DEBUG;
if (threshold >= 0 && parser_trace >= 5) print " ParseNoun returned ", threshold, "^";
#Endif; ! DEBUG
if (threshold < 0) wn++;
if (threshold > 0) { k = threshold; jump MMbyPN; }
if (threshold == 0 || Refers(obj,wn-1) == 0) {
.NoWordsMatch;
if (indef_mode ~= 0) {
k = 0; parser_action = NULL;
jump MMbyPN;
}
rfalse;
}
if (threshold < 0) {
threshold = 1;
dict_flags_of_noun = (w->#dict_par1) & $$01110100;
w = NextWord();
while (Refers(obj, wn-1)) {
threshold++;
if (w)
dict_flags_of_noun = dict_flags_of_noun | ((w->#dict_par1) & $$01110100);
w = NextWord();
}
}
k = threshold;
jump MMbyPN;
];
! ----------------------------------------------------------------------------
! Refers works out whether the word at number wnum can refer to the object
! obj, returning true or false. The standard method is to see if the
! word is listed under "name" for the object, but this is more complex
! in languages other than English.
! ----------------------------------------------------------------------------
[ Refers obj wnum wd k l m;
if (obj == 0) rfalse;
#Ifdef LanguageRefers;
k = LanguageRefers(obj,wnum); if (k >= 0) return k;
#Endif; ! LanguageRefers
k = wn; wn = wnum; wd = NextWordStopped(); wn = k;
if (parser_inflection >= 256) {
k = indirect(parser_inflection, obj, wd);
if (k >= 0) return k;
m = -k;
}
else
m = parser_inflection;
k = obj.&m; l = (obj.#m)/WORDSIZE-1;
for (m=0 : m<=l : m++)
if (wd == k-->m) rtrue;
rfalse;
];
[ WordInProperty wd obj prop k l m;
k = obj.&prop; l = (obj.#prop)/WORDSIZE-1;
for (m=0 : m<=l : m++)
if (wd == k-->m) rtrue;
rfalse;
];
#Ifdef TARGET_ZCODE;
[ DictionaryLookup b l i;
for (i=0 : i<l : i++) buffer2->(2+i) = b->i;
buffer2->1 = l;
Tokenise__(buffer2,parse2);
return parse2-->1;
];
#Ifnot; ! TARGET_GLULX
[ DictionaryLookup b l i;
for (i=0 : i<l : i++) buffer2->(WORDSIZE+i) = b->i;
buffer2-->0 = l;
VM_Tokenise(buffer2,parse2);
return parse2-->1;
];
#Endif; ! TARGET_
! ----------------------------------------------------------------------------
! NounWord (which takes no arguments) returns:
!
! 0 if the next word is unrecognised or does not carry the "noun" bit in
! its dictionary entry,
! 1 if a word meaning "me",
! the index in the pronoun table (plus 2) of the value field of a pronoun,
! if the word is a pronoun,
! the address in the dictionary if it is a recognised noun.
!
! The "current word" marker moves on one.
! ----------------------------------------------------------------------------
[ NounWord i j s;
i = NextWord();
if (i == 0) rfalse;
#ifdef LIBRARY_RUSSIAN;
if (i == ME1__WD or ME2__WD or ME3__WD or ME4__WD or ME5__WD or ME6__WD or ME7__WD) return 1;
#ifnot;
if (i == ME1__WD or ME2__WD or ME3__WD) return 1;
#endif; ! LIBRARY_RUSSIAN
s = LanguagePronouns-->0;
for (j=1 : j<=s : j=j+3)
if (i == LanguagePronouns-->j)
return j+2;
if ((i->#dict_par1)&128 == 0) rfalse;
return i;
];
! ----------------------------------------------------------------------------
! NextWord (which takes no arguments) returns:
!
! 0 if the next word is unrecognised,
! comma_word if a comma
! THEN1__WD if a full stop
! or the dictionary address if it is recognised.
! The "current word" marker is moved on.
!
! NextWordStopped does the same, but returns -1 when input has run out
! ----------------------------------------------------------------------------
#Ifdef TARGET_ZCODE;
[ NextWord i j;
if (wn > parse->1) { wn++; rfalse; }
i = wn*2-1; wn++;
j = parse-->i;
if (j == ',//') j = comma_word;
if (j == './/') j = THEN1__WD;
return j;
];
[ NextWordStopped;
if (wn > parse->1) { wn++; return -1; }
return NextWord();
];
[ WordAddress wordnum; return buffer + parse->(wordnum*4+1); ];
[ WordLength wordnum; return parse->(wordnum*4); ];
#Ifnot; ! TARGET_GLULX
[ NextWord i j;
if (wn > parse-->0) { wn++; rfalse; }
i = wn*3-2; wn++;
j = parse-->i;
if (j == ',//') j=comma_word;
if (j == './/') j=THEN1__WD;
return j;
];
[ NextWordStopped;
if (wn > parse-->0) {
wn++;
return -1;
}
return NextWord();
];
![ WordAddress wordnum; return buffer + parse-->(wordnum*3); ];
! Unicode version below
[ WordAddress wordnum; return buffer + WORDSIZE * parse-->(wordnum*3); ];
[ WordLength wordnum; return parse-->(wordnum*3-1); ];
#Endif; ! TARGET_
! ----------------------------------------------------------------------------
! TryNumber is the only routine which really does any character-level
! parsing, since that's normally left to the Z-machine.
! It takes word number "wordnum" and tries to parse it as an (unsigned)
! decimal number, returning
!
! -1000 if it is not a number
! the number if it has between 1 and 4 digits
! 10000 if it has 5 or more digits.
!
! (The danger of allowing 5 digits is that Z-machine integers are only
! 16 bits long, and anyway this isn't meant to be perfect.)
!
! Using NumberWord, it also catches "one" up to "twenty".
!
! Note that a game can provide a ParseNumber routine which takes priority,
! to enable parsing of odder numbers ("x45y12", say).
! ----------------------------------------------------------------------------
[ TryNumber wordnum i j c num len mul tot d digit;
i = wn; wn = wordnum; j = NextWord(); wn = i;
j = NumberWord(j); ! Test for verbal forms ONE to TWENTY
if (j >= 1) return j;
num = WordAddress(wordnum); len = WordLength(wordnum);
if (len >= 4) mul=1000;
if (len == 3) mul=100;
if (len == 2) mul=10;
if (len == 1) mul=1;
tot = 0; c = 0; len = len-1;
for (c=0 : c<=len : c++) {
digit=num-->c;
if (digit == '0') { d = 0; jump digok; }
if (digit == '1') { d = 1; jump digok; }
if (digit == '2') { d = 2; jump digok; }
if (digit == '3') { d = 3; jump digok; }
if (digit == '4') { d = 4; jump digok; }
if (digit == '5') { d = 5; jump digok; }
if (digit == '6') { d = 6; jump digok; }
if (digit == '7') { d = 7; jump digok; }
if (digit == '8') { d = 8; jump digok; }
if (digit == '9') { d = 9; jump digok; }
return -1000;
.digok;
tot = tot+mul*d; mul = mul/10;
}
if (len > 3) tot=10000;
return tot;
];
! ----------------------------------------------------------------------------
! GetGender returns 0 if the given animate object is female, and 1 if male
! (not all games will want such a simple decision function!)
! ----------------------------------------------------------------------------
[ GetGender person;
if (person hasnt female) rtrue;
rfalse;
];
[ GetGNAOfObject obj case gender;
if (obj hasnt animate) case = 6;
if (obj has male) gender = male;
if (obj has female) gender = female;
if (obj has neuter) gender = neuter;
if (gender == 0) {
if (case == 0) gender = LanguageAnimateGender;
else gender = LanguageInanimateGender;
}
if (gender == female) case = case + 1;
if (gender == neuter) case = case + 2;
if (obj has pluralname) case = case + 3;
return case;
];
! ----------------------------------------------------------------------------
! Converting between dictionary addresses and entry numbers
! ----------------------------------------------------------------------------
#Ifdef TARGET_ZCODE;
[ Dword__No w; return (w-(HDR_DICTIONARY-->0 + 7))/9; ];
[ No__Dword n; return HDR_DICTIONARY-->0 + 7 + 9*n; ];
#Ifnot; ! TARGET_GLULX
! In Glulx, dictionary entries *are* addresses.
[ Dword__No w; return w; ];
[ No__Dword n; return n; ];
#Endif; ! TARGET_
! ----------------------------------------------------------------------------
! For copying buffers
! ----------------------------------------------------------------------------
#Ifdef TARGET_ZCODE;
[ CopyBuffer bto bfrom i size;
size = bto->0;
for (i=1 : i<=size : i++) bto->i = bfrom->i;
];
#Ifnot; ! TARGET_GLULX
[ CopyBuffer bto bfrom i;
for (i=0 : i<INPUT_BUFFER_LEN : i++) bto->i = bfrom->i;
];
#Endif; ! TARGET_
! ----------------------------------------------------------------------------
! Provided for use by language definition files
! ----------------------------------------------------------------------------
#Ifdef TARGET_ZCODE;
[ 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->1;
if (y > b->0) y = b->0;
! Move the subsequent text along one character:
for (y=y+2 : y>i : y--) b->y = b->(y-1);
b->i = ch;
! And the text is now one character longer:
if (b->1 < b->0) (b->1)++;
];
#Ifnot; ! TARGET_GLULX
[ 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)++;
];
#Endif; ! TARGET_
! ============================================================================
[ PronounsSub x y c d;
L__M(##Pronouns, 1);
c = (LanguagePronouns-->0)/3;
if (player ~= selfobj) c++;
if (c == 0) return L__M(##Pronouns, 4);
for (x=1,d=0 : x<=LanguagePronouns-->0 : x=x+3) {
#ifdef LIBRARY_RUSSIAN;
print "@<<", (address) LanguagePronouns-->x, "@>> ";
#ifnot;
print "~", (address) LanguagePronouns-->x, "~ ";
#endif; ! LIBRARY_RUSSIAN
y = LanguagePronouns-->(x+2);
if (y == NULL) L__M(##Pronouns, 3);
else {
L__M(##Pronouns, 2);
print (the) y;
}
d++;
if (d < c-1) print (string) COMMA__TX;
if (d == c-1) print (string) AND__TX;
}
if (player ~= selfobj) {
#ifdef LIBRARY_RUSSIAN;
print "@<<", (address) ME1__WD, "@>> "; L__M(##Pronouns, 2);
#ifnot;
print "~", (address) ME1__WD, "~ "; L__M(##Pronouns, 2);
#endif; ! LIBRARY_RUSSIAN
c = player; player = selfobj;
print (the) c; player = c;
}
L__M(##Pronouns, 5);
];
[ SetPronoun dword value x;
for (x=1 : x<=LanguagePronouns-->0 : x=x+3)
if (LanguagePronouns-->x == dword) {
LanguagePronouns-->(x+2) = value; return;
}
RunTimeError(14);
];
[ PronounValue dword x;
for (x=1 : x<=LanguagePronouns-->0 : x=x+3)
if (LanguagePronouns-->x == dword)
return LanguagePronouns-->(x+2);
return 0;
];
[ ResetVagueWords obj; PronounNotice(obj); ];
#Ifdef EnglishNaturalLanguage;
[ PronounOldEnglish;
if (itobj ~= old_itobj) SetPronoun('it', itobj);
if (himobj ~= old_himobj) SetPronoun('him', himobj);
if (herobj ~= old_herobj) SetPronoun('her', herobj);
old_itobj = itobj; old_himobj = himobj; old_herobj = herobj;
];
#Endif; !EnglishNaturalLanguage
[ PronounNotice obj x bm;
if (obj == player) return;
#Ifdef EnglishNaturalLanguage;
PronounOldEnglish();
#Endif; ! EnglishNaturalLanguage
bm = PowersOfTwo_TB-->(GetGNAOfObject(obj));
for (x=1 : x<=LanguagePronouns-->0 : x=x+3)
if (bm & (LanguagePronouns-->(x+1)) ~= 0)
LanguagePronouns-->(x+2) = obj;
#Ifdef EnglishNaturalLanguage;
itobj = PronounValue('it'); old_itobj = itobj;
himobj = PronounValue('him'); old_himobj = himobj;
herobj = PronounValue('her'); old_herobj = herobj;
#Endif; ! EnglishNaturalLanguage
];
! ============================================================================
! End of the parser proper: the remaining routines are its front end.
! ----------------------------------------------------------------------------
Object InformLibrary "(Inform Library)"
with play [ i j k l;
#Ifdef TARGET_ZCODE;
standard_interpreter = HDR_TERPSTANDARD-->0;
transcript_mode = ((HDR_GAMEFLAGS-->0) & 1);
sys_statusline_flag = ( (HDR_TERPFLAGS->0) & 2 ) / 2;
#Ifnot; ! TARGET_GLULX
GGInitialise();
#Endif; ! TARGET_
ChangeDefault(cant_go, CANTGO__TX);
#Ifdef TARGET_ZCODE;
dict_start = HDR_DICTIONARY-->0;
dict_entry_size = dict_start->(dict_start->0 + 1);
dict_start = dict_start + dict_start->0 + 4;
dict_end = dict_start + (dict_start - 2)-->0 * dict_entry_size;
#Ifdef DEBUG;
if (dict_start > 0 && dict_end < 0 &&
((-dict_start) - dict_end) % dict_entry_size == 0)
print "** Warning: grammar properties might not work correctly **^";
#Endif; ! DEBUG
buffer->0 = INPUT_BUFFER_LEN;
buffer2->0 = INPUT_BUFFER_LEN;
buffer3->0 = INPUT_BUFFER_LEN;
parse->0 = 15;
parse2->0 = 15;
#Endif; ! TARGET_ZCODE
real_location = thedark;
player = selfobj; actor = player;
#Ifdef TARGET_ZCODE;
top_object = #largest_object-255;
#Endif; ! TARGET_ZCODE
selfobj.capacity = MAX_CARRIED; ! ### change?
#Ifdef LanguageInitialise;
LanguageInitialise();
#Endif; ! LanguageInitialise
new_line;
LibraryExtensions.RunAll(ext_initialise);
j = Initialise();
last_score = score;
move player to location;
while (parent(location) ~= 0) location = parent(location);
real_location = location;
actor = player; ! resync, because player may have been changed in initialise()
actors_location = location;
objectloop (i in player) give i moved ~concealed;
if (j ~= 2) Banner();
MoveFloatingObjects();
lightflag = OffersLight(parent(player));
if (lightflag == 0) {
real_location = location;
location = thedark;
}
<Look>;
for (i=1 : i<=100 : i++) j = random(i);
#Ifdef EnglishNaturalLanguage;
old_itobj = itobj; old_himobj = himobj; old_herobj = herobj;
#Endif;! EnglishNaturalLanguage
while (~~deadflag) { ! everything happens in this loop
#Ifdef EnglishNaturalLanguage;
PronounOldEnglish();
old_itobj = PronounValue('it');
old_himobj = PronounValue('him');
old_herobj = PronounValue('her');
#Endif; ! EnglishNaturalLanguage
.very__late__error;
if (score ~= last_score) {
if (notify_mode == 1) NotifyTheScore();
last_score = score;
}
.late__error;
inputobjs-->0 = 0; inputobjs-->1 = 0;
inputobjs-->2 = 0; inputobjs-->3 = 0; meta=false;
! The Parser writes its results into inputobjs and meta,
! a flag indicating a "meta-verb". This can only be set for
! commands by the player, not for orders to others.
InformParser.parse_input(inputobjs);
action = inputobjs-->0;
! --------------------------------------------------------------
! Reverse "give fred biscuit" into "give biscuit to fred"
if (action == ##GiveR or ##ShowR) {
i = inputobjs-->2; inputobjs-->2 = inputobjs-->3; inputobjs-->3 = i;
if (action == ##GiveR) action = ##Give; else action = ##Show;
}
! Convert "P, tell me about X" to "ask P about X"
if (action == ##Tell && inputobjs-->2 == player && actor ~= player) {
inputobjs-->2 = actor; actor = player; action = ##Ask;
}
! Convert "ask P for X" to "P, give X to me"
if (action == ##AskFor && inputobjs-->2 ~= player && actor == player) {
actor = inputobjs-->2; inputobjs-->2 = inputobjs-->3;
inputobjs-->3 = player; action = ##Give;
}
! For old, obsolete code: special_word contains the topic word
! in conversation
if (action == ##Ask or ##Tell or ##Answer)
special_word = special_number1;
! --------------------------------------------------------------
multiflag = false; onotheld_mode = notheld_mode; notheld_mode = false;
! For implicit taking and multiple object detection
.begin__action;
inp1 = 0; inp2 = 0; i = inputobjs-->1;
if (i >= 1) inp1 = inputobjs-->2;
if (i >= 2) inp2 = inputobjs-->3;
! inp1 and inp2 hold: object numbers, or 0 for "multiple object",
! or 1 for "a number or dictionary address"
if (inp1 == 1) noun = special_number1; else noun = inp1;
if (inp2 == 1) {
if (inp1 == 1) second = special_number2;
else second = special_number1;
}
else second = inp2;
! --------------------------------------------------------------
if (actor ~= player) {
! The player's "orders" property can refuse to allow conversation
! here, by returning true. If not, the order is sent to the
! other person's "orders" property. If that also returns false,
! then: if it was a misunderstood command anyway, it is converted
! to an Answer action (thus "floyd, grrr" ends up as
! "say grrr to floyd"). If it was a good command, it is finally
! offered to the Order: part of the other person's "life"
! property, the old-fashioned way of dealing with conversation.
j = RunRoutines(player, orders);
if (j == 0) {
j = RunRoutines(actor, orders);
if (j == 0) {
if (action == ##NotUnderstood) {
inputobjs-->3 = actor; actor = player; action = ##Answer;
jump begin__action;
}
if (RunLife(actor, ##Order) == 0) L__M(##Order, 1, actor);
}
}
jump turn__end;
}
! --------------------------------------------------------------
! Generate the action...
if ((i == 0) ||
(i == 1 && inp1 ~= 0) ||
(i == 2 && inp1 ~= 0 && inp2 ~= 0)) {
self.begin_action(action, noun, second, 0);
jump turn__end;
}
! ...unless a multiple object must be substituted. First:
! (a) check the multiple list isn't empty;
! (b) warn the player if it has been cut short because too long;
! (c) generate a sequence of actions from the list
! (stopping in the event of death or movement away).
multiflag = true;
j = multiple_object-->0;
if (j == 0) {
L__M(##Miscellany, 2);
jump late__error;
}
if (toomany_flag) {
toomany_flag = false;
L__M(##Miscellany, 1);
}
i = location;
for (k=1 : k<=j : k++) {
if (deadflag) break;
if (location ~= i) {
L__M(##Miscellany, 51);
break;
}
l = multiple_object-->k;
PronounNotice(l);
print (name) l, ": ";
if (inp1 == 0) {
inp1 = l; self.begin_action(action, l, second, 0); inp1 = 0;
}
else {
inp2 = l; self.begin_action(action, noun, l, 0); inp2 = 0;
}
}
! --------------------------------------------------------------
.turn__end;
! No time passes if either (i) the verb was meta, or
! (ii) we've only had the implicit take before the "real"
! action to follow.
if (notheld_mode == 1) { NoteObjectAcquisitions(); continue; }
if (meta) continue;
if (~~deadflag) self.end_turn_sequence();
else if (START_MOVE ~= 1) turns++;
} ! end of while()
if (deadflag ~= 2) AfterLife();
if (deadflag == 0) jump very__late__error;
print "^^ ";
#Ifdef TARGET_ZCODE;
#IfV5; style bold; #Endif; ! V5
#Ifnot; ! TARGET_GLULX
glk($0086, 5); ! set alert style
#Endif; ! TARGET_
print "***";
if (deadflag == 1) L__M(##Miscellany, 3);
if (deadflag == 2) L__M(##Miscellany, 4);
if (deadflag > 2) {
print " ";
DeathMessage();
print " ";
}
print "***";
#Ifdef TARGET_ZCODE;
#IfV5; style roman; #Endif; ! V5
#Ifnot; ! TARGET_GLULX
glk($0086, 0); ! set normal style
#Endif; ! TARGET_
#Ifdef NO_SCORE;
print "^^";
#Ifnot;
print "^^^";
#Endif; ! NO_SCORE
ScoreSub();
DisplayStatus();
AfterGameOver();
], ! end of 'play' property
end_turn_sequence [;
AdvanceWorldClock();
if (deadflag) return;
RunTimersAndDaemons();
if (deadflag) return;
RunEachTurnProperties();
if (deadflag) return;
TimePasses();
if (deadflag) return;
AdjustLight();
if (deadflag) return;
NoteObjectAcquisitions();
],
begin_action [ a n s source sa sn ss;
sa = action; sn = noun; ss = second;
action = a; noun = n; second = s;
#Ifdef DEBUG;
if (debug_flag & 2 ~= 0) TraceAction(source);
#Ifnot;
source = 0;
#Endif; ! DEBUG
#Iftrue (Grammar__Version == 1);
if ((meta || BeforeRoutines() == false) && action < 256)
ActionPrimitive();
#Ifnot;
if ((meta || BeforeRoutines() == false) && action < 4096)
ActionPrimitive();
#Endif; ! Grammar__Version
action = sa; noun = sn; second = ss;
],
has proper;
[ AdvanceWorldClock;
turns++;
if (the_time ~= NULL) {
if (time_rate >= 0) the_time=the_time+time_rate;
else {
time_step--;
if (time_step == 0) {
the_time++;
time_step = -time_rate;
}
}
the_time = the_time % 1440;
}
];
[ RunTimersAndDaemons i j;
#Ifdef DEBUG;
if (debug_flag & 4 ~= 0) {
for (i=0 : i<active_timers : i++) {
j = the_timers-->i;
if (j ~= 0) {
print (name) (j&~WORD_HIGHBIT), ": ";
if (j & WORD_HIGHBIT) print "daemon";
else
print "timer with ", j.time_left, " turns to go";
new_line;
}
}
}
#Endif; ! DEBUG
for (i=0 : i<active_timers : i++) {
if (deadflag) return;
j = the_timers-->i;
if (j ~= 0) {
if (j & WORD_HIGHBIT) RunRoutines(j&~WORD_HIGHBIT, daemon);
else {
if (j.time_left == 0) {
StopTimer(j);
RunRoutines(j, time_out);
}
else
j.time_left = j.time_left-1;
}
}
}
];
[ RunEachTurnProperties;
scope_reason = EACH_TURN_REASON; verb_word = 0;
DoScopeAction(location);
SearchScope(ScopeCeiling(player), player, 0);
scope_reason = PARSING_REASON;
];
#Ifdef TARGET_ZCODE;
[ ActionPrimitive; indirect(#actions_table-->action); ];
#Ifnot; ! TARGET_GLULX
[ ActionPrimitive; indirect(#actions_table-->(action+1)); ];
#Endif; ! TARGET_
[ AfterGameOver i;
.RRQPL;
L__M(##Miscellany,5);
.RRQL;
print "> ";
#Ifdef TARGET_ZCODE;
#IfV3; read buffer parse; #Endif; ! V3
temp_global=0;
#IfV5; read buffer parse DrawStatusLine; #Endif; ! V5
#Ifnot; ! TARGET_GLULX
KeyboardPrimitive(buffer, parse);
#Endif; ! TARGET_
i = parse-->1;
if (i == QUIT1__WD or QUIT2__WD) {
#Ifdef TARGET_ZCODE;
quit;
#Ifnot; ! TARGET_GLULX
quit;
#Endif; ! TARGET_
}
#ifdef LIBRARY_RUSSIAN;
if (i == RESTART__WD or RESTART2__WD) {
#ifnot;
if (i == RESTART__WD) {
#endif; ! LIBRARY_RUSSIAN;
#Ifdef TARGET_ZCODE;
@restart;
#Ifnot; ! TARGET_GLULX
@restart;
#Endif; ! TARGET_
}
#ifdef LIBRARY_RUSSIAN;
if (i == RESTORE__WD or RESTORE2__WD) {
#ifnot;
if (i == RESTORE__WD) {
#endif; ! LIBRARY_RUSSIAN;
RestoreSub();
jump RRQPL;
}
if (i == FULLSCORE1__WD or FULLSCORE2__WD && TASKS_PROVIDED==0) {
new_line; FullScoreSub();
jump RRQPL;
}
if (deadflag == 2 && i == AMUSING__WD && AMUSING_PROVIDED==0) {
new_line; Amusing();
jump RRQPL;
}
#IfV5;
if (i == UNDO1__WD or UNDO2__WD or UNDO3__WD) {
if (undo_flag == 0) {
L__M(##Miscellany, 6);
jump RRQPL;
}
if (undo_flag == 1) jump UndoFailed2;
#Ifdef TARGET_ZCODE;
@restore_undo i;
#Ifnot; ! TARGET_GLULX
@restoreundo i;
i = (~~i);
#Endif; ! TARGET_
if (i == 0) {
.UndoFailed2;
L__M(##Miscellany, 7);
}
jump RRQPL;
}
#Endif; ! V5
L__M(##Miscellany, 8);
jump RRQL;
];
[ R_Process a i j s1 s2;
s1 = inp1; s2 = inp2;
inp1 = i; inp2 = j; InformLibrary.begin_action(a, i, j, 1);
inp1 = s1; inp2 = s2;
];
[ NoteObjectAcquisitions i;
objectloop (i in player)
if (i hasnt moved) {
give i moved;
if (i has scored) {
score = score + OBJECT_SCORE;
things_score = things_score + OBJECT_SCORE;
}
}
];
! ----------------------------------------------------------------------------
[ TestScope obj act a al sr x y;
x = parser_one; y = parser_two;
parser_one = obj; parser_two = 0; a = actor; al = actors_location;
sr = scope_reason; scope_reason = TESTSCOPE_REASON;
if (act == 0) actor = player; else actor = act;
actors_location = ScopeCeiling(actor);
SearchScope(actors_location, actor, 0); scope_reason = sr; actor = a;
actors_location = al; parser_one = x; x = parser_two; parser_two = y;
return x;
];
[ LoopOverScope routine act x y a al;
x = parser_one; y = scope_reason; a = actor; al = actors_location;
parser_one = routine;
if (act == 0) actor = player; else actor = act;
actors_location = ScopeCeiling(actor);
scope_reason = LOOPOVERSCOPE_REASON;
SearchScope(actors_location, actor, 0);
parser_one = x; scope_reason = y; actor = a; actors_location = al;
];
[ BeforeRoutines;
if (GamePreRoutine() ~= 0) rtrue;
if (RunRoutines(player, orders) ~= 0) rtrue;
scope_reason = REACT_BEFORE_REASON; parser_one=0;
SearchScope(ScopeCeiling(player), player, 0);
scope_reason = PARSING_REASON;
if (parser_one ~= 0) rtrue;
if (location ~= 0 && RunRoutines(location, before) ~= 0) rtrue;
if (inp1 > 1 && RunRoutines(inp1, before) ~= 0) rtrue;
rfalse;
];
[ AfterRoutines;
scope_reason = REACT_AFTER_REASON; parser_one = 0;
SearchScope(ScopeCeiling(player), player, 0); scope_reason = PARSING_REASON;
if (parser_one ~= 0) rtrue;
if (location ~= 0 && RunRoutines(location, after) ~= 0) rtrue;
if (inp1 > 1 && RunRoutines(inp1, after) ~= 0) rtrue;
return GamePostRoutine();
];
[ RunLife a j;
#Ifdef DEBUG;
if (debug_flag & 2 ~= 0) TraceAction(2, j);
#Endif; ! DEBUG
reason_code = j; return RunRoutines(a,life);
];
[ ZRegion addr;
switch (metaclass(addr)) { ! Left over from Inform 5
nothing: return 0;
Object, Class: return 1;
Routine: return 2;
String: return 3;
}
];
[ PrintOrRun obj prop flag;
if (obj.#prop > WORDSIZE) return RunRoutines(obj,prop);
if (obj.prop == NULL) rfalse;
switch (metaclass(obj.prop)) {
Class, Object, nothing:
return RunTimeError(2,obj,prop);
String:
print (string) obj.prop;
if (flag == 0) new_line;
rtrue;
Routine:
return RunRoutines(obj,prop);
}
];
[ PrintOrRunVar var flag;
switch (metaclass(var)) {
Object:
print (name) var;
String:
print (string) var;
if (flag == 0) new_line;
Routine:
return var();
default:
print (char) '(', var, (char) ')';
}
rtrue;
];
[ ValueOrRun obj prop;
!### this is entirely unlikely to work. Does anyone care? (AP)
! Well, it's certainly used three times in verblibm.h (RF)
if (obj.prop < 256) return obj.prop;
return RunRoutines(obj, prop);
];
[ RunRoutines obj prop;
if (obj == thedark && prop ~= initial or short_name or description) obj = real_location;
if (obj.&prop == 0 && prop >= INDIV_PROP_START) rfalse;
return obj.prop();
];
#Ifdef TARGET_ZCODE;
[ ChangeDefault prop val a b;
! Use assembly-language here because -S compilation won't allow this:
@loadw 0 5 -> a;
b = prop-1;
@storew a b val;
];
#Ifnot; ! TARGET_GLULX
[ ChangeDefault prop val;
! Use assembly-language here because -S compilation won't allow this:
! #cpv__start-->prop = val;
@astore #cpv__start prop val;
];
#Endif; ! TARGET_
! ----------------------------------------------------------------------------
[ StartTimer obj timer i;
for (i=0 : i<active_timers : i++)
if (the_timers-->i == obj) rfalse;
for (i=0 : i<active_timers : i++)
if (the_timers-->i == 0) jump FoundTSlot;
i = active_timers++;
if (i >= MAX_TIMERS) { RunTimeError(4); return; }
.FoundTSlot;
if (obj.&time_left == 0) { RunTimeError(5,obj); return; }
the_timers-->i = obj; obj.time_left = timer;
];
[ StopTimer obj i;
for (i=0 : i<active_timers : i++)
if (the_timers-->i == obj) jump FoundTSlot2;
rfalse;
.FoundTSlot2;
if (obj.&time_left == 0) { RunTimeError(5,obj); return; }
the_timers-->i = 0; obj.time_left = 0;
];
[ StartDaemon obj i;
for (i=0 : i<active_timers : i++)
if (the_timers-->i == WORD_HIGHBIT + obj) rfalse;
for (i=0 : i<active_timers : i++)
if (the_timers-->i == 0) jump FoundTSlot3;
i = active_timers++;
if (i >= MAX_TIMERS) RunTimeError(4);
.FoundTSlot3;
the_timers-->i = WORD_HIGHBIT + obj;
];
[ StopDaemon obj i;
for (i=0 : i<active_timers : i++)
if (the_timers-->i == WORD_HIGHBIT + obj) jump FoundTSlot4;
rfalse;
.FoundTSlot4;
the_timers-->i = 0;
];
! ----------------------------------------------------------------------------
[ DisplayStatus;
if (sys_statusline_flag == 0) { sline1 = score; sline2 = turns; }
else { sline1 = the_time/60; sline2 = the_time%60;}
];
[ SetTime t s;
the_time = t; time_rate = s; time_step = 0;
if (s < 0) time_step = 0-s;
];
[ NotifyTheScore;
#Ifdef TARGET_GLULX;
glk($0086, 6); ! set note style
#Endif; ! TARGET_GLULX
print "^["; L__M(##Miscellany, 50, score-last_score); print ".]^";
#Ifdef TARGET_GLULX;
glk($0086, 0); ! set normal style
#Endif; ! TARGET_GLULX
];
! ----------------------------------------------------------------------------
[ AdjustLight flag i;
i = lightflag;
lightflag = OffersLight(parent(player));
if (i == 0 && lightflag == 1) {
location = real_location;
if (flag == 0) <Look>;
}
if (i == 1 && lightflag == 0) {
real_location = location; location = thedark;
if (flag == 0) {
NoteArrival();
return L__M(##Miscellany, 9);
}
}
if (i == 0 && lightflag == 0) location=thedark;
];
[ OffersLight i j;
if (i == 0) rfalse;
if (i has light) rtrue;
objectloop (j in i)
if (HasLightSource(j) == 1) rtrue;
if (i has container) {
if (i has open || i has transparent)
return OffersLight(parent(i));
}
else {
if (i has enterable || i has transparent || i has supporter)
return OffersLight(parent(i));
}
rfalse;
];
[ HidesLightSource obj;
if (obj == player) rfalse;
if (obj has transparent or supporter) rfalse;
if (obj has container) return (obj hasnt open);
return (obj hasnt enterable);
];
[ HasLightSource i j ad;
if (i == 0) rfalse;
if (i has light) rtrue;
if (i has enterable || IsSeeThrough(i) == 1)
if (~~(HidesLightSource(i)))
objectloop (j in i)
if (HasLightSource(j) == 1) rtrue;
ad = i.&add_to_scope;
if (parent(i) ~= 0 && ad ~= 0) {
if (metaclass(ad-->0) == Routine) {
ats_hls = 0; ats_flag = 1;
RunRoutines(i, add_to_scope);
ats_flag = 0; if (ats_hls == 1) rtrue;
}
else {
for (j=0 : (WORDSIZE*j)<i.#add_to_scope : j++)
if (HasLightSource(ad-->j) == 1) rtrue;
}
}
rfalse;
];
[ ChangePlayer obj flag i;
! if (obj.&number == 0) return RunTimeError(7,obj);
if (actor == player) actor=obj;
give player ~transparent ~concealed;
i = obj; while (parent(i) ~= 0) {
if (i has animate) give i transparent;
i = parent(i);
}
if (player == selfobj) player.short_name = FORMER__TX;
player = obj;
if (player == selfobj) player.short_name = NULL;
give player transparent concealed animate proper;
i = player; while (parent(i) ~= 0) i = parent(i);
location = i; real_location = location;
if (parent(player) == 0) return RunTimeError(10);
MoveFloatingObjects();
lightflag = OffersLight(parent(player));
if (lightflag == 0) location = thedark;
print_player_flag = flag;
];
! ----------------------------------------------------------------------------
#Ifdef DEBUG;
#Ifdef TARGET_ZCODE;
[ DebugParameter w;
print w;
if (w >= 1 && w <= top_object) print " (", (name) w, ")";
if (UnsignedCompare(w, dict_start) >= 0 &&
UnsignedCompare(w, dict_end) < 0 &&
(w - dict_start) % dict_entry_size == 0)
print " ('", (address) w, "')";
];
[ DebugAction a anames;
#Iftrue (Grammar__Version == 1);
if (a >= 256) { print "<fake action ", a-256, ">"; return; }
#Ifnot;
if (a >= 4096) { print "<fake action ", a-4096, ">"; return; }
#Endif; ! Grammar__Version
anames = #identifiers_table;
anames = anames + 2*(anames-->0) + 2*48;
print (string) anames-->a;
];
[ DebugAttribute a anames;
if (a < 0 || a >= 48) print "<invalid attribute ", a, ">";
else {
anames = #identifiers_table; anames = anames + 2*(anames-->0);
print (string) anames-->a;
}
];
#Ifnot; ! TARGET_GLULX
[ DebugParameter w endmem;
print w;
@getmemsize endmem;
if (w >= 1 && w < endmem) {
if (w->0 >= $70 && w->0 < $7F) print " (", (name) w, ")";
if (w->0 >= $60 && w->0 < $6F) print " ('", (address) w, "')";
}
];
[ 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; ! TARGET_
[ TraceAction source ar;
if (source < 2) print "[ Action ", (DebugAction) action;
else {
if (ar == ##Order)
print "[ Order to ", (name) actor, ": ", (DebugAction) action;
else
print "[ Life rule ", (DebugAction) ar;
}
if (noun ~= 0) print " with noun ", (DebugParameter) noun;
if (second ~= 0) print " and second ", (DebugParameter) second;
if (source == 0) print " ";
if (source == 1) print " (from < > statement) ";
print "]^";
];
[ DebugToken token;
AnalyseToken(token);
switch (found_ttype) {
ILLEGAL_TT:
print "<illegal token number ", token, ">";
ELEMENTARY_TT:
switch (found_tdata) {
NOUN_TOKEN: print "noun";
HELD_TOKEN: print "held";
MULTI_TOKEN: print "multi";
MULTIHELD_TOKEN: print "multiheld";
MULTIEXCEPT_TOKEN: print "multiexcept";
MULTIINSIDE_TOKEN: print "multiinside";
CREATURE_TOKEN: print "creature";
SPECIAL_TOKEN: print "special";
NUMBER_TOKEN: print "number";
TOPIC_TOKEN: print "topic";
ENDIT_TOKEN: print "END";
}
PREPOSITION_TT:
print "'", (address) found_tdata, "'";
ROUTINE_FILTER_TT:
#Ifdef INFIX;
print "noun=", (InfixPrintPA) found_tdata;
#Ifnot;
print "noun=Routine(", found_tdata, ")";
#Endif; ! INFIX
ATTR_FILTER_TT:
print (DebugAttribute) found_tdata;
SCOPE_TT:
#Ifdef INFIX;
print "scope=", (InfixPrintPA) found_tdata;
#Ifnot;
print "scope=Routine(", found_tdata, ")";
#Endif; ! INFIX
GPR_TT:
#Ifdef INFIX;
print (InfixPrintPA) found_tdata;
#Ifnot;
print "Routine(", found_tdata, ")";
#Endif; ! INFIX
}
];
[ DebugGrammarLine pcount;
print " * ";
for (: line_token-->pcount ~= ENDIT_TOKEN : pcount++) {
if ((line_token-->pcount)->0 & $10) print "/ ";
print (DebugToken) line_token-->pcount, " ";
}
print "-> ", (DebugAction) action_to_be;
if (action_reversed) print " reverse";
];
#Ifdef TARGET_ZCODE;
[ ShowVerbSub address lines da meta i j;
if (noun == 0 || ((noun->#dict_par1) & 1) == 0)
"Try typing ~showverb~ and then the name of a verb.";
meta = ((noun->#dict_par1) & 2)/2;
i = $ff-(noun->#dict_par2);
address = (HDR_STATICMEMORY-->0)-->i;
lines = address->0;
address++;
print "Verb ";
if (meta) print "meta ";
da = HDR_DICTIONARY-->0;
for (j=0 : j<(da+5)-->0 : j++)
if (da->(j*9 + 14) == $ff-i) print "'", (address) (da + 9*j + 7), "' ";
new_line;
if (lines == 0) "has no grammar lines.";
for (: lines>0 : lines--) {
address = UnpackGrammarLine(address);
print " "; DebugGrammarLine(); new_line;
}
];
#Ifnot; ! TARGET_GLULX
[ ShowVerbSub address lines i j meta wd dictlen entrylen;
if (noun == 0 || ((noun->#dict_par1) & 1) == 0)
"Try typing ~showverb~ and then the name of a verb.";
meta = ((noun->#dict_par1) & 2)/2;
i = $ff-(noun->#dict_par2);
address = (#grammar_table)-->(i+1);
lines = address->0;
address++;
print "Verb ";
if (meta) print "meta ";
dictlen = #dictionary_table-->0;
entrylen = DICT_WORD_SIZE + 7;
for (j=0 : j<dictlen : j++) {
wd = #dictionary_table + WORDSIZE + entrylen*j;
if (wd->#dict_par2 == $ff-i)
print "'", (address) wd, "' ";
}
new_line;
if (lines == 0) "has no grammar lines.";
for (: lines>0 : lines--) {
address = UnpackGrammarLine(address);
print " "; DebugGrammarLine(); new_line;
}
];
#Endif; ! TARGET_
[ ShowobjSub c f l a n x numattr;
if (noun == 0) noun = location;
objectloop (c ofclass Class) if (noun ofclass c) { f++; l=c; }
if (f == 1) print (name) l, " ~"; else print "Object ~";
print (name) noun, "~ (", noun, ")";
if (parent(noun) ~= 0) print " in ~", (name) parent(noun), "~";
new_line;
if (f > 1) {
print " class ";
objectloop (c ofclass Class) if (noun ofclass c) print (name) c, " ";
new_line;
}
#Ifdef TARGET_ZCODE;
numattr = 48;
#Ifnot; ! TARGET_GLULX
numattr = NUM_ATTR_BYTES * 8;
#Endif; ! TARGET_
for (a=0,f=0 : a<numattr : a++) if (noun has a) f=1;
if (f) {
print " has ";
for (a=0 : a<numattr : a++) if (noun has a) print (DebugAttribute) a, " ";
new_line;
}
if (noun ofclass Class) return;
f=0;
#Ifdef TARGET_ZCODE;
l = #identifiers_table-->0;
#Ifnot; ! TARGET_GLULX
l = INDIV_PROP_START + #identifiers_table-->3;
#Endif; ! TARGET_
for (a=1 : a<=l : a++) {
if ((a ~= 2 or 3) && noun.&a) {
if (f == 0) { print " with "; f=1; }
print (property) a;
n = noun.#a;
for (c=0 : WORDSIZE*c<n : c++) {
print " ";
x = (noun.&a)-->c;
if (a == name) print "'", (address) x, "'";
else {
if (a == number or capacity or time_left) print x;
else {
switch (x) {
NULL: print "NULL";
0: print "0";
1: print "1";
default:
switch (metaclass(x)) {
Class, Object:
print (name) x;
String:
print "~", (string) x, "~";
Routine:
print "[...]";
}
print " (", x, ")";
}
}
}
}
print ",^ ";
}
}
! if (f==1) new_line;
];
#Endif; ! DEBUG
! ----------------------------------------------------------------------------
! Miscellaneous display routines used by DrawStatusLine and available for
! user. Most of these vary according to which machine is being compiled to
! ----------------------------------------------------------------------------
#Ifdef TARGET_ZCODE;
[ ClearScreen window;
switch (window) {
WIN_ALL: @erase_window -1;
WIN_STATUS: @erase_window 1;
WIN_MAIN: @erase_window 0;
}
];
#Iftrue (#version_number == 6);
[ MoveCursorV6 line column charw; ! 1-based postion on text grid
@get_wind_prop 1 13 -> charw; ! font size
charw = charw & $FF;
line = 1 + charw*(line-1);
column = 1 + charw*(column-1);
@set_cursor line column;
];
#Endif;
#Ifndef MoveCursor;
[ MoveCursor line column; ! 1-based postion on text grid
if (~~statuswin_current) {
@set_window 1;
if (clr_on && clr_bgstatus > 1) @set_colour clr_fgstatus clr_bgstatus;
else style reverse;
}
if (line == 0) {
line = 1;
column = 1;
}
#Iftrue (#version_number == 6);
MoveCursorV6(line, column);
#Ifnot;
@set_cursor line column;
#Endif;
statuswin_current = true;
];
#Endif;
[ MainWindow;
if (statuswin_current) {
if (clr_on && clr_bgstatus > 1) @set_colour clr_fg clr_bg;
else style roman;
@set_window 0;
}
statuswin_current = false;
];
#Iftrue (#version_number == 6);
[ ScreenWidth width charw;
@get_wind_prop 1 3 -> width;
@get_wind_prop 1 13 -> charw;
charw = charw & $FF;
return (width+charw-1) / charw;
];
#Ifnot;
[ ScreenWidth;
return (HDR_SCREENWCHARS->0);
];
#Endif;
[ ScreenHeight;
return (HDR_SCREENHLINES->0);
];
#Iftrue (#version_number == 6);
[ StatusLineHeight height wx wy x y charh;
! Split the window. Standard 1.0 interpreters should keep the window 0
! cursor in the same absolute position, but older interpreters,
! including Infocom's don't - they keep the window 0 cursor in the
! same position relative to its origin. We therefore compensate
! manually.
@get_wind_prop 0 0 -> wy; @get_wind_prop 0 1 -> wx;
@get_wind_prop 0 13 -> charh; @log_shift charh $FFF8 -> charh;
@get_wind_prop 0 4 -> y; @get_wind_prop 0 5 -> x;
height = height * charh;
@split_window height;
y = y - height + wy - 1;
if (y < 1) y = 1;
x = x + wx - 1;
@set_cursor y x 0;
gg_statuswin_cursize = height;
];
#Ifnot;
[ StatusLineHeight height;
if (gg_statuswin_cursize ~= height)
@split_window height;
gg_statuswin_cursize = height;
];
#Endif;
[ SetColour f b window;
if (clr_on && f && b) {
if (window == 0) { ! if setting both together, set reverse
clr_fgstatus = b;
clr_bgstatus = f;
}
if (window == 1) {
clr_fgstatus = f;
clr_bgstatus = b;
}
if (window == 0 or 2) {
clr_fg = f;
clr_bg = b;
}
if (statuswin_current)
@set_colour clr_fgstatus clr_bgstatus;
else
@set_colour clr_fg clr_bg;
}
];
#Ifnot; ! TARGET_GLULX
[ ClearScreen window;
if (window == WIN_ALL or WIN_MAIN) {
glk($002A, gg_mainwin);
if (gg_quotewin) {
glk($0024, gg_quotewin, 0); ! close_window
gg_quotewin = 0;
}
}
if (gg_statuswin && window == WIN_ALL or WIN_STATUS) glk($002A, gg_statuswin);
];
[ MoveCursor line column; ! 0-based postion on text grid
if (gg_statuswin) {
glk($002F, gg_statuswin); ! set_window
}
if (line == 0) {
line = 1;
column = 1;
}
glk($002B, gg_statuswin, column-1, line-1); ! window_move_cursor
statuswin_current=1;
];
[ MainWindow;
glk($002F, gg_mainwin); ! set_window
statuswin_current=0;
];
[ MakeColourWord c;
if (c > 9) return c;
c = c-2;
return $ff0000*(c&1) + $ff00*(c&2 ~= 0) + $ff*(c&4 ~= 0);
];
[ ScreenWidth id;
id=gg_mainwin;
if (gg_statuswin && statuswin_current) id = gg_statuswin;
glk($0025, id, gg_arguments, 0); ! window_get_size
return gg_arguments-->0;
];
[ ScreenHeight;
glk($0025, gg_mainwin, 0, gg_arguments); ! window_get_size
return gg_arguments-->0;
];
[ SetColour 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<=10: i++) {
if (f == CLR_DEFAULT || b == CLR_DEFAULT) { ! remove style hints
glk($00B1, swin, i, 7);
glk($00B1, swin, i, 8);
}
else {
glk($00B0, swin, i, 7, fwd);
glk($00B0, swin, i, 8, bwd);
}
}
! Now re-open the windows to apply the hints
if (gg_statuswin) glk($0024, gg_statuswin, 0); ! close_window
if (doclear || ( window ~= 1 && (clr_fg ~= f || clr_bg ~= b) ) ) {
glk($0024, gg_mainwin, 0);
gg_mainwin = glk($0023, 0, 0, 0, 3, GG_MAINWIN_ROCK); ! window_open
if (gg_scriptstr ~= 0)
glk($002D, gg_mainwin, gg_scriptstr); ! window_set_echo_stream
}
gg_statuswin = glk($0023, gg_mainwin, $12, gg_statuswin_cursize,
4, GG_STATUSWIN_ROCK); ! window_open
if (statuswin_current && gg_statuswin) MoveCursor(); else MainWindow();
if (window ~= 2) {
clr_fgstatus = f;
clr_bgstatus = b;
}
if (window ~= 1) {
clr_fg = f;
clr_bg = b;
}
}
];
#Endif;
[ SetClr f b w;
SetColour (f, b, w);
];
[ RestoreColours; ! L61007
if (clr_on) { ! check colour has been used
SetColour(clr_fg, clr_bg, 2); ! make sure both sets of variables are restored
SetColour(clr_fgstatus, clr_bgstatus, 1, true);
ClearScreen();
}
#Ifdef TARGET_ZCODE;
#Iftrue (#version_number == 6); ! request screen update
(0-->8) = (0-->8) | $$00000100;
#Endif;
#Endif;
];
! ----------------------------------------------------------------------------
! Except in Version 3, the DrawStatusLine routine does just that: this is
! provided explicitly so that it can be Replace'd to change the style, and
! as written it emulates the ordinary Standard game status line, which is
! drawn in hardware
! ----------------------------------------------------------------------------
#Ifdef TARGET_ZCODE;
#IfV5;
#Iftrue (#version_number == 6);
[ DrawStatusLine width x charw scw mvw;
(0-->8) = (0-->8) &~ $$00000100;
StatusLineHeight(gg_statuswin_size);
! Now clear the window. This isn't totally trivial. Our approach is to select the
! fixed space font, measure its width, and print an appropriate
! number of spaces. We round up if the screen isn't a whole number
! of characters wide, and rely on window 1 being set to clip by default.
MoveCursor(1, 1);
@set_font 4 -> x;
width = ScreenWidth();
spaces width;
! Back to standard font for the display. We use output_stream 3 to
! measure the space required, the aim being to get 50 characters
! worth of space for the location name.
MoveCursor(1, 2);
@set_font 1 -> x;
if (location == thedark)
print (name) location;
else {
FindVisibilityLevels();
if (visibility_ceiling == location) print (name) location;
else print (The) visibility_ceiling;
}
@get_wind_prop 1 3 -> width;
@get_wind_prop 1 13 -> charw;
charw = charw & $FF;
@output_stream 3 StorageForShortName;
print (string) SCORE__TX, "00000";
@output_stream -3; scw = HDR_PIXELSTO3-->0 + charw;
@output_stream 3 StorageForShortName;
print (string) MOVES__TX, "00000";
@output_stream -3; mvw = HDR_PIXELSTO3-->0 + charw;
if (width - scw - mvw >= 50*charw) {
x = 1+width-scw-mvw;
@set_cursor 1 x; print (string) SCORE__TX, sline1;
x = x+scw;
@set_cursor 1 x; print (string) MOVES__TX, sline2;
}
else {
@output_stream 3 StorageForShortName;
print "00000/00000";
@output_stream -3; scw = HDR_PIXELSTO3-->0 + charw;
if (width - scw >= 50*charw) {
x = 1+width-scw;
@set_cursor 1 x; print sline1, "/", sline2;
}
}
! Reselect roman, as Infocom's interpreters interpreters go funny
! if reverse is selected twice.
MainWindow();
];
#Endif; ! #version_number == 6
#Endif; ! V5
#Endif; ! TARGET_ZCODE
#Ifndef DrawStatusLine;
[ DrawStatusLine width posa posb;
#Ifdef TARGET_GLULX;
! If we have no status window, we must not try to redraw it.
if (gg_statuswin == 0)
return;
#Endif;
! If there is no player location, we shouldn't try to draw status window
if (location == nothing || parent(player) == nothing)
return;
StatusLineHeight(gg_statuswin_size);
MoveCursor(1, 1);
width = ScreenWidth();
posa = width-26; posb = width-13;
spaces width;
MoveCursor(1, 2);
if (location == thedark) {
print (name) location;
}
else {
FindVisibilityLevels();
if (visibility_ceiling == location)
print (name) location;
else
print (The) visibility_ceiling;
}
if (sys_statusline_flag && width > 53) {
MoveCursor(1, posa);
print (string) TIME__TX;
LanguageTimeOfDay(sline1, sline2);
}
else {
if (width > 66) {
#Ifndef NO_SCORE;
MoveCursor(1, posa);
print (string) SCORE__TX, sline1;
#Endif;
MoveCursor(1, posb);
print (string) MOVES__TX, sline2;
}
#Ifndef NO_SCORE;
if (width > 53 && width <= 66) {
MoveCursor(1, posb);
print sline1, "/", sline2;
}
#Endif;
}
MainWindow(); ! set_window
];
#Endif;
#Ifdef TARGET_GLULX;
[ StatusLineHeight hgt parwin;
if (gg_statuswin == 0) return;
if (hgt == gg_statuswin_cursize) return;
parwin = glk($0029, gg_statuswin); ! window_get_parent
glk($0026, parwin, $12, hgt, 0); ! window_set_arrangement
gg_statuswin_cursize = hgt;
];
[ 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($0023, gg_mainwin, $12, lines, 3,
GG_QUOTEWIN_ROCK); ! window_open
}
else {
parwin = glk($0029, gg_quotewin); ! window_get_parent
glk($0026, parwin, $12, lines, 0); ! window_set_arrangement
}
lastnl = true;
if (gg_quotewin) {
glk($002A, gg_quotewin); ! window_clear
glk($002F, gg_quotewin); ! set_window
lastnl = false;
}
! If gg_quotewin is zero here, the quote just appears in the story window.
glk($0086, 7); ! set blockquote style
for (ix=0 : ix<lines : ix++) {
print (string) arr-->(ix+1);
if (ix < lines-1 || lastnl) new_line;
}
glk($0086, 0); ! set normal style
if (gg_quotewin) {
glk($002F, gg_mainwin); ! set_window
}
];
#Endif; ! TARGET_GLULX
#Ifdef TARGET_GLULX;
[ GGInitialise res;
@gestalt 4 2 res; ! Test if this interpreter has Glk.
if (res == 0) {
! Without Glk, we're entirely screwed.
quit;
}
! Set the VM's I/O system to be Glk.
@setiosys 2 0;
! 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();
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)
gg_mainwin = glk($0023, 0, 0, 0, 3, GG_MAINWIN_ROCK); ! window_open
if (gg_mainwin == 0) {
! If we can't even open one window, there's no point in going on.
quit;
}
}
else {
! There was already a story window. We should erase it.
glk($002A, gg_mainwin); ! window_clear
}
if (gg_statuswin == 0) {
res = InitGlkWindow(GG_STATUSWIN_ROCK);
if (res == 0) {
gg_statuswin_cursize = gg_statuswin_size;
gg_statuswin = glk($0023, gg_mainwin, $12, gg_statuswin_cursize,
4, GG_STATUSWIN_ROCK); ! window_open
}
}
! 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($002F, gg_mainwin); ! set_window
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;
gg_statuswin_cursize = 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($0040, 0, gg_arguments); ! stream_iterate
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($0040, id, gg_arguments); ! stream_iterate
}
id = glk($0020, 0, gg_arguments); ! window_iterate
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($0020, id, gg_arguments); ! window_iterate
}
id = glk($0064, 0, gg_arguments); ! fileref_iterate
while (id) {
switch (gg_arguments-->0) {
GG_SCRIPTFREF_ROCK: gg_scriptfref = id;
default: IdentifyGlkObject(1, 2, id, gg_arguments-->0);
}
id = glk($0064, id, gg_arguments); ! fileref_iterate
}
! Tell the game to tie up any loose ends.
IdentifyGlkObject(2);
];
! This somewhat obfuscated function will print anything.
! It handles strings, functions (with optional arguments), objects,
! object properties (with optional arguments), and dictionary words.
! It does *not* handle plain integers, but you can use
! DecimalNumber or EnglishNumber to handle that case.
!
! Calling: Is equivalent to:
! ------- ----------------
! PrintAnything() <nothing printed>
! PrintAnything(0) <nothing printed>
! PrintAnything("string"); print (string) "string";
! PrintAnything('word') print (address) 'word';
! PrintAnything(obj) print (name) obj;
! PrintAnything(obj, prop) obj.prop();
! PrintAnything(obj, prop, args...) obj.prop(args...);
! PrintAnything(func) func();
! PrintAnything(func, args...) func(args...);
[ 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;
}
];
! This does the same as PrintAnything, but the output is sent to a
! byte array in memory. The first two arguments must be the array
! address and length; the following arguments are interpreted as
! for 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.)
[ PrintAnyToArray _vararg_count arr arrlen str oldstr len;
@copy sp arr;
@copy sp arrlen;
_vararg_count = _vararg_count - 2;
oldstr = glk($0048); ! stream_get_current
str = glk($0043, arr, arrlen, 1, 0); ! stream_open_memory
if (str == 0) return 0;
glk($0047, str); ! stream_set_current
@call PrintAnything _vararg_count 0;
glk($0047, oldstr); ! stream_set_current
@copy $ffffffff sp;
@copy str sp;
@glk $0044 2 0; ! stream_close
@copy sp len;
@copy sp 0;
return len;
];
! And this calls PrintAnyToArray on a particular array, jiggering
! the result to be a Glulx C-style ($E0) string.
Constant GG_ANYTOSTRING_LEN 66;
Array AnyToStrArr -> GG_ANYTOSTRING_LEN+1;
[ ChangeAnyToCString _vararg_count ix len;
ix = GG_ANYTOSTRING_LEN-2;
@copy ix sp;
ix = AnyToStrArr+1;
@copy ix sp;
ix = _vararg_count+2;
@call PrintAnyToArray ix len;
AnyToStrArr->0 = $E0;
if (len >= GG_ANYTOSTRING_LEN)
len = GG_ANYTOSTRING_LEN-1;
AnyToStrArr->(len+1) = 0;
return AnyToStrArr;
];
#Endif; ! TARGET_GLULX
! This is a trivial function which just prints a number, in decimal
! digits. It may be useful as a stub to pass to PrintAnything.
[ DecimalNumber num; print num; ];
#IfV5;
#Ifdef VN_1630;
Array StorageForShortName buffer 160;
#Ifnot;
Array StorageForShortName -> 160 + WORDSIZE;
#Endif; ! VN_1630
#Endif; ! V5
#Ifdef TARGET_ZCODE;
! Platform-independent way of printing strings and properties to a
! buffer (defined as length word followed by byte characters).
[ PrintToBuffer buf len a b c;
@output_stream 3 buf;
switch (metaclass(a)) {
String:
print (string) a;
Routine:
a(b, c);
Object,Class:
if (b)
PrintOrRun(a, b, true);
else
print (name) a;
}
@output_stream -3;
if (buf-->0 > len) print "Error: Overflow in PrintToBuffer.^";
return buf-->0;
];
#Ifnot; ! TARGET_GLULX
[ PrintToBuffer buf len a b;
if (b) {
if (metaclass(a) == Object && a.#b == WORDSIZE
&& metaclass(a.b) == String)
buf-->0 = PrintAnyToArray(buf+WORDSIZE, len, a.b);
else
buf-->0 = PrintAnyToArray(buf+WORDSIZE, len, a, b);
}
else
buf-->0 = PrintAnyToArray(buf+WORDSIZE, len, a);
if (buf-->0 > len) buf-->0 = len;
return buf-->0;
];
#Endif; ! TARGET_
! None of the following functions should be called for zcode if the
! output exceeds the size of the buffer.
[ Length a b;
PrintToBuffer(StorageForShortName, 160, a, b);
return StorageForShortName-->0;
];
#Ifdef TARGET_ZCODE;
[ LowerCase c;
switch (c) {
'A' to 'Z':
c = c + 32;
202, 204, 212, 214, 221:
c--;
217, 218:
c = c - 2;
158 to 160, 167 to 169, 208 to 210:
c = c - 3;
186 to 190, 196 to 200:
c = c - 5 ;
175 to 180:
c = c - 6;
}
return c;
];
[ UpperCase c;
switch (c) {
'a' to 'z':
c = c - 32;
201, 203, 211, 213, 220:
c++;
215, 216:
c = c + 2;
155 to 157, 164 to 166, 205 to 207:
c = c + 3;
181 to 185, 191 to 195:
c = c + 5 ;
169 to 174:
c = c + 6;
}
return c;
];
#Ifnot; ! TARGET_GLULX
[ LowerCase c; return glk($00A0, c); ];
[ UpperCase c; return glk($00A1, c); ];
#Endif; ! TARGET_
[ PrintCapitalised obj prop flag nocaps centred length i width;
! a variation of PrintOrRun, capitalising the first letter and returning nothing
if (obj ofclass String || prop == 0) {
PrintToBuffer (StorageForShortName, 160, obj);
flag = 1;
}
else
if (obj.#prop > WORDSIZE || metaclass(obj.prop) == Routine or String) {
PrintToBuffer(StorageForShortName, 160, obj, prop);
}
else
if (obj.prop == NULL) rfalse;
else return RunTimeError(2, obj, prop);
length = StorageForShortName-->0;
width = ScreenWidth();
if (centred && length<width)
spaces ( (width-length)/2 );
if (~~nocaps)
StorageForShortName->WORDSIZE = UpperCase(StorageForShortName->WORDSIZE);
for (i=WORDSIZE: i<length+WORDSIZE: i++) print (char) StorageForShortName->i;
if (flag == 0 && obj.#prop == WORDSIZE && metaclass(obj.prop) == String)
new_line;
return;
];
[ Centre a b;
PrintCapitalised(a, b, 0, 1, 1);
];
[ Cap str nocaps;
if (nocaps) print (string) str;
else PrintCapitalised(str);
];
[ PrefaceByArticle o acode pluralise capitalise i artform findout artval;
if (o provides articles) {
artval=(o.&articles)-->(acode+short_name_case*LanguageCases);
if (capitalise)
print (Cap) artval, " ";
else
print (string) artval, " ";
if (pluralise) return;
print (PSN__) o; return;
}
i = GetGNAOfObject(o);
if (pluralise) {
if (i < 3 || (i >= 6 && i < 9)) i = i + 3;
}
i = LanguageGNAsToArticles-->i;
artform = LanguageArticles
+ 3*WORDSIZE*LanguageContractionForms*(short_name_case + i*LanguageCases);
#Iftrue (LanguageContractionForms == 2);
if (artform-->acode ~= artform-->(acode+3)) findout = true;
#Endif; ! LanguageContractionForms
#Iftrue (LanguageContractionForms == 3);
if (artform-->acode ~= artform-->(acode+3)) findout = true;
if (artform-->(acode+3) ~= artform-->(acode+6)) findout = true;
#Endif; ! LanguageContractionForms
#Iftrue (LanguageContractionForms == 4);
if (artform-->acode ~= artform-->(acode+3)) findout = true;
if (artform-->(acode+3) ~= artform-->(acode+6)) findout = true;
if (artform-->(acode+6) ~= artform-->(acode+9)) findout = true;
#Endif; ! LanguageContractionForms
#Iftrue (LanguageContractionForms > 4);
findout = true;
#Endif; ! LanguageContractionForms
#Ifdef TARGET_ZCODE;
if (standard_interpreter ~= 0 && findout) {
StorageForShortName-->0 = 160;
@output_stream 3 StorageForShortName;
if (pluralise) print (number) pluralise; else print (PSN__) o;
@output_stream -3;
acode = acode + 3*LanguageContraction(StorageForShortName + 2);
}
#Ifnot; ! TARGET_GLULX
if (findout) {
if (pluralise)
PrintAnyToArray(StorageForShortName, 160, EnglishNumber, pluralise);
else
PrintAnyToArray(StorageForShortName, 160, PSN__, o);
acode = acode + 3*LanguageContraction(StorageForShortName);
}
#Endif; ! TARGET_
Cap (artform-->acode, ~~capitalise); ! print article
if (pluralise) return;
print (PSN__) o;
];
[ PSN__ o;
if (o == 0) { print (string) NOTHING__TX; rtrue; }
switch (metaclass(o)) {
Routine: print "<routine ", o, ">"; rtrue;
String: print "<string ~", (string) o, "~>"; rtrue;
nothing: print "<illegal object number ", o, ">"; rtrue;
}
if (o == player) { print (string) YOURSELF__TX; rtrue; }
#Ifdef LanguagePrintShortName;
if (LanguagePrintShortName(o)) rtrue;
#Endif; ! LanguagePrintShortName
if (indef_mode && o.&short_name_indef ~= 0 && PrintOrRun(o, short_name_indef, 1) ~= 0) rtrue;
if (o.&short_name ~= 0 && PrintOrRun(o, short_name, 1) ~= 0) rtrue;
print (object) o;
];
[ Indefart o i;
i = indef_mode; indef_mode = true;
if (o has proper) { indef_mode = NULL; print (PSN__) o; return; }
if (o provides article) {
PrintOrRun(o, article, 1); print " ", (PSN__) o; indef_mode = i;
return;
}
PrefaceByArticle(o, 2); indef_mode = i;
];
[ CInDefArt o i;
i = indef_mode; indef_mode = true;
if (o has proper) { indef_mode = NULL; print (PSN__) o; return; }
if (o provides article) {
PrintCapitalised(o, article, 1); print " ", (PSN__) o; indef_mode = i;
return;
}
PrefaceByArticle(o, 2, 0, 1); indef_mode = i;
];
[ Defart o i;
i = indef_mode; indef_mode = false;
if ((~~o ofclass Object) || o has proper) {
indef_mode = NULL; print (PSN__) o; indef_mode = i;
return;
}
PrefaceByArticle(o, 1); indef_mode = i;
];
[ CDefart o i;
#Ifdef YOU__TX;
if (o == player) {
print (string) YOU__TX;
return;
}
#Endif;
i = indef_mode; indef_mode = false;
if ((~~o ofclass Object) || o has proper) {
indef_mode = NULL; print (PSN__) o; indef_mode = i;
return;
}
PrefaceByArticle(o, 0); indef_mode = i;
];
[ PrintShortName o i;
i = indef_mode; indef_mode = NULL;
PSN__(o); indef_mode = i;
];
[ EnglishNumber n; LanguageNumber(n); ];
[ NumberWord o i n;
n = LanguageNumbers-->0;
for (i=1 : i<=n : i=i+2)
if (o == LanguageNumbers-->i) return LanguageNumbers-->(i+1);
return 0;
];
[ RandomEntry tab;
if (tab-->0 == 0) return RunTimeError(8);
return tab-->(random(tab-->0));
];
! ----------------------------------------------------------------------------
! Useful routine: unsigned comparison (for addresses in Z-machine)
! Returns 1 if x>y, 0 if x=y, -1 if x<y
! ----------------------------------------------------------------------------
[ UnsignedCompare x y u v;
if (x == y) return 0;
if (x < 0 && y >= 0) return 1;
if (x >= 0 && y < 0) return -1;
u = x&~WORD_HIGHBIT; v= y&~WORD_HIGHBIT;
if (u > v) return 1;
return -1;
];
! ==============================================================================
#Ifdef NITFOL_HOOKS; ! Code contributed by Evin Robertson
#Ifdef TARGET_GLULX; ! Might be nice for Z-machine games too,
! but I'm not going to try to make this work
! given #Ifdef funniness.
Array magic_array --> ! This is so nitfol can do typo correction /
! automapping / debugging on Glulx games
$6e66726d $4d616763 $ff0010 ! Goes to 'NfrmMagc' 10 refers to length
Magic_Global_Dispatch__
DI__check_word ! DI__check_word(buf, length)
PrintShortName
WV__Pr RV__Pr CA__Pr ! obj.prop = x; x = obj.prop; obj.prop(x)
RA__Pr RL__Pr RA__Sc ! obj.&prop; obj.#prop; class::prop
OP__Pr OC__Cl ! obj provides prop; obj ofclass class
OB__Move OB__Remove
OB__Parent__ OB__Child__ OB__Sibling__ ! No explicit veneer for these
;
[ OB__Parent__ obj; return parent(obj); ];
[ OB__Child__ obj; return child(obj); ];
[ OB__Sibling__ obj; return sibling(obj); ];
[ Magic_Global_Dispatch__ glbl;
switch (glbl) {
0:
if (location == TheDark) return real_location; return location;
1:
return Player;
-1:
return CompassDirection::number; ! Silliness to make exist RA__Sc
! Should never be called.
}
return magic_array; ! Silences a warning.
];
[ DI__check_word buf wlen ix val res dictlen entrylen;
! Just like in Tokenise__. In fact, Tokenise__ could call this if
! it wanted, instead of doing this itself.
if (wlen > DICT_WORD_SIZE) wlen = DICT_WORD_SIZE;
for (ix=0 : ix<wlen : ix++) {
gg_tokenbuf->ix = glk($00A0, buf->ix);
}
for (: ix<DICT_WORD_SIZE : ix++) {
gg_tokenbuf->ix = 0;
}
val = #dictionary_table + WORDSIZE;
entrylen = DICT_WORD_SIZE + 7;
@binarysearch gg_tokenbuf DICT_WORD_SIZE val entrylen dictlen 1 1 res;
return res;
];
#Endif; ! TARGET_
#Endif; ! NITFOL_HOOKS
! ==============================================================================
Object LibraryExtensions "(Library Extensions)"
with RunAll [ prop a1 a2 a3
obj rval max;
objectloop (obj in self)
if (obj provides prop && obj.prop ofclass Routine) {
rval = obj.prop(a1, a2, a3);
if (rval > max) max = rval;
}
return max;
],
RunUntil [ prop exitval a1 a2 a3
obj rval;
objectloop (obj in self)
if (obj provides prop && obj.prop ofclass Routine) {
rval = obj.prop(a1, a2, a3);
if (rval == exitval) return rval;
}
return ~exitval;
],
RunWhile [ prop exitval a1 a2 a3
obj rval;
objectloop (obj in self)
if (obj provides prop && obj.prop ofclass Routine) {
rval = obj.prop(a1, a2, a3);
if (rval ~= exitval) return rval;
}
return exitval;
],
ext_initialise 0,
ext_messages 0,
has proper;
! ==============================================================================
[ 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;
print (object) 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;
}
];
! Like Glulx_PrintAnyToArray, but it writes to a word array. Returns
! the number of characters printed. (If the text printed is longer than
! the array, the extra characters are safely dropped rather than overflowing
! the array; they are still counted in the returned count.)
[ Glulx_PrintAnyToArrayUni _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_uni(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;
];
[ VM_PrintToBuffer buf len a b c;
if (b) {
if (metaclass(a) == Object && a.#b == WORDSIZE
&& metaclass(a.b) == String)
buf-->0 = Glulx_PrintAnyToArrayUni(buf+WORDSIZE, len, a.b);
else if (metaclass(a) == Routine)
buf-->0 = Glulx_PrintAnyToArrayUni(buf+WORDSIZE, len, a, b, c);
else
buf-->0 = Glulx_PrintAnyToArrayUni(buf+WORDSIZE, len, a, b);
}
else if (metaclass(a) == Routine)
buf-->0 = Glulx_PrintAnyToArrayUni(buf+WORDSIZE, len, a, b, c);
else
buf-->0 = Glulx_PrintAnyToArrayUni(buf+WORDSIZE, len, a);
if (buf-->0 > len) buf-->0 = len;
return buf-->0;
];