Added draft of proto compiler. Currently only barebones of Kotlin-class generated - builders are not suported yet

This commit is contained in:
dsavvinov
2016-07-12 20:26:40 +03:00
parent 4f85a7fc30
commit 9fcb992a7f
14 changed files with 791 additions and 0 deletions
@@ -0,0 +1,29 @@
CXX = g++
CXXFLAGS = -O2 -std=c++11
LDFLAGS = -lprotoc -lprotobuf
EXE = protoc
SRCDIR = src
BINDIR = bin
OBJECTS = $(patsubst $(SRCDIR)/%.cc,$(BINDIR)/%.o,$(wildcard $(SRCDIR)/*.cc))
all: $(EXE)
$(EXE): $(BINDIR) $(OBJECTS)
$(CXX) $(OBJECTS) -o $(EXE) $(LDFLAGS)
$(BINDIR)/%.o: $(SRCDIR)/%.cc
$(CXX) $(CXXFLAGS) -c -MMD -o $@ $< -lprotoc
include $(wildcard $(BINDIR)/*.d)
$(BINDIR):
mkdir -p $(BINDIR)
clean:
rm -rf $(BINDIR) $(EXE)
generate:
./protoc --kotlin_out=./ ./test/addressbook.proto
.PHONY: clean all generate
@@ -0,0 +1,22 @@
//
// Created by user on 7/12/16.
//
#include "kotlin_builder_generator.h"
namespace google {
namespace protobuf {
namespace compiler {
namespace kotlin {
BuilderGenerator::BuilderGenerator(const google::protobuf::Descriptor * descriptor) {
}
} // namespace kotlin
} // namespace compiler
} // namespace protobuf
} // namespace google
@@ -0,0 +1,33 @@
//
// Created by user on 7/12/16.
//
#ifndef PROTOBUF_KOTLIN_BUILDER_GENERATOR_H
#define PROTOBUF_KOTLIN_BUILDER_GENERATOR_H
#include <google/protobuf/io/printer.h>
#include <google/protobuf/descriptor.h>
namespace google {
namespace protobuf {
namespace compiler {
namespace kotlin {
}
class BuilderGenerator {
public:
string simpleName;
vector <
BuilderGenerator(Descriptor const * descriptor);
~BuilderGenerator();
void generateCode(io::Printer * printer);
};
} // namespace kotlin
} // namespace compiler
} // namespace protobuf
} // namespace google
#endif //PROTOBUF_KOTLIN_BUILDER_GENERATOR_H
@@ -0,0 +1,175 @@
//
// Created by user on 7/11/16.
//
#include "kotlin_class_generator.h"
#include <iostream>
#include "kotlin_enum_generator.h"
#include "kotlin_field_generator.h"
namespace google {
namespace protobuf {
namespace compiler {
namespace kotlin {
void ClassGenerator::generateCode(io::Printer *printer, bool isBuilder) const {
// print class header
map<string, string> vars;
vars["modifier"] = modifier.getName();
vars["name"] = (isBuilder? "Builder" : "") + simpleName;
printer->Print(vars,
"$modifier$ $name$ private constructor () {"
"\n"
);
printer->Indent();
// generate code for nested classes declarations
for (ClassGenerator *gen: classesDeclarations) {
gen->generateCode(printer, isBuilder);
printer->Print("\n\n"); // separate each definition from next code block with empty line
}
// generate code for nested enums declarations
for (EnumGenerator *gen: enumsDeclaraions) {
gen->generateCode(printer);
printer->Print("\n\n"); // separate each definitions from next code block with empty line
}
// generate code for fields
for (FieldGenerator *gen: properties) {
gen->generateCode(printer);
printer->Print("\n");
}
// generate constructor for builders
if (isBuilder) {
printer->Print("\n");
generateConstructor(printer);
}
// generate builder for fair classes
if (!isBuilder) {
printer->Print("\n");
generateBuilder(printer);
}
printer->Outdent();
printer->Print("}");
}
ClassGenerator::ClassGenerator(Descriptor const *descriptor) {
simpleName = descriptor->name(); // TODO: think about more careful class naming
modifier = ClassModifier(ClassModifier::CLASS);
int field_count = descriptor->field_count();
for (int i = 0; i < field_count; ++i) {
FieldDescriptor const * fieldDescriptor = descriptor->field(i);
properties.push_back(new FieldGenerator(fieldDescriptor));
}
int nested_types_count = descriptor->nested_type_count();
for (int i = 0; i < nested_types_count; ++i) {
Descriptor const * nestedClassDescriptor = descriptor->nested_type(i);
classesDeclarations.push_back(new ClassGenerator(nestedClassDescriptor));
}
int enums_declarations_count = descriptor->enum_type_count();
for (int i = 0; i < enums_declarations_count; ++i) {
EnumDescriptor const * nestedEnumDescriptor = descriptor->enum_type(i);
enumsDeclaraions.push_back(new EnumGenerator(nestedEnumDescriptor));
}
}
ClassGenerator::~ClassGenerator() {
for (int i = 0; i < properties.size(); ++i) {
delete properties[i];
}
for (int i = 0; i < classesDeclarations.size(); ++i) {
delete classesDeclarations[i];
}
for (int i = 0; i < enumsDeclaraions.size(); ++i) {
delete enumsDeclaraions[i];
}
}
void ClassGenerator::generateBuilder(io::Printer *) const {
}
void ClassGenerator::generateConstructor(io::Printer *printer, bool isBuilder) const {
// generate header
printer->Print("private constructor(\n");
// place each argument of constructor in separate line for a prettier code
// we indent twice to make arguments indentation larger than indentation of inner block
printer->Indent();
printer->Indent();
for (int i = 0; i < properties.size(); ++i) {
// generate argument definition
map<string, string> vars;
vars["name"] = properties[i] ->simpleName;
vars["field"] = properties[i]->fieldName;
printer->Print(vars,
"$name$: $field$");
// if it's last property, then print closing bracket for argument list, otherwise put comma
if (i + 1 == properties.size()) {
printer->Print(") : this()\n");
printer->Outdent();
printer->Outdent();
printer->Print("{");
printer->Indent();
}
else {
printer->Print(",");
}
printer->Print("\n");
}
// print body of constructor - just assign arguments to corresponding fields
for (int i = 0; i < properties.size(); ++i) {
map <string, string> vars;
vars["name"] = properties[i]->simpleName;
printer->Print(vars,
"this.$name$ = $name$"
"\n"
);
}
printer->Outdent();
printer->Print("}");
}
const string ClassModifier::getName() const {
string result = "";
switch (type) {
case CLASS:
result = "class";
break;
case INTERFACE:
result = "interface";
break;
}
return result;
}
ClassModifier::ClassModifier(ClassModifier::Type type)
: type(type)
{ }
ClassModifier::ClassModifier() {
type = CLASS;
}
} // namespace kotlin
} // namespace compiler
} // namespace protobuf
} // namespace google
@@ -0,0 +1,59 @@
//
// Created by user on 7/11/16.
//
#ifndef SRC_KOTLIN_CLASS_GENERATOR_H
#define SRC_KOTLIN_CLASS_GENERATOR_H
#include <vector>
#include <google/protobuf/io/printer.h>
#include <google/protobuf/descriptor.h>
#include "kotlin_field_generator.h"
#include "kotlin_enum_generator.h"
namespace google {
namespace protobuf {
namespace compiler {
namespace kotlin {
// wrapper for enum CLASS/INTERFACE with convenience method of getting name
class ClassModifier {
public:
enum Type {
CLASS,
INTERFACE
};
ClassModifier();
ClassModifier(Type type);
Type type;
string const getName() const;
};
class ClassGenerator {
public:
ClassModifier modifier;
string simpleName;
vector <FieldGenerator *> properties;
vector <ClassGenerator *> classesDeclarations;
vector <EnumGenerator *> enumsDeclaraions;
ClassGenerator (Descriptor const * descriptor);
~ClassGenerator ();
/**
* Flag isBuilder used for reducing code repeating, as code for class itself
* and for its inner builder are structurally very alike and can be generated
* with very little differences (like changing 'val's to 'var's and etc.)
*/
void generateCode (io::Printer * printer, bool isBuilder = false) const;
private:
void generateBuilder (io::Printer * printer) const;
void generateConstructor(io::Printer * printer, bool isBuilder = false) const;
};
} // namespace kotlin
} // namspace compiler
} // namespace protobuf
} // namespace google
#endif //SRC_KOTLIN_CLASS_GENERATOR_H
@@ -0,0 +1,73 @@
//
// Created by user on 7/12/16.
//
#include "kotlin_enum_generator.h"
#include <iostream>
namespace google {
namespace protobuf {
namespace compiler {
namespace kotlin {
EnumValueGenerator::EnumValueGenerator(EnumValueDescriptor const *descriptor) {
simpleName = descriptor->name();
ordinal = descriptor->number();
}
void EnumValueGenerator::generateCode(io::Printer * printer) const {
map <string, string> vars;
vars["name"] = simpleName;
vars["ordinal"] = std::to_string(ordinal);
printer->Print(vars,
"$name$ ($ordinal$)"
);
}
void EnumGenerator::generateCode(io::Printer * printer) const {
/**
* Generate enum class header.
* Note that according to protobuf encoding, wire stores enum fields as ints,
* and client is responsible for proper casting those ints to actual enum values.
* Therefore, we have to add int-constructor in generated Kotlin-enum, and assign
* ordinals for each enum-value properly.
*/
map <string, string> vars;
vars["name"] = simpleName;
printer->Print(vars,
"enum class $name$(val ord: Int) {"
"\n"
);
printer->Indent();
// Generate enum values.
for (int i = 0; i < enumValues.size(); ++i) {
enumValues[i]->generateCode(printer);
if (i + 1 != enumValues.size()) {
printer->Print(",");
}
printer->Print("\n");
}
printer->Outdent();
printer->Print("}");
}
EnumGenerator::~EnumGenerator() {
for (int i = 0; i < enumValues.size(); ++i) {
delete enumValues[i];
}
}
EnumGenerator::EnumGenerator(EnumDescriptor const *descriptor) {
simpleName = descriptor->name();
int values_count = descriptor->value_count();
for (int i = 0; i < values_count; ++i) {
enumValues.push_back(new EnumValueGenerator(descriptor->value(i)));
}
}
} // namespace kotlin
} // namspace compiler
} // namespace protobuf
} // namespace google
@@ -0,0 +1,42 @@
//
// Created by user on 7/12/16.
//
#ifndef PROTOBUF_KOTLIN_ENUM_GENERATOR_H
#define PROTOBUF_KOTLIN_ENUM_GENERATOR_H
#include <google/protobuf/io/printer.h>
#include <google/protobuf/descriptor.h>
namespace google {
namespace protobuf {
namespace compiler {
namespace kotlin {
class EnumValueGenerator {
public:
EnumValueGenerator(EnumValueDescriptor const * descriptor);
string simpleName;
int ordinal;
void generateCode(io::Printer *) const;
};
class EnumGenerator {
public:
EnumGenerator(EnumDescriptor const * descriptor);
~EnumGenerator();
string simpleName;
vector <EnumValueGenerator *> enumValues;
void generateCode(io::Printer *) const;
};
} // namespace kotlin
} // namspace compiler
} // namespace protobuf
} // namespace google
#endif //PROTOBUF_KOTLIN_ENUM_GENERATOR_H
@@ -0,0 +1,116 @@
//
// Created by user on 7/12/16.
//
#include "kotlin_field_generator.h"
#include <vector>
#include <google/protobuf/io/printer.h>
#include <google/protobuf/descriptor.h>
#include <iostream>
namespace google {
namespace protobuf {
namespace compiler {
namespace kotlin {
/**
* Gets equivalent Kotlin-type for a given field descriptor.
* This method takes protobuf field type into account, adding 'List<>'
* for repeated fields, and '?' for optional.
*/
string FieldGenerator::protobufToKotlinField() const {
string preamble = "",
postamble = "";
switch (modifier) {
case FieldDescriptor::LABEL_REQUIRED:
break;
case FieldDescriptor::LABEL_OPTIONAL:
postamble = "?";
break;
case FieldDescriptor::LABEL_REPEATED:
preamble = "List <";
postamble = "> ";
break;
}
return preamble + protobufToKotlinType() + postamble;
}
/**
* Simply maps protobuf field type to corresponding Kotlin.
*/
string FieldGenerator::protobufToKotlinType() const {
FieldDescriptor::Type type = descriptor->type();
switch(type) {
case FieldDescriptor::TYPE_BOOL:
return "Boolean";
case FieldDescriptor::TYPE_BYTES:
return ""; // TODO: support bytes type
case FieldDescriptor::TYPE_DOUBLE:
return "Double";
case FieldDescriptor::TYPE_ENUM:
return string(descriptor->enum_type()->name());
case FieldDescriptor::TYPE_FIXED32:
// we map uint32 into Int, storing top bit in sign bit
return "Int";
case FieldDescriptor::TYPE_FIXED64:
// we map uint64 into Long, storing top bit in sign bit
return "Long";
case FieldDescriptor::TYPE_FLOAT:
return "Float";
case FieldDescriptor::TYPE_GROUP:
return ""; // @deprecated //TODO: make proper error handling there
case FieldDescriptor::TYPE_INT32:
return "Int";
case FieldDescriptor::TYPE_INT64:
return "Long";
case FieldDescriptor::TYPE_MESSAGE:
return string(descriptor->message_type()->name());
case FieldDescriptor::TYPE_SFIXED32:
return "Int";
case FieldDescriptor::TYPE_SFIXED64:
return "Long";
case FieldDescriptor::TYPE_SINT32:
return "Int";
case FieldDescriptor::TYPE_SINT64:
return "Long";
case FieldDescriptor::TYPE_STRING:
return "kotlin.String";
case FieldDescriptor::TYPE_UINT32:
return "Int"; // see notes for TYPE_FIXED32
case FieldDescriptor::TYPE_UINT64:
return "Long"; // see notes for TYPE_FIXED64
}
}
string FieldGenerator::getInitValue() const {
if (descriptor->is_optional()) {
return "null";
}
if (descriptor->is_repeated())
return "listOf()";
return fieldName + "()";
}
void FieldGenerator::generateCode(io::Printer *printer) const {
/** Generate Kotlin-code for field.
* Note that we use 'val' everywhere, as we want messages to be immutable.
* For constructing Messages corresponding Builders should be used.
*/
map<string, string> vars;
vars["name"] = simpleName;
vars["field"] = protobufToKotlinField();
vars["initValue"] = initValue;
printer->Print(vars, "val $name$ : $field$ = $initValue$");
}
FieldGenerator::FieldGenerator(FieldDescriptor const * descriptor)
: descriptor(descriptor)
, modifier(descriptor->label())
, simpleName(descriptor->name())
, fieldName(protobufToKotlinField())
, initValue(getInitValue())
{ }
} // namespace kotlin
} // namspace compiler
} // namespace protobuf
} // namespace google
@@ -0,0 +1,40 @@
//
// Created by user on 7/12/16.
//
#ifndef PROTOBUF_KOTLIN_FIELD_GENERATOR_H
#define PROTOBUF_KOTLIN_FIELD_GENERATOR_H
#include <vector>
#include <google/protobuf/io/printer.h>
#include <google/protobuf/descriptor.h>
namespace google {
namespace protobuf {
namespace compiler {
namespace kotlin {
class FieldGenerator {
private:
FieldDescriptor const * descriptor;
string protobufToKotlinField() const;
string protobufToKotlinType () const;
string getInitValue() const;
public:
FieldDescriptor::Label modifier;
string simpleName;
string fieldName;
string initValue;
void generateCode(io::Printer *) const;
FieldGenerator(FieldDescriptor const * descriptor);
};
} // namespace kotlin
} // namspace compiler
} // namespace protobuf
} // namespace google
#endif //PROTOBUF_KOTLIN_FIELD_GENERATOR_H
@@ -0,0 +1,65 @@
//
// Created by user on 7/11/16.
//
#include <google/protobuf/descriptor.h>
#include <google/protobuf/stubs/shared_ptr.h>
#include <google/protobuf/io/zero_copy_stream.h>
#ifndef _SHARED_PTR_H
#include <google/protobuf/stubs/shared_ptr.h>
#endif
#include "kotlin_file_generator.h"
#include <iostream>
namespace google {
namespace protobuf {
namespace compiler {
namespace kotlin {
void FileGenerator::generateCode(io::Printer *printer, std::vector<ClassGenerator *> & classes) const {
for (int i = 0; i < classes.size(); ++i) {
classes[i]->generateCode(printer);
printer->Print("\n\n");
}
}
bool FileGenerator::Generate(const FileDescriptor *file, const string &parameter, GeneratorContext *context,
string *error) const {
std::vector<ClassGenerator *> classes;
//TODO: maybe wrap work with class names and stuff in separate class (like in Java implementation)
// Get output file name
string const proto_file_name = file->name();
size_t file_extension_index = proto_file_name.find(".proto");
string const file_name = proto_file_name.substr(0, file_extension_index) + ".kt";
google::protobuf::scoped_ptr<io::ZeroCopyOutputStream> output(context->Open(file_name));
io::Printer printer(output.get(), '$', /* annotation_collector = */ NULL);
// Create Generators for all top-level messages
int topLevelMessagesCount = file->message_type_count();
for (int i = 0; i < topLevelMessagesCount; ++i) {
Descriptor const * descriptor = file->message_type(i);
ClassGenerator * cgen = new ClassGenerator(descriptor);
classes.push_back(cgen);
}
// Generate code and clean up
generateCode(&printer, classes);
for (int i = 0; i < classes.size(); ++i) {
delete classes[i];
}
return true;
}
FileGenerator::FileGenerator() { }
FileGenerator::~FileGenerator() {
}
} // namespace kotlin
} // namespace compiler
} // namespace protobuf
} // namespace google
@@ -0,0 +1,37 @@
//
// Created by user on 7/11/16.
//
#ifndef SRC_KOTLIN_FILE_GENERATOR_H
#define SRC_KOTLIN_FILE_GENERATOR_H
#include "kotlin_class_generator.h"
#include <vector>
#include <google/protobuf/io/printer.h>
#include <google/protobuf/compiler/code_generator.h>
namespace google {
namespace protobuf {
namespace compiler {
namespace kotlin {
class FileGenerator : public CodeGenerator {
public:
FileGenerator();
~FileGenerator();
void generateCode(io::Printer *, std::vector<ClassGenerator *> &) const;
// implements CodeGenerator ----------------------------------------
bool Generate(const FileDescriptor* file,
const string& parameter,
GeneratorContext* context,
string* error) const;
};
} // namespace kotlin
} // namespace compiler
} // namespace protobuf
} // namespace google
#endif //SRC_KOTLIN_FILE_GENERATOR_H
@@ -0,0 +1,28 @@
#include <google/protobuf/compiler/command_line_interface.h>
#include <google/protobuf/compiler/cpp/cpp_generator.h>
#include <google/protobuf/compiler/python/python_generator.h>
#include <google/protobuf/compiler/java/java_generator.h>
#include <google/protobuf/compiler/javanano/javanano_generator.h>
// TODO(teboring): Add it back when php implementation is ready
// #include <google/protobuf/compiler/php/php_generator.h>
#include <google/protobuf/compiler/ruby/ruby_generator.h>
#include <google/protobuf/compiler/csharp/csharp_generator.h>
#include <google/protobuf/compiler/objectivec/objectivec_generator.h>
#include <google/protobuf/compiler/js/js_generator.h>
#include "kotlin_file_generator.h"
#include <iostream>
int main(int argc, const char* const * argv) {
google::protobuf::compiler::CommandLineInterface cli;
// Support generation of C++ source and headers.
google::protobuf::compiler::cpp::CppGenerator cpp_generator;
cli.RegisterGenerator("--cpp_out", &cpp_generator,
"Generate C++ source and header.");
google::protobuf::compiler::kotlin::FileGenerator kotlinGenerator;
cli.RegisterGenerator("--kotlin_out", &kotlinGenerator,
"Generate Foo file.");
return cli.Run(argc, argv);
}
@@ -0,0 +1,25 @@
class Person private constructor () {
class PhoneNumber private constructor () {
val number : kotlin.String? = null
val type : PhoneType? = null
}
enum class PhoneType(val ord: Int) {
MOBILE (0),
HOME (1),
WORK (2)
}
val name : kotlin.String? = null
val id : Int? = null
val email : kotlin.String? = null
val phones : List <PhoneNumber> = listOf()
}
class AddressBook private constructor () {
val people : List <Person> = listOf()
}
@@ -0,0 +1,47 @@
// See README.txt for information and build instructions.
//
// Note: START and END tags are used in comments to define sections used in
// tutorials. They are not part of the syntax for Protocol Buffers.
//
// To get an in-depth walkthrough of this file and the related examples, see:
// https://developers.google.com/protocol-buffers/docs/tutorials
// [START declaration]
syntax = "proto3";
package tutorial;
// [END declaration]
// [START java_declaration]
option java_package = "com.example.tutorial";
option java_outer_classname = "AddressBookProtos";
// [END java_declaration]
// [START csharp_declaration]
option csharp_namespace = "Google.Protobuf.Examples.AddressBook";
// [END csharp_declaration]
// [START messages]
message Person {
string name = 1;
int32 id = 2; // Unique ID number for this person.
string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phones = 4;
}
// Our address book file is just one of these.
message AddressBook {
repeated Person people = 1;
}
// [END messages]