/*****
 * stm.h
 * Andy Hammerlindl 2002/8/30
 *
 * Statements are objects in the language that do something on their
 * own.  Statements are different from declarations in that statements
 * do not modify the environment.  Translation of a statements puts the
 * stack code to run it into the instruction stream.
 *****/

#ifndef STM_H
#define STM_H

#include "types.h"
#include "symbol.h"
#include "dec.h"

namespace trans {
class coenv;
}

namespace absyntax {

using trans::coenv;
using sym::symbol;

class stm : public runnable {
public:
  stm(position pos)
    : runnable(pos) {}

  void prettyprint(ostream &out, Int indent);

  void transAsField(coenv &e, record *) {
    // Ignore the record.
    trans(e);
  }

  void trans(coenv &e) = 0;
};

class emptyStm : public stm {
public:
  emptyStm(position pos)
    : stm(pos) {}

  void prettyprint(ostream &out, Int indent);

  void trans(coenv &) {}
};

// Wrapper around a block to use it as a statement.
class blockStm : public stm {
  block *base;

public:
  blockStm(position pos, block *base)
    : stm(pos), base(base) {}

  void prettyprint(ostream &out, Int indent) override;

  void trans(coenv &e) override {
    return base->trans(e);
  }

  // A block is guaranteed to return iff its last statement is
  // guaranteed to return.
  bool returns() override {
    return base->returns();
  }

  void createSymMap(AsymptoteLsp::SymbolContext* symContext) override;
};

// A statement that consist of a single expression to evaluate.
class expStm : public stm {
  exp *body;

public:
  expStm(position pos, exp *body)
    : stm(pos), body(body) {}

  void prettyprint(ostream &out, Int indent) override;

  void trans(coenv &e) override;

  // Should be called when running an expStm at the interactive prompt.
  // The code will "write" the value of the expression at the prompt if
  // possible.
  void interactiveTrans(coenv &e) override;
  void createSymMap(AsymptoteLsp::SymbolContext* symContext) override;
};

class ifStm : public stm {
  exp *test;
  stm *onTrue;
  stm *onFalse;

public:
  ifStm(position pos, exp *test, stm* onTrue, stm* onFalse = 0)
    : stm(pos), test(test), onTrue(onTrue), onFalse(onFalse) {}

  void prettyprint(ostream &out, Int indent) override;

  void trans(coenv &e) override;

  // An if statement is guaranteed to return iff both its pieces are
  // guaranteed to return.
  bool returns() override {
    if (onTrue == 0 || onFalse == 0)
      return false;
    return onTrue->returns() && onFalse->returns();
  }

  void createSymMap(AsymptoteLsp::SymbolContext* symContext) override;
};

class whileStm : public stm {
  exp *test;
  stm *body;

public:
  whileStm(position pos, exp *test, stm *body)
    : stm(pos), test(test), body(body) {}

  void prettyprint(ostream &out, Int indent) override;


  void trans(coenv &e) override;

  void createSymMap(AsymptoteLsp::SymbolContext* symContext) override;
};

class doStm : public stm {
  stm *body;
  exp *test;

public:
  doStm(position pos, stm *body, exp *test)
    : stm(pos), body(body), test(test) {}

  void prettyprint(ostream &out, Int indent) override;

  void trans(coenv &e) override;

  void createSymMap(AsymptoteLsp::SymbolContext* symContext) override;
};

class forStm : public stm {
  runnable *init;
  exp *test;
  runnable *update;
  stm *body;

public:
  forStm(position pos, runnable *init, exp *test, runnable *update, stm *body)
    : stm(pos), init(init), test(test), update(update), body(body) {}

  void prettyprint(ostream &out, Int indent) override;

  void trans(coenv &e) override;

  void createSymMap(AsymptoteLsp::SymbolContext* symContext) override;
};

class extendedForStm : public stm {
  astType *start;
  symbol var;
  exp *set;

  stm *body;

  enum class LoopType { ARRAY, ITERABLE, TY_ERROR };

  // Declares an array a, initialized to `set`. May involve an implicit cast.
  LoopType transObjectDec(symbol a, coenv& e);

public:
  extendedForStm(position pos, astType *start, symbol var, exp *set, stm *body)
    : stm(pos), start(start), var(var), set(set), body(body) {}

  void createSymMap(AsymptoteLsp::SymbolContext* symContext) override;

  void prettyprint(ostream &out, Int indent) override;

  void trans(coenv &e) override;
};


class breakStm : public stm {
public:
  breakStm(position pos)
    : stm(pos) {}

  void prettyprint(ostream &out, Int indent) override;

  void trans(coenv &e) override;
};

class continueStm : public stm {
public:
  continueStm(position pos)
    : stm(pos) {}

  void prettyprint(ostream &out, Int indent) override;

  void trans(coenv &e) override;
};

class returnStm : public stm {
  exp *value;

public:
  returnStm(position pos, exp *value = 0)
    : stm(pos), value(value) {}

  void prettyprint(ostream &out, Int indent) override;

  void trans(coenv &e) override;

  // A return statement is, of course, guaranteed to return.
  bool returns() override {
    return true;
  }

  void createSymMap(AsymptoteLsp::SymbolContext* symContext) override;
};


// Used at the start of for loops.
class stmExpList : public stm {
  mem::list<stm *> stms;

public:
  stmExpList(position pos)
    : stm(pos) {}

  // To ensure list deallocates properly.
  virtual ~stmExpList() {}

  void add(stm *s) {
    stms.push_back(s);
  }

  void prettyprint(ostream &out, Int indent);

  void trans(coenv &e);
};

} // namespace absyntax

#endif
