#charset "us-ascii"

/*
*   Krister's debugging suite. This extension contains the following
*   debugging aids:
*
*   - a set of debugging actions:
*      - 'purloin', for getting hold of objects that are out of scope
*      - 'gonear', for teleporting to remote locations
*      - 'frotz', for making objects emit light (or stop emitting light, if
*        they already are)
*
*   - a framework for adding game-specific debugging actions (see comments
*     below for how to do this)
*
*   - an in-game expression evaluator (again, see below for details)
*
*   Note that this extension automatically includes reflect.t for debug
*   builds, so there is no need to do this manually.
*
*   kfDebug.t   Version 1
*   Copyright 2010, Krister Fundin ([email protected])
*/

#ifdef __DEBUG

#include <adv3.h>
#include <en_us.h>

#include <reflect.t>

/* ---------------------------------------------------------------------- */
/*
*   Purloin. This action allows the player to get hold of objects that are
*   either out of scope or for other reasons unreachable. It makes no
*   checks at all for logicalness, so it's quite possible to purloin
*   buildings or people or anything at all.
*/
DefineTAction(Purloin)
   objInScope(obj) { return true; }

   verifyAction() { }
   execAction()
   {
       dobjCur_.moveIntoForTravel(gActor);
       "Purloined. ";
   }
;

VerbRule(Purloin)
   'purloin' dobjList
   : PurloinAction

   verbPhrase = 'purloin/purloining (what)'
;

/* ---------------------------------------------------------------------- */
/*
*   Gonear. This action teleports the player to the location of a specified
*   object. It works with NestedRooms as well as Rooms, so a bit of caution
*   might be needed: if you type 'gonear bob' and Bob is sitting in an
*   armchair, then the destination will be the armchair. You can of course
*   name the rooms directly if you have assigned vocabulary words to them.
*/
DefineTAction(Gonear)
   objInScope(obj) { return true; }

   verifyAction() { }
   execAction()
   {
       local dest = dobjCur_.roomLocation;

       if (dest != nil)
       {
           gActor.moveIntoForTravel(dest);
           gActor.lookAround(true);
       }
       else
       {
           "You can't go there. ";
           return;
       }
   }
;

VerbRule(Gonear)
   'gonear' singleDobj
   : GonearAction

   verbPhrase = 'go/going near (what)'
;

/* ---------------------------------------------------------------------- */
/*
*   Frotz. This action makes arbitrary objects start emitting light, or
*   stop emitting light if they already are.
*/
DefineTAction(Frotz)
   verifyAction() { }
   execAction()
   {
       if (dobjCur_.brightness == 0)
       {
           dobjCur_.brightness = 3;

           "{The dobj/he} start{s} to glow. ";
       }
       else
       {
           dobjCur_.brightness = 0;

           "{The dobj/he} stop{s} glowing. ";
       }
   }
;

VerbRule(Frotz)
   'frotz' singleDobj
   : FrotzAction

   verbPhrase = 'frotz/frotzing (what)'
;

/* ---------------------------------------------------------------------- */
/*
*   Game-specific debugging commands. These can be used E.G. to jump to a
*   later chapter in the game. To add one, use this syntax:
*
*      grammar debugCommand:
*          'chapter5'
*          : BasicProd
*
*          execute()
*          {
*              // carry out the necessary steps here
*          }
*      ;
*
*   A definition such as this won't have any effect in a release build, so
*   it's not necessary to put an #ifdef around it. One might want to set up
*   a macro for creating these debug commands in order to save some typing,
*   however. Here is one way to do so:
*
*      #ifdef __DEBUG
*      #define dbgCommand(name, code) \
*          grammar debugCommand : name : BasicProd execute() code
*      #else
*      #define dbgCommand(name, code)
*      #endif
*
*   With this macro in place, the definition can be shortened to:
*
*      dbgCommand('chapter5',
*      {
*          // carry out the necessary steps here
*      });
*
*   In any case, the command is then executed by typing 'debug chapter5'.
*/
DefineSystemAction(CustomDebug)
   execSystemAction()
   {
       cmd_.execute();
   }
;

VerbRule(CustomDebug)
   'debug' debugCommand->cmd_
   : CustomDebugAction

   verbPhrase = 'debug/debugging'
;

grammar debugCommand(unknown):
   [badness 500] miscWordList
   : BasicProd

   execute()
   {
       "Unknown debugging command. ";
   }
;

/* ---------------------------------------------------------------------- */
/*
*   In-game expression evaluation. This part of the extension provides a
*   quick way to look up variables from within the game. Any command line
*   which starts with an equals sign is passed through a much simplified
*   version of the 'evaluate' command from the TADS 3 debugger, and the
*   result is then displayed if there was one.
*
*   The parser used here can evaluate properties and call methods on
*   objects, possibly with arguments, and it recognizes simple strings and
*   numbers, but that's as far as it goes. In particular, it should be
*   noted that macros such as gDobj can't be evaluated this way.
*/
evalPreParser: StringPreParser
   doParsing(str, which)
   {
       if (which == rmcAskLiteral)
           return str;

       if (!str.startsWith('='))
           return str;

       try
       {
           local toks = evalTokenizer.tokenize(str.substr(2));
           local match = evalExpr.parseTokens(toks, nil);

           if (match != [])
           {
               say (reflectionServices.valToSymbol(match[1].evaluate()));
               return nil;
           }
       }
       catch (Exception exc)
       {
       }

       return str;
   }
;

evalException: Exception;

evalTokenizer: Tokenizer
   rules_ = static
   [
       ['whitespace', new RexPattern('<space>+'), nil, &tokCvtSkip, nil],
       ['operators', new RexPattern('[.,()]'), tokPunct, nil, nil],
       ['strings', new RexPattern('<squote>.*?<squote>'),
           tokString, nil, nil],
       ['numbers', new RexPattern('<digit>+'), tokInt, nil, nil],
       ['symbols', new RexPattern('<alphanum|_>+'), tokWord, nil, nil]
   ]
;

grammar evalExpr(compound):
   evalExpr->lhs_ '.' evalObj->rhs_ evalArgList->args_
   : BasicProd

   evaluate()
   {
       local lhs = lhs_.evaluate();
       local rhs = rhs_.evaluate();
       local args = args_.evaluate();

       if (dataType(lhs) not in (TypeObject, TypeSString, TypeList)
           || dataType(rhs) != TypeProp)
       {
           throw evalException;
       }

       return lhs.(rhs)(args...);
   }
;

grammar evalExpr(simple):
   evalObj->obj_
   : BasicProd

   evaluate() { return obj_.evaluate(); }
;

grammar evalArgList(nonempty):
   '(' evalArgs->args_ ')'
   : BasicProd

   evaluate() { return args_.evaluate(); }
;

grammar evalArgList(empty):
   ('(' ')' | )
   : BasicProd

   evaluate() { return []; }
;

grammar evalArgs(compound):
   evalExpr->arg_ ',' evalArgs->args_
   : BasicProd

   evaluate() { return args_.evaluate().prepend(arg_.evaluate()); }
;

grammar evalArgs(single):
   evalExpr->arg_
   : BasicProd

   evaluate() { return [arg_.evaluate()]; }
;

grammar evalObj(symbol):
   tokWord->tok_
   : BasicProd

   evaluate()
   {
       switch (tok_)
       {
       case 'true': return true;
       case 'nil': return nil;
       default:
           local val = reflectionServices.symtab_[tok_];

           if (val != nil)
               return val;
           else
               throw evalException;
       }
   }
;

grammar evalObj(string):
   tokString->tok_
   : BasicProd

   evaluate() { return tok_.substr(2, tok_.length - 2); }
;

grammar evalObj(number):
   tokInt->tok_
   : BasicProd

   evaluate() { return toInteger(tok_); }
;

#endif // __DEBUG