// 
// File: script.cpp
// Script class implementation
// F.J. Alberti
// Created: 30/05/2001
// Last Modified: 05/06/2001
//
// Modification history:
//  FA   15/06/2001   SESTR() macro renamed to SECSTR()
//  FA   28/06/2001   Debugger-aware code
//  FA   16/07/2001   Use h2i() conversion function
//  FA   22/08/2001   Generate an error if "NIL" is encountered in SCOL Strict mode
//                    Tell the user when an unresolved function call is skipped
//                    Generate an error for unsupported types in SCOL Strict mode
//  FA   12/11/2001   Replace SCOL_DEBUGGER_AWARE by INCLUDE_DEBUGGER
//  FA   05/02/2002   Corrected MSGEFUNCALLSKIPPED message
//  FA   06/02/2002   Bug correction: 'typelex' initialised with a string that could
//                    be relocated by the kernel. (Yet another invalid pointer!)
//  FA   18/03/2002   Replace lex.eatUpTo(Token::kNewline) by lex.discardLine(), which
//                    corrects a bug that causes Lexer::eatUpTo() to loop indefinitely
//

#include <string.h>
#include "debug.h"
#include "interp.h"
#include "lexer.h"
#include "macros.h"
#include "script.h"
extern "C" {
# include "listlab.h"
# include "mbytec.h"
# include "scol.h"
}


Script::Script(mmachine m, const char* text)
  : _m(m)
{
  _text = new char[strlen(text)+1];
//assert(_text != 0);
  strcpy(_text, text);
}


Script::~Script()
{
  if (_text)
    delete [] _text;
}


//
// Script::execute() errors
//
#define MSGEENVNULL          "Environment is null"
#define MSGEFUNUNDEF         "Function '%s' is not defined in channel environment"
#define MSGEFUNMISSING       "'%s' is not a function"
#define MSGEFUNWRONGARITY    "Function expects %d argument(s)"
#define MSGEINVALIDTYPE      "Only integer and string literals are valid in commands"
#define MSGEARGILLTYPED      "Argument #%d should be of type '%s', not '%s'"
#define MSGEUNSUPPORTEDTYPE  "Function expects an argument of unsupported type"
#define MSGEFUNCALLSKIPPED   "Function call was skipped since it could not be resolved"



int Script::execute()
// Executes a script in the current channel
{
#if defined(INCLUDE_DEBUGGER) //$ FA(28/06/2001)
  // Notify debugger server that a new script is about to be executed
  SECHECK(DBGNotifyScriptOpened(_m, _text));
#endif
  Lexer lex(_text);
  lex.setMode(Lexer::kModeScript);

  SEINT sp = SEGETSP(_m);            // recall current stack position
  while (!lex.eoi()) {
    Token tok = lex.next();
    // Return on end-of-input
    if (tok.is(Token::kEOI))
      return MERROK;
    // Skip blank lines
    if (tok.is(Token::kNewline))
      continue;
    // First token should be a command (function) name
    if (tok.is(Token::kIdent)) {
      char cmd[kMaxTokenLength+1];
      strcpy(cmd, tok.lexeme());
      TextSpan span;                // command text span
      span.beg = tok.span().beg;    // recall beginning of command to print it later

      // Abort script execution if channel is null
      SEWORD chn = SEGETROOT(_m, OFFSCCUR);
      if (chn == NIL) {
        MMechostr(MSKRUNTIME, "(!) "MSGEENVNULL"\n");
        return MERRTYP;
      }

      // By definition, ignore malformed, ill-typed, or undefined commands
      SEWORD env = SEFETCH(_m, SEW2P(chn), OFFCHANENV);
      SEPTR  fun = Msearchinpak(_m, env, (char*)tok.lexeme());  
      if (fun == NIL) {
        MMechostr(MSKWARNING, "(!) "MSGEFUNUNDEF"\n", tok.lexeme());
        goto relax;
      }
      SEINT arity = SEW2I(SEFETCH(_m, fun, OFFVTYP));
      if (arity < 0) {
        MMechostr(MSKWARNING, "(!) "MSGEFUNMISSING"\n", tok.lexeme());
        goto relax;
      }
      // In the absence of a type graph, use another lexer to parse its 
      // string representation.
      char funType[256];
      strcpy(funType, SECSTR(_m, SEW2P(SEFETCH(_m, fun, OFFVSTYP))));
      Lexer typelex(funType);
      typelex.advance();  // drop 'fun' keyword
      typelex.advance();  // drop '['

      // Save function pointer on the stack for later use
      SECHECK(SEPUSH(_m, SEP2W(fun)));

      // Process command arguments
      for (int i = 1; i <= arity; i++) {
        const char* argType = 0;      // any
        tok = lex.next();
        switch (tok.kind()) {
          // We accept 'nil' written in uppercase or lowercase (new version)
          case Token::kIdent:
#if defined(SCOL_STRICT)
            if (!strcmp(tok.lexeme(), "nil"))
#else
            if (!strcmp(tok.lexeme(), "nil") || !strcmp(tok.lexeme(), "NIL"))
#endif
              SECHECK(SEPUSH(_m, NIL));
            else {
              // PATCH: A priority must be chosen in the case of reading 
              // identifiers and integer literals where the default base is 16
              int n;
              h2i(tok.lexeme(), &n);
              SECHECK(SEPUSH(_m, SEI2W(n)));
              argType = "I";
              break;
            }
            break;

          case Token::kInt: {   // integer
            int n;
            h2i(tok.lexeme(), &n);
            SECHECK(SEPUSH(_m, SEI2W(n)));
            argType = "I";
            break;
          }

          case Token::kString:  // string
            if (Mpushstrtok(_m, (char*)tok.lexeme()))
              return MERRMEM;
            argType = "S";
            break;

          case Token::kNewline:
            MMechostr(MSKWARNING, "(!) "MSGEFUNWRONGARITY"\n", arity);
            //BUG: Push kNewline back into the token stream
            goto relax;

          default:
            MMechostr(MSKWARNING, "(!) "MSGEINVALIDTYPE"\n");
            goto relax;
        } // switch

        const Token& typetok = typelex.next();
        // Check that function expects a type that is supported by scripts
        if (!typetok.is(Token::kIdent) 
           || (strcmp(typetok.lexeme(), "I")
              && strcmp(typetok.lexeme(), "S")
              && typetok.lexeme()[0] != 'u')) {
#if defined(SCOL_STRICT)
          MMechostr(MSKWARNING, "(!) "MSGEUNSUPPORTEDTYPE"\n");
          goto relax;
#else
          MMechostr(MSKWARNING, "(!) Warning: "MSGEUNSUPPORTEDTYPE"\n");
          lex.printPosition();
#endif
        }

        // Unify expected type with that of the signature of the function
        if (argType && typetok.lexeme()[0] != 'u' && strcmp(typetok.lexeme(), argType)) {
          MMechostr(MSKWARNING, "(!) "MSGEARGILLTYPED"\n", i, typetok.lexeme(), argType);
          goto relax;
        }
      } // for

      span.end = tok.span().end;
      // Check that call does not supply more arguments than expected
      if (!(tok = lex.next()).is(Token::kNewline) && !tok.is(Token::kEOI)) {
        MMechostr(MSKWARNING, "(!) "MSGEFUNWRONGARITY"\n", arity);
        goto relax;
      }
      // Print command onto the console
      MMechostr(MSKTRACE, "> exec: ");
      lex.printSource(span);
      MMechostr(MSKTRACE, "\n");

#if defined(INCLUDE_DEBUGGER) //$ FA(28/06/2001)
      debug.scriptBegLine = span.beg.line+1;
      debug.scriptBegOff  = span.beg.off;
      debug.scriptEndLine = span.end.line+1;
      debug.scriptEndOff  = span.end.off;
      if (debug.sock) {
        if (debug.traceMode != kTraceNone)
          DBGRequestBreak(_m, kBrksrcTraceScript);
        SECHECK(DBGHandleBreak(_m)); // possible breaking point
      }
#endif
      // Push pointer to function (already pushed below sp)
      SECHECK(SEPUSH(_m, SEFETCH(_m, SEW2P(SEGET(_m, sp-1)), OFFVVAL)));
      // Call interpreter to evaluate function application
      Interpreter interp(_m);
      int res = interp.call();
      if (res < 0)
        return res;
      // Drop result and saved function pointer
      SEDROP(_m, 2);

#if defined(INCLUDE_DEBUGGER) //$ FA(28/06/2001)
      debug.scriptBegLine = 0;
      debug.scriptBegOff  = 0;
      debug.scriptEndLine = 0;
      debug.scriptEndOff  = 0;
#endif
      // Process next command line
      continue;
    }
relax:
    // Print position of offending token
    lex.printPosition();
    MMechostr(MSKWARNING, "(!) "MSGEFUNCALLSKIPPED"\n");
    // Restore stack and ignore all tokens till the end of the line
    SESETSP(_m, sp);
    lex.discardLine();
  } // while

  return MERROK;
}


int SCexeccomm(mmachine m, char *comm)
// Executes a script line-command on the current channel
{
  Script script(m, comm);
  return script.execute();
}


