/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * 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 vm_BindingKind_h
#define vm_BindingKind_h

#include "mozilla/Assertions.h"  // MOZ_ASSERT, MOZ_ASSERT_IF
#include "mozilla/Casting.h"     // mozilla::AssertedCast

#include <stdint.h>  // uint16_t, uint32_t

#include "vm/BytecodeUtil.h"  // LOCALNO_LIMIT, ENVCOORD_SLOT_LIMIT

namespace js {

enum class BindingKind : uint8_t {
  Import,
  FormalParameter,
  Var,
  Let,
  Const,
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
  Using,
#endif

  // So you think named lambda callee names are consts? Nope! They don't
  // throw when being assigned to in sloppy mode.
  NamedLambdaCallee,

  // ClassBodyScope bindings that aren't bindings in the spec, but are put into
  // a scope as an implementation detail: `.privateBrand`,
  // `.staticInitializers`, private names, and private accessor functions.
  Synthetic,

  // ClassBodyScope binding that stores the function object for a non-static
  // private method.
  PrivateMethod,
};

static inline bool BindingKindIsLexical(BindingKind kind) {
  return kind == BindingKind::Let || kind == BindingKind::Const
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
         || kind == BindingKind::Using
#endif
      ;
}

class BindingLocation {
 public:
  enum class Kind {
    Global,
    Argument,
    Frame,
    Environment,
    Import,
    NamedLambdaCallee
  };

 private:
  Kind kind_;
  uint32_t slot_;

  BindingLocation(Kind kind, uint32_t slot) : kind_(kind), slot_(slot) {}

 public:
  static BindingLocation Global() {
    return BindingLocation(Kind::Global, UINT32_MAX);
  }

  static BindingLocation Argument(uint16_t slot) {
    return BindingLocation(Kind::Argument, slot);
  }

  static BindingLocation Frame(uint32_t slot) {
    MOZ_ASSERT(slot < LOCALNO_LIMIT);
    return BindingLocation(Kind::Frame, slot);
  }

  static BindingLocation Environment(uint32_t slot) {
    MOZ_ASSERT(slot < ENVCOORD_SLOT_LIMIT);
    return BindingLocation(Kind::Environment, slot);
  }

  static BindingLocation Import() {
    return BindingLocation(Kind::Import, UINT32_MAX);
  }

  static BindingLocation NamedLambdaCallee() {
    return BindingLocation(Kind::NamedLambdaCallee, UINT32_MAX);
  }

  bool operator==(const BindingLocation& other) const {
    return kind_ == other.kind_ && slot_ == other.slot_;
  }

  bool operator!=(const BindingLocation& other) const {
    return !operator==(other);
  }

  Kind kind() const { return kind_; }

  uint32_t slot() const {
    MOZ_ASSERT(kind_ == Kind::Frame || kind_ == Kind::Environment);
    return slot_;
  }

  uint16_t argumentSlot() const {
    MOZ_ASSERT(kind_ == Kind::Argument);
    return mozilla::AssertedCast<uint16_t>(slot_);
  }
};

}  // namespace js

#endif  // vm_BindingKind_h
