! scrutinize.h v1.0 2007-09-23
!
! Library extension for Inform 6 to check for common problems in objects.
! Requires Inform 6.30 or higher. Tested only with 6.31 and library 6/11.
! Written by Fredrik Ramsberg, fredrikXYZramsberg.net (change XYZ to @).
! Comments and corrections are welcome.
!
! To use this extension, include it after including Grammar.h, in your Glulx
! or Zcode game. If you only want it enabled in DEBUG mode, you need to
! surround the inclusion line with "#ifdef DEBUG;" and "#endif;".

! In the compiled game, type "scrutinize" and you will get a list of all
! objects which look like they may have problems. The only kind of problem
! which it currently checks for is if the object has words in its printed name
! which aren't recognized as synonyms for the object. It works even on objects
! which have their own parse_name routine, but with the limitation that it can
! only find a single missing word at a time. To see if there are more missing
! synonyms for such an object, you need to fix the first missing synonym,
! recompile and use "scrutinize" again. Of course there are situations where
! the scrutinize verb will indicate trouble but you know it's wrong, like if
! you have behind-the-scenes objects which the player shouldn't be able to
! refer to anyway.
!
! Here's an example of a programmer's mistake which the scrutinize verb can
! help in finding: Object -> tray "small tray" with name 'tray';
! (The programmer forgot to add 'small' as a synonym)
!
! The ScrutinizeSub routine was written to be easily extensible with more
! tests on objects. In fact, it started as an integral component of the
! Swedish Inform library (and it still is), where it checks for a number of
! mistakes which are easy to make when using that particular library
! translation.

#ifdef TARGET_ZCODE;

Array _ScrutNameTestBuffer -> (160 + 2);

Constant _SCRUT_MAXPARSE 15;

Array _ScrutBadWords -> _SCRUT_MAXPARSE;

#ifnot; !Glulx

Array _ScrutBadWords -> MAX_BUFFER_WORDS;

Array _ScrutNameTestBuffer buffer 160;

#endif;

[_ScrutCheckName o prop   i badcount;
 indef_mode=false;
 PrintToBuffer(_ScrutNameTestBuffer, 160, o, prop);
#ifdef TARGET_ZCODE;
 parse->0 = _SCRUT_MAXPARSE;
 for(i=2:i < (2 + _ScrutNameTestBuffer -> 1): i++) {
   _ScrutNameTestBuffer -> i = LowerCase(_ScrutNameTestBuffer -> i);
   if((_ScrutNameTestBuffer -> i)==','  or '.'  or '"')
     _ScrutNameTestBuffer -> i = ' ';
 }
#ifnot; !Glulx
 for(i=4:i < (4 + _ScrutNameTestBuffer --> 0): i++) {
   _ScrutNameTestBuffer -> i = LowerCase(_ScrutNameTestBuffer -> i);
   if((_ScrutNameTestBuffer -> i)==','  or '.'  or '"')
     _ScrutNameTestBuffer -> i = ' ';
 }
#endif;
 Tokenise__(_ScrutNameTestBuffer, parse);
 if(o.parse_name~=0) { ! Method 1: Ask the object's parse_name routine
   parser_action = NULL;
   wn=1;
   i = RunRoutines(o, parse_name);
#ifdef TARGET_ZCODE;
   if(i < parse->1)
     _ScrutBadWords->(badcount++) = i;
 } else {! Method 2: Check the object's name property
   for(i=0:i < parse->1 && i < _SCRUT_MAXPARSE: i++)
     if(~~WordInProperty(parse-->(1+i*2), o, name))
       _ScrutBadWords->(badcount++) = i;
 }
#ifnot; !Glulx
   if(i < parse-->0)
     _ScrutBadWords->(badcount++) = i;
 } else {! Method 2: Check the object's name property
   for(i=0:i < parse-->0 && i < MAX_BUFFER_WORDS: i++)
     if(~~WordInProperty(parse-->(1+i*3), o, name))
       _ScrutBadWords->(badcount++) = i;
 }
#endif;
 return badcount;
];

[_ScrutPrintBadWords count  i j k;
 print "(";
 for(k=0: k<count: k++) {
   i = _ScrutBadWords -> k;
#ifdef TARGET_ZCODE;
   for(j=0: j < parse -> (4+i*4): j++) {
     print (char) _ScrutNameTestBuffer -> (j + parse->(5+i*4));
   }
#ifnot; !Glulx
   for(j=0: j < parse --> (2+i*3): j++) {
     print (char) _ScrutNameTestBuffer -> (j + parse-->(3+i*3));
   }
#endif;
   if(k < count - 1)
     print ", ";
 }
 print ")";
];

[ScrutinizeSub i j err anyerr isNormalObj wordno;
 style bold;
 print  "Scrutinizing all objects, looking for the following indications of problems:
   ^* Has words in the strings/routines which print the object's name, which aren't synonyms for the object
   ^^";
 style roman;
 anyerr=false;
 objectloop(i>LibraryMessages && ~~(i ofclass class)) {
   err=false;
   isNormalObj=parent(i)~=0 || i hasnt light;
   if(isNormalObj && parent(i)==0) { ! Check if dark, lonely objects have exits
     objectloop(j in compass) isnormalobj=isnormalobj && (i.(j.door_dir)==0);
   }
   if(isNormalObj) {
     ! This is probably an object which the player should be able to refer to.
     ! Perform test #1
     if(i.short_name~=0) {
       wordno=_ScrutCheckName(i, short_name);
       if(wordno) {
         print "Has words in short_name which aren't synonyms for the object: ", (name) i, " ",(_ScrutPrintBadWords) wordno,".^";
         err=true;
       }
     }
     ! Perform test #2
     if(i.short_name_indef~=0) {
       wordno=_ScrutCheckName(i, short_name_indef);
       if(wordno) {
         print "Has words in short_name_indef which aren't synonyms for the object: ", (name) i, " ",(_ScrutPrintBadWords) wordno,".^";
         err=true;
       }
     }
     ! Perform test #3
     if(i.short_name==0 && i.short_name_indef==0) {
       wordno=_ScrutCheckName(i);
       if(wordno) {
         print "Has words in object name string which aren't synonyms for the object: ", (name) i, " ",(_ScrutPrintBadWords) wordno,".^";
         err=true;
       }
     }
   }


   if(err) {
     anyerr=true;
   }

 }
 if(~~anyerr) "No problems were found.";
];

Verb meta 'scrutinize' 'scrutinise'
   *                                           -> Scrutinize;