#include <ctype.h>
#include <stdlib.h>

#include <alex.h>
#include <automask.h>
#include <colors.h>
#include <defmask.h>
#include <dongle.h>
#include <prefix.h>
#include <recarray.h>
#include <statbar.h>
#include <urldefid.h>
#include <utility.h>

///////////////////////////////////////////////////////////
// TAVM_op
///////////////////////////////////////////////////////////

enum AVM_opcode 
{ 
  avm_nop, 
  avm_add, avm_and, 
  avm_begin,
  avm_call_word, avm_ceil, avm_cold, avm_cr,
  avm_cmp_eq, avm_cmp_gt, avm_cmp_gteq, avm_cmp_lt, avm_cmp_lteq, avm_cmp_noteq, 
  avm_cmp_emptyeq, avm_cmp_nulleq, avm_cmp_zeroeq,
  avm_div, avm_divide, avm_do, avm_dot, avm_drop, avm_dup, 
  avm_else,
  avm_fetch, avm_forget,
  avm_i, avm_if, avm_include, 
  avm_j,
  avm_loop,
  avm_mod, avm_mon, avm_mul,
  avm_negate, avm_null,
  avm_or, avm_over, 
  avm_perform, avm_pick, avm_plus_loop, avm_plus_store, avm_push, 
  avm_repeat, avm_rdrop, avm_rpeek, avm_rpush, avm_roll, avm_rot, avm_round,
  avm_strlen, avm_strmid, avm_strtok_fetch, avm_strtok_add, 
  avm_store, avm_sp, avm_sub, avm_swap, 
  avm_then, avm_trunc,
  avm_until, avm_usrword, 
  avm_warm, avm_while,
  avm_zzz
};

const char* AVM_TOKENS[avm_zzz+1] = 
{
  "$NOP$",
  "+", "AND",
  "BEGIN",
  "$CALL_WORD$", "CEIL", "COLD", "CR",
  "=", ">", ">=", "<", "<=", "<>", 
  "EMPTY=", "NULL=", "0=", 
  "DIV", "/", "DO", ".", "DROP", "DUP",
  "ELSE",
  "@", "FORGET",
  "I", "IF", "INCLUDE",
  "J",
  "LOOP",
  "MOD", "MON", "*",
  "NEGATE", "NULL",
  "OR", "OVER",
  "PERFORM", "PICK", "+LOOP", "+!", "$PUSH$",
  "REPEAT", "R>", "R@", ">R", "ROLL", "ROT", "ROUND",
  "STRLEN", "STRMID", "STRTOK@", "STRTOK+", 
  "!", "SP", "-", "SWAP",
  "THEN", "TRUNC",
  "UNTIL", "$USR$",
  "WARM", "WHILE"
};

enum TBreakpointType { brk_none = 0x0, brk_user = 0x1, brk_auto = 0x2 };

class TAVM_op : public TObject
{
  AVM_opcode _op;
  TVariant _var;
  int _break_pointer;

public:
  const TVariant& var() const { return _var; }
  TVariant& var() { return _var; }
  AVM_opcode op() const { return _op; }
  bool has_break() const { return _break_pointer != brk_none; }
  bool has_auto_break() const { return (_break_pointer & brk_auto) != 0; }
  void set_user_break(bool on);
  void set_auto_break(bool on);

  TAVM_op(AVM_opcode o, const TString& str);
  TAVM_op(AVM_opcode o, const real& num);
  TAVM_op(AVM_opcode o, const long num);
  TAVM_op(const TAVM_op& op);
  TAVM_op(AVM_opcode o);
};

void TAVM_op::set_user_break(bool on)
{
  _break_pointer = on ? brk_user : brk_none;
}

void TAVM_op::set_auto_break(bool on)
{
  if (on)
    _break_pointer |= brk_auto;
  else
    _break_pointer &= ~brk_auto;
}


TAVM_op::TAVM_op(AVM_opcode o, const TString& str)
       : _op(o), _var(str), _break_pointer(0)
{ }

TAVM_op::TAVM_op(AVM_opcode o, const real& num)
       : _op(o), _var(num), _break_pointer(0)
{ }

TAVM_op::TAVM_op(AVM_opcode o, const long num)
       : _op(o), _var(num), _break_pointer(0)
{ }

TAVM_op::TAVM_op(AVM_opcode o)
       : _op(o), _break_pointer(0)
{ }

TAVM_op::TAVM_op(const TAVM_op& op) : _op(op._op), _var(op._var), _break_pointer(0)
{
}

///////////////////////////////////////////////////////////
// TAVM_monitor
///////////////////////////////////////////////////////////

class TAVM_list_window : public TField_window
{
  TBytecode* _bc;
  int _ip;
  const TString_array* _user_words;

protected:
  virtual void update();
  virtual void handler(WINDOW win, EVENT* ep);

public:
  void set_bytecode(const TBytecode* bc, int ip, const TString_array& uw);
  TAVM_list_window(int x, int y, int dx, int dy, WINDOW parent, TWindowed_field* owner);
};

void TAVM_list_window::handler(WINDOW win, EVENT* ep)
{
  if (ep->type == E_MOUSE_DOWN)
  {
    const TPoint pt = dev2log(ep->v.mouse.where);
    if (pt.x <= 5)
    {
      TAVM_op& op = *(TAVM_op*)_bc->objptr(pt.y-1);
      op.set_user_break(ep->v.mouse.button == 0);
      force_update();
    }
  }
  TField_window::handler(win, ep);
}

void TAVM_list_window::update()
{
  clear(NORMAL_BACK_COLOR);
  if (_bc != NULL)
  {
    autoscroll(false);
    set_brush(DISABLED_BACK_COLOR);
    bar(0, 0, columns()+1, 1); 
    bar(0, 0, 5, rows()+1); 
    set_brush(NORMAL_BACK_COLOR);
    printat(0, 0, _bc->name());
    autoscroll(true);

    TString str;
    int tab = 6;

    const int last = min(_bc->items(), rows());
    for (int i = 0; i < last; i++)
    {

      const int y = i+1;
      if (_ip == i)
      {
        set_brush(FOCUS_BACK_COLOR);
        bar(0, y, 80, y+1); 
        set_brush(NORMAL_BACK_COLOR);
      }
      printat(0, y, "%04d", i);
      const TAVM_op& op = *(const TAVM_op*)_bc->objptr(i);
      const AVM_opcode co = op.op();
      const TVariant& var = op.var();

      if (op.has_break())
        printat(4, y, "<");

      if (co == avm_else || co == avm_then || 
          co == avm_loop || co == avm_plus_loop || 
          co == avm_repeat || co == avm_until || co == avm_while)
        tab -= 2;
      if (co == avm_push)
      {
        if (var.is_string() && var.as_string()[0] != '#')   
          str.cut(0) << '"' << var.as_string() << '"';
        else
        {
          if (var.is_null())
            str = "NULL";
          else
            str = var.as_string();
        }
      } else
      if (co == avm_call_word)
      {
        str = var.as_string();
      } else
      if (co == avm_usrword)
      {
        str = _user_words->row(var.as_int());
        str << " (#" << var.as_int() << ')';
      }
      else
      {
        str = AVM_TOKENS[co];
        if (!var.is_null())
          str << " (" << var.as_string() << ')';
      }
      printat(tab, y, str);
      if (co == avm_if || co == avm_else || 
        co == avm_do || co == avm_begin || co == avm_while)
        tab += 2;
    }
  }
}

void TAVM_list_window::set_bytecode(const TBytecode* bc, int ip, const TString_array& uw) 
{ 
  _bc = (TBytecode*)bc; 
  _ip = ip;
  _user_words = &uw;
  set_scroll_max(80, _bc->items() - rows());
}

TAVM_list_window::TAVM_list_window(int x, int y, int dx, int dy, WINDOW parent, TWindowed_field* owner)
           : TField_window(x, y, dx, dy, parent, owner), _bc(NULL)
{
  autoscroll(true);
}

class TAVM_stack_window : public TField_window
{
  TVariant_stack* _stack;

protected:
  virtual void update();

public:
  void set_stack(TVariant_stack& s);
  TAVM_stack_window(int x, int y, int dx, int dy, WINDOW parent, TWindowed_field* owner);
};

void TAVM_stack_window::update()
{
  clear(NORMAL_BACK_COLOR);
  if (_stack != NULL)
  {
    for (int i = 0; i < _stack->items(); i++)
    {
      const TVariant& var = _stack->peek(i);
      if (var.is_null())
        printat(0, i, "NULL");
      else
        printat(0, i, var.as_string());
    }
  }
}

void TAVM_stack_window::set_stack(TVariant_stack& s)
{
  _stack = &s;
  set_scroll_max(80, s.items() - rows());
}

TAVM_stack_window::TAVM_stack_window(int x, int y, int dx, int dy, WINDOW parent, TWindowed_field* owner)
           : TField_window(x, y, dx, dy, parent, owner), _stack(NULL)
{
  autoscroll(true);
}

class TAVM_list_field : public TWindowed_field
{
protected:
  virtual TField_window* create_window(int x, int y, int dx, int dy, WINDOW parent);

public:
  TAVM_list_field(TMask* m) : TWindowed_field(m) { }
  virtual ~TAVM_list_field() { }
};

TField_window* TAVM_list_field::create_window(int x, int y, int dx, int dy, WINDOW parent)
{
  return new TAVM_list_window(x, y, dx, dy, parent, this);
}

class TAVM_stack_field : public TWindowed_field
{
protected:
  virtual TField_window* create_window(int x, int y, int dx, int dy, WINDOW parent);

public:
  TAVM_stack_field(TMask* m) : TWindowed_field(m) { }
  virtual ~TAVM_stack_field() { }
};

TField_window* TAVM_stack_field::create_window(int x, int y, int dx, int dy, WINDOW parent)
{
  return new TAVM_stack_window(x, y, dx, dy, parent, this);
}

class TAVM_monitor : public TAutomask
{
  TAVM_list_window *_lw;
  TAVM_stack_window *_sw, *_rw;
  bool _ignore_mon;

protected:
  virtual bool on_field_event(TOperable_field& o, TField_event e, long jolly);
  virtual bool on_key(KEY k);

public:
  void set_ignore_mon(bool im) { _ignore_mon = im; }
  bool ignore_mon() const { return _ignore_mon; }
  TAVM_list_window& monitor() const { return *_lw; }
  TAVM_stack_window& stacker() const { return *_sw; }
  TAVM_stack_window& rstacker() const { return *_rw; }
  TAVM_monitor();
};


bool TAVM_monitor::on_field_event(TOperable_field& o, TField_event e, long jolly)
{
  switch (o.dlg())
  {
  case 104:
    if (e == fe_init)
      o.set(ignore_mon() ? "X" : "");
    if (e == fe_modify)
      set_ignore_mon(!o.get().blank());
    break;
  default:
    break;
  }
  return true;
}

bool TAVM_monitor::on_key(KEY k)
{
  switch (k)
  {
  case K_F10:
  case K_F11: 
    stop_run(k); 
    return true;
  default: break;
  }
  return TAutomask::on_key(k);
}

TAVM_monitor::TAVM_monitor() : TAutomask("Monitor", 1, 50, 20), _ignore_mon(false)
{
  TWindowed_field* wf = new TAVM_list_field(this);
  wf->create(101, 0, 0, 24, -4); add_field(wf);
  _lw = (TAVM_list_window*)&wf->win();

  TWindowed_field* sf = new TAVM_stack_field(this);
  sf->create(102, 27, 0, -3, 9); add_field(sf);
  _sw = (TAVM_stack_window*)&sf->win();

  TWindowed_field* rf = new TAVM_stack_field(this);
  rf->create(103, 27, 10, -3, -4); add_field(rf);
  _rw = (TAVM_stack_window*)&rf->win();

  add_boolean(104, 0, "Ignora MON d'ora in poi", 1, -3);

  add_button(DLG_NEXTREC, 0, "", -14, -1, 10, 2, "", 124).set_exit_key(K_F11);
  add_button(DLG_LASTREC, 0, "", -24, -1, 10, 2, "", 1671).set_exit_key(K_F10);
  add_button(DLG_ELABORA, 0, "", -34, -1, 10, 2, "", BMP_LASTREC).set_exit_key(K_F5);
  add_button(DLG_QUIT,    0, "", -44, -1, 10, 2).set_exit_key(K_QUIT);

  set_handlers();
}


///////////////////////////////////////////////////////////
// TAVM
///////////////////////////////////////////////////////////

class TAVM
{
  TAlex_virtual_machine* _vm;
  bool _interactive;
  TString _last_error;
  TVariant_stack _stack, _rstack;
  const TBytecode* _bc; // Current word (or command line)
  int _ip;              // Current instruction pointer
  ostream* _outstr;

  TAssoc_array _words;
  TAssoc_array _vars;
  TString_array _user_words;
  TAVM_monitor _mon;

protected:
  bool get_token(istream& instr, TString& str) const;
  AVM_opcode token2opcode(const TString& str) const;
  int compare_tos_nos();
  int find_matching(const TBytecode& bytecode, AVM_opcode op1, AVM_opcode op2 = avm_nop) const;
  void execute(const TAVM_op& op);
  void do_call(const TString& func);

  const TString_array& get_user_words();
  int compile_user_word(const char* n);

public:
  void log_error(const char* str);
  const TString& get_last_error() const { return _last_error; }

  bool compile(istream& instr, TBytecode& bc);
  bool execute(const TBytecode& bc);
  void do_restart(bool cold);
  bool do_include(const char* fname);
  void do_fetch(const TString& name);
  void do_add();
  void do_store(const TString& name);
  void set_interactive(bool inter) { _interactive = inter; }
  bool defined(const char* w);

  TAVM(TAlex_virtual_machine* vm);
  virtual ~TAVM();
};

void TAVM::log_error(const char* str)
{
  _last_error = str;
  if (_interactive)
    error_box(str);
#ifdef DBG
  else
    statbar_set_title(TASK_WIN, str);
#endif
}

bool TAVM::get_token(istream& instr, TString& str) const
{
  str.cut(0);
  eatwhite(instr);
  if (instr.eof())
    return false;
  char c;
  instr.get(c);
  str << c;
  if (c == '"')
  {
    char* buf = str.get_buffer()+1;
    const int bufsize = str.size()-1;
    instr.getline(buf, bufsize, '"');
    str << '"';
  }
  else
  {
    instr.get(c);
    while (!isspace(c) && !instr.eof())
    {
      str << c;
      instr.get(c);
    }
  }

  return *str > ' ';
}

#define ALEX_TOKENS 24


AVM_opcode TAVM::token2opcode(const TString& str) const
{
  for (int i = 0; AVM_TOKENS[i] != NULL; i++)
  {
    if (str == AVM_TOKENS[i])
      return AVM_opcode(i);
  }

  const TBytecode* bc =(const TBytecode*)_words.objptr(str);
  if (bc != NULL)
    return avm_call_word;

  return avm_nop;
}

int TAVM::find_matching(const TBytecode& bytecode, AVM_opcode op1, AVM_opcode op2) const
{
  int i;
  for (i = bytecode.last(); i >= 0; i--)
  {
    TAVM_op& theop = (TAVM_op&)bytecode[i];
    if ((theop.op() == op1 || theop.op() == op2) && theop.var().is_null())
    {
      theop.var() = long(bytecode.items());
      break;
    }
  }
  return i;
}

const TString_array& TAVM::get_user_words()
{
  if (_user_words.items() == 0)
  {
    _user_words.add("***");
    _vm->get_usr_words(_user_words);
  }
  return _user_words;
}

int TAVM::compile_user_word(const char* w)
{
  const TString_array& uw = get_user_words();
  const int i = uw.find(w);
  return i > 0 ? i : 0;
}

bool TAVM::compile(istream& instr, TBytecode& bytecode)
{
  TString str(256);
  bytecode.destroy();
  while (get_token(instr, str))
  {
    TAVM_op* op = NULL;
    if (str[0] == '"')
    {
      str.rtrim(1); str.ltrim(1); 
      op = new TAVM_op(avm_push, str);
    } else
    if ((isdigit(str[0]) || (str[0]=='-')) && isdigit(str[str.len()-1]))
    {
      const real r(str);
      op = new TAVM_op(avm_push, r);
    } else
    if (str[0] == '#') // User variable
    {
      op = new TAVM_op(avm_push, str);
    } else
    if (str == ":") // User word
    {
      if (get_token(instr, str))
      {
        TBytecode* bc = new TBytecode;
        bc->set_name(str);
        _words.add(str, bc, true);
        compile(instr, *bc);
        op = new TAVM_op(avm_nop);
      }
      else
      {
        _last_error = "Missing word after :";
        log_error(_last_error);
        return false;
      }
    } else
    if (str == "VARIABLE")
    {
      if (get_token(instr, str))
      {
        _vars.add(str, NULL_VARIANT);
        op = new TAVM_op(avm_nop);
      }
      else
      {
        _last_error = "Missing VARIABLE name";
        log_error(_last_error);
        return false;
      }
    } else
    if (str == ";")
    {
      return true;
    } else
    if (str == "(")
    {
      TString256 str;
      instr.getline(str.get_buffer(), str.size(), ')');
      op = new TAVM_op(avm_nop);
    } else
    if (str == "\\")
    {
      TString256 str;
      instr.getline(str.get_buffer(), str.size());
      op = new TAVM_op(avm_nop);
    } else
    if (_vars.objptr(str) != NULL)
    {
      op = new TAVM_op(avm_push, str);
    }
    else
    {
      const int oc = compile_user_word(str);
      if (oc > 0)
        op = new TAVM_op(avm_usrword, oc);
      else
      {
        const AVM_opcode oc = token2opcode(str);
        if (oc != avm_nop)
        {
          switch (oc)
          {
          case avm_else:
            if (find_matching(bytecode, avm_if) < 0)
            {
              _last_error = "ELSE without matching IF";
              log_error(_last_error);
              return false;
            }
            op = new TAVM_op(oc);
            break;
          case avm_then:
            if (find_matching(bytecode, avm_if, avm_else) < 0)
            {
              _last_error = "THEN without matching IF";
              log_error(_last_error);
              return false;
            }
            op = new TAVM_op(oc);
            break;
          case avm_loop:
          case avm_plus_loop:
            {
              const int do_pos = find_matching(bytecode, avm_do);
              if (do_pos < 0)
              {
                _last_error.cut(0) << str << " without matching DO";
                log_error(_last_error);
                return false;
              }
              op = new TAVM_op(oc, do_pos);
            }
            break;
          case avm_repeat:
          case avm_until:
            {
              const int begin_pos = find_matching(bytecode, avm_begin);
              if (begin_pos < 0)
              {
                _last_error.cut(0) << str << " without matching BEGIN";
                log_error(_last_error);
                return false;
              }
              find_matching(bytecode, avm_while);
              op = new TAVM_op(oc, begin_pos);
            }
            break;
          case avm_call_word:
            {
              const TBytecode* bc = (const TBytecode*)_words.objptr(str);
              if (bc != NULL && bc->items() == 1)
              {
                const TAVM_op* inline_op = (const TAVM_op*)bc->objptr(0);
                op = new TAVM_op(*inline_op);
              }
              else
                op = new TAVM_op(oc, str);
            }
            break;
          default:
            op = new TAVM_op(oc);
            break;
          }
        }
      }
    }
    if (op != NULL)
    {
      if (op->op() != avm_nop)
        bytecode.add(op);
      else
        delete op;
    }
    else
    {
      _last_error.cut(0) << "Unknown WORD: " << str;
      log_error(_last_error);
      // return false;  // Non e' un errore gravissimo!
    }
  }
  return true;
}

int TAVM::compare_tos_nos()
{
  const TVariant& tos = _stack.pop();
  const TVariant& nos = _stack.pop();
  return nos.compare(tos);
}

void TAVM::do_call(const TString& func)
{
  if (_stack.overflow())
  {
    log_error("Stack overflow");
    _bc = NULL;
    return;
  }
  TBytecode* bc = (TBytecode*)_words.objptr(func);
  if (bc != NULL)
  {
    _rstack.push(_bc->name());
    _rstack.push(_ip+1);
    _ip = -1; // will be incremented!
    _bc = bc;
  }
  else
  {
    _last_error = func; _last_error << " ?";
    log_error(_last_error);
    _bc = NULL;
  }
}

bool TAVM::do_include(const char* fname)
{
  TFilename name = fname;
  bool ok = name.custom_path();
  if (ok)
  {
    TBytecode bc;
    ifstream inf(name); 
    ok = compile(inf, bc);
    if (ok)
      execute(bc);
  }
  return ok;
}

// Mette sullo stack il valore della variabile name
void TAVM::do_fetch(const TString& name)
{
  const TVariant* var = name[0] != '#' ? (const TVariant*)_vars.objptr(name) : NULL;
  if (var != NULL)
    _stack.push(*var);
  else
  {
    TVariant var;
    _vm->get_usr_val(name, var);
    _stack.push(var);
  } 
}

void TAVM::do_add()
{
  const TVariant& v1 = _stack.pop();
  TVariant& v0 = (TVariant&)_stack.peek();
  v0.add(v1);
}

// Legge dallo stack il valore da asseganre alla variabile name
void TAVM::do_store(const TString& name)
{
  const TVariant& var = _stack.pop();
  TVariant* v = name[0] != '#' ? (TVariant*)_vars.objptr(name) : NULL;
  if (v != NULL)
    *v = var;
  else
    _vm->set_usr_val(name, var);
}

bool TAVM::defined(const char* name)
{
  if (_words.objptr(name) != NULL)
    return true;

  return compile_user_word(name) > 0;
}

void TAVM::execute(const TAVM_op& op)
{
  switch(op.op())
  {
  case avm_add: do_add(); break;
  case avm_and: 
    {
      const TVariant& v1 = _stack.pop();
      TVariant& v0 = (TVariant&)_stack.peek();
      const long r = v0.as_int() & v1.as_int();
      v0.set(r);
    }
    break;
  case avm_begin: break;
  case avm_call_word: do_call(op.var().as_string()); break;
  case avm_ceil: 
    {
      const long dec = _stack.pop().as_int();
      TVariant& v0 = (TVariant&)_stack.peek();
      real k = v0.as_real(); k.ceil(dec);
      v0 = k;
    }
    break;
  case avm_cold: do_restart(true); _bc = NULL; break;
  case avm_cr: _stack.push("\n"); break;
  case avm_cmp_eq   : _stack.push(compare_tos_nos() == 0); break;
  case avm_cmp_gt   : _stack.push(compare_tos_nos() > 0);  break;
  case avm_cmp_gteq : _stack.push(compare_tos_nos() >= 0); break;
  case avm_cmp_lt   : _stack.push(compare_tos_nos() < 0);  break;
  case avm_cmp_lteq : _stack.push(compare_tos_nos() <= 0); break;
  case avm_cmp_noteq: _stack.push(compare_tos_nos() != 0); break;
  case avm_cmp_emptyeq: _stack.push(_stack.pop().is_empty()); break;
  case avm_cmp_nulleq:_stack.push(_stack.pop().is_null()); break;
  case avm_cmp_zeroeq:_stack.push(_stack.pop().is_zero()); break;
  case avm_div:
    {
      const long r0 = _stack.pop().as_int();
      const long r1 = _stack.pop().as_int();
      if (r0 != 0)
      {
        const long n = r1 / r0;
        _stack.push(n);
      }
      else
        _stack.push(NULL_VARIANT);
    }

    break;
  case avm_divide: 
    {
      const real& r0 = _stack.pop().as_real();
      const real& r1 = _stack.pop().as_real();
      if (!r0.is_zero())
      {
        const real n = r1 / r0;
        _stack.push(n);
      }
      else
        _stack.push(NULL_VARIANT);
    }
    break;
  case avm_do: 
    {
      const TVariant& start = _stack.pop();
      const TVariant& limit = _stack.pop();
      if (start.compare(limit) < 0)
      {
        _rstack.push(limit);
        _rstack.push(start);
      }
      else
        _ip = op.var().as_int();
    }
    break;
  case avm_dot: 
    if (_outstr != NULL)
      *_outstr << _stack.pop().as_string(); 
    break;
  case avm_drop: 
    if (!_stack.drop())
    {
      log_error("Stack underflow");
      _bc = NULL;
    }
    break;
  case avm_dup: _stack.push(_stack.peek()); break;
  case avm_else:
    _ip = op.var().as_int();
    break;
  case avm_fetch: do_fetch(_stack.pop().as_string()); break;
  case avm_forget:
    {
      const TString& name = _stack.pop().as_string();
      _vars.remove(name);
      _words.remove(name);
    }
    break;
  case avm_i: _stack.push(_rstack.peek()); break;
  case avm_if:
    if (_stack.pop().is_zero())
      _ip = op.var().as_int();
    break;
  case avm_include: do_include(_stack.pop().as_string()); break;
  case avm_j: _stack.push(_rstack.peek(2)); break;
  case avm_loop: 
    {
      TVariant& start = _rstack.pop();
      const TVariant& limit = _rstack.pop();
      start.add(TVariant(UNO));
      _stack.push(limit);
      _stack.push(start);
      _ip = op.var().as_int()-1;
    }
    break;
  case avm_mod: 
    {
      const long i0 = _stack.pop().as_int();
      const long i1 = _stack.pop().as_int();
      _stack.push(i1 % i0);
    }
    break;
  case avm_mon: 
    if (!_mon.is_open() && !_mon.ignore_mon())
      _mon.open_modal();
    break;
  case avm_mul: 
    {
      const TVariant& v1 = _stack.pop();
      TVariant& v0 = (TVariant&)_stack.peek();
      const real m = v0.as_real() * v1.as_real();
      v0.set(m);
    }
    break;
  case avm_negate:
    {
      TVariant& tos = _stack.peek();
      tos.set(tos.as_bool() ? false : true);
    }
    break;
  case avm_null: _stack.push(NULL_VARIANT); break;
  case avm_or:
    {
      const TVariant& tos = _stack.pop();
      TVariant& nos = (TVariant&)_stack.peek();
      const long r = nos.as_int() | tos.as_int();
      nos.set(r);
    }
    break;
  case avm_over: _stack.push(_stack.peek(1)); break;
  case avm_perform: do_call(_stack.pop().as_string()); break;
  case avm_pick: _stack.push(_stack.peek(_stack.pop().as_int())); break;
  case avm_plus_loop: 
    {
      TVariant& start = _rstack.pop();
      const TVariant& limit = _rstack.pop();
      start.add(_stack.pop());
      _stack.push(limit);
      _stack.push(start);
      _ip = op.var().as_int()-1;
    }
    break;
  case avm_plus_store:
    {
      const TString name = _stack.pop().as_string();
      do_fetch(name);
      _stack.roll(1);
      do_add();
      do_store(name);
    }
    break;
  case avm_push: _stack.push(op.var()); break;
  case avm_repeat: _ip = op.var().as_int(); break;
  case avm_rdrop: _stack.push(_rstack.pop()); break;
  case avm_rpeek: _stack.push(_rstack.peek()); break;
  case avm_rpush: _rstack.push(_stack.pop()); break;
  case avm_roll: _stack.roll(_stack.pop().as_int()); break;
  case avm_rot: _stack.roll(2); break;
  case avm_round: 
    {
      const long dec = _stack.pop().as_int();
      TVariant& v0 = (TVariant&)_stack.peek();
      real k = v0.as_real(); k.round(dec);
      v0 = k;
    }
    break;
  case avm_store: do_store(_stack.pop().as_string()); break;
  case avm_strlen: _stack.push(_stack.pop().as_string().len()); break;
  case avm_strmid: 
    {
      const int len = _stack.pop().as_int();
      const int frm = _stack.pop().as_int();
      const TString& str = _stack.pop().as_string();
      _stack.push(str.mid(frm, len)); break;
    }
    break;
  case avm_strtok_fetch: 
    {
      const int pos = _stack.pop().as_int();
      TToken_string str(_stack.pop().as_string());
      const char* tok = str.get(pos);
      _stack.push(tok);
    }
    break;
  case avm_strtok_add: 
    {
      const TString& tok = _stack.pop().as_string();
      TToken_string str(_stack.pop().as_string());
      str.add(tok);
      _stack.push(str);
    }
    break;
  case avm_sp: _stack.push(_stack.items()); break;
  case avm_sub : 
    {
      const TVariant& v1 = _stack.pop();
      TVariant& v0 = (TVariant&)_stack.peek();
      v0.sub(v1);
    }
    break;
  case avm_swap: _stack.roll(1); break;
  case avm_then: break;
  case avm_trunc: 
    {
      const long dec = _stack.pop().as_int();
      TVariant& v0 = (TVariant&)_stack.peek();
      real k = v0.as_real(); k.trunc(dec);
      v0 = k;
    }
    break;

  case avm_until: 
    if (_stack.pop().is_zero())
      _ip = op.var().as_int(); 
    break;
  case avm_usrword: 
    {
      const long usrword = op.var().as_int();
      _vm->execute_usr_word(usrword, _stack); 
    }
    break;
  case avm_warm: do_restart(false); _bc = NULL; break;
  case avm_while: 
    if (_stack.pop().is_zero())
      _ip = op.var().as_int();  // Exit loop
    break; 
  default:
    _last_error << "Unimplemented op code: " << op.op() << '\n';
    log_error(_last_error);
    _bc = NULL; // force exit
    break;
  }
}

bool TAVM::execute(const TBytecode& cmdline)
{
  const TBytecode* old_bc = _bc;
  const int old_ip = _ip;
  bool aborted = false;
  
  _stack.reset();
  _rstack.reset();
  _bc = &cmdline;
  _ip = 0;
 
  while (_bc != NULL)
  {
    while (_ip >= _bc->items()) // Fine funzione
    {
      if (_rstack.items() >= 2) // Controllo il return stack
      {
        _ip = _rstack.pop().as_int();
        const TString& str = _rstack.pop().as_string();
        if (str == cmdline.name())
          _bc = &cmdline;
        else
        {
          _bc = (const TBytecode*)_words.objptr(str);
          if (_bc == NULL)
            break;
        }
      }
      else
      {
        _bc = NULL;
        break; // Fine esecuzione
      }
    }
    if (_bc == NULL || _ip > _bc->items())
      break;

    TAVM_op& op = *(TAVM_op*)_bc->objptr(_ip);
    if (op.has_break() && !_mon.is_open())
    {
      if (op.has_auto_break())
        op.set_auto_break(false);
      _mon.open_modal();
    }

    if (_mon.is_open())  // Gestione debugger
    {
      TAVM_list_window& monitor = _mon.monitor();
      monitor.set_bytecode(_bc, _ip, _user_words);
      TAVM_stack_window& stacker = _mon.stacker();
      stacker.set_stack(_stack);
      TAVM_stack_window& rstacker = _mon.rstacker();
      rstacker.set_stack(_rstack);
      const KEY k = _mon.run();
      switch (k)
      {
      case K_F11: monitor.force_update(); stacker.force_update(); rstacker.force_update(); break;
      case K_F10: 
        if (_ip < _bc->items()-1)
        {
          _mon.close_modal(); 
          TAVM_op& op = *(TAVM_op*)_bc->objptr(_ip+1);
          op.set_auto_break(true);
        }
        break;
      case K_QUIT: aborted = true;
      case K_F5  : _mon.close_modal(); break;
      default: break;
      }  
    }

    execute(op);

    _ip++;
  }

  if (_mon.is_open()) // Chiudi debugger
    _mon.close_modal();

  //const bool ok = _bc != NULL;  // Not aborted
  _bc = old_bc;
  _ip = old_ip;
  
  return !aborted;
}

void TAVM::do_restart(bool cold)
{
  _stack.reset();
  _rstack.reset();
  if (cold)
  {
    _words.destroy();
    _vars.destroy();
    do_include("alex.alx");
  }
  _mon.set_ignore_mon(false);
}

TAVM::TAVM(TAlex_virtual_machine* vm) 
    : _vm(vm), _interactive(false), _outstr(NULL)
{ 
  do_restart(true);
}

TAVM::~TAVM()
{
}

///////////////////////////////////////////////////////////
// TAlex_virtual_machine
///////////////////////////////////////////////////////////

TAVM& TAlex_virtual_machine::avm()
{
  if (_avm == NULL)
    _avm = new TAVM(this);
  return *_avm;
}

void TAlex_virtual_machine::log_error(const char* str)
{
  if (_avm != NULL)
    _avm->log_error(str);
}

const TString& TAlex_virtual_machine::get_last_error() const
{
  if (_avm != NULL)
    return _avm->get_last_error();
 return EMPTY_STRING;
}

bool TAlex_virtual_machine::compile(istream& instr, TBytecode& bc)
{
  return avm().compile(instr, bc);
}

bool TAlex_virtual_machine::execute(const TBytecode& bc)
{
  return avm().execute(bc);
}

bool TAlex_virtual_machine::compile(const char* cmd, TBytecode& bc)
{
#ifdef LINUX
	string s(cmd);
  istringstream instr(s);
#else
  istrstream instr((char*)cmd, (size_t)strlen(cmd));
#endif
  return compile(instr, bc);
}

void TAlex_virtual_machine::warm_restart() // Ripartenza a caldo
{
  avm().do_restart(false);
}

void TAlex_virtual_machine::cold_restart() // Ripartenza a freddo
{
  avm().do_restart(true);
}

bool TAlex_virtual_machine::get_usr_val(const TString& name, TVariant& var) const
{
  if (name.starts_with("#SYSTEM."))
  {
    const TFixed_string n((const char*)name+8);
    if (n == "ADMINISTATOR")
    {
      var.set(dongle().administrator());
      return true;
    }
    if (n == "CLOCK")
    {
      const long msec = clock() / (CLOCKS_PER_SEC / 1000);
      var.set(msec);
      return true;
    }
    if (n == "FIRM")
    {
      var.set(prefix().get_codditta());
      return true;
    }
    if (n == "RAGSOC")
    {
      const long code = prefix().get_codditta();
      const TString& ragsoc = cache().get(LF_NDITTE, code, n);
      var.set(ragsoc);
      return true;
    }
    if (n == "STUDY")
    {
      var.set(firm2dir(-1));
      return true;
    }
    if (n == "DATE")
    {
      const TDate oggi(TODAY);
      var.set(oggi);
      return true;
    }
    if (n == "TIME")
    {
      time_t lt; time(&lt);
      struct tm* t = localtime(&lt);
      TString16 str; 
      str.format("%02d:%02d:%02d", t->tm_hour, t->tm_min, t->tm_sec);
      var.set(str);
      return true;
    }
    if (n == "USER")
    {
      var.set(user());
      return true;
    }
  }
  return false;
}

bool TAlex_virtual_machine::set_usr_val(const TString& name, const TVariant& var)
{
  if (name.starts_with("#SYSTEM."))
  {
    const TFixed_string n((const char*)name+8);
    if (n == "FIRM")
    {
      return prefix().set_codditta(var.as_int());
    }
  }
  return false;
}

unsigned int TAlex_virtual_machine::get_usr_words(TString_array&) const
{
  return 0;
}

bool TAlex_virtual_machine::execute_usr_word(unsigned int opcode, TVariant_stack& stack)
{
  return false;
}

bool TAlex_virtual_machine::include(const char* fname)
{
  return avm().do_include(fname);
}

void TAlex_virtual_machine::include_libraries(bool reload)
{
  if (reload || !defined("2DUP"))
    include("alex.alx");
}

void TAlex_virtual_machine::set_interactive(bool inter) 
{ avm().set_interactive(inter); }

bool TAlex_virtual_machine::defined(const char* name) 
{
  return avm().defined(name);
}

TAlex_virtual_machine::TAlex_virtual_machine() : _avm(NULL)
{
}

TAlex_virtual_machine::~TAlex_virtual_machine()
{
  if (_avm != NULL)
    delete _avm;
}