// Copyright (c) 2013, Kenton Varda <temporal@gmail.com>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

// This program is a code generator plugin for capnpc which writes the schema back to stdout in
// roughly capnpc format.

#include "../schema.capnp.h"
#include "../serialize.h"
#include <kj/debug.h>
#include <kj/io.h>
#include "../schema-loader.h"
#include "../dynamic.h"
#include <unistd.h>
#include <unordered_map>
#include <vector>

namespace capnp {
namespace {

class TextBlob {
public:
  TextBlob() = default;
  template <typename... Params>
  explicit TextBlob(Params&&... params);
  TextBlob(kj::Array<TextBlob>&& params);

  void writeTo(kj::OutputStream& out) const;

private:
  kj::Array<char> text;
  struct Branch;
  kj::Array<Branch> branches;

  void allocate(size_t textSize, size_t branchCount);
  template <typename First, typename... Rest>
  void allocate(size_t textSize, size_t branchCount, const First& first, Rest&&... rest);
  template <typename... Rest>
  void allocate(size_t textSize, size_t branchCount, const TextBlob& first, Rest&&... rest);

  void fill(char* textPos, Branch* branchesPos);
  template <typename First, typename... Rest>
  void fill(char* textPos, Branch* branchesPos, First&& first, Rest&&... rest);
  template <typename... Rest>
  void fill(char* textPos, Branch* branchesPos, TextBlob&& first, Rest&&... rest);

  template <typename T>
  auto toContainer(T&& t) -> decltype(kj::toCharSequence(kj::fwd<T>(t))) {
    return kj::toCharSequence(kj::fwd<T>(t));
  }
  TextBlob&& toContainer(TextBlob&& t) {
    return kj::mv(t);
  }
  TextBlob& toContainer(TextBlob& t) {
    return t;
  }
  const TextBlob& toContainer(const TextBlob& t) {
    return t;
  }

  template <typename... Params>
  void init(Params&&... params);
};

struct TextBlob::Branch {
  char* pos;
  TextBlob content;
};

template <typename... Params>
TextBlob::TextBlob(Params&&... params) {
  init(toContainer(kj::fwd<Params>(params))...);
}

TextBlob::TextBlob(kj::Array<TextBlob>&& params) {
  branches = kj::heapArray<Branch>(params.size());
  for (size_t i = 0; i < params.size(); i++) {
    branches[i].pos = nullptr;
    branches[i].content = kj::mv(params[i]);
  }
}

void TextBlob::writeTo(kj::OutputStream& out) const {
  const char* pos = text.begin();
  for (auto& branch: branches) {
    out.write(pos, branch.pos - pos);
    pos = branch.pos;
    branch.content.writeTo(out);
  }
  out.write(pos, text.end() - pos);
}

void TextBlob::allocate(size_t textSize, size_t branchCount) {
  text = kj::heapArray<char>(textSize);
  branches = kj::heapArray<Branch>(branchCount);
}

template <typename First, typename... Rest>
void TextBlob::allocate(size_t textSize, size_t branchCount, const First& first, Rest&&... rest) {
  allocate(textSize + first.size(), branchCount, kj::fwd<Rest>(rest)...);
}

template <typename... Rest>
void TextBlob::allocate(size_t textSize, size_t branchCount,
                        const TextBlob& first, Rest&&... rest) {
  allocate(textSize, branchCount + 1, kj::fwd<Rest>(rest)...);
}

void TextBlob::fill(char* textPos, Branch* branchesPos) {
  KJ_ASSERT(textPos == text.end(), textPos - text.end());
  KJ_ASSERT(branchesPos == branches.end(), branchesPos - branches.end());
}

template <typename First, typename... Rest>
void TextBlob::fill(char* textPos, Branch* branchesPos, First&& first, Rest&&... rest) {
  textPos = kj::_::fill(textPos, kj::fwd<First>(first));
  fill(textPos, branchesPos, kj::fwd<Rest>(rest)...);
}

template <typename... Rest>
void TextBlob::fill(char* textPos, Branch* branchesPos, TextBlob&& first, Rest&&... rest) {
  branchesPos->pos = textPos;
  branchesPos->content = kj::mv(first);
  ++branchesPos;
  fill(textPos, branchesPos, kj::fwd<Rest>(rest)...);
}

template <typename... Params>
void TextBlob::init(Params&&... params) {
  allocate(0, 0, params...);
  fill(text.begin(), branches.begin(), kj::fwd<Params>(params)...);
}

template <typename... Params>
TextBlob text(Params&&... params) {
  return TextBlob(kj::fwd<Params>(params)...);
}

template <typename List, typename Func>
TextBlob forText(List&& list, Func&& func) {
  kj::Array<TextBlob> items = kj::heapArray<TextBlob>(list.size());
  for (size_t i = 0; i < list.size(); i++) {
    items[i] = func(list[i]);
  }
  return TextBlob(kj::mv(items));
}

template <typename T>
struct ForTextHack {
  T list;
  ForTextHack(T list): list(kj::fwd<T>(list)) {}
  template <typename Func>
  TextBlob operator*(Func&& func) {
    return forText(list, func);
  }
};

#define FOR_EACH(list, name) ForTextHack<decltype(list)>(list) * \
    [&](decltype((list)[0]) name) -> TextBlob

struct Indent {
  uint amount;
  Indent() = default;
  inline Indent(int amount): amount(amount) {}

  Indent next() {
    return Indent(amount + 2);
  }

  struct Iterator {
    uint i;
    Iterator() = default;
    inline Iterator(uint i): i(i) {}
    inline char operator*() const { return ' '; }
    inline Iterator& operator++() { ++i; return *this; }
    inline Iterator operator++(int) { Iterator result = *this; ++i; return result; }
    inline bool operator==(const Iterator& other) const { return i == other.i; }
    inline bool operator!=(const Iterator& other) const { return i != other.i; }
  };

  inline size_t size() const { return amount; }

  inline Iterator begin() const { return Iterator(0); }
  inline Iterator end() const { return Iterator(amount); }
};

inline Indent KJ_STRINGIFY(const Indent& indent) {
  return indent;
}

// =======================================================================================

SchemaLoader schemaLoader;

Text::Reader getUnqualifiedName(Schema schema) {
  auto proto = schema.getProto();
  auto parent = schemaLoader.get(proto.getScopeId());
  for (auto nested: parent.getProto().getNestedNodes()) {
    if (nested.getId() == proto.getId()) {
      return nested.getName();
    }
  }
  KJ_FAIL_REQUIRE("A schema Node's supposed scope did not contain the node as a NestedNode.");
  return "(?)";
}

TextBlob nodeName(Schema target, Schema scope) {
  std::vector<Schema> targetParents;
  std::vector<Schema> scopeParts;

  {
    Schema parent = target;
    while (parent.getProto().getScopeId() != 0) {
      parent = schemaLoader.get(parent.getProto().getScopeId());
      targetParents.push_back(parent);
    }
  }

  {
    Schema parent = scope;
    scopeParts.push_back(parent);
    while (parent.getProto().getScopeId() != 0) {
      parent = schemaLoader.get(parent.getProto().getScopeId());
      scopeParts.push_back(parent);
    }
  }

  // Remove common scope.
  while (!scopeParts.empty() && !targetParents.empty() &&
         scopeParts.back() == targetParents.back()) {
    scopeParts.pop_back();
    targetParents.pop_back();
  }

  // TODO(someday):  This is broken in that we aren't checking for shadowing.

  TextBlob path = text();
  while (!targetParents.empty()) {
    auto part = targetParents.back();
    auto proto = part.getProto();
    if (proto.getScopeId() == 0) {
      path = text(kj::mv(path), "import \"", proto.getDisplayName(), "\".");
    } else {
      path = text(kj::mv(path), getUnqualifiedName(part), ".");
    }
    targetParents.pop_back();
  }

  return text(kj::mv(path), getUnqualifiedName(target));
}

TextBlob genType(schema::Type::Reader type, Schema scope) {
  auto body = type.getBody();
  switch (body.which()) {
    case schema::Type::Body::VOID_TYPE: return text("Void");
    case schema::Type::Body::BOOL_TYPE: return text("Bool");
    case schema::Type::Body::INT8_TYPE: return text("Int8");
    case schema::Type::Body::INT16_TYPE: return text("Int16");
    case schema::Type::Body::INT32_TYPE: return text("Int32");
    case schema::Type::Body::INT64_TYPE: return text("Int64");
    case schema::Type::Body::UINT8_TYPE: return text("UInt8");
    case schema::Type::Body::UINT16_TYPE: return text("UInt16");
    case schema::Type::Body::UINT32_TYPE: return text("UInt32");
    case schema::Type::Body::UINT64_TYPE: return text("UInt64");
    case schema::Type::Body::FLOAT32_TYPE: return text("Float32");
    case schema::Type::Body::FLOAT64_TYPE: return text("Float64");
    case schema::Type::Body::TEXT_TYPE: return text("Text");
    case schema::Type::Body::DATA_TYPE: return text("Data");
    case schema::Type::Body::LIST_TYPE:
      return text("List(", genType(body.getListType(), scope), ")");
    case schema::Type::Body::ENUM_TYPE:
      return nodeName(scope.getDependency(body.getEnumType()), scope);
    case schema::Type::Body::STRUCT_TYPE:
      return nodeName(scope.getDependency(body.getStructType()), scope);
    case schema::Type::Body::INTERFACE_TYPE:
      return nodeName(scope.getDependency(body.getInterfaceType()), scope);
    case schema::Type::Body::OBJECT_TYPE: return text("Object");
  }
  return text();
}

int typeSizeBits(schema::Type::Reader type) {
  switch (type.getBody().which()) {
    case schema::Type::Body::VOID_TYPE: return 0;
    case schema::Type::Body::BOOL_TYPE: return 1;
    case schema::Type::Body::INT8_TYPE: return 8;
    case schema::Type::Body::INT16_TYPE: return 16;
    case schema::Type::Body::INT32_TYPE: return 32;
    case schema::Type::Body::INT64_TYPE: return 64;
    case schema::Type::Body::UINT8_TYPE: return 8;
    case schema::Type::Body::UINT16_TYPE: return 16;
    case schema::Type::Body::UINT32_TYPE: return 32;
    case schema::Type::Body::UINT64_TYPE: return 64;
    case schema::Type::Body::FLOAT32_TYPE: return 32;
    case schema::Type::Body::FLOAT64_TYPE: return 64;
    case schema::Type::Body::TEXT_TYPE: return -1;
    case schema::Type::Body::DATA_TYPE: return -1;
    case schema::Type::Body::LIST_TYPE: return -1;
    case schema::Type::Body::ENUM_TYPE: return 16;
    case schema::Type::Body::STRUCT_TYPE: return -1;
    case schema::Type::Body::INTERFACE_TYPE: return -1;
    case schema::Type::Body::OBJECT_TYPE: return -1;
  }
  return 0;
}

bool isEmptyValue(schema::Value::Reader value) {
  auto body = value.getBody();
  switch (body.which()) {
    case schema::Value::Body::VOID_VALUE: return true;
    case schema::Value::Body::BOOL_VALUE: return body.getBoolValue() == false;
    case schema::Value::Body::INT8_VALUE: return body.getInt8Value() == 0;
    case schema::Value::Body::INT16_VALUE: return body.getInt16Value() == 0;
    case schema::Value::Body::INT32_VALUE: return body.getInt32Value() == 0;
    case schema::Value::Body::INT64_VALUE: return body.getInt64Value() == 0;
    case schema::Value::Body::UINT8_VALUE: return body.getUint8Value() == 0;
    case schema::Value::Body::UINT16_VALUE: return body.getUint16Value() == 0;
    case schema::Value::Body::UINT32_VALUE: return body.getUint32Value() == 0;
    case schema::Value::Body::UINT64_VALUE: return body.getUint64Value() == 0;
    case schema::Value::Body::FLOAT32_VALUE: return body.getFloat32Value() == 0;
    case schema::Value::Body::FLOAT64_VALUE: return body.getFloat64Value() == 0;
    case schema::Value::Body::TEXT_VALUE: return !body.hasTextValue();
    case schema::Value::Body::DATA_VALUE: return !body.hasDataValue();
    case schema::Value::Body::LIST_VALUE: return !body.hasListValue();
    case schema::Value::Body::ENUM_VALUE: return body.getEnumValue() == 0;
    case schema::Value::Body::STRUCT_VALUE: return !body.hasStructValue();
    case schema::Value::Body::INTERFACE_VALUE: return true;
    case schema::Value::Body::OBJECT_VALUE: return true;
  }
  return true;
}

TextBlob genValue(schema::Type::Reader type, schema::Value::Reader value, Schema scope) {
  auto body = value.getBody();
  switch (body.which()) {
    case schema::Value::Body::VOID_VALUE: return text("void");
    case schema::Value::Body::BOOL_VALUE: return text(body.getBoolValue() ? "true" : "false");
    case schema::Value::Body::INT8_VALUE: return text((int)body.getInt8Value());
    case schema::Value::Body::INT16_VALUE: return text(body.getInt16Value());
    case schema::Value::Body::INT32_VALUE: return text(body.getInt32Value());
    case schema::Value::Body::INT64_VALUE: return text(body.getInt64Value());
    case schema::Value::Body::UINT8_VALUE: return text((uint)body.getUint8Value());
    case schema::Value::Body::UINT16_VALUE: return text(body.getUint16Value());
    case schema::Value::Body::UINT32_VALUE: return text(body.getUint32Value());
    case schema::Value::Body::UINT64_VALUE: return text(body.getUint64Value());
    case schema::Value::Body::FLOAT32_VALUE: return text(body.getFloat32Value());
    case schema::Value::Body::FLOAT64_VALUE: return text(body.getFloat64Value());
    case schema::Value::Body::TEXT_VALUE: return text(DynamicValue::Reader(body.getTextValue()));
    case schema::Value::Body::DATA_VALUE: return text(DynamicValue::Reader(body.getDataValue()));
    case schema::Value::Body::LIST_VALUE: {
      KJ_REQUIRE(type.getBody().which() == schema::Type::Body::LIST_TYPE, "type/value mismatch");
      auto value = body.getListValue<DynamicList>(
          ListSchema::of(type.getBody().getListType(), scope));
      return text(value);
    }
    case schema::Value::Body::ENUM_VALUE: {
      KJ_REQUIRE(type.getBody().which() == schema::Type::Body::ENUM_TYPE, "type/value mismatch");
      auto enumNode = scope.getDependency(type.getBody().getEnumType()).asEnum().getProto();
      auto enumType = enumNode.getBody().getEnumNode();
      auto enumerants = enumType.getEnumerants();
      KJ_REQUIRE(body.getEnumValue() < enumerants.size(),
              "Enum value out-of-range.", body.getEnumValue(), enumNode.getDisplayName());
      return text(enumerants[body.getEnumValue()].getName());
    }
    case schema::Value::Body::STRUCT_VALUE: {
      KJ_REQUIRE(type.getBody().which() == schema::Type::Body::STRUCT_TYPE, "type/value mismatch");
      auto value = body.getStructValue<DynamicStruct>(
          scope.getDependency(type.getBody().getStructType()).asStruct());
      return text(value);
    }
    case schema::Value::Body::INTERFACE_VALUE: {
      return text("");
    }
    case schema::Value::Body::OBJECT_VALUE: {
      return text("");
    }
  }
  return text("");
}

TextBlob genAnnotation(schema::Annotation::Reader annotation,
                       Schema scope,
                       const char* prefix = " ", const char* suffix = "") {
  auto decl = schemaLoader.get(annotation.getId());
  auto body = decl.getProto().getBody();
  KJ_REQUIRE(body.which() == schema::Node::Body::ANNOTATION_NODE);
  auto annDecl = body.getAnnotationNode();

  return text(prefix, "$", nodeName(decl, scope), "(",
              genValue(annDecl.getType(), annotation.getValue(), scope), ")", suffix);
}

TextBlob genAnnotations(List<schema::Annotation>::Reader list, Schema scope) {
  return FOR_EACH(list, ann) { return genAnnotation(ann, scope); };
}
TextBlob genAnnotations(Schema schema) {
  auto proto = schema.getProto();
  return genAnnotations(proto.getAnnotations(), schemaLoader.get(proto.getScopeId()));
}

const char* elementSizeName(schema::ElementSize size) {
  switch (size) {
    case schema::ElementSize::EMPTY: return "void";
    case schema::ElementSize::BIT: return "1-bit";
    case schema::ElementSize::BYTE: return "8-bit";
    case schema::ElementSize::TWO_BYTES: return "16-bit";
    case schema::ElementSize::FOUR_BYTES: return "32-bit";
    case schema::ElementSize::EIGHT_BYTES: return "64-bit";
    case schema::ElementSize::POINTER: return "pointer";
    case schema::ElementSize::INLINE_COMPOSITE: return "inline composite";
  }
  return "";
}

TextBlob genStructMember(schema::StructNode::Member::Reader member,
                         Schema scope, Indent indent, int unionTag = -1) {
  switch (member.getBody().which()) {
    case schema::StructNode::Member::Body::FIELD_MEMBER: {
      auto field = member.getBody().getFieldMember();
      int size = typeSizeBits(field.getType());
      return text(indent, member.getName(), " @", member.getOrdinal(),
                  " :", genType(field.getType(), scope),
                  isEmptyValue(field.getDefaultValue()) ? text("") :
                      text(" = ", genValue(field.getType(), field.getDefaultValue(), scope)),
                  genAnnotations(member.getAnnotations(), scope),
                  ";  # ", size == -1 ? text("ptr[", field.getOffset(), "]")
                                      : text("bits[", field.getOffset() * size, ", ",
                                                     (field.getOffset() + 1) * size, ")"),
                  unionTag != -1 ? text(", union tag = ", unionTag) : text(),
                  "\n");
    }
    case schema::StructNode::Member::Body::UNION_MEMBER: {
      auto un = member.getBody().getUnionMember();
      int i = 0;
      return text(indent, member.getName(), " @", member.getOrdinal(),
                  " union", genAnnotations(member.getAnnotations(), scope),
                  " {  # tag bits[", un.getDiscriminantOffset(), ", ",
                  un.getDiscriminantOffset() + 16, ")\n",
                  FOR_EACH(un.getMembers(), member) {
                    return genStructMember(member, scope, indent.next(), i++);
                  },
                  indent, "}\n");
    }
  }
  return text();
}

TextBlob genNestedDecls(Schema schema, Indent indent);

TextBlob genDecl(Schema schema, Text::Reader name, uint64_t scopeId, Indent indent) {
  auto proto = schema.getProto();
  if (proto.getScopeId() != scopeId) {
    // This appears to be an alias for something declared elsewhere.
    KJ_FAIL_REQUIRE("Aliases not implemented.");
  }

  switch (proto.getBody().which()) {
    case schema::Node::Body::FILE_NODE:
      KJ_FAIL_REQUIRE("Encountered nested file node.");
      break;
    case schema::Node::Body::STRUCT_NODE: {
      auto body = proto.getBody().getStructNode();
      return text(
          indent, "struct ", name, " @0x", kj::hex(proto.getId()), genAnnotations(schema), " {  # ",
          body.getDataSectionWordSize() * 8, " bytes, ",
          body.getPointerSectionSize(), " ptrs",
          body.getPreferredListEncoding() == schema::ElementSize::INLINE_COMPOSITE
              ? text()
              : text(", packed as ", elementSizeName(body.getPreferredListEncoding())),
          "\n",
          FOR_EACH(body.getMembers(), member) {
            return genStructMember(member, schema, indent.next());
          },
          genNestedDecls(schema, indent.next()),
          indent, "}\n");
    }
    case schema::Node::Body::ENUM_NODE: {
      auto body = proto.getBody().getEnumNode();
      uint i = 0;
      return text(
          indent, "enum ", name, " @0x", kj::hex(proto.getId()), genAnnotations(schema), " {\n",
          FOR_EACH(body.getEnumerants(), enumerant) {
            return text(indent.next(), enumerant.getName(), " @", i++,
                        genAnnotations(enumerant.getAnnotations(), schema), ";\n");
          },
          genNestedDecls(schema, indent.next()),
          indent, "}\n");
    }
    case schema::Node::Body::INTERFACE_NODE: {
      auto body = proto.getBody().getInterfaceNode();
      uint i = 0;
      return text(
          indent, "interface ", name, " @0x", kj::hex(proto.getId()),
          genAnnotations(schema), " {\n",
          FOR_EACH(body.getMethods(), method) {
            int j = 0;
            return text(
                indent.next(), method.getName(), " @", i++, "(",
                FOR_EACH(method.getParams(), param) {
                  bool hasDefault = j >= method.getRequiredParamCount() ||
                      !isEmptyValue(param.getDefaultValue());
                  return text(
                      j++ > 0 ? ", " : "",
                      param.getName(), ": ", genType(param.getType(), schema),
                      hasDefault
                          ? text(" = ", genValue(param.getType(), param.getDefaultValue(), schema))
                          : text(),
                      genAnnotations(param.getAnnotations(), schema));
                },
                ") :", genType(method.getReturnType(), schema),
                genAnnotations(method.getAnnotations(), schema), ";\n");
          },
          genNestedDecls(schema, indent.next()),
          indent, "}\n");
    }
    case schema::Node::Body::CONST_NODE: {
      auto body = proto.getBody().getConstNode();
      return text(
          indent, "const ", name, " @0x", kj::hex(proto.getId()), " :",
          genType(body.getType(), schema), " = ",
          genValue(body.getType(), body.getValue(), schema), ";\n");
    }
    case schema::Node::Body::ANNOTATION_NODE: {
      auto body = proto.getBody().getAnnotationNode();
      kj::CappedArray<const char*, 11> targets;
      uint i = 0;
      if (body.getTargetsFile()) targets[i++] = "file";
      if (body.getTargetsConst()) targets[i++] = "const";
      if (body.getTargetsEnum()) targets[i++] = "enum";
      if (body.getTargetsEnumerant()) targets[i++] = "enumerant";
      if (body.getTargetsStruct()) targets[i++] = "struct";
      if (body.getTargetsField()) targets[i++] = "field";
      if (body.getTargetsUnion()) targets[i++] = "union";
      if (body.getTargetsInterface()) targets[i++] = "interface";
      if (body.getTargetsMethod()) targets[i++] = "method";
      if (body.getTargetsParam()) targets[i++] = "param";
      if (body.getTargetsAnnotation()) targets[i++] = "annotation";
      if (i == targets.size()) {
        targets[0] = "*";
        targets.setSize(1);
      } else {
        targets.setSize(i);
      }
      return text(
          indent, "annotation ", name, " @0x", kj::hex(proto.getId()),
          " (", strArray(targets, ", "), ") :",
          genType(body.getType(), schema), genAnnotations(schema), ";\n");
    }
  }

  return text();
}

TextBlob genNestedDecls(Schema schema, Indent indent) {
  uint64_t id = schema.getProto().getId();
  return FOR_EACH(schema.getProto().getNestedNodes(), nested) {
    return genDecl(schemaLoader.get(nested.getId()), nested.getName(), id, indent);
  };
}

TextBlob genFile(Schema file) {
  auto proto = file.getProto();
  auto body = proto.getBody();
  KJ_REQUIRE(body.which() == schema::Node::Body::FILE_NODE, "Expected a file node.",
          (uint)body.which());

  return text(
    "# ", proto.getDisplayName(), "\n",
    "@0x", kj::hex(proto.getId()), ";\n",
    FOR_EACH(proto.getAnnotations(), ann) { return genAnnotation(ann, file, "", ";\n"); },
    genNestedDecls(file, Indent(0)));
}

int main(int argc, char* argv[]) {
  ReaderOptions options;
  options.traversalLimitInWords = 1 << 30;  // Don't limit.
  StreamFdMessageReader reader(STDIN_FILENO, options);
  auto request = reader.getRoot<schema::CodeGeneratorRequest>();

  for (auto node: request.getNodes()) {
    schemaLoader.load(node);
  }

  kj::FdOutputStream rawOut(STDOUT_FILENO);
  kj::BufferedOutputStreamWrapper out(rawOut);

  for (auto fileId: request.getRequestedFiles()) {
    genFile(schemaLoader.get(fileId)).writeTo(out);
  }

  return 0;
}

}  // namespace
}  // namespace capnp

int main(int argc, char* argv[]) {
  return capnp::main(argc, argv);
}