//===- LLDBPropertyDefEmitter.cpp -----------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// These tablegen backends emits LLDB's PropertyDefinition values.
//
//===----------------------------------------------------------------------===//

#include "LLDBTableGenBackends.h"
#include "LLDBTableGenUtils.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/TableGen/Record.h"
#include "llvm/TableGen/StringMatcher.h"
#include "llvm/TableGen/TableGenBackend.h"
#include <vector>

using namespace llvm;
using namespace lldb_private;

static void emitPropertyEnum(const Record *Property, raw_ostream &OS) {
  OS << "eProperty";
  OS << Property->getName();
  OS << ",\n";
}

static void emitProperty(const Record *Property, raw_ostream &OS) {
  OS << "  {";

  // Emit the property name.
  OS << "\"" << Property->getValueAsString("Name") << "\"";
  OS << ", ";

  // Emit the property type.
  llvm::StringRef type = Property->getValueAsString("Type");
  OS << "OptionValue::eType";
  OS << type;
  OS << ", ";

  // Emit the property's global value.
  OS << (Property->getValue("Global") ? "true" : "false");
  OS << ", ";

  bool hasDefaultUnsignedValue = Property->getValue("HasDefaultUnsignedValue");
  bool hasDefaultEnumValue = Property->getValue("HasDefaultEnumValue");
  bool hasDefaultStringValue = Property->getValue("HasDefaultStringValue");
  bool hasElementType = Property->getValue("HasElementType");

  // Guarantee that every property has a default value.
  assert((hasDefaultUnsignedValue || hasDefaultEnumValue ||
          hasDefaultStringValue || hasElementType) &&
         "Property must have a default value or an element type");

  // Guarantee that no property has both a default unsigned value and a default
  // enum value, since they're bothed stored in the same field.
  assert(!(hasDefaultUnsignedValue && hasDefaultEnumValue) &&
         "Property cannot have both a unsigned and enum default value.");

  // Guarantee that every boolean property has a boolean default value.
  assert(!(Property->getValueAsString("Type") == "Boolean" &&
           !Property->getValue("HasDefaultBooleanValue")) &&
         "Boolean property must have a boolean default value.");

  // Guarantee that every string property has a string default value.
  assert(!(Property->getValueAsString("Type") == "String" &&
           !hasDefaultStringValue) &&
         "String property must have a string default value.");

  // Guarantee that every enum property has an enum default value.
  assert(
      !(Property->getValueAsString("Type") == "Enum" && !hasDefaultEnumValue) &&
      "Enum property must have a enum default value.");

  // Guarantee that only arrays and dictionaries have an element type;
  assert(((type != "Array" && type != "Dictionary") || hasElementType) &&
         "Only dictionaries and arrays can have an element type.");

  // Emit the default uint value.
  if (hasDefaultUnsignedValue) {
    OS << std::to_string(Property->getValueAsInt("DefaultUnsignedValue"));
  } else if (hasDefaultEnumValue) {
    OS << Property->getValueAsString("DefaultEnumValue");
  } else if (hasElementType) {
    OS << "OptionValue::eType";
    OS << Property->getValueAsString("ElementType");
  } else {
    OS << "0";
  }
  OS << ", ";

  // Emit the default string value.
  if (hasDefaultStringValue) {
    if (auto D = Property->getValue("DefaultStringValue")) {
      OS << "\"";
      OS << D->getValue()->getAsUnquotedString();
      OS << "\"";
    } else {
      OS << "\"\"";
    }
  } else {
    OS << "nullptr";
  }
  OS << ", ";

  // Emit the enum values value.
  if (Property->getValue("EnumValues"))
    OS << Property->getValueAsString("EnumValues");
  else
    OS << "{}";
  OS << ", ";

  // Emit the property description.
  if (auto D = Property->getValue("Description")) {
    OS << "\"";
    OS << D->getValue()->getAsUnquotedString();
    OS << "\"";
  } else {
    OS << "\"\"";
  }

  OS << "},\n";
}

/// Emits all property initializers to the raw_ostream.
static void emityProperties(std::string PropertyName,
                            const std::vector<const Record *> &PropertyRecords,
                            raw_ostream &OS) {
  // Generate the macro that the user needs to define before including the
  // *.inc file.
  std::string NeededMacro = "LLDB_PROPERTIES_" + PropertyName;
  std::replace(NeededMacro.begin(), NeededMacro.end(), ' ', '_');

  // All options are in one file, so we need put them behind macros and ask the
  // user to define the macro for the options that are needed.
  OS << "// Property definitions for " << PropertyName << "\n";
  OS << "#ifdef " << NeededMacro << "\n";
  OS << "static constexpr PropertyDefinition g_" << PropertyName
     << "_properties[] = {\n";
  for (const Record *R : PropertyRecords)
    emitProperty(R, OS);
  OS << "};\n";
  // We undefine the macro for the user like Clang's include files are doing it.
  OS << "#undef " << NeededMacro << "\n";
  OS << "#endif // " << PropertyName << " Property\n\n";
}

/// Emits all property initializers to the raw_ostream.
static void emitPropertyEnum(std::string PropertyName,
                             ArrayRef<const Record *> PropertyRecords,
                             raw_ostream &OS) {
  // Generate the macro that the user needs to define before including the
  // *.inc file.
  std::string NeededMacro = "LLDB_PROPERTIES_" + PropertyName;
  std::replace(NeededMacro.begin(), NeededMacro.end(), ' ', '_');

  // All options are in one file, so we need put them behind macros and ask the
  // user to define the macro for the options that are needed.
  OS << "// Property enum cases for " << PropertyName << "\n";
  OS << "#ifdef " << NeededMacro << "\n";
  for (const Record *R : PropertyRecords)
    emitPropertyEnum(R, OS);
  // We undefine the macro for the user like Clang's include files are doing it.
  OS << "#undef " << NeededMacro << "\n";
  OS << "#endif // " << PropertyName << " Property\n\n";
}

void lldb_private::EmitPropertyDefs(const RecordKeeper &Records,
                                    raw_ostream &OS) {
  emitSourceFileHeader("Property definitions for LLDB.", OS, Records);

  ArrayRef<const Record *> Properties =
      Records.getAllDerivedDefinitions("Property");
  for (auto &PropertyRecordPair : getRecordsByName(Properties, "Definition")) {
    emityProperties(PropertyRecordPair.first, PropertyRecordPair.second, OS);
  }
}

void lldb_private::EmitPropertyEnumDefs(const RecordKeeper &Records,
                                        raw_ostream &OS) {
  emitSourceFileHeader("Property definition enum for LLDB.", OS, Records);

  ArrayRef<const Record *> Properties =
      Records.getAllDerivedDefinitions("Property");
  for (auto &PropertyRecordPair : getRecordsByName(Properties, "Definition")) {
    emitPropertyEnum(PropertyRecordPair.first, PropertyRecordPair.second, OS);
  }
}
