B/rtpt: RTP Template. @Purpose: To issue run-time problem messages, and to perform some run-time type checking which may issue such messages. @------------------------------------------------------------------------------- @p Reporting. All RTPs are produced by calling the following routine, which takes one compulsory argument: |n|, the RTP number. When I7 is being used with the Inform user interface, it's important that these numbers correspond to the explanatory web pages stored within the interface application: those in turn are created by the |inrtps| utility. The interface knows to display these pages because it parses the printed output during play to look for the text layout produced by the following routine: so do not reformat RTPs without ensuring that corresponding changes have been made to the Inform user interface applications. The arguments |par1|, |par2| and |par3| are optional parameters clarifying the message; |ln| is an optional parameter specifying a paragraph number in the original source text (though in fact I7 does not use this at present). @c [ RunTimeProblem n par1 par2 par3 ln i c; if (enable_rte == false) return; enable_rte = false; print "^*** Run-time problem P", n; if (ln) print " (at paragraph ", ln, " in the source text)"; print ": "; switch(n) { RTP_BACKDROP: print "Tried to move ", (the) par1, " (a backdrop) to ", (the) par2, ", which is not a region.^"; RTP_CANTCHANGE: print "Tried to change player to ", (the) par1, ", which is not a player-character.^"; RTP_NOEXIT: print "Tried to change ", (the) par2, " exit of ", (the) par1, ", but it didn't seem to have such an exit to change.^"; RTP_EXITDOOR: print "Tried to change ", (the) par2, " exit of ", (the) par1, ", but it led to a door, not a room.^"; RTP_IMPREL: print "Tried to access an inappropriate relation for ", (the) par1, ", violating '", (string) par2-->RR_DESCRIPTION, "'.^"; RTP_RULESTACK: print "Too many procedural rules acting all at once.^"; RTP_TOOMANYRULEBOOKS: print "Too many rulebooks in simultaneous use.^"; RTP_TOOMANYEVENTS: print "Too many timed events are going on at once.^"; RTP_BADPROPERTY: print "Tried to access non-existent property for ", (the) par1, ".^"; RTP_UNPROVIDED: print "Since ", (the) par1, " is not allowed the property ~", (string) par2, "~, it is against the rules to try to use it.^"; RTP_UNSET: print "Although ", (the) par1, " is allowed to have the property ~", (string) par2, "~, no value was ever given, so it can't now be used.^"; RTP_TOOMANYACTS: print "Too many activities are going on at once.^"; RTP_CANTABANDON: print "Tried to abandon an activity which wasn't going on.^"; RTP_CANTEND: print "Tried to end an activity which wasn't going on.^"; RTP_CANTMOVENOTHING: print "You can't move nothing.^"; RTP_CANTREMOVENOTHING: print "You can't remove nothing from play.^"; RTP_DIVZERO: print "You can't divide by zero.^"; RTP_BADVALUEPROPERTY: print "Tried to access property for a value which didn't fit: ", "if this were a number it would be ", par1, ".^"; RTP_NOTBACKDROP: print "Tried to move ", (the) par1, " (not a backdrop) to ", (the) par2, ", which is a region.^"; RTP_TABLE_NOCOL: print "Attempt to look up a non-existent column in the table '", (PrintTableName) par1, "'.^"; RTP_TABLE_NOCORR: print "Attempt to look up a non-existent correspondence in the table '", (PrintTableName) par1, "'.^"; RTP_TABLE_NOROW: print "Attempt to look up a non-existent row in the table '", (PrintTableName) par1, "'.^"; RTP_TABLE_NOENTRY: print "Attempt to look up a non-existent entry at column ", par2, ", row ", par3, " of the table '", (PrintTableName) par1, "'.^"; RTP_TABLE_NOTABLE: print "Attempt to blank out a row from a non-existent table (value ", par1, ").^"; RTP_TABLE_NOMOREBLANKS: print "Attempt to choose a blank row in a table with none left: table '", (PrintTableName) par1, "'.^"; RTP_TABLE_NOROWS: print "Attempt to choose a random row in an entirely blank table: table '", (PrintTableName) par1, "'.^"; RTP_TABLE_CANTRUNTHROUGH: print "Attempt to repeat through a table in a tricky column order: table '", (PrintTableName) par1, "'.^"; RTP_TABLE_CANTSORT: print "Attempt to sort a table whose ordering must remain fixed: table '", (PrintTableName) par1, "'.^"; RTP_TABLE_CANTSAVE: print "Attempt to save a table to a file whose data is unstable: table '", (PrintTableName) par1, "'.^"; RTP_TABLE_WONTFIT: print "File being read has too many rows or columns to fit into table: table '", (PrintTableName) par1, "'.^"; RTP_TABLE_BADFILE: print "File being read is not a previously saved table: table '", (PrintTableName) par1, "'.^"; RTP_NOTINAROOM: print "Attempt to test if the current location is '", (the) par1, "', which is not a room or region.^"; RTP_BADTOPIC: print "Attempt to see if a snippet of text matches something which is not a topic.^"; RTP_ROUTELESS: print "Attempt to find route or count steps through an implicit relation.^"; RTP_PROPOFNOTHING: print "Attempt to use a property of the 'nothing' non-object: property ", (PrintPropertyName) par2, "^"; RTP_DECIDEONWRONGKIND: print "Attempt to 'decide on V' where V is the wrong kind of object.^"; RTP_DECIDEONNOTHING: print "Attempt to 'decide on nothing'.^"; RTP_LOWLEVELERROR: print "Low level error.^"; RTP_DONTIGNORETURNSEQUENCE: print "Attempt to ignore the turn sequence rules.^"; RTP_SAYINVALIDSNIPPET: print "Attempt to say a snippet value which is currently invalid: words ", par1, " to ", par2, ".^"; RTP_SPLICEINVALIDSNIPPET: print "Attempt to splice a snippet value which is currently invalid: words ", par1, " to ", par2, ".^"; RTP_INCLUDEINVALIDSNIPPET: print "Attempt to match a snippet value which is currently invalid: words ", par1, " to ", par2, ".^"; RTP_LISTWRITERMEMORY: print "The list-writer has run out of memory.^"; RTP_CANTREMOVEPLAYER: print "Attempt to remove the player from play.^"; RTP_CANTREMOVEDOORS: print "Attempt to remove a door from play.^"; RTP_CANTCHANGEOFFSTAGE: print "Attempt to change the player to a person off-stage.^"; RTP_MSTACKMEMORY: print "The memory stack is exhausted.^"; RTP_TYPECHECK: print "Phrase applied to an incompatible kind of value.^"; RTP_FILEIOERROR: print "Error handling external file.^"; RTP_HEAPERROR: print "Memory allocation proved impossible.^"; RTP_LISTRANGEERROR: print "Attempt to use list item which does not exist.^"; RTP_REGEXPSYNTAXERROR: print "Syntax error in regular expression.^"; RTP_NOGLULXUNICODE: print "This interpreter does not support Unicode.^"; RTP_BACKDROPONLY: print "Only backdrops can be moved to multiple places.^"; RTP_NOTBACKDROP: print "Tried to move ", (the) par1, " (not a thing) to ", (the) par2, ", but only things can move around.^"; RTP_SCENEHASNTSTARTED: print "The scene ", (PrintSceneName) par1, " hasn't started, so you can't ask when it did.^"; RTP_SCENEHASNTENDED: print "The scene ", (PrintSceneName) par1, " hasn't ended, so you can't ask when it did.^"; RTP_NEGATIVEROOT: print "You can't take the square root of a negative number.^"; RTP_CANTITERATE: print "You can't implicitly repeat through the values of this kind: ", "a problem arising from a description which started out here - ~", (string) par1, "~.^"; RTP_WRONGASSIGNEDKIND: print "Attempt to set a variable to the wrong kind of object: ", "you wrote '", (string) par2, "', which sets the value to ", (the) par1, " - but that doesn't have the kind '", (string) par3, "'.^"; } print "^"; ]; @p Low-Level Errors. The following is a residue from the old I6 library, and most of its possible messages can't be seen in I7 use -- for instance I7 has no timers or daemons, so error 4 is out of the question. But we retain the routine because the veneer code added by I6 requires it to be present. @c Constant MAX_TIMERS = 0; [ RunTimeError n p1 p2; #Ifdef DEBUG; print "** Library error ", n, " (", p1, ",", p2, ") **^** "; switch (n) { 1: print "preposition not found (this should not occur)"; 2: print "Property value not routine or string: ~", (property) p2, "~ of ~", (name) p1, "~ (", p1, ")"; 3: print "Entry in property list not routine or string: ~", (property) p2, "~ list of ~", (name) p1, "~ (", p1, ")"; 4: print "Too many timers/daemons are active simultaneously. The limit is the library constant MAX_TIMERS (currently ", MAX_TIMERS, ") and should be increased"; 5: print "Object ~", (name) p1, "~ has no ~time_left~ property"; 7: print "The object ~", (name) p1, "~ can only be used as a player object if it has the ~number~ property"; 8: print "Attempt to take random entry from an empty table array"; 9: print p1, " is not a valid direction property number"; 10: print "The player-object is outside the object tree"; 11: print "The room ~", (name) p1, "~ has no ~description~ property"; 12: print "Tried to set a non-existent pronoun using SetPronoun"; 13: print "A 'topic' token can only be followed by a preposition"; default: print "(unexplained)"; } print " **^"; #Ifnot; print "** Library error ", n, " (", p1, ",", p2, ") **^"; #Endif; ! DEBUG RunTimeProblem(RTP_LOWLEVELERROR); ]; @p Argument Type Checking Failed. This is called when run-time type checking for the argument of a phrase fails, so that no definition for the phrase can be applied. @c [ ArgumentTypeFailed file line arg; RunTimeProblem(RTP_TYPECHECK, 0, 0, 0, line); ]; @p Return Type Checking Failed. Similarly, though in a more restricted set of circumstances. NI can usually prove that the value returned by a phrase matches its supposed kind, but because of the deliberately weak type-checking within the kind of value ``object'' it will allow a broader kind of object to be returned when the requirement is for a narrower one. In such cases, it checks the result with the following routine. The value |V| is a valid ``object'', but that means it can be |nothing|, and we must check that it matches a given I7 kind |K| (where |nothing| is not valid). @c [ CheckKindReturned V K; if (V ofclass K) return V; if (v == nothing) RunTimeProblem(RTP_DECIDEONNOTHING); else RunTimeProblem(RTP_DECIDEONWRONGKIND); return V; ]; @p Whether Provides. This routine defines the phrase ``if O provides P'': there are three tests to pass, and if any of the three fail, we return |false|. (The |issue_rtp| flag, causing RTPs to be issued depending on which test fails, is never set when the routine is simply testing the condition.) Firstly, P has to be a property known to I7. Secondly, there has to be permission either for this individual object to have it, or for its kind to have it, or its kind's kind, and so on. Thirdly, the object has to actually have the property in question at an I6 level -- having permission to have it doesn't mean it actually does have. @c [ WhetherProvides obj either_or p issue_rtp off i textual a l; if (metaclass(obj) ~= Object) rfalse; if (p<0) p = ~p; if (either_or) { if (p < FBNA_PROP_NUMBER) off = attributed_property_offsets-->p; else off = valued_property_offsets-->p; } else off = valued_property_offsets-->p; if (off<0) { if (issue_rtp) RunTimeProblem(RTP_BADPROPERTY, obj); rfalse; } textual = property_metadata-->off; off++; if (ScanPropertyMetadata(obj, off)) jump PermissionFound; if (obj provides KD_Count) { l = obj.KD_Count; while (l > 0) { a = l*2; if (ScanPropertyMetadata(KindHierarchy-->a, off)) jump PermissionFound; l = KindHierarchy-->(a+1); } } if (issue_rtp) RunTimeProblem(RTP_UNPROVIDED, obj, textual); rfalse; .PermissionFound; if (either_or) rtrue; if (obj provides p) rtrue; if (issue_rtp) RunTimeProblem(RTP_UNSET, obj, textual); rfalse; ]; [ PrintPropertyName p off textual; if (p<0) p = ~p; off = valued_property_offsets-->p; textual = property_metadata-->off; print (string) textual; ]; @p Scan Property Metadata. The |property_metadata| table is a series of zero-terminated lists of objects (or class objects, representing I7 kinds). Each list corresponds to a single property; the position in the table is called the ``offset'' for the property. The following searches from a given offset. @c [ ScanPropertyMetadata obj off i; for (i=off: property_metadata-->i >= 0: i++) if (obj == property_metadata-->i) rtrue; rfalse; ]; @p Get Either-Or Property. If |p| represents a property, then |~p| (its bitwise negation) represents its logical negation. The bitwise negation will change the sign bit; since all properties are ordinarily positive, we can detect bitwise negation by looking for negative property numbers. (This could fail on Glulx story files above 1GB in size, but that's about 1000 times the size of the record-sized file created thus far.) FBNA stands for First Boolean Not to be an Attribute, in the I6 sense. Some either/or (i.e., boolean) properties are stored as I6 attributes, others as I6 properties whose values are always |true| or |false|. Note that we allow either/or properties to be {\it read} for any object, regardless of permissions, returning |false| if the object does not have the property. This is so that a description such as ``open things'' can be applied against any object without run-time errors, even though it is only normally valid for doors and containers. @c [ GetEitherOrProperty o p; if (o == nothing) rfalse; if (p<0) p = ~p; if (WhetherProvides(o, true, p, false)) { if (pK; if (obj == 0) { RunTimeProblem(RTP_PROPOFNOTHING, obj, pr); rfalse; } if (obj provides pr) { if (K == OBJECT_TY) { if (pr == door_to) return obj.pr(); if (WhetherProvides(V, false, pr, true)) return obj.pr; rfalse; } if (obj ofclass K0_kind) WhetherProvides(V, false, pr, true); ! to force a run-time problem if ((V < 1) || (V > obj.value_range)) { RunTimeProblem(RTP_BADVALUEPROPERTY); return 0; } return (obj.pr)-->(V+COL_HSIZE); } else { if (obj ofclass K0_kind) WhetherProvides(V, false, pr, true); ! to force a run-time problem } rfalse; ]; @p Write Value Property. This routine's name must consist of the read-value-property routine's name with the prefix |Write|, as that is how a reference to such a property is converted from an rvalue to an lvalue. @c [ WriteGProperty K V pr val obj; if (K == OBJECT_TY) obj = V; else obj = KOV_representatives-->K; if (obj == 0) { RunTimeProblem(RTP_PROPOFNOTHING, obj, pr); rfalse; } if (K == OBJECT_TY) { if (WhetherProvides(V, false, pr, true)) obj.pr = val; } else { if ((V < 1) || (V > obj.value_range)) return RunTimeProblem(RTP_BADVALUEPROPERTY); if (obj provides pr) { (obj.pr)-->(V+COL_HSIZE) = val; } } ]; @p Printing Property Names. Inform doesn't print property names prettily; it more or less prints them only as decimal numbers. @c [ PROPERTY_TY_Say v; print "property ", v; ];