Commit de742607 authored by Kenton Varda's avatar Kenton Varda

Implement code generator plugins. The Cap'n Proto compiler, when given -o foo,…

Implement code generator plugins.  The Cap'n Proto compiler, when given -o foo, invokes the binary capnp-foo and passes the schema, in Cap'n Proto format, to its stdin.  The binary is executed with the current working directory set to the desired output directory, so all it has to do is write the files.
parent 0fb40a47
// 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.
#define CAPNPROTO_PRIVATE
#include "../schema.capnp.h"
#include "../serialize.h"
#include "../logging.h"
#include "../io.h"
#include <unistd.h>
#include <unordered_map>
#include <vector>
namespace capnproto {
namespace {
class TextBlob {
public:
TextBlob() = default;
template <typename... Params>
TextBlob(Params&&... params);
TextBlob(Array<TextBlob>&& params);
void writeTo(OutputStream& out) const;
private:
Array<char> text;
struct Branch;
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(STR * t) {
return STR * t;
}
TextBlob&& toContainer(TextBlob&& t) {
return move(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(capnproto::forward<Params>(params))...);
}
TextBlob::TextBlob(Array<TextBlob>&& params) {
branches = newArray<Branch>(params.size());
for (size_t i = 0; i < params.size(); i++) {
branches[i].pos = nullptr;
branches[i].content = move(params[i]);
}
}
void TextBlob::writeTo(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 = newArray<char>(textSize);
branches = newArray<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, capnproto::forward<Rest>(rest)...);
}
template <typename... Rest>
void TextBlob::allocate(size_t textSize, size_t branchCount,
const TextBlob& first, Rest&&... rest) {
allocate(textSize, branchCount + 1, capnproto::forward<Rest>(rest)...);
}
void TextBlob::fill(char* textPos, Branch* branchesPos) {
CHECK(textPos == text.end(), textPos - text.end());
CHECK(branchesPos == branches.end(), branchesPos - branches.end());
}
template <typename First, typename... Rest>
void TextBlob::fill(char* textPos, Branch* branchesPos, First&& first, Rest&&... rest) {
textPos = capnproto::fill(textPos, capnproto::forward<First>(first));
fill(textPos, branchesPos, capnproto::forward<Rest>(rest)...);
}
template <typename... Rest>
void TextBlob::fill(char* textPos, Branch* branchesPos, TextBlob&& first, Rest&&... rest) {
branchesPos->pos = textPos;
branchesPos->content = move(first);
++branchesPos;
fill(textPos, branchesPos, capnproto::forward<Rest>(rest)...);
}
template <typename... Params>
void TextBlob::init(Params&&... params) {
allocate(0, 0, params...);
fill(text.begin(), branches.begin(), capnproto::forward<Params>(params)...);
}
template <typename... Params>
TextBlob text(Params&&... params) {
return TextBlob(capnproto::forward<Params>(params)...);
}
template <typename List, typename Func>
TextBlob forText(List&& list, Func&& func) {
Array<TextBlob> items = newArray<TextBlob>(list.size());
for (size_t i = 0; i < list.size(); i++) {
items[i] = func(list[i]);
}
return TextBlob(capnproto::move(items));
}
template <typename T>
struct ForTextHack {
T list;
ForTextHack(T list): list(capnproto::forward<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 operator*(Stringifier, Indent i) { return i; }
// =======================================================================================
std::unordered_map<uint64_t, schema::Node::Reader> schemaMap;
schema::Node::Reader findNode(uint64_t id) {
auto iter = schemaMap.find(id);
PRECOND(iter != schemaMap.end(), "Missing schema node.", hex(id));
return iter->second;
}
Text::Reader getUnqualifiedName(schema::Node::Reader node) {
auto parent = findNode(node.getScopeId());
for (auto nested: parent.getNestedNodes()) {
if (nested.getId() == node.getId()) {
return nested.getName();
}
}
FAIL_PRECOND("A schema Node's supposed scope did not contain the node as a NestedNode.");
return "(?)";
}
TextBlob nodeName(schema::Node::Reader target, schema::Node::Reader scope) {
std::vector<schema::Node::Reader> targetParents;
std::vector<schema::Node::Reader> scopeParts;
{
schema::Node::Reader parent = target;
while (parent.getScopeId() != 0) {
parent = findNode(parent.getScopeId());
targetParents.push_back(parent);
}
}
{
schema::Node::Reader parent = scope;
scopeParts.push_back(scope);
while (parent.getScopeId() != 0) {
parent = findNode(parent.getScopeId());
scopeParts.push_back(parent);
}
}
// Remove common scope.
while (!scopeParts.empty() && !targetParents.empty() &&
scopeParts.back().getId() == targetParents.back().getId()) {
scopeParts.pop_back();
targetParents.pop_back();
}
TextBlob path = text();
while (!targetParents.empty()) {
auto part = targetParents.back();
if (part.getScopeId() == 0) {
path = text(move(path), "import \"", part.getDisplayName(), "\".");
} else {
path = text(move(path), getUnqualifiedName(part), ".");
}
targetParents.pop_back();
}
return text(move(path), getUnqualifiedName(target));
}
TextBlob genType(schema::Type::Reader type, schema::Node::Reader 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(findNode(body.getEnumType()), scope);
case schema::Type::Body::STRUCT_TYPE: return nodeName(findNode(body.getStructType()), scope);
case schema::Type::Body::INTERFACE_TYPE:
return nodeName(findNode(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.getTextValue().size() == 0;
case schema::Value::Body::DATA_VALUE: return body.getDataValue().size() == 0;
case schema::Value::Body::LIST_VALUE: return true; // TODO(soon): list values
case schema::Value::Body::ENUM_VALUE: return body.getEnumValue() == 0;
case schema::Value::Body::STRUCT_VALUE: return true; // TODO(soon): struct values
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) {
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("TODO"); // TODO(soon): escape strings
case schema::Value::Body::DATA_VALUE: return text("TODO"); // TODO(soon): escape strings
case schema::Value::Body::LIST_VALUE: {
PRECOND(type.getBody().which() == schema::Type::Body::LIST_TYPE, "type/value mismatch");
// TODO(soon): Requires dynamic message reading.
return text("TODO");
// int i = 0;
// return text("[",
// FOR_EACH(body.getListValue(), element) {
// return text(i++ > 0 ? ", " : "",
// genValue(type.getBody().getListType(), element));
// },
// "]");
}
case schema::Value::Body::ENUM_VALUE: {
PRECOND(type.getBody().which() == schema::Type::Body::ENUM_TYPE, "type/value mismatch");
auto enumNode = findNode(type.getBody().getEnumType());
PRECOND(enumNode.getBody().which() == schema::Node::Body::ENUM_NODE,
"schema.Type claimed to be an enum, but referred to some other node type.");
auto enumType = enumNode.getBody().getEnumNode();
auto enumerants = enumType.getEnumerants();
PRECOND(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: {
PRECOND(type.getBody().which() == schema::Type::Body::STRUCT_TYPE, "type/value mismatch");
// TODO(soon): Requires dynamic message reading.
return text("TODO");
}
case schema::Value::Body::INTERFACE_VALUE: {
return text("");
}
case schema::Value::Body::OBJECT_VALUE: {
return text("");
}
}
return 0;
}
TextBlob genAnnotation(schema::Annotation::Reader annotation,
schema::Node::Reader scope,
const char* prefix = " ", const char* suffix = "") {
auto decl = findNode(annotation.getId());
auto body = decl.getBody();
PRECOND(body.which() == schema::Node::Body::ANNOTATION_NODE);
auto annDecl = body.getAnnotationNode();
// TODO: Don't use displayName.
return text(prefix, "$", nodeName(decl, scope), "(",
genValue(annDecl.getType(), annotation.getValue()), ")", suffix);
}
TextBlob genAnnotations(List<schema::Annotation>::Reader list, schema::Node::Reader scope) {
return FOR_EACH(list, ann) { return genAnnotation(ann, scope); };
}
TextBlob genAnnotations(schema::Node::Reader node) {
return genAnnotations(node.getAnnotations(), findNode(node.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::Node::Reader 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())),
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::Node::Reader node, Indent indent);
TextBlob genDecl(schema::Node::Reader node, Text::Reader name, uint64_t scopeId, Indent indent) {
if (node.getScopeId() != scopeId) {
// This appears to be an alias for something declared elsewhere.
FAIL_PRECOND("Aliases not implemented.");
}
switch (node.getBody().which()) {
case schema::Node::Body::FILE_NODE:
FAIL_PRECOND("Encountered nested file node.");
break;
case schema::Node::Body::STRUCT_NODE: {
auto body = node.getBody().getStructNode();
return text(
indent, "struct ", name, " @0x", hex(node.getId()), genAnnotations(node), " { # ",
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, node, indent.next());
},
genNestedDecls(node, indent.next()),
indent, "}\n");
}
case schema::Node::Body::ENUM_NODE: {
auto body = node.getBody().getEnumNode();
uint i = 0;
return text(
indent, "enum ", name, " @0x", hex(node.getId()), genAnnotations(node), " {\n",
FOR_EACH(body.getEnumerants(), enumerant) {
return text(indent.next(), enumerant.getName(), " @", i++,
genAnnotations(enumerant.getAnnotations(), node), ";\n");
},
genNestedDecls(node, indent.next()),
indent, "}\n");
}
case schema::Node::Body::INTERFACE_NODE: {
auto body = node.getBody().getInterfaceNode();
uint i = 0;
return text(
indent, "interface ", name, " @0x", hex(node.getId()), genAnnotations(node), " {\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(), node),
hasDefault
? text(" = ", genValue(param.getType(), param.getDefaultValue()))
: text(),
genAnnotations(param.getAnnotations(), node));
},
") :", genType(method.getReturnType(), node),
genAnnotations(method.getAnnotations(), node), ";\n");
},
genNestedDecls(node, indent.next()),
indent, "}\n");
}
case schema::Node::Body::CONST_NODE: {
auto body = node.getBody().getConstNode();
return text(
indent, "const ", name, " @0x", hex(node.getId()), " :", genType(body.getType(), node),
" = ", genValue(body.getType(), body.getValue()), ";\n");
}
case schema::Node::Body::ANNOTATION_NODE: {
auto body = node.getBody().getAnnotationNode();
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", hex(node.getId()),
" (", strArray(targets, ", "), ") :",
genType(body.getType(), node), genAnnotations(node), ";\n");
}
}
return text();
}
TextBlob genNestedDecls(schema::Node::Reader node, Indent indent) {
return FOR_EACH(node.getNestedNodes(), nested) {
return genDecl(findNode(nested.getId()), nested.getName(), node.getId(), indent);
};
}
TextBlob genFile(schema::Node::Reader file) {
auto body = file.getBody();
PRECOND(body.which() == schema::Node::Body::FILE_NODE, "Expected a file node.", body.which());
return text(
"# ", file.getDisplayName(), "\n",
"@0x", hex(file.getId()), ";\n",
FOR_EACH(file.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()) {
schemaMap[node.getId()] = node;
}
FdOutputStream rawOut(STDOUT_FILENO);
BufferedOutputStreamWrapper out(rawOut);
for (auto fileId: request.getRequestedFiles()) {
genFile(findNode(fileId)).writeTo(out);
}
return 0;
}
} // namespace
} // namespace capnproto
int main(int argc, char* argv[]) {
return capnproto::main(argc, argv);
}
......@@ -167,6 +167,12 @@ static typename RootType::Reader readMessageTrusted(const word* data);
// a message MyMessage, you can read its default value like so:
// MyMessage::Reader reader = Message<MyMessage>::ReadTrusted(MyMessage::DEFAULT.words);
template <typename Type>
static typename Type::Reader defaultValue();
// Get a default instance of the given struct or list type.
//
// TODO(cleanup): Find a better home for this function?
// =======================================================================================
class SegmentArrayMessageReader: public MessageReader {
......@@ -282,6 +288,12 @@ typename RootType::Reader readMessageTrusted(const word* data) {
return typename RootType::Reader(internal::StructReader::readRootTrusted(data));
}
template <typename Type>
static typename Type::Reader defaultValue() {
// TODO(soon): Correctly handle lists. Maybe primitives too?
return typename Type::Reader(internal::StructReader());
}
} // namespace capnproto
#endif // CAPNPROTO_MESSAGE_H_
# 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.
using Cxx = import "c++.capnp";
@0xb471df2f45ca32c7;
$Cxx.namespace("capnproto::schema");
# WARNING: This protocol is still subject to backwards-incompatible change.
using Id = UInt64;
# The globally-unique ID of a file, type, or annotation.
struct Node {
id @0 :Id;
displayName @1 :Text;
# Name to present to humans to identify this Node. You should not attempt to parse this. Its
# format could change. It is not guaranteed to be unique.
#
# (On Zooko's triangle, this is the node's nickname.)
scopeId @2 :Id = 0;
# ID of the lexical parent node. Typically, the scope node will have a NestedNode pointing back
# at this node, but robust code should avoid relying on this. `scopeId` is zero if the node has
# no parent, which is normally only the case with files, but should be allowed for any kind of
# node (in order to make runtime type generation easier).
nestedNodes @3 :List(NestedNode);
# List of nodes nested within this node, along with the names under which they were declared.
struct NestedNode {
name @0 :Text;
# Unqualified symbol name. Unlike Node.name, this *can* be used programmatically.
#
# (On Zooko's triangle, this is the node's petname according to its parent scope.)
id @1 :Id;
# ID of the nested node. Typically, the target node's scopeId points back to this node, but
# robust code should avoid relying on this.
}
annotations @4 :List(Annotation);
# Annotations applied to this node.
body @5 union {
# Info specific to each kind of node.
fileNode @6 :FileNode;
structNode @7 :StructNode;
enumNode @8 :EnumNode;
interfaceNode @9 :InterfaceNode;
constNode @10 :ConstNode;
annotationNode @11 :AnnotationNode;
}
}
struct Type {
# Represents a type expression.
body @0 union {
voidType @1 :Void;
boolType @2 :Void;
int8Type @3 :Void;
int16Type @4 :Void;
int32Type @5 :Void;
int64Type @6 :Void;
uint8Type @7 :Void;
uint16Type @8 :Void;
uint32Type @9 :Void;
uint64Type @10 :Void;
float32Type @11 :Void;
float64Type @12 :Void;
textType @13 :Void;
dataType @14 :Void;
listType @15 :Type; # Value = the element type.
enumType @16 :Id;
structType @17 :Id;
interfaceType @18 :Id;
objectType @19 :Void;
}
}
struct Value {
# Represents a value, e.g. a field default value, constant value, or annotation value.
body @0 union {
# Note ordinals 1 and 10 are intentionally swapped to improve union layout.
voidValue @10 :Void;
boolValue @2 :Bool;
int8Value @3 :Int8;
int16Value @4 :Int16;
int32Value @5 :Int32;
int64Value @6 :Int64;
uint8Value @7 :UInt8;
uint16Value @8 :UInt16;
uint32Value @9 :UInt32;
uint64Value @1 :UInt64;
float32Value @11 :Float32;
float64Value @12 :Float64;
textValue @13 :Text;
dataValue @14 :Data;
listValue @15 :Object;
enumValue @16 :UInt16;
structValue @17 :Object;
interfaceValue @18 :Void;
# The only interface value that can be represented statically is "null", whose methods always
# throw exceptions.
objectValue @19 :Object;
}
}
struct Annotation {
# Describes an annotation applied to a declaration. Note AnnotationNode describes the
# annotation's declaration, while this describes a use of the annotation.
id @0 :Id;
# ID of the annotation node.
value @1 :Value;
}
struct FileNode {
imports @0 :List(Import);
struct Import {
id @0 :Id;
# ID of the imported file.
name @1 :Text;
# Name which *this* file used to refer to the foreign file. This may be a relative name.
# This information is provided because it might be useful for code generation, e.g. to generate
# #include directives in C++.
#
# (On Zooko's triangle, this is the import's petname according to the importing file.)
}
}
enum ElementSize {
# Possible element sizes for encoded lists. These correspond exactly to the possible values of
# the 3-bit element size component of a list pointer.
empty @0; # aka "void", but that's a keyword.
bit @1;
byte @2;
twoBytes @3;
fourBytes @4;
eightBytes @5;
pointer @6;
inlineComposite @7;
}
struct StructNode {
dataSectionWordSize @0 :UInt16;
pointerSectionSize @1 :UInt16;
preferredListEncoding @2 :ElementSize;
# The preferred element size to use when encoding a list of this struct. If this is anything
# other than `inlineComposite` then the struct is one word or less in size and is a candidate for
# list packing optimization.
members @3 :List(Member);
# Top-level fields and unions of the struct, ordered by ordinal number, except that members of
# unions are not included in this list (because they are nested inside the union declaration).
# Note that this ordering is stable as the protocol evolves -- new members can only be added to
# the end. So, when encoding a struct as tag/value pairs with numeric tags, it actually may make
# sense to use the field's position in this list rather than the original ordinal number to
# identify fields.
struct Member {
name @0 :Text;
ordinal @1 :UInt16;
codeOrder @2 :UInt16;
# Indicates where this member appeared in the code, relative to other members.
# Code ordering may have semantic relevance -- programmers tend to place related fields
# together. So, using code ordering makes sense in human-readable formats where ordering is
# otherwise irrelevant, like JSON. The values of codeOrder are tightly-packed, so for unions
# and non-union fields the maximum value of codeOrder is count(fields) + count(unions).
# Fields that are members of a union are only ordered relative to the other members of that
# union, so the maximum value there is count(union.fields).
annotations @3 :List(Annotation);
body @4 union {
# More member types could be added over time. Consumers should skip those that they
# don't understand.
fieldMember @5 :Field;
unionMember @6 :Union;
}
}
struct Field {
offset @0 :UInt32;
# Offset, in units of the field's size, from the beginning of the section in which the field
# resides. E.g. for a UInt32 field, multiply this by 4 to get the byte offset from the
# beginning of the data section.
type @1 :Type;
defaultValue @2 :Value;
}
struct Union {
discriminantOffset @0 :UInt32;
# Offset of the union's 16-bit discriminant within the struct's data section, in 16-bit units.
members @1 :List(Member);
# Fields of this union, ordered by ordinal. Currently all members are fields, but
# consumers should skip member types that they don't understand. The first member in this list
# gets discriminant value zero, the next gets one, and so on.
#
# TODO(soon): Discriminant zero should be reserved to mean "unset", unless the first field in
# the union actually predates the union (it was retroactively unionized), in which case it
# gets discriminant zero.
}
}
struct EnumNode {
enumerants @0 :List(Enumerant);
# Enumerants, in order by ordinal.
struct Enumerant {
name @0 :Text;
codeOrder @1 :UInt16;
# Specifies order in which the enumerants were declared in the code.
# Like Struct.Field.codeOrder.
annotations @2 :List(Annotation);
}
}
struct InterfaceNode {
methods @0 :List(Method);
# Methods, in order by ordinal.
struct Method {
name @0 :Text;
codeOrder @1 :UInt16;
# Specifies order in which the methods were declared in the code.
# Like Struct.Field.codeOrder.
params @2 :List(Param);
struct Param {
name @0 :Text;
type @1 :Type;
defaultValue @2 :Value;
annotations @3 :List(Annotation);
}
requiredParamCount @3 :UInt16;
# One plus the index of the last parameter that has no default value. In languages where
# method calls look like function calls, this is the minimum number of parameters that must
# always be specified, while subsequent parameters are optional.
returnType @4 :Type;
annotations @5 :List(Annotation);
}
}
struct ConstNode {
type @0 :Type;
value @1 :Value;
}
struct AnnotationNode {
type @0 :Type;
targetsFile @1 :Bool;
targetsConst @2 :Bool;
targetsEnum @3 :Bool;
targetsEnumerant @4 :Bool;
targetsStruct @5 :Bool;
targetsField @6 :Bool;
targetsUnion @7 :Bool;
targetsInterface @8 :Bool;
targetsMethod @9 :Bool;
targetsParam @10 :Bool;
targetsAnnotation @11 :Bool;
}
struct CodeGeneratorRequest {
nodes @0 :List(Node);
# All nodes parsed by the compiler, including for the files on the command line and their
# imports.
requestedFiles @1 :List(Id);
# IDs of files which were listed on the command line.
}
......@@ -212,7 +212,19 @@ inline Array<T> newArray(size_t size) {
template <typename T>
class ArrayBuilder {
union Slot { T value; char dummy; };
// TODO(cleanup): This class doesn't work for non-primitive types because Slot is not
// constructable. Giving Slot a constructor/destructor means arrays of it have to be tagged
// so operator delete can run the destructors. If we reinterpret_cast the array to an array
// of T and delete it as that type, operator delete gets very upset.
//
// Perhaps we should bite the bullet and make the Array family do manual memory allocation,
// bypassing the rather-stupid C++ array new/delete operators which store a redundant copy of
// the size anyway.
union Slot {
T value;
char dummy;
};
static_assert(sizeof(Slot) == sizeof(T), "union is bigger than content?");
public:
......
......@@ -51,6 +51,20 @@ STRINGIFY_INT(const void*, "%p");
#undef STRINGIFY_INT
#define HEXIFY_INT(type, format) \
CappedArray<char, sizeof(type) * 4> hex(type i) { \
CappedArray<char, sizeof(type) * 4> result; \
result.setSize(sprintf(result.begin(), format, i)); \
return result; \
}
HEXIFY_INT(unsigned short, "%x");
HEXIFY_INT(unsigned int, "%x");
HEXIFY_INT(unsigned long, "%lx");
HEXIFY_INT(unsigned long long, "%llx");
#undef HEXIFY_INT
namespace {
// ----------------------------------------------------------------------
......
......@@ -32,6 +32,7 @@
#include <utility>
#include <type_traits>
#include "type-safety.h"
#include "blob.h"
#include <string.h>
namespace capnproto {
......@@ -82,9 +83,15 @@ public:
inline const T* begin() const { return content; }
inline const T* end() const { return content + currentSize; }
inline operator ArrayPtr<T>() const {
return arrayPtr(content, fixedSize);
inline operator ArrayPtr<T>() {
return arrayPtr(content, currentSize);
}
inline operator ArrayPtr<const T>() const {
return arrayPtr(content, currentSize);
}
inline T& operator[](size_t index) { return content[index]; }
inline const T& operator[](size_t index) const { return content[index]; }
private:
size_t currentSize;
......@@ -168,6 +175,10 @@ struct Stringifier {
// anything.
inline ArrayPtr<const char> operator*(ArrayPtr<const char> s) const { return s; }
inline ArrayPtr<const char> operator*(const Array<const char>& s) const { return s; }
inline ArrayPtr<const char> operator*(const Array<char>& s) const { return s; }
template<size_t n>
inline ArrayPtr<const char> operator*(const CappedArray<char, n>& s) const { return s; }
inline ArrayPtr<const char> operator*(const char* s) const { return arrayPtr(s, strlen(s)); }
inline FixedArray<char, 1> operator*(char c) const {
......@@ -176,6 +187,10 @@ struct Stringifier {
return result;
}
inline ArrayPtr<const char> operator*(Text::Reader text) const {
return arrayPtr(text.data(), text.size());
}
CappedArray<char, sizeof(short) * 4> operator*(short i) const;
CappedArray<char, sizeof(unsigned short) * 4> operator*(unsigned short i) const;
CappedArray<char, sizeof(int) * 4> operator*(int i) const;
......@@ -190,9 +205,16 @@ struct Stringifier {
template <typename T>
Array<char> operator*(ArrayPtr<T> arr) const;
template <typename T>
Array<char> operator*(const Array<T>& arr) const;
};
static constexpr Stringifier STR;
CappedArray<char, sizeof(unsigned short) * 4> hex(unsigned short i);
CappedArray<char, sizeof(unsigned int) * 4> hex(unsigned int i);
CappedArray<char, sizeof(unsigned long) * 4> hex(unsigned long i);
CappedArray<char, sizeof(unsigned long long) * 4> hex(unsigned long long i);
template <typename... Params>
Array<char> str(Params&&... params) {
// Magic function which builds a string from a bunch of arbitrary values. Example:
......@@ -205,7 +227,7 @@ Array<char> str(Params&&... params) {
}
template <typename T>
Array<char> strArray(ArrayPtr<T> arr, const char* delim) {
Array<char> strArray(T&& arr, const char* delim) {
size_t delimLen = strlen(delim);
decltype(STR * arr[0]) pieces[arr.size()];
size_t size = 0;
......@@ -232,6 +254,22 @@ inline Array<char> Stringifier::operator*(ArrayPtr<T> arr) const {
return strArray(arr, ", ");
}
template <typename T>
inline Array<char> Stringifier::operator*(const Array<T>& arr) const {
return strArray(arr, ", ");
}
template <typename T, typename Func>
auto mapArray(T&& arr, Func&& func) -> Array<decltype(func(arr[0]))> {
// TODO(cleanup): Use ArrayBuilder.
Array<decltype(func(arr[0]))> result = newArray<decltype(func(arr[0]))>(arr.size());
size_t pos = 0;
for (auto& element: arr) {
result[pos++] = func(element);
}
return result;
}
} // namespace capnproto
#endif // CAPNPROTO_UTIL_H_
......@@ -30,7 +30,8 @@ executable capnpc
directory,
syb,
transformers,
entropy
entropy,
process
ghc-options: -Wall -fno-warn-missing-signatures
other-modules:
Lexer,
......
......@@ -800,6 +800,7 @@ compileDecl scope (ConstantDecl (Located _ name) t annotations (Located valuePos
compiledAnnotations <- compileAnnotations scope ConstantAnnotation annotations
return (DescConstant ConstantDesc
{ constantName = name
, constantId = childId name Nothing scope
, constantParent = scope
, constantType = typeDesc
, constantValue = valueDesc
......
......@@ -449,7 +449,10 @@ hastacheConfig = MuConfig
generateCxxHeader file = hastacheStr hastacheConfig (encodeStr headerTemplate) (fileContext file)
generateCxxSource file = hastacheStr hastacheConfig (encodeStr srcTemplate) (fileContext file)
generateCxx file = do
header <- generateCxxHeader file
source <- generateCxxSource file
return [(fileName file ++ ".h", header), (fileName file ++ ".c++", source)]
generateCxx files _ = do
let handleFile file = do
header <- generateCxxHeader file
source <- generateCxxSource file
return [(fileName file ++ ".h", header), (fileName file ++ ".c++", source)]
results <- mapM handleFile files
return $ concat results
......@@ -25,15 +25,17 @@ module Main ( main ) where
import System.Environment
import System.Console.GetOpt
import System.Exit(exitFailure, exitSuccess)
import System.IO(hPutStr, stderr)
import System.Exit(exitFailure, exitSuccess, ExitCode(..))
import System.IO(hPutStr, stderr, hSetBinaryMode, hClose)
import System.FilePath(takeDirectory)
import System.Directory(createDirectoryIfMissing, doesDirectoryExist, doesFileExist)
import System.Entropy(getEntropy)
import System.Process(createProcess, proc, std_in, cwd, StdStream(CreatePipe), waitForProcess)
import Control.Monad
import Control.Monad.IO.Class(MonadIO, liftIO)
import Control.Exception(IOException, catch)
import Control.Monad.Trans.State(StateT, state, modify, execStateT)
import Control.Monad.Trans.State(StateT, state, modify, evalStateT)
import qualified Control.Monad.Trans.State as State
import Prelude hiding (catch)
import Compiler
import Util(delimit)
......@@ -43,18 +45,21 @@ import Text.Printf(printf)
import qualified Data.List as List
import qualified Data.Map as Map
import qualified Data.ByteString.Lazy.Char8 as LZ
import Data.ByteString(unpack)
import Data.ByteString(unpack, pack, hPut)
import Data.Word(Word64, Word8)
import Data.Maybe(fromMaybe, catMaybes)
import Semantics
import WireFormat(encodeSchema)
import CxxGenerator(generateCxx)
type GeneratorFn = FileDesc -> IO [(FilePath, LZ.ByteString)]
type GeneratorFn = [FileDesc] -> [FileDesc] -> IO [(FilePath, LZ.ByteString)]
generatorFns :: Map.Map String GeneratorFn
generatorFns = Map.fromList [ ("c++", generateCxx) ]
data Opt = SearchPathOpt FilePath
| OutputOpt String (Maybe GeneratorFn) FilePath
| OutputOpt String GeneratorFn FilePath
| VerboseOpt
| HelpOpt
| GenIdOpt
......@@ -78,10 +83,7 @@ main = do
\Generate source code based on Cap'n Proto definition FILEs.\n"
optionDescs
args <- getArgs
let (options, files, optErrs) = getOpt Permute optionDescs args
let langErrs = map (printf "Unknown output language: %s\n")
[lang | OutputOpt lang Nothing _ <- options]
let errs = optErrs ++ langErrs
let (options, files, errs) = getOpt Permute optionDescs args
unless (null errs) (do
mapM_ (hPutStr stderr) errs
hPutStr stderr usage
......@@ -104,7 +106,7 @@ main = do
exitSuccess)
let isVerbose = not $ null [opt | opt@VerboseOpt <- options]
let outputs = [(fn, dir) | OutputOpt _ (Just fn) dir <- options]
let outputs = [(fn, dir) | OutputOpt _ fn dir <- options]
let searchPath = [dir | SearchPathOpt dir <- options]
let verifyDirectoryExists dir = do
......@@ -114,15 +116,49 @@ main = do
exitFailure)
mapM_ verifyDirectoryExists [dir | (_, dir) <- outputs]
CompilerState failed _ <-
execStateT (mapM_ (handleFile outputs isVerbose searchPath) files)
(failed, requestedFiles, allFiles) <-
evalStateT (handleFiles isVerbose searchPath files)
(CompilerState False Map.empty)
mapM_ (doOutput requestedFiles allFiles) outputs
when failed exitFailure
handleFiles isVerbose searchPath files = do
requestedFiles <- liftM catMaybes $ mapM (handleFile isVerbose searchPath) files
CompilerState failed importMap <- State.get
return (failed, requestedFiles, [ file | (_, ImportSucceeded file) <- Map.toList importMap ])
parseOutputArg :: String -> Opt
parseOutputArg str = case List.elemIndex ':' str of
Just i -> let (lang, _:dir) = splitAt i str in OutputOpt lang (Map.lookup lang generatorFns) dir
Nothing -> OutputOpt str (Map.lookup str generatorFns) "."
parseOutputArg str = let
generatorFn lang wd = fromMaybe (callPlugin lang wd) $ Map.lookup lang generatorFns
in case List.elemIndex ':' str of
Just i -> let
(lang, _:dir) = splitAt i str
in OutputOpt lang (generatorFn lang (Just dir)) dir
Nothing -> OutputOpt str (generatorFn str Nothing) "."
pluginName lang = if '/' `elem` lang then lang else "capnpc-" ++ lang
callPlugin lang wd descs transitiveImports = do
let schema = encodeSchema descs transitiveImports
(Just hin, _, _, p) <- createProcess (proc (pluginName lang) [])
{ std_in = CreatePipe, cwd = wd }
hSetBinaryMode hin True
hPut hin (pack schema)
hClose hin
exitCode <- waitForProcess p
case exitCode of
ExitFailure 126 -> do
_ <- printf "Plugin for language '%s' is not executable.\n" lang
exitFailure
ExitFailure 127 -> do
_ <- printf "No plugin found for language '%s'.\n" lang
exitFailure
ExitFailure i -> do
_ <- printf "Plugin for language '%s' failed with exit code: %d\n" lang i
exitFailure
ExitSuccess -> return []
-- As always, here I am, writing my own path manipulation routines, because the ones in the
-- standard lib don't do what I want.
......@@ -227,21 +263,23 @@ parseFile isVerbose searchPath filename text = do
liftIO $ mapM_ printError (List.sortBy compareErrors e)
return $ Right "File contained errors."
handleFile :: [(GeneratorFn, FilePath)] -> Bool -> [FilePath] -> FilePath -> CompilerMonad ()
handleFile outputs isVerbose searchPath filename = do
handleFile :: Bool -> [FilePath] -> FilePath -> CompilerMonad (Maybe FileDesc)
handleFile isVerbose searchPath filename = do
result <- importFile isVerbose searchPath filename
case result of
Right _ -> return ()
Left desc -> do
let write dir (name, content) = do
let outFilename = dir ++ "/" ++ name
createDirectoryIfMissing True $ takeDirectory outFilename
LZ.writeFile outFilename content
generate (generatorFn, dir) = do
files <- generatorFn desc
mapM_ (write dir) files
liftIO $ mapM_ generate outputs
Right _ -> return Nothing
Left desc -> return $ Just desc
doOutput requestedFiles allFiles output = do
let write dir (name, content) = do
let outFilename = dir ++ "/" ++ name
createDirectoryIfMissing True $ takeDirectory outFilename
LZ.writeFile outFilename content
generate (generatorFn, dir) = do
files <- generatorFn requestedFiles allFiles
mapM_ (write dir) files
liftIO $ generate output
compareErrors a b = compare (errorPos a) (errorPos b)
......
......@@ -89,6 +89,7 @@ descId (DescFile d) = fileId d
descId (DescEnum d) = enumId d
descId (DescStruct d) = structId d
descId (DescInterface d) = interfaceId d
descId (DescConstant d) = constantId d
descId (DescAnnotation d) = annotationId d
descId _ = error "This construct does not have an ID."
......@@ -363,6 +364,14 @@ typeName _ (InlineDataType s) = printf "InlineData(%d)" s
-- symbol, and use them if so. A particularly important case of this is imports -- typically
-- the import will have a `using` in the file scope.
descQualifiedName :: Desc -> Desc -> String
-- Builtin descs can be aliased with "using", so we need to support them.
descQualifiedName _ (DescBuiltinType t) = builtinTypeName t
descQualifiedName _ DescBuiltinList = "List"
descQualifiedName _ DescBuiltinInline = "Inline"
descQualifiedName _ DescBuiltinInlineList = "InlineList"
descQualifiedName _ DescBuiltinInlineData = "InlineData"
descQualifiedName (DescFile scope) (DescFile desc) =
if fileName scope == fileName desc
then ""
......@@ -394,6 +403,7 @@ usingRuntimeImports _ = []
data ConstantDesc = ConstantDesc
{ constantName :: String
, constantId :: Word64
, constantParent :: Desc
, constantType :: TypeDesc
, constantAnnotations :: AnnotationMap
......@@ -544,20 +554,22 @@ descToCode indent self@(DescEnum desc) = printf "%senum %s @0x%016x%s {\n%s%s}\n
descToCode indent self@(DescEnumerant desc) = printf "%s%s @%d%s;\n" indent
(enumerantName desc) (enumerantNumber desc)
(annotationsCode self)
descToCode indent self@(DescStruct desc) = printf "%sstruct %s @0x%016x%s%s {\n%s%s}\n" indent
(structName desc)
(structId desc)
(if structIsFixedWidth desc
then printf " fixed(%s, %d pointers) "
(dataSectionSizeString $ structDataSize desc)
(structPointerCount desc)
else "")
(annotationsCode self)
(blockCode indent (structMembers desc))
indent
descToCode indent self@(DescField desc) = printf "%s%s@%d%s: %s%s%s; # %s\n" indent
descToCode indent self@(DescStruct desc) =
printf "%sstruct %s @0x%016x%s%s { # %d bytes, %d pointers\n%s%s}\n" indent
(structName desc)
(structId desc)
(if structIsFixedWidth desc
then printf " fixed(%s, %d pointers) "
(dataSectionSizeString $ structDataSize desc)
(structPointerCount desc)
else "")
(annotationsCode self)
(div (dataSectionBits $ structDataSize desc) 8)
(structPointerCount desc)
(blockCode indent (structMembers desc))
indent
descToCode indent self@(DescField desc) = printf "%s%s@%d: %s%s%s; # %s%s\n" indent
(fieldName desc) (fieldNumber desc)
(case fieldUnion desc of { Nothing -> ""; Just (u, _) -> " in " ++ unionName u})
(typeName (descParent self) (fieldType desc))
(case fieldDefaultValue desc of { Nothing -> ""; Just v -> " = " ++ valueString v; })
(annotationsCode self)
......@@ -572,6 +584,8 @@ descToCode indent self@(DescField desc) = printf "%s%s@%d%s: %s%s%s; # %s\n" in
DataOffset dataSize offset -> let
bits = dataSizeInBits dataSize
in printf "bits[%d, %d)" (offset * bits) ((offset + 1) * bits))
(case fieldUnion desc of { Nothing -> ""; Just (_, i) -> printf ", union tag = %d" i})
descToCode indent self@(DescUnion desc) = printf "%sunion %s@%d%s { # [%d, %d)\n%s%s}\n" indent
(unionName desc) (unionNumber desc)
(annotationsCode self)
......@@ -596,11 +610,11 @@ descToCode _ self@(DescParam desc) = printf "%s: %s%s%s"
Just v -> printf " = %s" $ valueString v
Nothing -> "")
(annotationsCode self)
descToCode indent self@(DescAnnotation desc) = printf "%sannotation %s @0x%016x: %s on(%s)%s;\n" indent
descToCode indent self@(DescAnnotation desc) = printf "%sannotation %s @0x%016x(%s): %s%s;\n" indent
(annotationName desc)
(annotationId desc)
(typeName (descParent self) (annotationType desc))
(delimit ", " $ map show $ Set.toList $ annotationTargets desc)
(typeName (descParent self) (annotationType desc))
(annotationsCode self)
descToCode _ (DescBuiltinType _) = error "Can't print code for builtin type."
descToCode _ DescBuiltinList = error "Can't print code for builtin type."
......
......@@ -21,17 +21,22 @@
-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
module WireFormat(encodeMessage) where
module WireFormat(encodeMessage, encodeSchema) where
import Data.List(sortBy, genericLength, genericReplicate)
import Data.Word
import Data.Bits(shiftL, Bits, setBit, xor)
import Data.Function(on)
import Data.Maybe(mapMaybe, listToMaybe, isNothing)
import Data.List(findIndices)
import qualified Data.Map as Map
import qualified Data.Set as Set
import Semantics
import Data.Binary.IEEE754(floatToWord, doubleToWord)
import Text.Printf(printf)
import qualified Codec.Binary.UTF8.String as UTF8
import Util(intToBytes)
import Grammar(AnnotationTarget(..))
padToWord b = let
trailing = mod (length b) 8
......@@ -77,7 +82,8 @@ encodePointerValue _ (DataDesc d) =
(encodeListReference (SizeData Size8) (genericLength d), padToWord d)
encodePointerValue (StructType desc) (StructValueDesc assignments) = let
(dataBytes, refBytes, childBytes) = encodeStruct desc assignments 0
in (encodeStructReference desc, concat [dataBytes, refBytes, childBytes])
in (encodeStructReference (structDataSize desc, structPointerCount desc),
concat [dataBytes, refBytes, childBytes])
encodePointerValue (InlineStructType _) _ =
error "Tried to encode inline struct as a pointer."
encodePointerValue (ListType elementType) (ListDesc items) = encodeList elementType items
......@@ -126,17 +132,10 @@ packPointers size items o = loop 0 items (o + size - 1) where
in (genericReplicate (padCount * 8) 0 ++ restPtrs, restChildren)
loop idx [] _ = (genericReplicate ((size - idx) * 8) 0, [])
encodeStructReference desc offset =
encodeStructReference (dataSize, pointerCount) offset =
intToBytes (offset * 4 + structTag) 4 ++
intToBytes (dataSectionWordSize $ structDataSize desc) 2 ++
intToBytes (structPointerCount desc) 2
encodeInlineStructListReference elementDataSize elementPointerCount elementCount offset = let
dataBits = dataSectionBits elementDataSize * elementCount
dataWords = div (dataBits + 63) 64
in intToBytes (offset * 4 + structTag) 4 ++
intToBytes dataWords 2 ++
intToBytes (elementPointerCount * elementCount) 2
intToBytes (dataSectionWordSize dataSize) 2 ++
intToBytes pointerCount 2
encodeListReference elemSize@(SizeInlineComposite ds rc) elementCount offset =
intToBytes (offset * 4 + listTag) 4 ++
......@@ -242,7 +241,7 @@ encodeList (StructType desc@StructDesc {
-- Encode a list of any other sort of struct.
encodeList (StructType desc) elements = let
count = genericLength elements
tag = encodeStructReference desc count
tag = encodeStructReference (structDataSize desc, structPointerCount desc) count
eSize = dataSectionWordSize (structDataSize desc) + structPointerCount desc
structElems = [v | StructValueDesc v <- elements]
(elemBytes, childBytes) = loop (eSize * genericLength structElems) structElems
......@@ -340,8 +339,373 @@ inlineStructListPointerSectionValues elementDesc elements = do
encodeMessage (StructType desc) (StructValueDesc assignments) = let
(dataBytes, refBytes, childBytes) = encodeStruct desc assignments 0
in concat [encodeStructReference desc (0::Integer), dataBytes, refBytes, childBytes]
in concat [encodeStructReference (structDataSize desc, structPointerCount desc) (0::Integer),
dataBytes, refBytes, childBytes]
encodeMessage (ListType elementType) (ListDesc elements) = let
(ptr, listBytes) = encodeList elementType elements
in ptr (0::Integer) ++ listBytes
encodeMessage _ _ = error "Not a message."
------------------------------------------------------------------------------------------
type EncodedPtr = (Integer -> [Word8], [Word8])
encodeSchema :: [FileDesc] -> [FileDesc] -> [Word8]
encodeSchema requestedFiles allFiles = encRoot where
encUInt64 = EncodedBytes . flip intToBytes 8
encUInt32 = EncodedBytes . flip intToBytes 4
encUInt16 :: (Integral a, Bits a) => a -> EncodedData
encUInt16 = EncodedBytes . flip intToBytes 2
encText :: String -> EncodedPtr
encText v = encodePointerValue (BuiltinType BuiltinText) (TextDesc v)
encDataList :: DataSize -> [EncodedData] -> EncodedPtr
encDataList elementSize elements = let
elemBits = dataSizeInBits elementSize
bytes = packBytes (elemBits * genericLength elements)
$ zip [0,elemBits..] elements
in (encodeListReference (SizeData elementSize) (genericLength elements), bytes)
encPtrList :: [EncodedPtr] -> EncodedPtr
encPtrList elements = let
(ptrBytes, childBytes) = packPointers (genericLength elements) (zip [0..] elements) 0
in (encodeListReference SizeReference (genericLength elements), ptrBytes ++ childBytes)
encStructList :: (DataSectionSize, Integer)
-> [([(Integer, EncodedData)], [(Integer, EncodedPtr)])]
-> EncodedPtr
encStructList elementSize@(dataSize, pointerCount) elements = let
count = genericLength elements
tag = encodeStructReference elementSize count
eSize = dataSectionWordSize dataSize + pointerCount
(elemBytes, childBytes) = loop (eSize * genericLength elements) elements
loop _ [] = ([], [])
loop offset ((dataValues, ptrValues):rest) = let
offsetFromElementEnd = offset - eSize
(dataBytes, ptrBytes, childBytes2) =
encStructBody elementSize dataValues ptrValues offsetFromElementEnd
childLen = genericLength childBytes2
childWordLen = if mod childLen 8 == 0
then div childLen 8
else error "Child not word-aligned."
(restBytes, restChildren) = loop (offsetFromElementEnd + childWordLen) rest
in (concat [dataBytes, ptrBytes, restBytes], childBytes2 ++ restChildren)
in (encodeListReference (SizeInlineComposite dataSize pointerCount) (genericLength elements),
concat [tag, elemBytes, childBytes])
encStructBody :: (DataSectionSize, Integer)
-> [(Integer, EncodedData)]
-> [(Integer, EncodedPtr)]
-> Integer
-> ([Word8], [Word8], [Word8])
encStructBody (dataSize, pointerCount) dataValues ptrValues offsetFromElementEnd = let
dataBytes = packBytes (dataSectionBits dataSize) dataValues
(ptrBytes, childBytes) = packPointers pointerCount ptrValues offsetFromElementEnd
in (dataBytes, ptrBytes, childBytes)
encStruct :: (DataSectionSize, Integer)
-> ([(Integer, EncodedData)], [(Integer, EncodedPtr)])
-> EncodedPtr
encStruct size (dataValues, ptrValues) = let
(dataBytes, ptrBytes, childBytes) = encStructBody size dataValues ptrValues 0
in (encodeStructReference size, concat [dataBytes, ptrBytes, childBytes])
---------------------------------------------
isNodeDesc (DescFile _) = True
isNodeDesc (DescStruct _) = True
isNodeDesc (DescEnum _) = True
isNodeDesc (DescInterface _) = True
isNodeDesc (DescConstant _) = True
isNodeDesc (DescAnnotation _) = True
isNodeDesc _ = False
descNestedNodes (DescFile d) = filter isNodeDesc $ fileMembers d
descNestedNodes (DescStruct d) = filter isNodeDesc $ structMembers d
descNestedNodes (DescInterface d) = filter isNodeDesc $ interfaceMembers d
descNestedNodes _ = []
flattenDescs desc = desc : concatMap flattenDescs (descNestedNodes desc)
allDescs = concatMap flattenDescs $ map DescFile allFiles
---------------------------------------------
encRoot = let
ptrVal = encStruct codeGeneratorRequestSize encCodeGeneratorRequest
(ptrBytes, childBytes) = packPointers 1 [(0, ptrVal)] 0
segment = ptrBytes ++ childBytes
in concat [[0,0,0,0], intToBytes (div (length segment) 8) 4, segment]
codeGeneratorRequestSize = (DataSectionWords 0, 2)
encCodeGeneratorRequest = (dataValues, ptrValues) where
dataValues = []
ptrValues = [ (0, encStructList nodeSize $ map encNode allDescs)
, (1, encDataList Size64 $ map (encUInt64 . fileId) requestedFiles)
]
typeSize = (DataSectionWords 2, 1)
encType t = (dataValues, ptrValues) where
dataValues = [ (0, encUInt16 discrim)
, (64, encUInt64 typeId)
]
ptrValues = case listElementType of
Nothing -> []
Just et -> [ (0, encStruct typeSize $ encType et) ]
(discrim, typeId, listElementType) = case t of
BuiltinType BuiltinVoid -> (0::Word16, 0, Nothing)
BuiltinType BuiltinBool -> (1, 0, Nothing)
BuiltinType BuiltinInt8 -> (2, 0, Nothing)
BuiltinType BuiltinInt16 -> (3, 0, Nothing)
BuiltinType BuiltinInt32 -> (4, 0, Nothing)
BuiltinType BuiltinInt64 -> (5, 0, Nothing)
BuiltinType BuiltinUInt8 -> (6, 0, Nothing)
BuiltinType BuiltinUInt16 -> (7, 0, Nothing)
BuiltinType BuiltinUInt32 -> (8, 0, Nothing)
BuiltinType BuiltinUInt64 -> (9, 0, Nothing)
BuiltinType BuiltinFloat32 -> (10, 0, Nothing)
BuiltinType BuiltinFloat64 -> (11, 0, Nothing)
BuiltinType BuiltinText -> (12, 0, Nothing)
BuiltinType BuiltinData -> (13, 0, Nothing)
BuiltinType BuiltinObject -> (18, 0, Nothing)
ListType et -> (14, 0, Just et)
EnumType d -> (15, enumId d, Nothing)
StructType d -> (16, structId d, Nothing)
InterfaceType d -> (17, interfaceId d, Nothing)
InlineStructType _ -> error "Inline types not currently supported by codegen plugins."
InlineListType _ _ -> error "Inline types not currently supported by codegen plugins."
InlineDataType _ -> error "Inline types not currently supported by codegen plugins."
valueSize = (DataSectionWords 2, 1)
encValue t maybeValue = (dataValues, ptrValues) where
dataValues = (0, encUInt16 discrim) : (case (maybeValue, fieldSize t) of
(Nothing, _) -> []
(_, SizeVoid) -> []
(Just value, SizeData _) -> [ (64, encodeDataValue t value) ]
(_, SizeReference) -> []
(_, SizeInlineComposite _ _) ->
error "Inline types not currently supported by codegen plugins.")
ptrValues = case (maybeValue, fieldSize t) of
(Nothing, _) -> []
(_, SizeVoid) -> []
(_, SizeData _) -> []
(Just value, SizeReference) -> [ (0, encodePointerValue t value) ]
(_, SizeInlineComposite _ _) ->
error "Inline types not currently supported by codegen plugins."
discrim = case t of
BuiltinType BuiltinVoid -> 9::Word16
BuiltinType BuiltinBool -> 1
BuiltinType BuiltinInt8 -> 2
BuiltinType BuiltinInt16 -> 3
BuiltinType BuiltinInt32 -> 4
BuiltinType BuiltinInt64 -> 5
BuiltinType BuiltinUInt8 -> 6
BuiltinType BuiltinUInt16 -> 7
BuiltinType BuiltinUInt32 -> 8
BuiltinType BuiltinUInt64 -> 0
BuiltinType BuiltinFloat32 -> 10
BuiltinType BuiltinFloat64 -> 11
BuiltinType BuiltinText -> 12
BuiltinType BuiltinData -> 13
BuiltinType BuiltinObject -> 19
ListType _ -> 14
EnumType _ -> 15
StructType _ -> 16
InterfaceType _ -> 17
InlineStructType _ -> error "Inline types not currently supported by codegen plugins."
InlineListType _ _ -> error "Inline types not currently supported by codegen plugins."
InlineDataType _ -> error "Inline types not currently supported by codegen plugins."
annotationSize = (DataSectionWords 1, 1)
encAnnotation (annId, (desc, value)) = (dataValues, ptrValues) where
dataValues = [ (0, encUInt64 annId) ]
ptrValues = [ (0, encStruct valueSize $ encValue (annotationType desc) (Just value)) ]
encAnnotationList annotations =
encStructList annotationSize $ map encAnnotation $ Map.toList annotations
nodeSize = (DataSectionWords 3, 4)
encNode :: Desc -> ([(Integer, EncodedData)], [(Integer, EncodedPtr)])
encNode desc = (dataValues, ptrValues) where
dataValues = [ (0, encUInt64 $ descId desc)
, (64, encUInt64 $ scopedId desc)
, (128, encUInt16 discrim)
]
ptrValues = [ (0, encText $ displayName desc)
, (1, encStructList nestedNodeSize $ map encNestedNode $ descNestedNodes desc)
, (2, encAnnotationList $ descAnnotations desc)
, (3, encStruct bodySize body)
]
(discrim, bodySize, body) = case desc of
DescFile d -> (0::Word16, fileNodeSize, encFileNode d)
DescStruct d -> (1, structNodeSize, encStructNode d)
DescEnum d -> (2, enumNodeSize, encEnumNode d)
DescInterface d -> (3, interfaceNodeSize, encInterfaceNode d)
DescConstant d -> (4, constNodeSize, encConstNode d)
DescAnnotation d -> (5, annotationNodeSize, encAnnotationNode d)
_ -> error "Not a node type."
displayName (DescFile f) = fileName f
displayName desc = concat [fileName (descFile desc), ":", descName desc]
nestedNodeSize = (DataSectionWords 1, 1)
encNestedNode desc = (dataValues, ptrValues) where
dataValues = [ (0, encUInt64 $ descId desc) ]
ptrValues = [ (0, encText $ descName desc) ]
scopedId (DescFile _) = 0
scopedId desc = descId $ descParent desc
fileNodeSize = (DataSectionWords 0, 1)
encFileNode desc = (dataValues, ptrValues) where
dataValues = []
ptrValues = [ (0, encStructList importSize $ map encImport $ Map.toList $ fileImportMap desc) ]
importSize = (DataSectionWords 1, 1)
encImport (impName, impFile) = (dataValues2, ptrValues2) where
dataValues2 = [ (0, encUInt64 $ fileId impFile) ]
ptrValues2 = [ (0, encText impName) ]
structNodeSize = (DataSectionWords 1, 1)
encStructNode desc = (dataValues, ptrValues) where
dataValues = [ (0, encUInt16 $ dataSectionWordSize $ structDataSize desc)
, (16, encUInt16 $ structPointerCount desc)
, (32, encUInt16 (fieldSizeEnum preferredListEncoding::Word16))
]
ptrValues = [ (0, encStructList memberSize $ map encMember $
sortMembers $ structMembers desc) ]
preferredListEncoding = case (structDataSize desc, structPointerCount desc) of
(DataSectionWords 0, 0) -> SizeVoid
(DataSectionWords 0, 1) -> SizeReference
(DataSection1, 0) -> SizeData Size1
(DataSection8, 0) -> SizeData Size8
(DataSection16, 0) -> SizeData Size16
(DataSection32, 0) -> SizeData Size32
(DataSectionWords 1, 0) -> SizeData Size64
(ds, pc) -> SizeInlineComposite ds pc
-- Extract just the field and union members, annotate them with ordinals and code order,
-- and then sort by ordinal.
sortMembers members = sortBy (compare `on` (fst . snd)) $ zip [0::Word16 ..]
$ mapMaybe selectFieldOrUnion members
selectFieldOrUnion d@(DescField f) = Just (fieldNumber f, d)
selectFieldOrUnion d@(DescUnion u) = Just (unionNumber u, d)
selectFieldOrUnion _ = Nothing
memberSize = (DataSectionWords 1, 3)
encMember (codeOrder, (_, DescField field)) = (dataValues2, ptrValues2) where
dataValues2 = [ (0, encUInt16 $ fieldNumber field)
, (16, encUInt16 codeOrder)
, (32, encUInt16 (0::Word16)) -- discriminant
]
ptrValues2 = [ (0, encText $ fieldName field)
, (1, encAnnotationList $ fieldAnnotations field)
, (2, encStruct (DataSection32, 2) (dataValues3, ptrValues3))
]
-- StructNode.Field
dataValues3 = [ (0, encUInt32 $ offsetToInt $ fieldOffset field) ]
ptrValues3 = [ (0, encStruct typeSize $ encType $ fieldType field)
, (1, encStruct valueSize $ encValue (fieldType field) $
fieldDefaultValue field)
]
offsetToInt VoidOffset = 0
offsetToInt (DataOffset _ i) = i
offsetToInt (PointerOffset i) = i
offsetToInt (InlineCompositeOffset {}) =
error "Inline types not currently supported by codegen plugins."
encMember (codeOrder, (_, DescUnion union)) = (dataValues2, ptrValues2) where
dataValues2 = [ (0, encUInt16 $ unionNumber union)
, (16, encUInt16 codeOrder)
, (32, encUInt16 (1::Word16)) -- discriminant
]
ptrValues2 = [ (0, encText $ unionName union)
, (1, encAnnotationList $ unionAnnotations union)
, (2, encStruct (DataSection32, 1) (dataValues3, ptrValues3))
]
-- StructNode.Union
dataValues3 = [ (0, encUInt32 $ unionTagOffset union) ]
ptrValues3 = [ (0, encStructList memberSize $ map encMember $ sortMembers $
unionMembers union) ]
encMember _ = error "Not a field or union?"
enumNodeSize = (DataSectionWords 0, 1)
encEnumNode desc = (dataValues, ptrValues) where
dataValues = []
ptrValues = [ (0, encStructList enumerantSize $ map encEnumerant sortedEnumerants) ]
sortedEnumerants = sortBy (compare `on` (enumerantNumber . snd))
$ zip [0::Word16 ..] $ enumerants desc
enumerantSize = (DataSection16, 2)
encEnumerant (codeOrder, enumerant) = (dataValues2, ptrValues2) where
dataValues2 = [ (0, encUInt16 codeOrder) ]
ptrValues2 = [ (0, encText $ enumerantName enumerant)
, (1, encAnnotationList $ enumerantAnnotations enumerant)
]
interfaceNodeSize = (DataSectionWords 0, 1)
encInterfaceNode desc = (dataValues, ptrValues) where
dataValues = []
ptrValues = [ (0, encStructList methodSize $ map encMethod sortedMethods) ]
sortedMethods = sortBy (compare `on` (methodNumber . snd))
$ zip [0::Word16 ..] $ interfaceMethods desc
methodSize = (DataSection32, 4)
encMethod (codeOrder, method) = (dataValues2, ptrValues2) where
dataValues2 = [ (0, encUInt16 codeOrder)
, (16, encUInt16 requiredParamCount) ]
ptrValues2 = [ (0, encText $ methodName method)
, (1, encStructList paramSize $ map encParam $ methodParams method)
, (2, encStruct typeSize $ encType $ methodReturnType method)
, (3, encAnnotationList $ methodAnnotations method)
]
paramIndicesWithoutDefaults =
findIndices (isNothing . paramDefaultValue) $ methodParams method
requiredParamCount = maybe 0 (+1) $ listToMaybe
$ reverse paramIndicesWithoutDefaults
paramSize = (DataSectionWords 0, 4)
encParam param = (dataValues2, ptrValues2) where
dataValues2 = []
ptrValues2 = [ (0, encText $ paramName param)
, (1, encStruct typeSize $ encType $ paramType param)
, (2, encStruct valueSize $ encValue (paramType param) $
paramDefaultValue param)
, (3, encAnnotationList $ paramAnnotations param)
]
constNodeSize = (DataSectionWords 0, 2)
encConstNode desc = (dataValues, ptrValues) where
dataValues = []
ptrValues = [ (0, encStruct typeSize $ encType $ constantType desc)
, (1, encStruct valueSize $ encValue (constantType desc) $ Just $
constantValue desc)
]
annotationNodeSize = (DataSection16, 1)
encAnnotationNode desc = (dataValues, ptrValues) where
dataValues = [ (0, encTarget FileAnnotation)
, (1, encTarget ConstantAnnotation)
, (2, encTarget EnumAnnotation)
, (3, encTarget EnumerantAnnotation)
, (4, encTarget StructAnnotation)
, (5, encTarget FieldAnnotation)
, (6, encTarget UnionAnnotation)
, (7, encTarget InterfaceAnnotation)
, (8, encTarget MethodAnnotation)
, (9, encTarget ParamAnnotation)
, (10, encTarget AnnotationAnnotation)
]
ptrValues = [ (0, encStruct typeSize $ encType $ annotationType desc) ]
encTarget t = EncodedBit $ Set.member t $ annotationTargets desc
......@@ -392,7 +392,7 @@ annotation qux @0xf8a1bedf44c89f00 (field) :Text;
If you omit the ID for a type or annotation, one will be assigned automatically. This default
ID is derived by taking the first 8 bytes of the MD5 hash of the parent scope's ID concatenated
with the declaration's name (where the parent scope means the file for top-level delarations, or
with the declaration's name (where the "parent scope" is the file for top-level delarations, or
the outer type for nested declarations). You can see the automatically-generated IDs by running
`capnpc -v` on a file. In general, you would only specify an explicit ID for a declaration if that
declaration has been renamed or moved and you want the ID to stay the same for
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment