/* ScummVM Tools
 *
 * ScummVM Tools is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <engines/grim/lua/lua.h>
#include <engines/grim/lua/lundump.h>
#include <engines/grim/lua/lopcodes.h>
#include <engines/grim/lua/lzio.h>

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#include <iostream>
#include <sstream>
#include <map>
#include <queue>
#include <stack>
#include <list>
#include <set>

// Provide debug.cpp functions which don't call SDL_Quit.
void warning(const char *fmt, ...) {
  fprintf(stderr, "WARNING: ");
  va_list va;
  va_start(va, fmt);
  vfprintf(stderr, fmt, va);
  va_end(va);
  fprintf(stderr, "\n");
}

void error(const char *fmt, ...) {
  fprintf(stderr, "ERROR: ");
  va_list va;
  va_start(va, fmt);
  vfprintf(stderr, fmt, va);
  va_end(va);
  fprintf(stderr, "\n");
  exit(1);
}

class Expression;

void decompile(std::ostream &os, TProtoFunc *tf, std::string indent_str,
	       Expression **upvals, int num_upvals);

std::string localname(TProtoFunc *tf, int n) {
	LocVar *l = tf->locvars;

	if (l != NULL) {
		for (int i = 0; i < n && l->line != -1; ++i, ++l);

		if (l->varname != NULL)
			return l->varname->str;
	}
	std::ostringstream s;
	if (n < tf->code[1])
		s << "arg" << n + 1;
	else
		s << "local" << n - tf->code[1] + 1;
	return s.str();
}

class Expression {
public:
  Expression(Byte *p) : pos(p) { }
  Byte *pos;			// Position just after the expression
				// is pushed onto the stack
  virtual void print(std::ostream &os) const = 0;
  virtual int precedence() const { return 100; }
  virtual ~Expression() { }
};

inline std::ostream& operator <<(std::ostream &os, const Expression &e) {
  e.print(os);
  return os;
}

class NumberExpr : public Expression {
public:
  NumberExpr(Byte *p, float val) : Expression(p), value(val) { }
  float value;
  void print(std::ostream &os) const { os << value; }
};

class VarExpr : public Expression {
public:
  VarExpr(Byte *p, std::string varname) : Expression(p), name(varname) { }
  std::string name;
  void print(std::ostream &os) const { os << name; }
};

class StringExpr : public Expression {
public:
  StringExpr(Byte *p, TaggedString *txt) : Expression(p), text(txt) { }
  TaggedString *text;
  bool validIdentifier() const {
    static const char *reserved [] = {"and", "do", "else", "elseif", "end", "function",
      "if", "local", "nil", "not", "or", "repeat", "return", "then",
      "until", "while"};	//Taken from llex.cpp
    if (text->u.s.len == 0)
      return false;
    if (isdigit(text->str[0]))
      return false;
    if (text->str[0] >= '0' && text->str[0] <= '9')
      return false;
    for (int i = 0; i < text->u.s.len; i++) {
      char c = text->str[i];
      if ((! isalnum(c)) && c != '_')
	return false;
    }
	for (unsigned int i = 0; i < (sizeof(reserved) / sizeof(reserved[0])); i++)
		if (strncmp(text->str, reserved[i], text->u.s.len) == 0)
			return false;
    return true;
  }
  void print(std::ostream &os) const {
    static const char *specials = "\a\b\f\n\r\t\v\\\"";
    static const char *special_text[] = {
      "\\a", "\\b", "\\f", "\\n", "\\r", "\\t", "\\v", "\\\\", "\\\""
    };

    os << "\"";
    std::string str(text->str, text->u.s.len);
    for (std::string::iterator i = str.begin(); i != str.end(); i++) {
      unsigned char c = *i;
      if (strchr(specials, c)) {
	int index = strchr(specials, c) - specials;
	os << special_text[index];
      }
      else if (! isprint(c))
	os << "\\" << std::dec << (unsigned int)(c);
      else
	os << c;
    }
    os << "\"";
  }
};

class FuncExpr : public Expression {
public:
  FuncExpr(Byte *p, TProtoFunc *tf0, std::string is) :
    Expression(p), indent_str(is), tf(tf0), upvals(NULL), num_upvals(0) { }
  std::string indent_str;
  TProtoFunc *tf;
  Expression **upvals;
  int num_upvals;

  void print(std::ostream &os) const {
    os << "function(";
    for (int i = 0; i < tf->code[1]; i++) {
      os << localname(tf, i);
      if (i + 1 < tf->code[1])
	os << ", ";
    }
    os << ") -- line " << tf->lineDefined << std::endl;
    decompile(os, tf, indent_str + std::string(4, ' '),
	      upvals, num_upvals);
    os << indent_str << "end";
  }
  ~FuncExpr() {
    for (int i = 0; i < num_upvals; i++)
      delete upvals[i];
    delete[] upvals;
  }
};

class IndexExpr : public Expression {
public:
  IndexExpr(Byte *p, Expression *tbl, Expression *i)
    : Expression(p), table(tbl), index(i) { }
  Expression *table, *index;
  virtual void print(std::ostream &os) const {
    table->print(os);
    StringExpr *field = dynamic_cast<StringExpr *>(index);
    if (field != NULL && field->validIdentifier())
      os << "." << field->text->str;
    else
      os << "[" << *index << "]";
  }

  ~IndexExpr() {
    delete table;
    delete index;
  }
};

class DotIndexExpr : public IndexExpr {
public:
  DotIndexExpr(Byte *p, Expression *tbl, Expression *i) : IndexExpr(p, tbl, i) { }
  virtual void print(std::ostream &os) const {
    table->print(os);
    StringExpr *field = dynamic_cast<StringExpr *>(index);
    os << "." << field->text->str;
  }
};

class BracketsIndexExpr : public IndexExpr {
public:
  BracketsIndexExpr(Byte *p, Expression *tbl, Expression *i) : IndexExpr(p, tbl, i) { }
  virtual void print(std::ostream &os) const {
    table->print(os);
    os << "[" << *index << "]";
  }
};

class SelfExpr : public IndexExpr {
public:
  SelfExpr(Byte *p, Expression *tbl, StringExpr *i) : IndexExpr(p, tbl, i) { }
  virtual void print(std::ostream &os) const {
    StringExpr *field = static_cast<StringExpr *>(index);
    os << *table << ":" << field->text->str;
  }
};

class FuncCallExpr : public Expression {
public:
  FuncCallExpr(Byte *p) : Expression(p) { }
  int num_args;
  Expression **args;
  Expression *func;
  void print(std::ostream &os) const {
    os << *func << "(";
    int i = 0;

    // Skip implicit self argument in a:b(c)
    if (dynamic_cast<SelfExpr *>(func) != NULL)
      i = 1;
    for (; i < num_args; i++) {
      args[i]->print(os);
      if (i + 1 < num_args)
	os << ", ";
    }
    os << ")";
  }
  ~FuncCallExpr() {
    for (int i = 0; i < num_args; i++)
      delete args[i];
    delete[] args;
    delete func;
  }
};

class ArrayExpr : public Expression {
public:
  ArrayExpr(Byte *p) : Expression(p) { }
  typedef std::pair<Expression *, Expression *> mapping;
  typedef std::list<mapping> mapping_list;
  mapping_list mappings;
  void print(std::ostream &os) const {
    os << "{";
    mapping_list::const_iterator i = mappings.begin();
    while (i != mappings.end()) {
      if (i->first != NULL) {
	StringExpr *field = dynamic_cast<StringExpr *>(i->first);
	if (field != NULL && field->validIdentifier())
	  os << " " << field->text->str;
	else
	  os << " [" << *i->first << "]";
	os << " =";
      }
      os << " " << *i->second;
      i++;
      if (i != mappings.end())
	os << ",";
    }
    os << " }";
  }
  ~ArrayExpr() {
    for (mapping_list::iterator i = mappings.begin();
	 i != mappings.end(); i++) {
      delete i->first;
      delete i->second;
    }
  }
};

class BinaryExpr : public Expression {
public:
  BinaryExpr(Byte *ps, Expression *l, Expression *r, int p, bool ra,
	     std::string o) :
    Expression(ps), left(l), right(r), prec(p), right_assoc(ra), op(o) { }
  Expression *left, *right;
  int prec;
  bool right_assoc;
  std::string op;
  int precedence() const { return prec; }
  void print(std::ostream &os) const {
    if (left->precedence() < prec ||
	(left->precedence() == prec && right_assoc))
      os << "(" << *left << ")";
    else
      os << *left;
    os << op;
    if (right->precedence() < prec ||
	(right->precedence() == prec && ! right_assoc))
      os << "(" << *right << ")";
    else
      os << *right;
  }
  ~BinaryExpr() { delete left; delete right; }
};

class UnaryExpr : public Expression {
public:
  UnaryExpr(Byte *ps, Expression *a, int p, std::string o) :
    Expression(ps), arg(a), prec(p), op(o) { }
  Expression *arg;
  int prec;
  std::string op;
  int precedence() const { return prec; }
  void print(std::ostream &os) const {
    os << op;
    if (arg->precedence() < prec)
      os << "(" << *arg << ")";
    else
      os << *arg;
  }
  ~UnaryExpr() { delete arg; }
};

typedef std::stack<Expression *> ExprStack;

class Decompiler {
public:
  void decompileRange(Byte *start, Byte *end);

  std::ostream *os;
  ExprStack *stk;
  TProtoFunc *tf;
  std::string indent_str;
  Byte *break_pos;
  Expression **upvals; int num_upvals;
  std::multiset<Byte *> *local_var_defs;

private:
  void do_multi_assign(Byte *&start);
  void do_binary_op(Byte *pos, int prec, bool right_assoc, std::string op);
  void do_unary_op(Byte *pos, int prec, std::string op);
  static bool is_expr_opc(Byte opc);
  void get_else_part(Byte *start, Byte *&if_part_end,
		     bool &has_else, Byte *&else_part_end);
};

// Scan for a series of assignments
void Decompiler::do_multi_assign(Byte *&start) {
  std::queue<Expression *> results;
  ExprStack values;

  bool done;
  int num_tables = 0;
  do {
    int aux, opc;
    done = false;

    opc = *start++;
    switch (opc) {
    case SETLOCAL:
      aux = *start++;
      goto setlocal;

    case SETLOCAL0:
    case SETLOCAL1:
    case SETLOCAL2:
    case SETLOCAL3:
    case SETLOCAL4:
    case SETLOCAL5:
    case SETLOCAL6:
    case SETLOCAL7:
      aux = opc - SETLOCAL0;
    setlocal:
      results.push(new VarExpr(start, localname(tf, aux)));
      break;

    case SETGLOBAL:
      aux = *start++;
      goto setglobal;

    case SETGLOBAL0:
    case SETGLOBAL1:
    case SETGLOBAL2:
    case SETGLOBAL3:
    case SETGLOBAL4:
    case SETGLOBAL5:
    case SETGLOBAL6:
    case SETGLOBAL7:
      aux = opc - SETGLOBAL0;
      goto setglobal;

    case SETGLOBALW:
      aux = start[0] | (start[1] << 8);
      start += 2;
    setglobal:
      results.push(new VarExpr(start, svalue(tf->consts + aux)));
      break;

    case SETTABLE:
      start++;			// assume offset is correct
      num_tables++;
      // this needs stuff from farther up the stack, wait until
      // it's available
      // fall-through

    case SETTABLE0:
      results.push(new IndexExpr(start, NULL, NULL));
      break;

    default:
      start--;
      done = true;
    }

    if (! done) {
      Expression *e = stk->top();
      // Check for fake result from function calls with multiple return values
      VarExpr *v = dynamic_cast<VarExpr *>(e);
      if (v != NULL && v->name == "<extra result>")
	delete e;
      else
	values.push(e);
      stk->pop();
    }
  } while (! done);

  // Check for popping tables and indices
  if (num_tables > 0 && (*start == POP || *start == POP0 || *start == POP1)) {
    start++;
    if (start[-1] == POP)
      start++;
  }

  // Now get actual tables and indices from the stack, reversing
  // the list to the right order at the same time

  ExprStack results2;
  while (! results.empty()) {
    Expression *var = results.front(); results.pop();
    IndexExpr *tbl = dynamic_cast<IndexExpr *>(var);
    if (tbl != NULL) {
      tbl->index = stk->top(); stk->pop();
      tbl->table = stk->top(); stk->pop();
    }
    results2.push(var);
  }

  *os << indent_str;
  while (! results2.empty()) {
    Expression *var = results2.top(); results2.pop();
    *os << *var;
    delete var;
    if (! results2.empty())
      *os << ", ";
  }
  *os << " = ";
  while (! values.empty()) {
    Expression *val = values.top(); values.pop();
    *os << *val;
    delete val;
    if (! values.empty())
      *os << ", ";
  }
  *os << std::endl;
}

void Decompiler::do_binary_op(Byte *pos, int prec, bool right_assoc,
			      std::string op) {
  Expression *right = stk->top(); stk->pop();
  Expression *left = stk->top(); stk->pop();
  stk->push(new BinaryExpr(pos, left, right, prec, right_assoc, op));
}

void Decompiler::do_unary_op(Byte *pos, int prec, std::string op) {
  Expression *arg = stk->top(); stk->pop();
  stk->push(new UnaryExpr(pos, arg, prec, op));
}

// Provide instruction lengths to make it easy to scan through instructions
int instr_lens[] = {
  1,				// ENDCODE
  2,				// PUSHNIL
  1,				// PUSHNIL0
  2,				// PUSHNUMBER
  1, 1, 1,			// PUSHNUMBER0..2
  3,				// PUSHNUMBERW
  2,				// PUSHCONSTANT
  1, 1, 1, 1, 1, 1, 1, 1,	// PUSHCONSTANT0..7
  3,				// PUSHCONSTANTW
  2,				// PUSHUPVALUE
  1, 1,				// PUSHUPVALUE0,1
  2,				// PUSHLOCAL
  1, 1, 1, 1, 1, 1, 1, 1,	// PUSHLOCAL0..7
  2,				// GETGLOBAL
  1, 1, 1, 1, 1, 1, 1, 1,	// GETGLOBAL0..7
  3,				// GETGLOBALW
  1,				// GETTABLE
  2,				// GETDOTTED
  1, 1, 1, 1, 1, 1, 1, 1,	// GETDOTTED0..7
  3,				// GETDOTTEDW
  2,				// PUSHSELF
  1, 1, 1, 1, 1, 1, 1, 1,	// PUSHSELF0..7
  3,				// PUSHSELFW
  2,				// CREATEARRAY
  1, 1,				// CREATEARRAY0,1
  3,				// CREATEARRAYW
  2,				// SETLOCAL
  1, 1, 1, 1, 1, 1, 1, 1,	// SETLOCAL0..7
  2,				// SETGLOBAL
  1, 1, 1, 1, 1, 1, 1, 1,	// SETGLOBAL0..7
  3,				// SETGLOBALW
  1,				// SETTABLE0
  2,				// SETTABLE
  3,				// SETLIST
  2,				// SETLIST0
  4,				// SETLISTW
  2,				// SETMAP
  1,				// SETMAP0
  1,				// EQOP
  1,				// NEQOP
  1,				// LTOP
  1,				// LEOP
  1,				// GTOP
  1,				// GEOP
  1,				// ADDOP
  1,				// SUBOP
  1,				// MULTOP
  1,				// DIVOP
  1,				// POWOP
  1,				// CONCOP
  1,				// MINUSOP
  1,				// NOTOP
  2, 3,				// ONTJMP, ONTJMPW
  2, 3,				// ONFJMP, ONFJMPW
  2, 3,				// JMP, JMPW
  2, 3,				// IFFJMP, IFFJMPW
  2, 3,				// IFTUPJMP, IFTUPJMPW
  2, 3,				// IFFUPJMP, IFFUPJMPW
  2,				// CLOSURE
  1, 1,				// CLOSURE0,1
  3,				// CALLFUNC
  2, 2,				// CALLFUNC0,1
  2,				// RETCODE
  2,				// SETLINE
  3,				// SETLINEW
  2,				// POP
  1, 1				// POP0,1
};

int get_instr_len(Byte opc) {
	if (opc > sizeof(instr_lens)/sizeof(int))
		return 1;
	else
		return instr_lens[opc];
}

bool Decompiler::is_expr_opc(Byte opc) {
  if (opc >= PUSHNIL && opc <= CREATEARRAYW)
    return true;
  if (opc == SETLIST0)
    return true;
  if (opc >= SETMAP && opc <= ONFJMPW)
    return true;
  if (opc >= CLOSURE && opc <= CLOSURE1)
    return true;
  if (opc == CALLFUNC1 || opc == SETLINE || opc == SETLINEW)
    return true;
  return false;
}

// Check for JMP or JMPW at end of "if" part
void Decompiler::get_else_part(Byte *start, Byte *&if_part_end,
			       bool &has_else, Byte *&else_part_end) {
  Byte *last_instr = NULL;
  has_else = false;
  else_part_end = NULL;

  for (Byte *instr_scan = start; instr_scan < if_part_end;
       instr_scan += get_instr_len(*instr_scan))
    last_instr = instr_scan;
  if (last_instr != NULL &&
      (*last_instr == JMP || *last_instr == JMPW)) {
    has_else = true;
    else_part_end = if_part_end + last_instr[1];
    if (*last_instr == JMPW)
      else_part_end += (last_instr[2] << 8);
    if_part_end = last_instr;
  }
}

void Decompiler::decompileRange(Byte *start, Byte *end) {
  // First, scan for IFFUPJMP, which is used for repeat/until, so
  // we can recognize the start of such loops.  We only keep the
  // last value to match each address, which represents the outermost
  // repeat/until loop starting at that point.
  std::map<Byte *, Byte *> rev_iffupjmp_map;

  for (Byte *scan = start; end == NULL || scan < end;
       scan += get_instr_len(*scan)) {
    if (*scan == IFFUPJMP)
      rev_iffupjmp_map[scan + 2 - scan[1]] = scan;
    else if (*scan == IFFUPJMPW)
      rev_iffupjmp_map[scan + 3 - (scan[1] | (scan[2] << 8))] = scan;
    else if (*scan == ENDCODE)
      break;
  }

  while (end == NULL || start < end) {
    int locs_here = local_var_defs->count(start);
    if (locs_here > 0) {
      // There were local variable slots just pushed onto the stack
      // Print them out (in the second pass)

      // First, if there are multiple defined, it must be from
      // local x, y, z = f() or local a, b.  So just ignore the extra
      // entries.
      for (int i = 1; i < locs_here; i++) {
	delete stk->top(); stk->pop();
      }
      Expression *def = stk->top(); stk->pop();

      // Print the local variable names, and at the same time push
      // fake values onto the stack
      *os << indent_str << "local ";
      for (int i = 0; i < locs_here; i++) {
	std::string locname = localname(tf, tf->code[1] + stk->size());
	*os << locname;
	if (i + 1 < locs_here)
	  *os << ", ";
	stk->push(new VarExpr(start, "<" + locname + " stack slot>"));
      }

      // Print the definition, unless it's nil
      VarExpr *v = dynamic_cast<VarExpr *>(def);
      if (v == NULL || v->name != "nil")
	*os << " = " << *def;
      *os << std::endl;
	  delete def;

      local_var_defs->erase(start);
    }

    if (rev_iffupjmp_map.find(start) != rev_iffupjmp_map.end()) {
      // aha, do a repeat/until loop
      *os << indent_str << "repeat\n";
      Decompiler indented_dc = *this;
      indented_dc.indent_str += std::string(4, ' ');
      indented_dc.break_pos = rev_iffupjmp_map[start];
      indented_dc.break_pos += get_instr_len(*indented_dc.break_pos);
      indented_dc.decompileRange(start, rev_iffupjmp_map[start]);

      Expression *e = stk->top(); stk->pop();
      *os << indent_str << "until " << *e << std::endl;
      delete e;

      start = indented_dc.break_pos;
      continue;
    }

    Byte opc = *start++;
    int aux;

    switch (opc) {
    case ENDCODE:
      return;

    case PUSHNIL:
      aux = *start++;
      goto pushnil;

    case PUSHNIL0:
      aux = 0;
    pushnil:
      for (int i = 0; i <= aux; i++)
	stk->push(new VarExpr(start, "nil")); // Cheat a little :)
      break;

    case PUSHNUMBER:
      aux = *start++;
      goto pushnumber;

    case PUSHNUMBER0:
    case PUSHNUMBER1:
    case PUSHNUMBER2:
      aux = opc - PUSHNUMBER0;
      goto pushnumber;

    case PUSHNUMBERW:
      aux = start[0] | (start[1] << 8);
      start += 2;
    pushnumber:
      stk->push(new NumberExpr(start, aux));
      break;

    case PUSHCONSTANT:
      aux = *start++;
      goto pushconst;

    case PUSHCONSTANT0:
    case PUSHCONSTANT1:
    case PUSHCONSTANT2:
    case PUSHCONSTANT3:
    case PUSHCONSTANT4:
    case PUSHCONSTANT5:
    case PUSHCONSTANT6:
    case PUSHCONSTANT7:
      aux = opc - PUSHCONSTANT0;
      goto pushconst;

    case PUSHCONSTANTW:
      aux = start[0] | (start[1] << 8);
      start += 2;
    pushconst:
      switch (ttype(tf->consts + aux)) {
      case LUA_T_STRING:
	stk->push(new StringExpr(start, tsvalue(tf->consts + aux)));
	break;
      case LUA_T_NUMBER:
	stk->push(new NumberExpr(start, nvalue(tf->consts + aux)));
	break;
      case LUA_T_PROTO:
	stk->push(new FuncExpr(start, tfvalue(tf->consts + aux), indent_str));
	break;
      default:
	*os << indent_str << "error: invalid constant type "
	    << int(ttype(tf->consts + aux)) << std::endl;
      }
      break;

    case PUSHUPVALUE:
      aux = *start++;
      goto pushupvalue;

    case PUSHUPVALUE0:
    case PUSHUPVALUE1:
      aux = opc - PUSHUPVALUE0;
    pushupvalue:
      {
	if (aux >= num_upvals) {
	  *os << indent_str << "error: invalid upvalue #"
	      << aux << std::endl;
	}

	std::ostringstream s;
	s << "%" << *upvals[aux];
	stk->push(new VarExpr(start, s.str()));
      }
      break;

    case PUSHLOCAL:
      aux = *start++;
      goto pushlocal;

    case PUSHLOCAL0:
    case PUSHLOCAL1:
    case PUSHLOCAL2:
    case PUSHLOCAL3:
    case PUSHLOCAL4:
    case PUSHLOCAL5:
    case PUSHLOCAL6:
    case PUSHLOCAL7:
      aux = opc - PUSHLOCAL0;
    pushlocal:
      stk->push(new VarExpr(start, localname(tf, aux)));
      break;

    case GETGLOBAL:
      aux = *start++;
      goto getglobal;

    case GETGLOBAL0:
    case GETGLOBAL1:
    case GETGLOBAL2:
    case GETGLOBAL3:
    case GETGLOBAL4:
    case GETGLOBAL5:
    case GETGLOBAL6:
    case GETGLOBAL7:
      aux = opc - GETGLOBAL0;
      goto getglobal;

    case GETGLOBALW:
      aux = start[0] | (start[1] << 8);
      start += 2;
    getglobal:
      stk->push(new VarExpr(start, svalue(tf->consts + aux)));
      break;

    case GETTABLE:
      {
	Expression *index = stk->top(); stk->pop();
	Expression *table = stk->top(); stk->pop();

	stk->push(new BracketsIndexExpr(start, table, index));
      }
      break;

    case GETDOTTED:
      aux = *start++;
      goto getdotted;

    case GETDOTTED0:
    case GETDOTTED1:
    case GETDOTTED2:
    case GETDOTTED3:
    case GETDOTTED4:
    case GETDOTTED5:
    case GETDOTTED6:
    case GETDOTTED7:
      aux = opc - GETDOTTED0;
      goto getdotted;

    case GETDOTTEDW:
      aux = start[0] | (start[1] << 8);
      start += 2;
    getdotted:
      {
	Expression *tbl = stk->top(); stk->pop();
	stk->push(new DotIndexExpr(start, tbl, new StringExpr
				(start, tsvalue(tf->consts + aux))));
      }
      break;

    case PUSHSELF:
      aux = *start++;
      goto pushself;

    case PUSHSELF0:
    case PUSHSELF1:
    case PUSHSELF2:
    case PUSHSELF3:
    case PUSHSELF4:
    case PUSHSELF5:
    case PUSHSELF6:
    case PUSHSELF7:
      aux = opc - PUSHSELF0;
      goto pushself;

    case PUSHSELFW:
      aux = start[0] | (start[1] << 8);
      start += 2;
    pushself:
      {
	Expression *tbl = stk->top(); stk->pop();
	stk->push(new SelfExpr(start, tbl, new StringExpr
			       (start, tsvalue(tf->consts + aux))));
	stk->push(new VarExpr(start, "<self>"));
	// Fake value, FuncCallExpr will handle it
      }
      break;

    case CREATEARRAY:
      start++;
      goto createarray;

    case CREATEARRAY0:
    case CREATEARRAY1:
      goto createarray;

    case CREATEARRAYW:
      start += 2;
    createarray:
      stk->push(new ArrayExpr(start));
      break;

    case SETLOCAL:
    case SETLOCAL0:
    case SETLOCAL1:
    case SETLOCAL2:
    case SETLOCAL3:
    case SETLOCAL4:
    case SETLOCAL5:
    case SETLOCAL6:
    case SETLOCAL7:
    case SETGLOBAL:
    case SETGLOBAL0:
    case SETGLOBAL1:
    case SETGLOBAL2:
    case SETGLOBAL3:
    case SETGLOBAL4:
    case SETGLOBAL5:
    case SETGLOBAL6:
    case SETGLOBAL7:
    case SETGLOBALW:
    case SETTABLE0:
    case SETTABLE:
      start--;
      do_multi_assign(start);
      break;

    case SETLIST:
      start++;			// assume offset is correct
      goto setlist;

    case SETLISTW:
      start += 2;
      goto setlist;

    case SETLIST0:
    setlist:
      aux = *start++;
      {
	ArrayExpr::mapping_list new_mappings;
	for (int i = 0; i < aux; i++) {
	  Expression *val = stk->top(); stk->pop();
	  new_mappings.push_front(std::make_pair((Expression *) NULL, val));
	}
	ArrayExpr *a = dynamic_cast<ArrayExpr *>(stk->top());
	if (a == NULL) {
	  *os << indent_str
	      << "error: attempt to setlist a non-array object\n";
	}
	// Append the new list
	a->mappings.splice(a->mappings.end(), new_mappings);
	a->pos = start;
      }
      break;

    case SETMAP:
      aux = *start++;
      goto setmap;

    case SETMAP0:
      aux = 0;
    setmap:
      {
	ArrayExpr::mapping_list new_mappings;
	for (int i = 0; i <= aux; i++) {
	  Expression *val = stk->top(); stk->pop();
	  Expression *key = stk->top(); stk->pop();
	  new_mappings.push_front(std::make_pair(key, val));
	}
	ArrayExpr *a = dynamic_cast<ArrayExpr *>(stk->top());
	if (a == NULL) {
	  *os << indent_str
	      << "error: attempt to setmap a non-array object\n";
	}
	// Append the new list
	a->mappings.splice(a->mappings.end(), new_mappings);
	a->pos = start;
      }
      break;

    case EQOP:
      do_binary_op(start, 1, false, " == ");
      break;

    case NEQOP:
      do_binary_op(start, 1, false, " ~= ");
      break;

    case LTOP:
      do_binary_op(start, 1, false, " < ");
      break;

    case LEOP:
      do_binary_op(start, 1, false, " <= ");
      break;

    case GTOP:
      do_binary_op(start, 1, false, " > ");
      break;

    case GEOP:
      do_binary_op(start, 1, false, " >= ");
      break;

    case ADDOP:
      do_binary_op(start, 3, false, " + ");
      break;

    case SUBOP:
      do_binary_op(start, 3, false, " - ");
      break;

    case MULTOP:
      do_binary_op(start, 4, false, " * ");
      break;

    case DIVOP:
      do_binary_op(start, 4, false, " / ");
      break;

    case POWOP:
      do_binary_op(start, 6, true, " ^ ");
      break;

    case CONCOP:
      do_binary_op(start, 2, false, " .. ");
      break;

    case MINUSOP:
      do_unary_op(start, 5, "-");
      break;

    case NOTOP:
      do_unary_op(start, 5, "not ");
      break;

    case ONTJMP:
      aux = *start++;
      goto ontjmp;

    case ONTJMPW:
      aux = start[0] | (start[1] << 8);
      start += 2;
    ontjmp:
      // push_expr_1 ontjmp(label) push_expr_2 label:  -> expr_1 || expr_2
      decompileRange(start, start + aux);
      do_binary_op(start + aux, 0, false, " or ");
      start = start + aux;
      break;

    case ONFJMP:
      aux = *start++;
      goto onfjmp;

    case ONFJMPW:
      aux = start[0] | (start[1] << 8);
      start += 2;
    onfjmp:
      // push_expr_1 onfjmp(label) push_expr_2 label:  -> expr_2 && expr_2
      decompileRange(start, start + aux);
      do_binary_op(start + aux, 0, false, " and ");
      start = start + aux;
      break;

    case JMP:
      aux = *start++;
      goto jmp;

    case JMPW:
      aux = start[0] | (start[1] << 8);
      start += 2;
    jmp:
      {
	Byte *dest = start + aux;
	if (dest == break_pos) {
	  *os << indent_str << "break\n";
	  break;
	}

	// otherwise, must be the start of a while statement
	Byte *while_cond_end;
	for (while_cond_end = dest; end == NULL || while_cond_end < end;
	     while_cond_end += get_instr_len(*while_cond_end))
	  if (*while_cond_end == IFTUPJMP || *while_cond_end == IFTUPJMPW)
	    break;
	if (end != NULL && while_cond_end >= end) {
	  *os << indent_str
	      << "error: JMP not in break, while, if/else\n";
	}

	// push the while condition onto the stack
	decompileRange(dest, while_cond_end);

	*os << indent_str << "while " << *stk->top()
	    << " do\n";
	delete stk->top();
	stk->pop();

	// decompile the while body
	Decompiler indented_dc = *this;
	indented_dc.indent_str += std::string(4, ' ');
	indented_dc.break_pos = while_cond_end + get_instr_len(*while_cond_end);
	indented_dc.decompileRange(start, dest);

	*os << indent_str << "end\n";
	start = indented_dc.break_pos;
      }
      break;

    case IFFJMP:
      aux = *start++;
      goto iffjmp;

    case IFFJMPW:
      aux = start[0] | (start[1] << 8);
      start += 2;
    iffjmp:
      {
	// Output an if/end, if/else/end, if/elseif/else/end, ... statement
	Byte *if_part_end = start + aux;
	Decompiler indented_dc = *this;
	indented_dc.indent_str += std::string(4, ' ');

	*os << indent_str << "if " << *stk->top();
	delete stk->top();
	stk->pop();
	*os << " then\n";

	bool has_else;
	Byte *else_part_end;
	get_else_part(start, if_part_end, has_else, else_part_end);

	// Output the if part
      output_if:
	indented_dc.decompileRange(start, if_part_end);
	start = start + aux;

	if (has_else) {
	  // Check whether the entire else part is a single
	  // if or if/else statement
	  Byte *instr_scan = start;
	  while (is_expr_opc(*instr_scan) &&
		 (end == NULL || instr_scan < else_part_end))
	    instr_scan += get_instr_len(*instr_scan);
	  if ((end == NULL || instr_scan < else_part_end) &&
	      (*instr_scan == IFFJMP || *instr_scan == IFFJMPW)) {
	    // OK, first line will be if, check if it will go all
	    // the way through
	    Byte *new_start, *new_if_part_end, *new_else_part_end;
	    bool new_has_else;
	    if (*instr_scan == IFFJMP) {
	      aux = instr_scan[1];
	      new_start = instr_scan + 2;
	    }
	    else {
	      aux = instr_scan[1] | (instr_scan[2] << 8);
	      new_start = instr_scan + 3;
	    }
	    new_if_part_end = new_start + aux;
	    get_else_part(new_start, new_if_part_end, new_has_else,
			  new_else_part_end);
	    if (new_if_part_end == else_part_end ||
		(new_has_else && new_else_part_end == else_part_end)) {
	      // Yes, output an elseif
	      decompileRange(start, instr_scan); // push condition
	      *os << indent_str << "elseif " << *stk->top() << " then\n";
	      delete stk->top();
	      stk->pop();

	      start = new_start;
	      if_part_end = new_if_part_end;
	      has_else = new_has_else;
	      else_part_end = new_else_part_end;
	      goto output_if;
	    }
	  }
	  *os << indent_str << "else\n";
	  indented_dc.decompileRange(start, else_part_end);
	  start = else_part_end;
	}
	*os << indent_str << "end\n";
      }
      break;

    case CLOSURE:
      aux = *start++;
      goto closure;

    case CLOSURE0:
    case CLOSURE1:
      aux = opc - CLOSURE0;
    closure:
      {
	FuncExpr *f = dynamic_cast<FuncExpr *>(stk->top());
	if (f == NULL) {
	  *os << indent_str
	      << "error: closure requires a function\n";
	}
	stk->pop();
	f->num_upvals = aux;
	f->upvals = new Expression*[aux];
	for (int i = aux - 1; i >= 0; i--) {
	  f->upvals[i] = stk->top(); stk->pop();
	}
	stk->push(f);
      }
      break;

    case CALLFUNC:
      aux = *start++;
      goto callfunc;

    case CALLFUNC0:
    case CALLFUNC1:
      aux = opc - CALLFUNC0;
    callfunc:
      {
	int num_args = *start++;
	FuncCallExpr *e = new FuncCallExpr(start);
	e->num_args = num_args;
	e->args = new Expression*[num_args];
	for (int i = num_args - 1; i >= 0; i--) {
	  e->args[i] = stk->top();
	  stk->pop();
	}
	e->func = stk->top();
	stk->pop();
	if (aux == 0) {
	  *os << indent_str << *e << std::endl;
	  delete e;
	}
	else if (aux == 1 || aux == 255) // 255 for return f()
	  stk->push(e);
	else {
	  stk->push(e);
	  for (int i = 1; i < aux; i++)
	    stk->push(new VarExpr(start, "<extra result>"));
	}
      }
      break;

    case RETCODE:
      {
	int num_rets = stk->size() + tf->code[1] - *start++;
	ExprStack rets;

	for (int i = 0; i < num_rets; i++) {
	  rets.push(stk->top());
	  stk->pop();
	}
	*os << indent_str << "return";
	for (int i = 0; i < num_rets; i++) {
	  *os << " " << *rets.top();
	  delete rets.top();
	  rets.pop();
	  if (i + 1 < num_rets)
	    *os << ",";
	}
	*os << std::endl;
      }
      break;

    case SETLINE:
      aux = *start++;
      goto setline;

    case SETLINEW:
      aux = start[0] | (start[1] << 8);
      start += 2;
    setline:
      break;			// ignore line info

    case POP:
      aux = *start++;
      goto pop;

    case POP0:
    case POP1:
      aux = opc - POP0;
    pop:
      for (int i = 0; i <= aux; i++) {
	local_var_defs->insert(stk->top()->pos);
	delete stk->top(); stk->pop();
      }
      break;

    //Nop
    default:
      break;
    }
  }
}
  
// Decompile the body of a function.
void decompile(std::ostream &os, TProtoFunc *tf, std::string indent_str,
	       Expression **upvals, int num_upvals) {
  Byte *instr = tf->code + 2;
  ExprStack s;
  std::ostringstream first_time;
  std::multiset<Byte *> loc_vars;

  //set the maximum precision, in order to avoid round errors with float numbers
  os.precision(9);
  first_time.precision(9);

  // First, do a preliminary pass to see where local variables are defined
  Decompiler dc;
  dc.os = &first_time;
  dc.stk = &s;
  dc.tf = tf;
  dc.indent_str = indent_str;
  dc.break_pos = NULL;
  dc.upvals = upvals;
  dc.num_upvals = num_upvals;
  dc.local_var_defs = &loc_vars;
  dc.decompileRange(instr, NULL);

  if (s.empty() && loc_vars.empty()) {
    // OK, it didn't actually have any local variables.  Just output
    // the results right now.
    os << first_time.str();
    return;
  }

  // See where the local variables were defined.
  while (! s.empty()) {
    loc_vars.insert(s.top()->pos);
    delete s.top(); s.pop();
  }

  // Now do the real decompilation
  dc.os = &os;
  dc.decompileRange(instr, NULL);

  while (! s.empty()) {
    delete s.top(); s.pop();
  }
}

int main(int argc, char *argv[]) {
  int filename_pos = 1;

  if (argc != filename_pos + 1) {
    fprintf(stderr, "Usage: delua file.lua\n");
    exit(1);
  }
  char *filename = argv[filename_pos];
  FILE *f = fopen(filename, "rb");
  if (f == NULL) {
    perror(filename);
    exit(1);
  }

  lua_open();
  ZIO z;
  luaZ_Fopen(&z, f, filename);
  TProtoFunc *tf = luaU_undump1(&z);
  fclose(f);

  if (tf == NULL) {
    fprintf(stderr, "%s isn't a valid lua script\n", filename);
    exit(1);
  }

  decompile(std::cout, tf, "", NULL, 0);

  lua_close();
  return 0;
}
