/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef frontend_ElemOpEmitter_h
#define frontend_ElemOpEmitter_h

#include "mozilla/Attributes.h"

#include <stddef.h>

namespace js {
namespace frontend {

struct BytecodeEmitter;
enum class ValueUsage;

// Class for emitting bytecode for element operation.
//
// Usage: (check for the return value is omitted for simplicity)
//
//   `obj[key];`
//     ElemOpEmitter eoe(this,
//                       ElemOpEmitter::Kind::Get,
//                       ElemOpEmitter::ObjKind::Other);
//     eoe.prepareForObj();
//     emit(obj);
//     eoe.prepareForKey();
//     emit(key);
//     eoe.emitGet();
//
//   `super[key];`
//     ElemOpEmitter eoe(this,
//                       ElemOpEmitter::Kind::Get,
//                       ElemOpEmitter::ObjKind::Super);
//     eoe.prepareForObj();
//     emit(this_for_super);
//     eoe.prepareForKey();
//     emit(key);
//     eoe.emitGet();
//
//   `obj[key]();`
//     ElemOpEmitter eoe(this,
//                       ElemOpEmitter::Kind::Call,
//                       ElemOpEmitter::ObjKind::Other);
//     eoe.prepareForObj();
//     emit(obj);
//     eoe.prepareForKey();
//     emit(key);
//     eoe.emitGet();
//     emit_call_here();
//
//   `new obj[key]();`
//     ElemOpEmitter eoe(this,
//                       ElemOpEmitter::Kind::Call,
//                       ElemOpEmitter::ObjKind::Other);
//     eoe.prepareForObj();
//     emit(obj);
//     eoe.prepareForKey();
//     emit(key);
//     eoe.emitGet();
//     emit_call_here();
//
//   `delete obj[key];`
//     ElemOpEmitter eoe(this,
//                       ElemOpEmitter::Kind::Delete,
//                       ElemOpEmitter::ObjKind::Other);
//     eoe.prepareForObj();
//     emit(obj);
//     eoe.prepareForKey();
//     emit(key);
//     eoe.emitDelete();
//
//   `delete super[key];`
//     ElemOpEmitter eoe(this,
//                       ElemOpEmitter::Kind::Delete,
//                       ElemOpEmitter::ObjKind::Super);
//     eoe.prepareForObj();
//     emit(this_for_super);
//     eoe.prepareForKey();
//     emit(key);
//     eoe.emitDelete();
//
//   `obj[key]++;`
//     ElemOpEmitter eoe(this,
//                       ElemOpEmitter::Kind::PostIncrement,
//                       ElemOpEmitter::ObjKind::Other);
//     eoe.prepareForObj();
//     emit(obj);
//     eoe.prepareForKey();
//     emit(key);
//     eoe.emitIncDec();
//
//   `obj[key] = value;`
//     ElemOpEmitter eoe(this,
//                       ElemOpEmitter::Kind::SimpleAssignment,
//                       ElemOpEmitter::ObjKind::Other);
//     eoe.prepareForObj();
//     emit(obj);
//     eoe.prepareForKey();
//     emit(key);
//     eoe.prepareForRhs();
//     emit(value);
//     eoe.emitAssignment();
//
//   `obj[key] += value;`
//     ElemOpEmitter eoe(this,
//                       ElemOpEmitter::Kind::CompoundAssignment,
//                       ElemOpEmitter::ObjKind::Other);
//     eoe.prepareForObj();
//     emit(obj);
//     eoe.prepareForKey();
//     emit(key);
//     eoe.emitGet();
//     eoe.prepareForRhs();
//     emit(value);
//     emit_add_op_here();
//     eoe.emitAssignment();
//
class MOZ_STACK_CLASS ElemOpEmitter {
 public:
  enum class Kind {
    Get,
    Call,
    Delete,
    PostIncrement,
    PreIncrement,
    PostDecrement,
    PreDecrement,
    SimpleAssignment,
    PropInit,
    CompoundAssignment
  };
  enum class ObjKind { Super, Other };

 private:
  BytecodeEmitter* bce_;

  Kind kind_;
  ObjKind objKind_;

#ifdef DEBUG
  // The state of this emitter.
  //
  //
  // +-------+   prepareForObj +-----+ prepareForKey +-----+
  // | Start |---------------->| Obj |-------------->| Key |-+
  // +-------+                 +-----+               +-----+ |
  //                                                         |
  // +-------------------------------------------------------+
  // |
  // | [Get]
  // | [Call]
  // |   emitGet +-----+
  // +---------->| Get |
  // |           +-----+
  // |
  // | [Delete]
  // |   emitDelete +--------+
  // +------------->| Delete |
  // |              +--------+
  // |
  // | [PostIncrement]
  // | [PreIncrement]
  // | [PostDecrement]
  // | [PreDecrement]
  // |   emitIncDec +--------+
  // +------------->| IncDec |
  // |              +--------+
  // |
  // | [SimpleAssignment]
  // | [PropInit]
  // |                        prepareForRhs    +-----+
  // +--------------------->+----------------->| Rhs |-+
  // |                      ^                  +-----+ |
  // |                      |                          |
  // |                      |            +-------------+
  // | [CompoundAssignment] |            |
  // |   emitGet +-----+    |            | emitAssignment +------------+
  // +---------->| Get |----+            +--------------->| Assignment |
  //             +-----+                                  +------------+
  enum class State {
    // The initial state.
    Start,

    // After calling prepareForObj.
    Obj,

    // After calling emitKey.
    Key,

    // After calling emitGet.
    Get,

    // After calling emitDelete.
    Delete,

    // After calling emitIncDec.
    IncDec,

    // After calling prepareForRhs.
    Rhs,

    // After calling emitAssignment.
    Assignment,
  };
  State state_ = State::Start;
#endif

 public:
  ElemOpEmitter(BytecodeEmitter* bce, Kind kind, ObjKind objKind);

 private:
  [[nodiscard]] bool isCall() const { return kind_ == Kind::Call; }

  [[nodiscard]] bool isSimpleAssignment() const {
    return kind_ == Kind::SimpleAssignment;
  }

  [[nodiscard]] bool isPropInit() const { return kind_ == Kind::PropInit; }

  [[nodiscard]] bool isDelete() const { return kind_ == Kind::Delete; }

  [[nodiscard]] bool isCompoundAssignment() const {
    return kind_ == Kind::CompoundAssignment;
  }

  [[nodiscard]] bool isIncDec() const {
    return isPostIncDec() || isPreIncDec();
  }

  [[nodiscard]] bool isPostIncDec() const {
    return kind_ == Kind::PostIncrement || kind_ == Kind::PostDecrement;
  }

  [[nodiscard]] bool isPreIncDec() const {
    return kind_ == Kind::PreIncrement || kind_ == Kind::PreDecrement;
  }

  [[nodiscard]] bool isInc() const {
    return kind_ == Kind::PostIncrement || kind_ == Kind::PreIncrement;
  }

  [[nodiscard]] bool isSuper() const { return objKind_ == ObjKind::Super; }

 public:
  [[nodiscard]] bool prepareForObj();
  [[nodiscard]] bool prepareForKey();

  [[nodiscard]] bool emitGet();

  [[nodiscard]] bool prepareForRhs();

  [[nodiscard]] bool emitDelete();

  [[nodiscard]] bool emitAssignment();

  [[nodiscard]] bool emitIncDec(ValueUsage valueUsage);

  size_t numReferenceSlots() const { return 2 + isSuper(); }
};

} /* namespace frontend */
}  // namespace js

#endif /* frontend_ElemOpEmitter_h */
