! 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;

