capnpc-capnp.c++ 27.4 KB
Newer Older
Kenton Varda's avatar
Kenton Varda committed
1 2
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
// Licensed under the MIT License:
3
//
Kenton Varda's avatar
Kenton Varda committed
4 5 6 7 8 9
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
10
//
Kenton Varda's avatar
Kenton Varda committed
11 12
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
13
//
Kenton Varda's avatar
Kenton Varda committed
14 15 16 17 18 19 20
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
21

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

25
#ifndef _GNU_SOURCE
Ivan Shynkarenka's avatar
Ivan Shynkarenka committed
26 27 28
#define _GNU_SOURCE
#endif

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

#if HAVE_CONFIG_H
#include "config.h"
Kenton Varda's avatar
Kenton Varda committed
46 47 48 49
#endif

#ifndef VERSION
#define VERSION "(unknown)"
50
#endif
51

52
namespace capnp {
53 54
namespace {

55 56 57 58
bool hasDiscriminantValue(const schema::Field::Reader& reader) {
  return reader.getDiscriminantValue() != schema::Field::NO_DISCRIMINANT;
}

59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
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); }
};

85 86 87 88
inline Indent KJ_STRINGIFY(const Indent& indent) {
  return indent;
}

89 90
// =======================================================================================

91 92 93 94 95 96 97 98 99 100 101 102 103
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();
  }
104

105 106 107 108 109 110 111 112 113 114 115 116
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();
      }
117
    }
118 119
    KJ_FAIL_REQUIRE("A schema Node's supposed scope did not contain the node as a NestedNode.");
    return "(?)";
120 121
  }

122 123
  kj::StringTree nodeName(Schema target, Schema scope, schema::Brand::Reader brand,
                          kj::Maybe<InterfaceSchema::Method> method) {
Kenton Varda's avatar
Kenton Varda committed
124
    kj::Vector<Schema> targetPath;
125
    kj::Vector<Schema> scopeParts;
126

Kenton Varda's avatar
Kenton Varda committed
127 128
    targetPath.add(target);

129 130 131 132 133
    std::map<uint64_t, List<schema::Brand::Binding>::Reader> scopeBindings;
    for (auto scopeBrand: brand.getScopes()) {
      switch (scopeBrand.which()) {
        case schema::Brand::Scope::BIND:
          scopeBindings[scopeBrand.getScopeId()] = scopeBrand.getBind();
134
          break;
135
        case schema::Brand::Scope::INHERIT:
136 137 138 139 140
          // TODO(someday): We need to pay attention to INHERIT and be sure to explicitly override
          //   any bindings that are not inherited. This requires a way to determine which of our
          //   parent scopes have a non-empty parameter list.
          break;
      }
Kenton Varda's avatar
Kenton Varda committed
141 142
    }

143 144 145 146
    {
      Schema parent = target;
      while (parent.getProto().getScopeId() != 0) {
        parent = schemaLoader.get(parent.getProto().getScopeId());
Kenton Varda's avatar
Kenton Varda committed
147
        targetPath.add(parent);
148
      }
149 150
    }

151 152 153 154 155 156 157
    {
      Schema parent = scope;
      scopeParts.add(parent);
      while (parent.getProto().getScopeId() != 0) {
        parent = schemaLoader.get(parent.getProto().getScopeId());
        scopeParts.add(parent);
      }
158 159
    }

Kenton Varda's avatar
Kenton Varda committed
160 161 162 163 164
    // Remove common scope (unless it has been reparameterized).
    // TODO(someday):  This is broken in that we aren't checking for shadowing.
    while (!scopeParts.empty() && targetPath.size() > 1 &&
           scopeParts.back() == targetPath.back() &&
           scopeBindings.count(scopeParts.back().getProto().getId()) == 0) {
165
      scopeParts.removeLast();
Kenton Varda's avatar
Kenton Varda committed
166
      targetPath.removeLast();
167
    }
168

Kenton Varda's avatar
Kenton Varda committed
169 170 171
    auto parts = kj::heapArrayBuilder<kj::StringTree>(targetPath.size());
    while (!targetPath.empty()) {
      auto part = targetPath.back();
172
      auto proto = part.getProto();
Kenton Varda's avatar
Kenton Varda committed
173
      kj::StringTree partStr;
174
      if (proto.getScopeId() == 0) {
Kenton Varda's avatar
Kenton Varda committed
175
        partStr = kj::strTree("import \"/", proto.getDisplayName(), '\"');
176
      } else {
Kenton Varda's avatar
Kenton Varda committed
177 178 179 180 181 182 183
        partStr = kj::strTree(getUnqualifiedName(part));
      }

      auto iter = scopeBindings.find(proto.getId());
      if (iter != scopeBindings.end()) {
        auto bindings = KJ_MAP(binding, iter->second) {
          switch (binding.which()) {
184
            case schema::Brand::Binding::UNBOUND:
Kenton Varda's avatar
Kenton Varda committed
185
              return kj::strTree("AnyPointer");
186
            case schema::Brand::Binding::TYPE:
187
              return genType(binding.getType(), scope, method);
Kenton Varda's avatar
Kenton Varda committed
188 189 190 191
          }
          return kj::strTree("<unknown binding>");
        };
        partStr = kj::strTree(kj::mv(partStr), "(", kj::StringTree(kj::mv(bindings), ", "), ")");
192
      }
Kenton Varda's avatar
Kenton Varda committed
193 194 195

      parts.add(kj::mv(partStr));
      targetPath.removeLast();
196 197
    }

Kenton Varda's avatar
Kenton Varda committed
198
    return kj::StringTree(parts.finish(), ".");
199 200
  }

201 202
  kj::StringTree genType(schema::Type::Reader type, Schema scope,
                         kj::Maybe<InterfaceSchema::Method> method) {
Kenton Varda's avatar
Kenton Varda committed
203
    switch (type.which()) {
Kenton Varda's avatar
Kenton Varda committed
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
      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:
219
        return kj::strTree("List(", genType(type.getList().getElementType(), scope, method), ")");
Kenton Varda's avatar
Kenton Varda committed
220
      case schema::Type::ENUM:
Kenton Varda's avatar
Kenton Varda committed
221
        return nodeName(schemaLoader.get(type.getEnum().getTypeId()), scope,
222
                        type.getEnum().getBrand(), method);
Kenton Varda's avatar
Kenton Varda committed
223
      case schema::Type::STRUCT:
Kenton Varda's avatar
Kenton Varda committed
224
        return nodeName(schemaLoader.get(type.getStruct().getTypeId()), scope,
225
                        type.getStruct().getBrand(), method);
Kenton Varda's avatar
Kenton Varda committed
226
      case schema::Type::INTERFACE:
Kenton Varda's avatar
Kenton Varda committed
227
        return nodeName(schemaLoader.get(type.getInterface().getTypeId()), scope,
228
                        type.getInterface().getBrand(), method);
Kenton Varda's avatar
Kenton Varda committed
229 230 231 232
      case schema::Type::ANY_POINTER: {
        auto anyPointer = type.getAnyPointer();
        switch (anyPointer.which()) {
          case schema::Type::AnyPointer::UNCONSTRAINED:
233 234 235 236 237 238 239 240 241 242 243
            switch (anyPointer.getUnconstrained().which()) {
              case schema::Type::AnyPointer::Unconstrained::ANY_KIND:
                return kj::strTree("AnyPointer");
              case schema::Type::AnyPointer::Unconstrained::STRUCT:
                return kj::strTree("AnyStruct");
              case schema::Type::AnyPointer::Unconstrained::LIST:
                return kj::strTree("AnyList");
              case schema::Type::AnyPointer::Unconstrained::CAPABILITY:
                return kj::strTree("Capability");
            }
            KJ_UNREACHABLE;
Kenton Varda's avatar
Kenton Varda committed
244 245 246
          case schema::Type::AnyPointer::PARAMETER: {
            auto param = anyPointer.getParameter();
            auto scopeProto = scope.getProto();
247
            auto targetScopeId = param.getScopeId();
Kenton Varda's avatar
Kenton Varda committed
248
            while (scopeProto.getId() != targetScopeId) {
249
              scopeProto = schemaLoader.get(param.getScopeId()).getProto();
Kenton Varda's avatar
Kenton Varda committed
250 251 252 253 254
            }
            auto params = scopeProto.getParameters();
            KJ_REQUIRE(param.getParameterIndex() < params.size());
            return kj::strTree(params[param.getParameterIndex()].getName());
          }
255 256 257 258 259 260
          case schema::Type::AnyPointer::IMPLICIT_METHOD_PARAMETER: {
            auto params = KJ_REQUIRE_NONNULL(method).getProto().getImplicitParameters();
            uint index = anyPointer.getImplicitMethodParameter().getParameterIndex();
            KJ_REQUIRE(index < params.size());
            return kj::strTree(params[index].getName());
          }
Kenton Varda's avatar
Kenton Varda committed
261 262 263
        }
        KJ_UNREACHABLE;
      }
264 265
    }
    return kj::strTree();
266 267
  }

Kenton Varda's avatar
Kenton Varda committed
268
  int typeSizeBits(schema::Type::Reader type) {
Kenton Varda's avatar
Kenton Varda committed
269
    switch (type.which()) {
Kenton Varda's avatar
Kenton Varda committed
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
      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;
288
      case schema::Type::ANY_POINTER: return -1;
289 290
    }
    return 0;
291 292
  }

Kenton Varda's avatar
Kenton Varda committed
293
  bool isEmptyValue(schema::Value::Reader value) {
Kenton Varda's avatar
Kenton Varda committed
294
    switch (value.which()) {
Kenton Varda's avatar
Kenton Varda committed
295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312
      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;
313
      case schema::Value::ANY_POINTER: return true;
314
    }
315
    return true;
316 317
  }

318
  kj::StringTree genValue(Type type, schema::Value::Reader value) {
Kenton Varda's avatar
Kenton Varda committed
319
    switch (value.which()) {
Kenton Varda's avatar
Kenton Varda committed
320 321
      case schema::Value::VOID: return kj::strTree("void");
      case schema::Value::BOOL:
Kenton Varda's avatar
Kenton Varda committed
322
        return kj::strTree(value.getBool() ? "true" : "false");
Kenton Varda's avatar
Kenton Varda committed
323 324 325 326 327 328 329 330 331 332 333
      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
334
        return kj::strTree(DynamicValue::Reader(value.getText()));
Kenton Varda's avatar
Kenton Varda committed
335
      case schema::Value::DATA:
Kenton Varda's avatar
Kenton Varda committed
336
        return kj::strTree(DynamicValue::Reader(value.getData()));
Kenton Varda's avatar
Kenton Varda committed
337
      case schema::Value::LIST: {
338
        auto listValue = value.getList().getAs<DynamicList>(type.asList());
Kenton Varda's avatar
Kenton Varda committed
339
        return kj::strTree(listValue);
340
      }
Kenton Varda's avatar
Kenton Varda committed
341
      case schema::Value::ENUM: {
342
        auto enumNode = type.asEnum().getProto();
343
        auto enumerants = enumNode.getEnum().getEnumerants();
Kenton Varda's avatar
Kenton Varda committed
344 345 346
        KJ_REQUIRE(value.getEnum() < enumerants.size(),
                "Enum value out-of-range.", value.getEnum(), enumNode.getDisplayName());
        return kj::strTree(enumerants[value.getEnum()].getName());
347
      }
Kenton Varda's avatar
Kenton Varda committed
348
      case schema::Value::STRUCT: {
349 350
        KJ_REQUIRE(type.which() == schema::Type::STRUCT, "type/value mismatch");
        auto structValue = value.getStruct().getAs<DynamicStruct>(type.asStruct());
Kenton Varda's avatar
Kenton Varda committed
351
        return kj::strTree(structValue);
352
      }
Kenton Varda's avatar
Kenton Varda committed
353
      case schema::Value::INTERFACE: {
354 355
        return kj::strTree("");
      }
356
      case schema::Value::ANY_POINTER: {
357 358 359 360 361
        return kj::strTree("");
      }
    }
    return kj::strTree("");
  }
362

Kenton Varda's avatar
Kenton Varda committed
363 364 365 366 367 368 369 370 371 372 373 374 375
  kj::StringTree genGenericParams(List<schema::Node::Parameter>::Reader params, Schema scope) {
    if (params.size() == 0) {
      return kj::strTree();
    }

    return kj::strTree(" (", kj::StringTree(
        KJ_MAP(param, params) { return kj::strTree(param.getName()); }, ", "), ')');
  }
  kj::StringTree genGenericParams(Schema schema) {
    auto proto = schema.getProto();
    return genGenericParams(proto.getParameters(), schemaLoader.get(proto.getScopeId()));
  }

Kenton Varda's avatar
Kenton Varda committed
376
  kj::StringTree genAnnotation(schema::Annotation::Reader annotation,
377 378
                               Schema scope,
                               const char* prefix = " ", const char* suffix = "") {
379
    auto decl = schemaLoader.get(annotation.getId(), annotation.getBrand(), scope);
Kenton Varda's avatar
Kenton Varda committed
380
    auto proto = decl.getProto();
381
    KJ_REQUIRE(proto.isAnnotation());
Kenton Varda's avatar
Kenton Varda committed
382
    auto annDecl = proto.getAnnotation();
383

384 385
    auto value = genValue(schemaLoader.getType(annDecl.getType(), decl),
                          annotation.getValue()).flatten();
386
    if (value.startsWith("(")) {
387
      return kj::strTree(prefix, "$", nodeName(decl, scope, annotation.getBrand(), nullptr),
Kenton Varda's avatar
Kenton Varda committed
388
                         value, suffix);
389
    } else {
390
      return kj::strTree(prefix, "$", nodeName(decl, scope, annotation.getBrand(), nullptr),
Kenton Varda's avatar
Kenton Varda committed
391
                         "(", value, ")", suffix);
392
    }
393
  }
394

Kenton Varda's avatar
Kenton Varda committed
395
  kj::StringTree genAnnotations(List<schema::Annotation>::Reader list, Schema scope) {
396
    return kj::strTree(KJ_MAP(ann, list) { return genAnnotation(ann, scope); });
397 398 399 400
  }
  kj::StringTree genAnnotations(Schema schema) {
    auto proto = schema.getProto();
    return genAnnotations(proto.getAnnotations(), schemaLoader.get(proto.getScopeId()));
401 402
  }

Kenton Varda's avatar
Kenton Varda committed
403
  const char* elementSizeName(schema::ElementSize size) {
404
    switch (size) {
Kenton Varda's avatar
Kenton Varda committed
405 406 407 408 409 410 411 412
      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";
413
    }
414
    return "";
415 416
  }

Kenton Varda's avatar
Kenton Varda committed
417 418 419 420 421 422 423 424 425
  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) {
426
    auto sorted = KJ_MAP(item, list) { return item; };
Kenton Varda's avatar
Kenton Varda committed
427 428 429 430 431 432 433 434 435 436 437
    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;
438
    return KJ_MAP(field, sortByCodeOrder(schema.getFields())) {
439
      if (hasDiscriminantValue(field.getProto())) {
Kenton Varda's avatar
Kenton Varda committed
440 441 442 443 444
        if (seenUnion) {
          return kj::strTree();
        } else {
          seenUnion = true;
          uint offset = schema.getProto().getStruct().getDiscriminantOffset();
Kenton Varda's avatar
Kenton Varda committed
445 446 447

          // GCC 4.7.3 crashes if you inline unionFields.
          auto unionFields = sortByCodeOrder(schema.getUnionFields());
Kenton Varda's avatar
Kenton Varda committed
448 449
          return kj::strTree(
              indent, "union {  # tag bits [", offset * 16, ", ", offset * 16 + 16, ")\n",
450
              KJ_MAP(uField, unionFields) {
451
                return genStructField(uField, schema, indent.next());
Kenton Varda's avatar
Kenton Varda committed
452 453 454 455
              },
              indent, "}\n");
        }
      } else {
456
        return genStructField(field, schema, indent);
457
      }
Kenton Varda's avatar
Kenton Varda committed
458 459 460
    };
  }

461 462 463
  kj::StringTree genStructField(StructSchema::Field field, Schema scope, Indent indent) {
    auto proto = field.getProto();
    switch (proto.which()) {
464
      case schema::Field::SLOT: {
465
        auto slot = proto.getSlot();
466
        int size = typeSizeBits(slot.getType());
467
        return kj::strTree(
468
            indent, proto.getName(), " @", proto.getOrdinal().getExplicit(),
469
            " :", genType(slot.getType(), scope, nullptr),
470
            isEmptyValue(slot.getDefaultValue()) ? kj::strTree("") :
471 472
                kj::strTree(" = ", genValue(field.getType(), slot.getDefaultValue())),
            genAnnotations(proto.getAnnotations(), scope),
473 474 475
            ";  # ", size == -1 ? kj::strTree("ptr[", slot.getOffset(), "]")
                                : kj::strTree("bits[", slot.getOffset() * size, ", ",
                                              (slot.getOffset() + 1) * size, ")"),
476 477
            hasDiscriminantValue(proto)
                ? kj::strTree(", union tag = ", proto.getDiscriminantValue()) : kj::strTree(),
Kenton Varda's avatar
Kenton Varda committed
478
            "\n");
479
      }
Kenton Varda's avatar
Kenton Varda committed
480
      case schema::Field::GROUP: {
481
        auto group = field.getType().asStruct();
482
        return kj::strTree(
483 484 485 486
            indent, proto.getName(),
            " :group", genAnnotations(proto.getAnnotations(), scope), " {",
            hasDiscriminantValue(proto)
                ? kj::strTree("  # union tag = ", proto.getDiscriminantValue()) : kj::strTree(),
Kenton Varda's avatar
Kenton Varda committed
487 488
            "\n",
            genStructFields(group, indent.next()),
489 490 491 492
            indent, "}\n");
      }
    }
    return kj::strTree();
493 494
  }

Kenton Varda's avatar
Kenton Varda committed
495
  kj::StringTree genParamList(InterfaceSchema interface, StructSchema schema,
496
                              schema::Brand::Reader brand, InterfaceSchema::Method method) {
497 498 499
    if (schema.getProto().getId() == typeId<StreamResult>()) {
      return kj::strTree("stream");
    } else if (schema.getProto().getScopeId() == 0) {
500 501 502 503 504 505 506
      // A named parameter list.
      return kj::strTree("(", kj::StringTree(
          KJ_MAP(field, schema.getFields()) {
            auto proto = field.getProto();
            auto slot = proto.getSlot();

            return kj::strTree(
507
                proto.getName(), " :", genType(slot.getType(), interface, nullptr),
508
                isEmptyValue(slot.getDefaultValue()) ? kj::strTree("") :
509
                    kj::strTree(" = ", genValue(field.getType(), slot.getDefaultValue())),
510
                genAnnotations(proto.getAnnotations(), interface));
511 512
          }, ", "), ")");
    } else {
513
      return nodeName(schema, interface, brand, method);
514 515 516
    }
  }

517 518 519
  kj::StringTree genSuperclasses(InterfaceSchema interface) {
    auto superclasses = interface.getProto().getInterface().getSuperclasses();
    if (superclasses.size() == 0) {
520
      return kj::strTree();
Kenton Varda's avatar
Kenton Varda committed
521
    } else {
522 523
      return kj::strTree(" superclasses(", kj::StringTree(
          KJ_MAP(superclass, superclasses) {
524 525
            return nodeName(schemaLoader.get(superclass.getId()), interface,
                            superclass.getBrand(), nullptr);
526
          }, ", "), ")");
Kenton Varda's avatar
Kenton Varda committed
527
    }
528 529
  }

530 531 532 533 534
  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.");
535
    }
536

Kenton Varda's avatar
Kenton Varda committed
537
    switch (proto.which()) {
Kenton Varda's avatar
Kenton Varda committed
538
      case schema::Node::FILE:
539 540
        KJ_FAIL_REQUIRE("Encountered nested file node.");
        break;
Kenton Varda's avatar
Kenton Varda committed
541
      case schema::Node::STRUCT: {
Kenton Varda's avatar
Kenton Varda committed
542
        auto structProto = proto.getStruct();
543 544
        return kj::strTree(
            indent, "struct ", name,
Kenton Varda's avatar
Kenton Varda committed
545 546
            " @0x", kj::hex(proto.getId()), genGenericParams(schema),
            genAnnotations(schema), " {  # ",
547 548
            structProto.getDataWordCount() * 8, " bytes, ",
            structProto.getPointerCount(), " ptrs",
Kenton Varda's avatar
Kenton Varda committed
549
            structProto.getPreferredListEncoding() == schema::ElementSize::INLINE_COMPOSITE
550
                ? kj::strTree()
551 552
                : kj::strTree(", packed as ",
                              elementSizeName(structProto.getPreferredListEncoding())),
553
            "\n",
Kenton Varda's avatar
Kenton Varda committed
554
            genStructFields(schema.asStruct(), indent.next()),
555 556 557
            genNestedDecls(schema, indent.next()),
            indent, "}\n");
      }
Kenton Varda's avatar
Kenton Varda committed
558
      case schema::Node::ENUM: {
559 560
        return kj::strTree(
            indent, "enum ", name, " @0x", kj::hex(proto.getId()), genAnnotations(schema), " {\n",
561
            KJ_MAP(enumerant, sortByCodeOrder(schema.asEnum().getEnumerants())) {
Kenton Varda's avatar
Kenton Varda committed
562 563 564 565
              return kj::strTree(indent.next(), enumerant.getProto().getName(), " @",
                                 enumerant.getIndex(),
                                 genAnnotations(enumerant.getProto().getAnnotations(), schema),
                                 ";\n");
566 567 568 569
            },
            genNestedDecls(schema, indent.next()),
            indent, "}\n");
      }
Kenton Varda's avatar
Kenton Varda committed
570
      case schema::Node::INTERFACE: {
571
        auto interface = schema.asInterface();
572
        return kj::strTree(
Kenton Varda's avatar
Kenton Varda committed
573
            indent, "interface ", name, " @0x", kj::hex(proto.getId()), genGenericParams(schema),
574
            genSuperclasses(interface),
575
            genAnnotations(schema), " {\n",
576
            KJ_MAP(method, sortByCodeOrder(interface.getMethods())) {
Kenton Varda's avatar
Kenton Varda committed
577
              auto methodProto = method.getProto();
578 579 580 581 582 583 584 585 586 587

              auto implicits = methodProto.getImplicitParameters();
              kj::StringTree implicitsStr;
              if (implicits.size() > 0) {
                implicitsStr = kj::strTree(
                    "[", kj::StringTree(KJ_MAP(implicit, implicits) {
                      return kj::strTree(implicit.getName());
                    }, ", "), "] ");
              }

588 589
              auto params = schemaLoader.get(methodProto.getParamStructType()).asStruct();
              auto results = schemaLoader.get(methodProto.getResultStructType()).asStruct();
590
              return kj::strTree(
591 592 593 594
                  indent.next(), methodProto.getName(),
                  " @", method.getIndex(), " ", kj::mv(implicitsStr),
                  genParamList(interface, params, methodProto.getParamBrand(), method), " -> ",
                  genParamList(interface, results, methodProto.getResultBrand(), method),
595
                  genAnnotations(methodProto.getAnnotations(), interface), ";\n");
596 597 598 599
            },
            genNestedDecls(schema, indent.next()),
            indent, "}\n");
      }
Kenton Varda's avatar
Kenton Varda committed
600
      case schema::Node::CONST: {
Kenton Varda's avatar
Kenton Varda committed
601
        auto constProto = proto.getConst();
602 603
        return kj::strTree(
            indent, "const ", name, " @0x", kj::hex(proto.getId()), " :",
604
            genType(constProto.getType(), schema, nullptr), " = ",
605
            genValue(schema.asConst().getType(), constProto.getValue()),
606
            genAnnotations(schema), ";\n");
607
      }
Kenton Varda's avatar
Kenton Varda committed
608
      case schema::Node::ANNOTATION: {
Kenton Varda's avatar
Kenton Varda committed
609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625
        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;
            }
          }
626
        }
Kenton Varda's avatar
Kenton Varda committed
627 628 629

        if (targetsAll) {
          targets = kj::Vector<kj::String>(1);
Kenton Varda's avatar
Kenton Varda committed
630
          targets.add(kj::heapString("*"));
Kenton Varda's avatar
Kenton Varda committed
631 632
        }

633 634 635
        return kj::strTree(
            indent, "annotation ", name, " @0x", kj::hex(proto.getId()),
            " (", strArray(targets, ", "), ") :",
636
            genType(annotationProto.getType(), schema, nullptr), genAnnotations(schema), ";\n");
637 638
      }
    }
639 640

    return kj::strTree();
641 642
  }

643 644
  kj::StringTree genNestedDecls(Schema schema, Indent indent) {
    uint64_t id = schema.getProto().getId();
645
    return kj::strTree(KJ_MAP(nested, schema.getProto().getNestedNodes()) {
646 647 648
      return genDecl(schemaLoader.get(nested.getId()), nested.getName(), id, indent);
    });
  }
649

650 651
  kj::StringTree genFile(Schema file) {
    auto proto = file.getProto();
652
    KJ_REQUIRE(proto.isFile(), "Expected a file node.", (uint)proto.which());
653 654 655 656

    return kj::strTree(
      "# ", proto.getDisplayName(), "\n",
      "@0x", kj::hex(proto.getId()), ";\n",
657
      KJ_MAP(ann, proto.getAnnotations()) { return genAnnotation(ann, file, "", ";\n"); },
658 659
      genNestedDecls(file, Indent(0)));
  }
660

661 662 663 664
  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
665
    auto request = reader.getRoot<schema::CodeGeneratorRequest>();
666

667 668 669
    for (auto node: request.getNodes()) {
      schemaLoader.load(node);
    }
670

671 672
    kj::FdOutputStream rawOut(STDOUT_FILENO);
    kj::BufferedOutputStreamWrapper out(rawOut);
673

Kenton Varda's avatar
Kenton Varda committed
674 675
    for (auto requestedFile: request.getRequestedFiles()) {
      genFile(schemaLoader.get(requestedFile.getId())).visit(
676 677 678 679
          [&](kj::ArrayPtr<const char> text) {
            out.write(text.begin(), text.size());
          });
    }
680

681
    return true;
682
  }
683
};
684 685

}  // namespace
686
}  // namespace capnp
687

688
KJ_MAIN(capnp::CapnpcCapnpMain);