capnpc-capnp.c++ 21.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
// 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.

Kenton Varda's avatar
Kenton Varda committed
24 25
// This program is a code generator plugin for `capnp compile` which writes the schema back to
// stdout in roughly capnpc format.
26

27
#include <capnp/schema.capnp.h>
28
#include "../serialize.h"
Kenton Varda's avatar
Kenton Varda committed
29
#include <kj/debug.h>
30
#include <kj/io.h>
31
#include <kj/string-tree.h>
32
#include <kj/vector.h>
33 34
#include "../schema-loader.h"
#include "../dynamic.h"
35 36
#include <unistd.h>
#include <unordered_map>
37
#include <kj/main.h>
Kenton Varda's avatar
Kenton Varda committed
38
#include <algorithm>
39 40 41

#if HAVE_CONFIG_H
#include "config.h"
Kenton Varda's avatar
Kenton Varda committed
42 43 44 45
#endif

#ifndef VERSION
#define VERSION "(unknown)"
46
#endif
47

48
namespace capnp {
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
namespace {

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); }
};

77 78 79 80
inline Indent KJ_STRINGIFY(const Indent& indent) {
  return indent;
}

81 82
// =======================================================================================

83 84 85 86 87 88 89 90 91 92 93 94 95
class CapnpcCapnpMain {
public:
  CapnpcCapnpMain(kj::ProcessContext& context): context(context) {}

  kj::MainFunc getMain() {
    return kj::MainBuilder(context, "Cap'n Proto loopback plugin version " VERSION,
          "This is a Cap'n Proto compiler plugin which \"de-compiles\" the schema back into "
          "Cap'n Proto schema language format, with comments showing the offsets chosen by the "
          "compiler.  This is meant to be run using the Cap'n Proto compiler, e.g.:\n"
          "    capnp compile -ocapnp foo.capnp")
        .callAfterParsing(KJ_BIND_METHOD(*this, run))
        .build();
  }
96

97 98 99 100 101 102 103 104 105 106 107 108
private:
  kj::ProcessContext& context;
  SchemaLoader schemaLoader;

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

114 115 116
  kj::StringTree nodeName(Schema target, Schema scope) {
    kj::Vector<Schema> targetParents;
    kj::Vector<Schema> scopeParts;
117

118 119 120 121 122 123
    {
      Schema parent = target;
      while (parent.getProto().getScopeId() != 0) {
        parent = schemaLoader.get(parent.getProto().getScopeId());
        targetParents.add(parent);
      }
124 125
    }

126 127 128 129 130 131 132
    {
      Schema parent = scope;
      scopeParts.add(parent);
      while (parent.getProto().getScopeId() != 0) {
        parent = schemaLoader.get(parent.getProto().getScopeId());
        scopeParts.add(parent);
      }
133 134
    }

135 136 137 138 139 140
    // Remove common scope.
    while (!scopeParts.empty() && !targetParents.empty() &&
           scopeParts.back() == targetParents.back()) {
      scopeParts.removeLast();
      targetParents.removeLast();
    }
141

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

144 145 146 147 148 149 150 151 152 153
    kj::StringTree path = kj::strTree();
    while (!targetParents.empty()) {
      auto part = targetParents.back();
      auto proto = part.getProto();
      if (proto.getScopeId() == 0) {
        path = kj::strTree(kj::mv(path), "import \"/", proto.getDisplayName(), "\".");
      } else {
        path = kj::strTree(kj::mv(path), getUnqualifiedName(part), ".");
      }
      targetParents.removeLast();
154 155
    }

156
    return kj::strTree(kj::mv(path), getUnqualifiedName(target));
157 158
  }

Kenton Varda's avatar
Kenton Varda committed
159
  kj::StringTree genType(schema::Type::Reader type, Schema scope) {
Kenton Varda's avatar
Kenton Varda committed
160
    switch (type.which()) {
Kenton Varda's avatar
Kenton Varda committed
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
      case schema::Type::VOID: return kj::strTree("Void");
      case schema::Type::BOOL: return kj::strTree("Bool");
      case schema::Type::INT8: return kj::strTree("Int8");
      case schema::Type::INT16: return kj::strTree("Int16");
      case schema::Type::INT32: return kj::strTree("Int32");
      case schema::Type::INT64: return kj::strTree("Int64");
      case schema::Type::UINT8: return kj::strTree("UInt8");
      case schema::Type::UINT16: return kj::strTree("UInt16");
      case schema::Type::UINT32: return kj::strTree("UInt32");
      case schema::Type::UINT64: return kj::strTree("UInt64");
      case schema::Type::FLOAT32: return kj::strTree("Float32");
      case schema::Type::FLOAT64: return kj::strTree("Float64");
      case schema::Type::TEXT: return kj::strTree("Text");
      case schema::Type::DATA: return kj::strTree("Data");
      case schema::Type::LIST:
176
        return kj::strTree("List(", genType(type.getList().getElementType(), scope), ")");
Kenton Varda's avatar
Kenton Varda committed
177
      case schema::Type::ENUM:
178
        return nodeName(scope.getDependency(type.getEnum().getTypeId()), scope);
Kenton Varda's avatar
Kenton Varda committed
179
      case schema::Type::STRUCT:
180
        return nodeName(scope.getDependency(type.getStruct().getTypeId()), scope);
Kenton Varda's avatar
Kenton Varda committed
181
      case schema::Type::INTERFACE:
182
        return nodeName(scope.getDependency(type.getInterface().getTypeId()), scope);
Kenton Varda's avatar
Kenton Varda committed
183
      case schema::Type::OBJECT: return kj::strTree("Object");
184 185
    }
    return kj::strTree();
186 187
  }

Kenton Varda's avatar
Kenton Varda committed
188
  int typeSizeBits(schema::Type::Reader type) {
Kenton Varda's avatar
Kenton Varda committed
189
    switch (type.which()) {
Kenton Varda's avatar
Kenton Varda committed
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
      case schema::Type::VOID: return 0;
      case schema::Type::BOOL: return 1;
      case schema::Type::INT8: return 8;
      case schema::Type::INT16: return 16;
      case schema::Type::INT32: return 32;
      case schema::Type::INT64: return 64;
      case schema::Type::UINT8: return 8;
      case schema::Type::UINT16: return 16;
      case schema::Type::UINT32: return 32;
      case schema::Type::UINT64: return 64;
      case schema::Type::FLOAT32: return 32;
      case schema::Type::FLOAT64: return 64;
      case schema::Type::TEXT: return -1;
      case schema::Type::DATA: return -1;
      case schema::Type::LIST: return -1;
      case schema::Type::ENUM: return 16;
      case schema::Type::STRUCT: return -1;
      case schema::Type::INTERFACE: return -1;
      case schema::Type::OBJECT: return -1;
209 210
    }
    return 0;
211 212
  }

Kenton Varda's avatar
Kenton Varda committed
213
  bool isEmptyValue(schema::Value::Reader value) {
Kenton Varda's avatar
Kenton Varda committed
214
    switch (value.which()) {
Kenton Varda's avatar
Kenton Varda committed
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
      case schema::Value::VOID: return true;
      case schema::Value::BOOL: return value.getBool() == false;
      case schema::Value::INT8: return value.getInt8() == 0;
      case schema::Value::INT16: return value.getInt16() == 0;
      case schema::Value::INT32: return value.getInt32() == 0;
      case schema::Value::INT64: return value.getInt64() == 0;
      case schema::Value::UINT8: return value.getUint8() == 0;
      case schema::Value::UINT16: return value.getUint16() == 0;
      case schema::Value::UINT32: return value.getUint32() == 0;
      case schema::Value::UINT64: return value.getUint64() == 0;
      case schema::Value::FLOAT32: return value.getFloat32() == 0;
      case schema::Value::FLOAT64: return value.getFloat64() == 0;
      case schema::Value::TEXT: return !value.hasText();
      case schema::Value::DATA: return !value.hasData();
      case schema::Value::LIST: return !value.hasList();
      case schema::Value::ENUM: return value.getEnum() == 0;
      case schema::Value::STRUCT: return !value.hasStruct();
      case schema::Value::INTERFACE: return true;
      case schema::Value::OBJECT: return true;
234
    }
235
    return true;
236 237
  }

Kenton Varda's avatar
Kenton Varda committed
238
  kj::StringTree genValue(schema::Type::Reader type, schema::Value::Reader value, Schema scope) {
Kenton Varda's avatar
Kenton Varda committed
239
    switch (value.which()) {
Kenton Varda's avatar
Kenton Varda committed
240 241
      case schema::Value::VOID: return kj::strTree("void");
      case schema::Value::BOOL:
Kenton Varda's avatar
Kenton Varda committed
242
        return kj::strTree(value.getBool() ? "true" : "false");
Kenton Varda's avatar
Kenton Varda committed
243 244 245 246 247 248 249 250 251 252 253
      case schema::Value::INT8: return kj::strTree((int)value.getInt8());
      case schema::Value::INT16: return kj::strTree(value.getInt16());
      case schema::Value::INT32: return kj::strTree(value.getInt32());
      case schema::Value::INT64: return kj::strTree(value.getInt64());
      case schema::Value::UINT8: return kj::strTree((uint)value.getUint8());
      case schema::Value::UINT16: return kj::strTree(value.getUint16());
      case schema::Value::UINT32: return kj::strTree(value.getUint32());
      case schema::Value::UINT64: return kj::strTree(value.getUint64());
      case schema::Value::FLOAT32: return kj::strTree(value.getFloat32());
      case schema::Value::FLOAT64: return kj::strTree(value.getFloat64());
      case schema::Value::TEXT:
Kenton Varda's avatar
Kenton Varda committed
254
        return kj::strTree(DynamicValue::Reader(value.getText()));
Kenton Varda's avatar
Kenton Varda committed
255
      case schema::Value::DATA:
Kenton Varda's avatar
Kenton Varda committed
256
        return kj::strTree(DynamicValue::Reader(value.getData()));
Kenton Varda's avatar
Kenton Varda committed
257
      case schema::Value::LIST: {
258
        KJ_REQUIRE(type.isList(), "type/value mismatch");
259 260
        auto listValue = value.getList<DynamicList>(
            ListSchema::of(type.getList().getElementType(), scope));
Kenton Varda's avatar
Kenton Varda committed
261
        return kj::strTree(listValue);
262
      }
Kenton Varda's avatar
Kenton Varda committed
263
      case schema::Value::ENUM: {
264
        KJ_REQUIRE(type.isEnum(), "type/value mismatch");
265
        auto enumNode = scope.getDependency(type.getEnum().getTypeId()).asEnum().getProto();
266
        auto enumerants = enumNode.getEnum().getEnumerants();
Kenton Varda's avatar
Kenton Varda committed
267 268 269
        KJ_REQUIRE(value.getEnum() < enumerants.size(),
                "Enum value out-of-range.", value.getEnum(), enumNode.getDisplayName());
        return kj::strTree(enumerants[value.getEnum()].getName());
270
      }
Kenton Varda's avatar
Kenton Varda committed
271
      case schema::Value::STRUCT: {
272
        KJ_REQUIRE(type.isStruct(), "type/value mismatch");
Kenton Varda's avatar
Kenton Varda committed
273
        auto structValue = value.getStruct<DynamicStruct>(
274
            scope.getDependency(type.getStruct().getTypeId()).asStruct());
Kenton Varda's avatar
Kenton Varda committed
275
        return kj::strTree(structValue);
276
      }
Kenton Varda's avatar
Kenton Varda committed
277
      case schema::Value::INTERFACE: {
278 279
        return kj::strTree("");
      }
Kenton Varda's avatar
Kenton Varda committed
280
      case schema::Value::OBJECT: {
281 282 283 284 285
        return kj::strTree("");
      }
    }
    return kj::strTree("");
  }
286

Kenton Varda's avatar
Kenton Varda committed
287
  kj::StringTree genAnnotation(schema::Annotation::Reader annotation,
288 289 290
                               Schema scope,
                               const char* prefix = " ", const char* suffix = "") {
    auto decl = schemaLoader.get(annotation.getId());
Kenton Varda's avatar
Kenton Varda committed
291
    auto proto = decl.getProto();
292
    KJ_REQUIRE(proto.isAnnotation());
Kenton Varda's avatar
Kenton Varda committed
293
    auto annDecl = proto.getAnnotation();
294

295 296 297 298 299 300
    auto value = genValue(annDecl.getType(), annotation.getValue(), decl).flatten();
    if (value.startsWith("(")) {
      return kj::strTree(prefix, "$", nodeName(decl, scope), value, suffix);
    } else {
      return kj::strTree(prefix, "$", nodeName(decl, scope), "(", value, ")", suffix);
    }
301
  }
302

Kenton Varda's avatar
Kenton Varda committed
303
  kj::StringTree genAnnotations(List<schema::Annotation>::Reader list, Schema scope) {
304
    return kj::strTree(KJ_MAP(ann, list) { return genAnnotation(ann, scope); });
305 306 307 308
  }
  kj::StringTree genAnnotations(Schema schema) {
    auto proto = schema.getProto();
    return genAnnotations(proto.getAnnotations(), schemaLoader.get(proto.getScopeId()));
309 310
  }

Kenton Varda's avatar
Kenton Varda committed
311
  const char* elementSizeName(schema::ElementSize size) {
312
    switch (size) {
Kenton Varda's avatar
Kenton Varda committed
313 314 315 316 317 318 319 320
      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";
321
    }
322
    return "";
323 324
  }

Kenton Varda's avatar
Kenton Varda committed
325 326 327 328 329 330 331 332 333
  struct OrderByCodeOrder {
    template <typename T>
    inline bool operator()(const T& a, const T& b) const {
      return a.getProto().getCodeOrder() < b.getProto().getCodeOrder();
    }
  };

  template <typename MemberList>
  kj::Array<decltype(kj::instance<MemberList>()[0])> sortByCodeOrder(MemberList&& list) {
334
    auto sorted = KJ_MAP(item, list) { return item; };
Kenton Varda's avatar
Kenton Varda committed
335 336 337 338 339 340 341 342 343 344 345
    std::sort(sorted.begin(), sorted.end(), OrderByCodeOrder());
    return kj::mv(sorted);
  }

  kj::Array<kj::StringTree> genStructFields(StructSchema schema, Indent indent) {
    // Slightly hacky:  We want to print in code order, but we also need to print the union in one
    //   chunk.  Its fields should be together in code order anyway, but it's easier to simply
    //   output the whole union in place of the first union field, and then output nothing for the
    //   subsequent fields.

    bool seenUnion = false;
346
    return KJ_MAP(field, sortByCodeOrder(schema.getFields())) {
Kenton Varda's avatar
Kenton Varda committed
347 348 349 350 351 352
      if (field.getProto().hasDiscriminantValue()) {
        if (seenUnion) {
          return kj::strTree();
        } else {
          seenUnion = true;
          uint offset = schema.getProto().getStruct().getDiscriminantOffset();
Kenton Varda's avatar
Kenton Varda committed
353 354 355

          // GCC 4.7.3 crashes if you inline unionFields.
          auto unionFields = sortByCodeOrder(schema.getUnionFields());
Kenton Varda's avatar
Kenton Varda committed
356 357
          return kj::strTree(
              indent, "union {  # tag bits [", offset * 16, ", ", offset * 16 + 16, ")\n",
358
              KJ_MAP(uField, unionFields) {
Kenton Varda's avatar
Kenton Varda committed
359 360 361 362 363 364
                return genStructField(uField.getProto(), schema, indent.next());
              },
              indent, "}\n");
        }
      } else {
        return genStructField(field.getProto(), schema, indent);
365
      }
Kenton Varda's avatar
Kenton Varda committed
366 367 368
    };
  }

Kenton Varda's avatar
Kenton Varda committed
369
  kj::StringTree genStructField(schema::Field::Reader field, Schema scope, Indent indent) {
Kenton Varda's avatar
Kenton Varda committed
370
    switch (field.which()) {
371 372 373
      case schema::Field::SLOT: {
        auto slot = field.getSlot();
        int size = typeSizeBits(slot.getType());
374
        return kj::strTree(
Kenton Varda's avatar
Kenton Varda committed
375
            indent, field.getName(), " @", field.getOrdinal().getExplicit(),
376 377
            " :", genType(slot.getType(), scope),
            isEmptyValue(slot.getDefaultValue()) ? kj::strTree("") :
Kenton Varda's avatar
Kenton Varda committed
378
                kj::strTree(" = ", genValue(
379
                    slot.getType(), slot.getDefaultValue(), scope)),
Kenton Varda's avatar
Kenton Varda committed
380
            genAnnotations(field.getAnnotations(), scope),
381 382 383
            ";  # ", size == -1 ? kj::strTree("ptr[", slot.getOffset(), "]")
                                : kj::strTree("bits[", slot.getOffset() * size, ", ",
                                              (slot.getOffset() + 1) * size, ")"),
Kenton Varda's avatar
Kenton Varda committed
384 385 386
            field.hasDiscriminantValue()
                ? kj::strTree(", union tag = ", field.getDiscriminantValue()) : kj::strTree(),
            "\n");
387
      }
Kenton Varda's avatar
Kenton Varda committed
388
      case schema::Field::GROUP: {
389
        auto group = scope.getDependency(field.getGroup().getTypeId()).asStruct();
390
        return kj::strTree(
Kenton Varda's avatar
Kenton Varda committed
391 392 393 394 395 396
            indent, field.getName(),
            " :group", genAnnotations(field.getAnnotations(), scope), " {",
            field.hasDiscriminantValue()
                ? kj::strTree(", union tag = ", field.getDiscriminantValue()) : kj::strTree(),
            "\n",
            genStructFields(group, indent.next()),
397 398 399 400
            indent, "}\n");
      }
    }
    return kj::strTree();
401 402
  }

403 404 405 406 407
  kj::StringTree 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.");
408
    }
409

Kenton Varda's avatar
Kenton Varda committed
410
    switch (proto.which()) {
Kenton Varda's avatar
Kenton Varda committed
411
      case schema::Node::FILE:
412 413
        KJ_FAIL_REQUIRE("Encountered nested file node.");
        break;
Kenton Varda's avatar
Kenton Varda committed
414
      case schema::Node::STRUCT: {
Kenton Varda's avatar
Kenton Varda committed
415
        auto structProto = proto.getStruct();
416 417 418
        return kj::strTree(
            indent, "struct ", name,
            " @0x", kj::hex(proto.getId()), genAnnotations(schema), " {  # ",
419 420
            structProto.getDataWordCount() * 8, " bytes, ",
            structProto.getPointerCount(), " ptrs",
Kenton Varda's avatar
Kenton Varda committed
421
            structProto.getPreferredListEncoding() == schema::ElementSize::INLINE_COMPOSITE
422
                ? kj::strTree()
Kenton Varda's avatar
Kenton Varda committed
423
                : kj::strTree(", packed as ", elementSizeName(structProto.getPreferredListEncoding())),
424
            "\n",
Kenton Varda's avatar
Kenton Varda committed
425
            genStructFields(schema.asStruct(), indent.next()),
426 427 428
            genNestedDecls(schema, indent.next()),
            indent, "}\n");
      }
Kenton Varda's avatar
Kenton Varda committed
429
      case schema::Node::ENUM: {
430 431
        return kj::strTree(
            indent, "enum ", name, " @0x", kj::hex(proto.getId()), genAnnotations(schema), " {\n",
432
            KJ_MAP(enumerant, sortByCodeOrder(schema.asEnum().getEnumerants())) {
Kenton Varda's avatar
Kenton Varda committed
433 434 435 436
              return kj::strTree(indent.next(), enumerant.getProto().getName(), " @",
                                 enumerant.getIndex(),
                                 genAnnotations(enumerant.getProto().getAnnotations(), schema),
                                 ";\n");
437 438 439 440
            },
            genNestedDecls(schema, indent.next()),
            indent, "}\n");
      }
Kenton Varda's avatar
Kenton Varda committed
441
      case schema::Node::INTERFACE: {
442 443 444
        return kj::strTree(
            indent, "interface ", name, " @0x", kj::hex(proto.getId()),
            genAnnotations(schema), " {\n",
445
            KJ_MAP(method, sortByCodeOrder(schema.asInterface().getMethods())) {
Kenton Varda's avatar
Kenton Varda committed
446 447
              int i = 0;
              auto methodProto = method.getProto();
448
              return kj::strTree(
Kenton Varda's avatar
Kenton Varda committed
449
                  indent.next(), methodProto.getName(), " @", method.getIndex(), "(",
450
                  KJ_MAP(param, methodProto.getParams()) {
Kenton Varda's avatar
Kenton Varda committed
451
                    bool hasDefault = i >= methodProto.getRequiredParamCount() ||
452 453
                        !isEmptyValue(param.getDefaultValue());
                    return kj::strTree(
Kenton Varda's avatar
Kenton Varda committed
454
                        i++ > 0 ? ", " : "",
455 456 457 458 459 460 461
                        param.getName(), ": ", genType(param.getType(), schema),
                        hasDefault
                            ? kj::strTree(" = ", genValue(
                                param.getType(), param.getDefaultValue(), schema))
                            : kj::strTree(),
                        genAnnotations(param.getAnnotations(), schema));
                  },
Kenton Varda's avatar
Kenton Varda committed
462 463
                  ") :", genType(methodProto.getReturnType(), schema),
                  genAnnotations(methodProto.getAnnotations(), schema), ";\n");
464 465 466 467
            },
            genNestedDecls(schema, indent.next()),
            indent, "}\n");
      }
Kenton Varda's avatar
Kenton Varda committed
468
      case schema::Node::CONST: {
Kenton Varda's avatar
Kenton Varda committed
469
        auto constProto = proto.getConst();
470 471
        return kj::strTree(
            indent, "const ", name, " @0x", kj::hex(proto.getId()), " :",
Kenton Varda's avatar
Kenton Varda committed
472 473
            genType(constProto.getType(), schema), " = ",
            genValue(constProto.getType(), constProto.getValue(), schema), ";\n");
474
      }
Kenton Varda's avatar
Kenton Varda committed
475
      case schema::Node::ANNOTATION: {
Kenton Varda's avatar
Kenton Varda committed
476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492
        auto annotationProto = proto.getAnnotation();

        kj::Vector<kj::String> targets(8);
        bool targetsAll = true;

        auto dynamic = toDynamic(annotationProto);
        for (auto field: dynamic.getSchema().getFields()) {
          auto fieldName = field.getProto().getName();
          if (fieldName.startsWith("targets")) {
            if (dynamic.get(field).as<bool>()) {
              auto target = kj::str(fieldName.slice(strlen("targets")));
              target[0] = target[0] - 'A' + 'a';
              targets.add(kj::mv(target));
            } else {
              targetsAll = false;
            }
          }
493
        }
Kenton Varda's avatar
Kenton Varda committed
494 495 496 497 498 499

        if (targetsAll) {
          targets = kj::Vector<kj::String>(1);
          targets[0] = kj::heapString("*");
        }

500 501 502
        return kj::strTree(
            indent, "annotation ", name, " @0x", kj::hex(proto.getId()),
            " (", strArray(targets, ", "), ") :",
Kenton Varda's avatar
Kenton Varda committed
503
            genType(annotationProto.getType(), schema), genAnnotations(schema), ";\n");
504 505
      }
    }
506 507

    return kj::strTree();
508 509
  }

510 511
  kj::StringTree genNestedDecls(Schema schema, Indent indent) {
    uint64_t id = schema.getProto().getId();
512
    return kj::strTree(KJ_MAP(nested, schema.getProto().getNestedNodes()) {
513 514 515
      return genDecl(schemaLoader.get(nested.getId()), nested.getName(), id, indent);
    });
  }
516

517 518
  kj::StringTree genFile(Schema file) {
    auto proto = file.getProto();
519
    KJ_REQUIRE(proto.isFile(), "Expected a file node.", (uint)proto.which());
520 521 522 523

    return kj::strTree(
      "# ", proto.getDisplayName(), "\n",
      "@0x", kj::hex(proto.getId()), ";\n",
524
      KJ_MAP(ann, proto.getAnnotations()) { return genAnnotation(ann, file, "", ";\n"); },
525 526
      genNestedDecls(file, Indent(0)));
  }
527

528 529 530 531
  kj::MainBuilder::Validity run() {
    ReaderOptions options;
    options.traversalLimitInWords = 1 << 30;  // Don't limit.
    StreamFdMessageReader reader(STDIN_FILENO, options);
Kenton Varda's avatar
Kenton Varda committed
532
    auto request = reader.getRoot<schema::CodeGeneratorRequest>();
533

534 535 536
    for (auto node: request.getNodes()) {
      schemaLoader.load(node);
    }
537

538 539
    kj::FdOutputStream rawOut(STDOUT_FILENO);
    kj::BufferedOutputStreamWrapper out(rawOut);
540

Kenton Varda's avatar
Kenton Varda committed
541 542
    for (auto requestedFile: request.getRequestedFiles()) {
      genFile(schemaLoader.get(requestedFile.getId())).visit(
543 544 545 546
          [&](kj::ArrayPtr<const char> text) {
            out.write(text.begin(), text.size());
          });
    }
547

548
    return true;
549
  }
550
};
551 552

}  // namespace
553
}  // namespace capnp
554

555
KJ_MAIN(capnp::CapnpcCapnpMain);