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

#include "frontend/ObjLiteral.h"

#include "mozilla/DebugOnly.h"

#include "frontend/CompilationInfo.h"  // frontend::CompilationAtomCache
#include "frontend/ParserAtom.h"  // frontend::ParserAtom, frontend::ParserAtomTable
#include "js/RootingAPI.h"
#include "vm/JSAtom.h"
#include "vm/JSObject.h"
#include "vm/JSONPrinter.h"  // js::JSONPrinter
#include "vm/ObjectGroup.h"
#include "vm/Printer.h"  // js::Fprinter

#include "gc/ObjectKind-inl.h"
#include "vm/JSAtom-inl.h"
#include "vm/JSObject-inl.h"

namespace js {

static void InterpretObjLiteralValue(JSContext* cx,
                                     frontend::CompilationAtomCache& atomCache,
                                     const ObjLiteralInsn& insn,
                                     JS::Value* valOut) {
  switch (insn.getOp()) {
    case ObjLiteralOpcode::ConstValue:
      *valOut = insn.getConstValue();
      return;
    case ObjLiteralOpcode::ConstAtom: {
      frontend::TaggedParserAtomIndex index = insn.getAtomIndex();
      JSAtom* jsatom = atomCache.getExistingAtomAt(cx, index);
      MOZ_ASSERT(jsatom);
      *valOut = StringValue(jsatom);
      return;
    }
    case ObjLiteralOpcode::Null:
      *valOut = NullValue();
      return;
    case ObjLiteralOpcode::Undefined:
      *valOut = UndefinedValue();
      return;
    case ObjLiteralOpcode::True:
      *valOut = BooleanValue(true);
      return;
    case ObjLiteralOpcode::False:
      *valOut = BooleanValue(false);
      return;
    default:
      MOZ_CRASH("Unexpected object-literal instruction opcode");
  }
}

static JSObject* InterpretObjLiteralObj(
    JSContext* cx, frontend::CompilationAtomCache& atomCache,
    const mozilla::Span<const uint8_t> literalInsns, ObjLiteralFlags flags) {
  bool singleton = flags.contains(ObjLiteralFlag::Singleton);

  ObjLiteralReader reader(literalInsns);
  ObjLiteralInsn insn;

  Rooted<IdValueVector> properties(cx, IdValueVector(cx));

  // Compute property values and build the key/value-pair list.
  while (reader.readInsn(&insn)) {
    MOZ_ASSERT(insn.isValid());

    jsid propId;
    if (insn.getKey().isArrayIndex()) {
      propId = INT_TO_JSID(insn.getKey().getArrayIndex());
    } else {
      JSAtom* jsatom =
          atomCache.getExistingAtomAt(cx, insn.getKey().getAtomIndex());
      MOZ_ASSERT(jsatom);
      propId = AtomToId(jsatom);
    }

    JS::Value propVal;
    if (singleton) {
      InterpretObjLiteralValue(cx, atomCache, insn, &propVal);
    }

    if (!properties.emplaceBack(propId, propVal)) {
      return nullptr;
    }
  }

  return NewPlainObjectWithProperties(cx, properties.begin(),
                                      properties.length(), TenuredObject);
}

static JSObject* InterpretObjLiteralArray(
    JSContext* cx, frontend::CompilationAtomCache& atomCache,
    const mozilla::Span<const uint8_t> literalInsns, ObjLiteralFlags flags) {
  ObjLiteralReader reader(literalInsns);
  ObjLiteralInsn insn;

  Rooted<ValueVector> elements(cx, ValueVector(cx));

  while (reader.readInsn(&insn)) {
    MOZ_ASSERT(insn.isValid());

    JS::Value propVal;
    InterpretObjLiteralValue(cx, atomCache, insn, &propVal);
    if (!elements.append(propVal)) {
      return nullptr;
    }
  }

  return NewDenseCopiedArray(cx, elements.length(), elements.begin(),
                             /* proto = */ nullptr,
                             NewObjectKind::TenuredObject);
}

JSObject* InterpretObjLiteral(JSContext* cx,
                              frontend::CompilationAtomCache& atomCache,
                              const mozilla::Span<const uint8_t> literalInsns,
                              ObjLiteralFlags flags) {
  return flags.contains(ObjLiteralFlag::Array)
             ? InterpretObjLiteralArray(cx, atomCache, literalInsns, flags)
             : InterpretObjLiteralObj(cx, atomCache, literalInsns, flags);
}

#if defined(DEBUG) || defined(JS_JITSPEW)

static void DumpObjLiteralFlagsItems(js::JSONPrinter& json,
                                     ObjLiteralFlags flags) {
  if (flags.contains(ObjLiteralFlag::Array)) {
    json.value("Array");
    flags -= ObjLiteralFlag::Array;
  }
  if (flags.contains(ObjLiteralFlag::Singleton)) {
    json.value("Singleton");
    flags -= ObjLiteralFlag::Singleton;
  }

  if (!flags.isEmpty()) {
    json.value("Unknown(%x)", flags.serialize());
  }
}

static void DumpObjLiteral(js::JSONPrinter& json,
                           frontend::BaseCompilationStencil* stencil,
                           mozilla::Span<const uint8_t> code,
                           const ObjLiteralFlags& flags) {
  json.beginListProperty("flags");
  DumpObjLiteralFlagsItems(json, flags);
  json.endList();

  json.beginListProperty("code");
  ObjLiteralReader reader(code);
  ObjLiteralInsn insn;
  while (reader.readInsn(&insn)) {
    json.beginObject();

    if (insn.getKey().isNone()) {
      json.nullProperty("key");
    } else if (insn.getKey().isAtomIndex()) {
      frontend::TaggedParserAtomIndex index = insn.getKey().getAtomIndex();
      json.beginObjectProperty("key");
      DumpTaggedParserAtomIndex(json, index, stencil);
      json.endObject();
    } else if (insn.getKey().isArrayIndex()) {
      uint32_t index = insn.getKey().getArrayIndex();
      json.formatProperty("key", "ArrayIndex(%u)", index);
    }

    switch (insn.getOp()) {
      case ObjLiteralOpcode::ConstValue: {
        const Value& v = insn.getConstValue();
        json.formatProperty("op", "ConstValue(%f)", v.toNumber());
        break;
      }
      case ObjLiteralOpcode::ConstAtom: {
        frontend::TaggedParserAtomIndex index = insn.getAtomIndex();
        json.beginObjectProperty("op");
        DumpTaggedParserAtomIndex(json, index, stencil);
        json.endObject();
        break;
      }
      case ObjLiteralOpcode::Null:
        json.property("op", "Null");
        break;
      case ObjLiteralOpcode::Undefined:
        json.property("op", "Undefined");
        break;
      case ObjLiteralOpcode::True:
        json.property("op", "True");
        break;
      case ObjLiteralOpcode::False:
        json.property("op", "False");
        break;
      default:
        json.formatProperty("op", "Invalid(%x)", uint8_t(insn.getOp()));
        break;
    }

    json.endObject();
  }
  json.endList();
}

void ObjLiteralWriter::dump() {
  js::Fprinter out(stderr);
  js::JSONPrinter json(out);
  dump(json, nullptr);
}

void ObjLiteralWriter::dump(js::JSONPrinter& json,
                            frontend::BaseCompilationStencil* stencil) {
  json.beginObject();
  dumpFields(json, stencil);
  json.endObject();
}

void ObjLiteralWriter::dumpFields(js::JSONPrinter& json,
                                  frontend::BaseCompilationStencil* stencil) {
  DumpObjLiteral(json, stencil, getCode(), flags_);
}

void ObjLiteralStencil::dump() {
  js::Fprinter out(stderr);
  js::JSONPrinter json(out);
  dump(json, nullptr);
}

void ObjLiteralStencil::dump(js::JSONPrinter& json,
                             frontend::BaseCompilationStencil* stencil) {
  json.beginObject();
  dumpFields(json, stencil);
  json.endObject();
}

void ObjLiteralStencil::dumpFields(js::JSONPrinter& json,
                                   frontend::BaseCompilationStencil* stencil) {
  DumpObjLiteral(json, stencil, code_, flags_);
}

#endif  // defined(DEBUG) || defined(JS_JITSPEW)

}  // namespace js