//===- BytecodeDialectGen.cpp - Dialect bytecode read/writer gen  ---------===//
//
// 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
//
//===----------------------------------------------------------------------===//

#include "mlir/Support/IndentedOstream.h"
#include "mlir/TableGen/GenInfo.h"
#include "llvm/ADT/MapVector.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVectorExtras.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/TableGen/Error.h"
#include "llvm/TableGen/Record.h"
#include <regex>

using namespace llvm;

static cl::OptionCategory dialectGenCat("Options for -gen-bytecode");
static cl::opt<std::string>
    selectedBcDialect("bytecode-dialect", cl::desc("The dialect to gen for"),
                      cl::cat(dialectGenCat), cl::CommaSeparated);

namespace {

/// Helper class to generate C++ bytecode parser helpers.
class Generator {
public:
  Generator(raw_ostream &output) : output(output) {}

  /// Returns whether successfully emitted attribute/type parsers.
  void emitParse(StringRef kind, const Record &x);

  /// Returns whether successfully emitted attribute/type printers.
  void emitPrint(StringRef kind, StringRef type,
                 ArrayRef<std::pair<int64_t, const Record *>> vec);

  /// Emits parse dispatch table.
  void emitParseDispatch(StringRef kind, ArrayRef<const Record *> vec);

  /// Emits print dispatch table.
  void emitPrintDispatch(StringRef kind, ArrayRef<std::string> vec);

private:
  /// Emits parse calls to construct given kind.
  void emitParseHelper(StringRef kind, StringRef returnType, StringRef builder,
                       ArrayRef<const Init *> args,
                       ArrayRef<std::string> argNames, StringRef failure,
                       mlir::raw_indented_ostream &ios);

  /// Emits print instructions.
  void emitPrintHelper(const Record *memberRec, StringRef kind,
                       StringRef parent, StringRef name,
                       mlir::raw_indented_ostream &ios);

  raw_ostream &output;
};
} // namespace

/// Helper to replace set of from strings to target in `s`.
/// Assumed: non-overlapping replacements.
static std::string format(StringRef templ,
                          std::map<std::string, std::string> &&map) {
  std::string s = templ.str();
  for (const auto &[from, to] : map)
    // All replacements start with $, don't treat as anchor.
    s = std::regex_replace(s, std::regex("\\" + from), to);
  return s;
}

/// Return string with first character capitalized.
static std::string capitalize(StringRef str) {
  return ((Twine)toUpper(str[0]) + str.drop_front()).str();
}

/// Return the C++ type for the given record.
static std::string getCType(const Record *def) {
  std::string format = "{0}";
  if (def->isSubClassOf("Array")) {
    def = def->getValueAsDef("elemT");
    format = "SmallVector<{0}>";
  }

  StringRef cType = def->getValueAsString("cType");
  if (cType.empty()) {
    if (def->isAnonymous())
      PrintFatalError(def->getLoc(), "Unable to determine cType");

    return formatv(format.c_str(), def->getName().str());
  }
  return formatv(format.c_str(), cType.str());
}

void Generator::emitParseDispatch(StringRef kind,
                                  ArrayRef<const Record *> vec) {
  mlir::raw_indented_ostream os(output);
  char const *head =
      R"(static {0} read{0}(MLIRContext* context, DialectBytecodeReader &reader))";
  os << formatv(head, capitalize(kind));
  auto funScope = os.scope(" {\n", "}\n\n");

  if (vec.empty()) {
    os << "return reader.emitError() << \"unknown attribute\", "
       << capitalize(kind) << "();\n";
    return;
  }

  os << "uint64_t kind;\n";
  os << "if (failed(reader.readVarInt(kind)))\n"
     << "  return " << capitalize(kind) << "();\n";
  os << "switch (kind) ";
  {
    auto switchScope = os.scope("{\n", "}\n");
    for (const auto &it : llvm::enumerate(vec)) {
      if (it.value()->getName() == "ReservedOrDead")
        continue;

      os << formatv("case {1}:\n  return read{0}(context, reader);\n",
                    it.value()->getName(), it.index());
    }
    os << "default:\n"
       << "  reader.emitError() << \"unknown attribute code: \" "
       << "<< kind;\n"
       << "  return " << capitalize(kind) << "();\n";
  }
  os << "return " << capitalize(kind) << "();\n";
}

void Generator::emitParse(StringRef kind, const Record &x) {
  if (x.getNameInitAsString() == "ReservedOrDead")
    return;

  char const *head =
      R"(static {0} read{1}(MLIRContext* context, DialectBytecodeReader &reader) )";
  mlir::raw_indented_ostream os(output);
  std::string returnType = getCType(&x);
  os << formatv(head,
                kind == "attribute" ? "::mlir::Attribute" : "::mlir::Type",
                x.getName());
  const DagInit *members = x.getValueAsDag("members");
  SmallVector<std::string> argNames =
      llvm::map_to_vector(members->getArgNames(), [](const StringInit *init) {
        return init->getAsUnquotedString();
      });
  StringRef builder = x.getValueAsString("cBuilder").trim();
  emitParseHelper(kind, returnType, builder, members->getArgs(), argNames,
                  returnType + "()", os);
  os << "\n\n";
}

static void printParseConditional(mlir::raw_indented_ostream &ios,
                                  ArrayRef<const Init *> args,
                                  ArrayRef<std::string> argNames) {
  ios << "if ";
  auto parenScope = ios.scope("(", ") {");
  ios.indent();

  auto listHelperName = [](StringRef name) {
    return formatv("read{0}", capitalize(name));
  };

  auto parsedArgs = llvm::filter_to_vector(args, [](const Init *const attr) {
    const Record *def = cast<DefInit>(attr)->getDef();
    if (def->isSubClassOf("Array"))
      return true;
    return !def->getValueAsString("cParser").empty();
  });

  interleave(
      zip(parsedArgs, argNames),
      [&](std::tuple<const Init *&, const std::string &> it) {
        const Record *attr = cast<DefInit>(std::get<0>(it))->getDef();
        std::string parser;
        if (auto optParser = attr->getValueAsOptionalString("cParser")) {
          parser = *optParser;
        } else if (attr->isSubClassOf("Array")) {
          const Record *def = attr->getValueAsDef("elemT");
          bool composite = def->isSubClassOf("CompositeBytecode");
          if (!composite && def->isSubClassOf("AttributeKind"))
            parser = "succeeded($_reader.readAttributes($_var))";
          else if (!composite && def->isSubClassOf("TypeKind"))
            parser = "succeeded($_reader.readTypes($_var))";
          else
            parser = ("succeeded($_reader.readList($_var, " +
                      listHelperName(std::get<1>(it)) + "))")
                         .str();
        } else {
          PrintFatalError(attr->getLoc(), "No parser specified");
        }
        std::string type = getCType(attr);
        ios << format(parser, {{"$_reader", "reader"},
                               {"$_resultType", type},
                               {"$_var", std::get<1>(it)}});
      },
      [&]() { ios << " &&\n"; });
}

void Generator::emitParseHelper(StringRef kind, StringRef returnType,
                                StringRef builder, ArrayRef<const Init *> args,
                                ArrayRef<std::string> argNames,
                                StringRef failure,
                                mlir::raw_indented_ostream &ios) {
  auto funScope = ios.scope("{\n", "}");

  if (args.empty()) {
    ios << formatv(
        "return getChecked<{0}>([&]() {{ return reader.emitError(); }, "
        "context);\n",
        returnType);
    return;
  }

  // Print decls.
  std::string lastCType = "";
  for (auto [arg, name] : zip(args, argNames)) {
    const DefInit *first = dyn_cast<DefInit>(arg);
    if (!first)
      PrintFatalError("Unexpected type for " + name);
    const Record *def = first->getDef();

    // Create variable decls, if there are a block of same type then create
    // comma separated list of them.
    std::string cType = getCType(def);
    if (lastCType == cType) {
      ios << ", ";
    } else {
      if (!lastCType.empty())
        ios << ";\n";
      ios << cType << " ";
    }
    ios << name;
    lastCType = cType;
  }
  ios << ";\n";

  // Returns the name of the helper used in list parsing. E.g., the name of the
  // lambda passed to array parsing.
  auto listHelperName = [](StringRef name) {
    return formatv("read{0}", capitalize(name));
  };

  // Emit list helper functions.
  for (auto [arg, name] : zip(args, argNames)) {
    const Record *attr = cast<DefInit>(arg)->getDef();
    if (!attr->isSubClassOf("Array"))
      continue;

    // TODO: Dedupe readers.
    const Record *def = attr->getValueAsDef("elemT");
    if (!def->isSubClassOf("CompositeBytecode") &&
        (def->isSubClassOf("AttributeKind") || def->isSubClassOf("TypeKind")))
      continue;

    std::string returnType = getCType(def);
    ios << "auto " << listHelperName(name) << " = [&]() -> FailureOr<"
        << returnType << "> ";
    SmallVector<const Init *> args;
    SmallVector<std::string> argNames;
    if (def->isSubClassOf("CompositeBytecode")) {
      const DagInit *members = def->getValueAsDag("members");
      args = llvm::to_vector(members->getArgs());
      argNames = llvm::map_to_vector(
          members->getArgNames(),
          [](const StringInit *init) { return init->getAsUnquotedString(); });
    } else {
      args = {def->getDefInit()};
      argNames = {"temp"};
    }
    StringRef builder = def->getValueAsString("cBuilder");
    emitParseHelper(kind, returnType, builder, args, argNames, "failure()",
                    ios);
    ios << ";\n";
  }

  // Print parse conditional.
  printParseConditional(ios, args, argNames);

  // Compute args to pass to create method.
  auto passedArgs = llvm::filter_to_vector(
      argNames, [](StringRef str) { return !str.starts_with("_"); });
  std::string argStr;
  raw_string_ostream argStream(argStr);
  interleaveComma(passedArgs, argStream,
                  [&](const std::string &str) { argStream << str; });
  // Return the invoked constructor.
  ios << "\nreturn "
      << format(builder, {{"$_resultType", returnType.str()},
                          {"$_args", argStream.str()}})
      << ";\n";
  ios.unindent();

  // TODO: Emit error in debug.
  // This assumes the result types in error case can always be empty
  // constructed.
  ios << "}\nreturn " << failure << ";\n";
}

void Generator::emitPrint(StringRef kind, StringRef type,
                          ArrayRef<std::pair<int64_t, const Record *>> vec) {
  if (type == "ReservedOrDead")
    return;

  char const *head =
      R"(static LogicalResult write({0} {1}, DialectBytecodeWriter &writer) )";
  mlir::raw_indented_ostream os(output);
  os << formatv(head, type, kind);
  auto funScope = os.scope("{\n", "}\n\n");

  // Check that predicates specified if multiple bytecode instances.
  for (const Record *rec : make_second_range(vec)) {
    StringRef pred = rec->getValueAsString("printerPredicate");
    if (vec.size() > 1 && pred.empty()) {
      for (auto [index, rec] : vec) {
        (void)index;
        StringRef pred = rec->getValueAsString("printerPredicate");
        if (vec.size() > 1 && pred.empty())
          PrintError(rec->getLoc(),
                     "Requires printing predicate given common cType");
      }
      PrintFatalError("Unspecified for shared cType " + type);
    }
  }

  for (auto [index, rec] : vec) {
    StringRef pred = rec->getValueAsString("printerPredicate");
    if (!pred.empty()) {
      os << "if (" << format(pred, {{"$_val", kind.str()}}) << ") {\n";
      os.indent();
    }

    os << "writer.writeVarInt(/* " << rec->getName() << " */ " << index
       << ");\n";

    auto *members = rec->getValueAsDag("members");
    for (auto [arg, name] :
         llvm::zip(members->getArgs(), members->getArgNames())) {
      const DefInit *def = dyn_cast<DefInit>(arg);
      assert(def);
      const Record *memberRec = def->getDef();
      emitPrintHelper(memberRec, kind, kind, name->getAsUnquotedString(), os);
    }

    if (!pred.empty()) {
      os << "return success();\n";
      os.unindent();
      os << "}\n";
    } else {
      os << "return success();\n";
    }
  }
  os << "return failure();\n";
}

void Generator::emitPrintHelper(const Record *memberRec, StringRef kind,
                                StringRef parent, StringRef name,
                                mlir::raw_indented_ostream &ios) {
  std::string getter;
  if (auto cGetter = memberRec->getValueAsOptionalString("cGetter");
      cGetter && !cGetter->empty()) {
    getter = format(
        *cGetter,
        {{"$_attrType", parent.str()},
         {"$_member", name.str()},
         {"$_getMember", "get" + convertToCamelFromSnakeCase(name, true)}});
  } else {
    getter =
        formatv("{0}.get{1}()", parent, convertToCamelFromSnakeCase(name, true))
            .str();
  }

  if (memberRec->isSubClassOf("Array")) {
    const Record *def = memberRec->getValueAsDef("elemT");
    if (!def->isSubClassOf("CompositeBytecode")) {
      if (def->isSubClassOf("AttributeKind")) {
        ios << "writer.writeAttributes(" << getter << ");\n";
        return;
      }
      if (def->isSubClassOf("TypeKind")) {
        ios << "writer.writeTypes(" << getter << ");\n";
        return;
      }
    }
    std::string returnType = getCType(def);
    std::string nestedName = kind.str();
    ios << "writer.writeList(" << getter << ", [&](" << returnType << " "
        << nestedName << ") ";
    auto lambdaScope = ios.scope("{\n", "});\n");
    return emitPrintHelper(def, kind, nestedName, nestedName, ios);
  }
  if (memberRec->isSubClassOf("CompositeBytecode")) {
    auto *members = memberRec->getValueAsDag("members");
    for (auto [arg, argName] :
         zip(members->getArgs(), members->getArgNames())) {
      const DefInit *def = dyn_cast<DefInit>(arg);
      assert(def);
      emitPrintHelper(def->getDef(), kind, parent,
                      argName->getAsUnquotedString(), ios);
    }
  }

  if (std::string printer = memberRec->getValueAsString("cPrinter").str();
      !printer.empty())
    ios << format(printer, {{"$_writer", "writer"},
                            {"$_name", kind.str()},
                            {"$_getter", getter}})
        << ";\n";
}

void Generator::emitPrintDispatch(StringRef kind, ArrayRef<std::string> vec) {
  mlir::raw_indented_ostream os(output);
  char const *head = R"(static LogicalResult write{0}({0} {1},
                                DialectBytecodeWriter &writer))";
  os << formatv(head, capitalize(kind), kind);
  auto funScope = os.scope(" {\n", "}\n\n");

  os << "return TypeSwitch<" << capitalize(kind) << ", LogicalResult>(" << kind
     << ")";
  auto switchScope = os.scope("", "");
  for (StringRef type : vec) {
    if (type == "ReservedOrDead")
      continue;

    os << "\n.Case([&](" << type << " t)";
    auto caseScope = os.scope(" {\n", "})");
    os << "return write(t, writer);\n";
  }
  os << "\n.Default([&](" << capitalize(kind) << ") { return failure(); });\n";
}

namespace {
/// Container of Attribute or Type for Dialect.
struct AttrOrType {
  std::vector<const Record *> attr, type;
};
} // namespace

static bool emitBCRW(const RecordKeeper &records, raw_ostream &os) {
  MapVector<StringRef, AttrOrType> dialectAttrOrType;
  for (const Record *it :
       records.getAllDerivedDefinitions("DialectAttributes")) {
    if (!selectedBcDialect.empty() &&
        it->getValueAsString("dialect") != selectedBcDialect)
      continue;
    dialectAttrOrType[it->getValueAsString("dialect")].attr =
        it->getValueAsListOfDefs("elems");
  }
  for (const Record *it : records.getAllDerivedDefinitions("DialectTypes")) {
    if (!selectedBcDialect.empty() &&
        it->getValueAsString("dialect") != selectedBcDialect)
      continue;
    dialectAttrOrType[it->getValueAsString("dialect")].type =
        it->getValueAsListOfDefs("elems");
  }

  if (dialectAttrOrType.size() != 1)
    PrintFatalError("Single dialect per invocation required (either only "
                    "one in input file or specified via dialect option)");

  auto it = dialectAttrOrType.front();
  Generator gen(os);

  SmallVector<std::vector<const Record *> *, 2> vecs;
  SmallVector<std::string, 2> kinds;
  vecs.push_back(&it.second.attr);
  kinds.push_back("attribute");
  vecs.push_back(&it.second.type);
  kinds.push_back("type");
  for (auto [vec, kind] : zip(vecs, kinds)) {
    // Handle Attribute/Type emission.
    std::map<std::string, std::vector<std::pair<int64_t, const Record *>>>
        perType;
    for (auto kt : llvm::enumerate(*vec))
      perType[getCType(kt.value())].emplace_back(kt.index(), kt.value());
    for (const auto &jt : perType) {
      for (auto kt : jt.second)
        gen.emitParse(kind, *std::get<1>(kt));
      gen.emitPrint(kind, jt.first, jt.second);
    }
    gen.emitParseDispatch(kind, *vec);

    SmallVector<std::string> types;
    for (const auto &it : perType)
      types.push_back(it.first);
    gen.emitPrintDispatch(kind, types);
  }

  return false;
}

static mlir::GenRegistration
    genBCRW("gen-bytecode", "Generate dialect bytecode readers/writers",
            [](const RecordKeeper &records, raw_ostream &os) {
              return emitBCRW(records, os);
            });
