From d0535cc09e6eac1bddddd51c20b5738c0e841765 Mon Sep 17 00:00:00 2001 From: Harshit Chopra Date: Fri, 25 Aug 2017 12:11:15 -0700 Subject: Adds support for proto2 syntax for Ruby gem. This change only adds basic proto2 support without advanced features like extensions, custom options, maps, etc. The protoc binary now generates ruby code for proto2 syntax. However, for now, it is restricted to proto2 files without advanced features like extensions, in which case it still errors out. This change also modifies the DSL to add proto messages to the DescriptorPool. There is a new DSL Builder#add_file to create a new FileDescriptor. With this, the generated ruby DSL looks something like: Google::Protobuf::DescriptorPool.generated_pool.build do add_file "test.proto" do add_message "foo" do optional :val, :int32, 1 end end end --- ruby/ext/google/protobuf_c/defs.c | 615 ++++++++++++++++++++++++++++++++++---- 1 file changed, 549 insertions(+), 66 deletions(-) (limited to 'ruby/ext/google/protobuf_c/defs.c') diff --git a/ruby/ext/google/protobuf_c/defs.c b/ruby/ext/google/protobuf_c/defs.c index 9fe04503..dbbff88c 100644 --- a/ruby/ext/google/protobuf_c/defs.c +++ b/ruby/ext/google/protobuf_c/defs.c @@ -122,7 +122,7 @@ void DescriptorPool_register(VALUE module) { module, "DescriptorPool", rb_cObject); rb_define_alloc_func(klass, DescriptorPool_alloc); rb_define_method(klass, "add", DescriptorPool_add, 1); - rb_define_method(klass, "build", DescriptorPool_build, 0); + rb_define_method(klass, "build", DescriptorPool_build, -1); rb_define_method(klass, "lookup", DescriptorPool_lookup, 1); rb_define_singleton_method(klass, "generated_pool", DescriptorPool_generated_pool, 0); @@ -181,7 +181,7 @@ VALUE DescriptorPool_add(VALUE _self, VALUE def) { * Builder#add_enum within the block as appropriate. This is the recommended, * idiomatic way to define new message and enum types. */ -VALUE DescriptorPool_build(VALUE _self) { +VALUE DescriptorPool_build(int argc, VALUE* argv, VALUE _self) { VALUE ctx = rb_class_new_instance(0, NULL, cBuilder); VALUE block = rb_block_proc(); rb_funcall_with_block(ctx, rb_intern("instance_eval"), 0, NULL, block); @@ -289,6 +289,7 @@ void Descriptor_register(VALUE module) { VALUE klass = rb_define_class_under( module, "Descriptor", rb_cObject); rb_define_alloc_func(klass, Descriptor_alloc); + rb_define_method(klass, "initialize", Descriptor_initialize, 1); rb_define_method(klass, "each", Descriptor_each, 0); rb_define_method(klass, "lookup", Descriptor_lookup, 1); rb_define_method(klass, "add_field", Descriptor_add_field, 1); @@ -298,11 +299,42 @@ void Descriptor_register(VALUE module) { rb_define_method(klass, "msgclass", Descriptor_msgclass, 0); rb_define_method(klass, "name", Descriptor_name, 0); rb_define_method(klass, "name=", Descriptor_name_set, 1); + rb_define_method(klass, "file_descriptor", Descriptor_file_descriptor, 0); rb_include_module(klass, rb_mEnumerable); rb_gc_register_address(&cDescriptor); cDescriptor = klass; } +/* + * call-seq: + * Descriptor.new(file_descriptor) + * + * Initializes a new descriptor and assigns a file descriptor to it. + */ +VALUE Descriptor_initialize(VALUE _self, VALUE file_descriptor_rb) { + DEFINE_SELF(Descriptor, self, _self); + + FileDescriptor* file_descriptor = ruby_to_FileDescriptor(file_descriptor_rb); + + CHECK_UPB( + upb_filedef_addmsg(file_descriptor->filedef, self->msgdef, NULL, &status), + "Failed to associate message to file descriptor."); + add_def_obj(file_descriptor->filedef, file_descriptor_rb); + + return Qnil; +} + +/* + * call-seq: + * Descriptor.file_descriptor + * + * Returns the FileDescriptor object this message belongs to. + */ +VALUE Descriptor_file_descriptor(VALUE _self) { + DEFINE_SELF(Descriptor, self, _self); + return get_def_obj(upb_def_file(self->msgdef)); +} + /* * call-seq: * Descriptor.name => name @@ -470,6 +502,142 @@ VALUE Descriptor_msgclass(VALUE _self) { return self->klass; } +// ----------------------------------------------------------------------------- +// FileDescriptor. +// ----------------------------------------------------------------------------- + +DEFINE_CLASS(FileDescriptor, "Google::Protobuf::FileDescriptor"); + +void FileDescriptor_mark(void* _self) { +} + +void FileDescriptor_free(void* _self) { + FileDescriptor* self = _self; + upb_filedef_unref(self->filedef, &self->filedef); + xfree(self); +} + +/* + * call-seq: + * FileDescriptor.new => file + * + * Returns a new file descriptor. The syntax must be set before it's passed + * to a builder. + */ +VALUE FileDescriptor_alloc(VALUE klass) { + FileDescriptor* self = ALLOC(FileDescriptor); + VALUE ret = TypedData_Wrap_Struct(klass, &_FileDescriptor_type, self); + upb_filedef* filedef = upb_filedef_new(&self->filedef); + self->filedef = filedef; + return ret; +} + +void FileDescriptor_register(VALUE module) { + VALUE klass = rb_define_class_under( + module, "FileDescriptor", rb_cObject); + rb_define_alloc_func(klass, FileDescriptor_alloc); + rb_define_method(klass, "initialize", FileDescriptor_initialize, -1); + rb_define_method(klass, "name", FileDescriptor_name, 0); + rb_define_method(klass, "syntax", FileDescriptor_syntax, 0); + rb_define_method(klass, "syntax=", FileDescriptor_syntax_set, 1); + cFileDescriptor = klass; + rb_gc_register_address(&cFileDescriptor); +} + +/* + * call-seq: + * FileDescriptor.new(name, options = nil) => file + * + * Initializes a new file descriptor with the given file name. + * Also accepts an optional "options" hash, specifying other optional + * metadata about the file. The options hash currently accepts the following + * * "syntax": :proto2 or :proto3 (default: :proto3) + */ +VALUE FileDescriptor_initialize(int argc, VALUE* argv, VALUE _self) { + DEFINE_SELF(FileDescriptor, self, _self); + + VALUE name_rb; + VALUE options = Qnil; + rb_scan_args(argc, argv, "11", &name_rb, &options); + + if (name_rb != Qnil) { + Check_Type(name_rb, T_STRING); + const char* name = get_str(name_rb); + CHECK_UPB(upb_filedef_setname(self->filedef, name, &status), + "Error setting file name"); + } + + // Default syntax is proto3. + VALUE syntax = ID2SYM(rb_intern("proto3")); + if (options != Qnil) { + Check_Type(options, T_HASH); + + if (rb_funcall(options, rb_intern("key?"), 1, + ID2SYM(rb_intern("syntax"))) == Qtrue) { + syntax = rb_hash_lookup(options, ID2SYM(rb_intern("syntax"))); + } + } + FileDescriptor_syntax_set(_self, syntax); + + return Qnil; +} + +/* + * call-seq: + * FileDescriptor.name => name + * + * Returns the name of the file. + */ +VALUE FileDescriptor_name(VALUE _self) { + DEFINE_SELF(FileDescriptor, self, _self); + const char* name = upb_filedef_name(self->filedef); + return name == NULL ? Qnil : rb_str_new2(name); +} + +/* + * call-seq: + * FileDescriptor.syntax => syntax + * + * Returns this file descriptors syntax. + * + * Valid syntax versions are: + * :proto2 or :proto3. + */ +VALUE FileDescriptor_syntax(VALUE _self) { + DEFINE_SELF(FileDescriptor, self, _self); + + switch (upb_filedef_syntax(self->filedef)) { + case UPB_SYNTAX_PROTO3: return ID2SYM(rb_intern("proto3")); + case UPB_SYNTAX_PROTO2: return ID2SYM(rb_intern("proto2")); + default: return Qnil; + } +} + +/* + * call-seq: + * FileDescriptor.syntax = version + * + * Sets this file descriptor's syntax, can be :proto3 or :proto2. + */ +VALUE FileDescriptor_syntax_set(VALUE _self, VALUE syntax_rb) { + DEFINE_SELF(FileDescriptor, self, _self); + Check_Type(syntax_rb, T_SYMBOL); + + upb_syntax_t syntax; + if (SYM2ID(syntax_rb) == rb_intern("proto3")) { + syntax = UPB_SYNTAX_PROTO3; + } else if (SYM2ID(syntax_rb) == rb_intern("proto2")) { + syntax = UPB_SYNTAX_PROTO2; + } else { + rb_raise(rb_eArgError, "Expected :proto3 or :proto3, received '%s'", + rb_id2name(SYM2ID(syntax_rb))); + } + + CHECK_UPB(upb_filedef_setsyntax(self->filedef, syntax, &status), + "Error setting file syntax for proto"); + return Qnil; +} + // ----------------------------------------------------------------------------- // FieldDescriptor. // ----------------------------------------------------------------------------- @@ -509,6 +677,8 @@ void FieldDescriptor_register(VALUE module) { rb_define_method(klass, "name=", FieldDescriptor_name_set, 1); rb_define_method(klass, "type", FieldDescriptor_type, 0); rb_define_method(klass, "type=", FieldDescriptor_type_set, 1); + rb_define_method(klass, "default", FieldDescriptor_default, 0); + rb_define_method(klass, "default=", FieldDescriptor_default_set, 1); rb_define_method(klass, "label", FieldDescriptor_label, 0); rb_define_method(klass, "label=", FieldDescriptor_label_set, 1); rb_define_method(klass, "number", FieldDescriptor_number, 0); @@ -516,6 +686,8 @@ void FieldDescriptor_register(VALUE module) { rb_define_method(klass, "submsg_name", FieldDescriptor_submsg_name, 0); rb_define_method(klass, "submsg_name=", FieldDescriptor_submsg_name_set, 1); rb_define_method(klass, "subtype", FieldDescriptor_subtype, 0); + rb_define_method(klass, "has?", FieldDescriptor_has, 1); + rb_define_method(klass, "clear", FieldDescriptor_clear, 1); rb_define_method(klass, "get", FieldDescriptor_get, 1); rb_define_method(klass, "set", FieldDescriptor_set, 2); rb_gc_register_address(&cFieldDescriptor); @@ -691,6 +863,71 @@ VALUE FieldDescriptor_type_set(VALUE _self, VALUE type) { return Qnil; } +/* + * call-seq: + * FieldDescriptor.default => default + * + * Returns this field's default, as a Ruby object, or nil if not yet set. + */ +VALUE FieldDescriptor_default(VALUE _self) { + DEFINE_SELF(FieldDescriptor, self, _self); + return layout_get_default(self->fielddef); +} + +/* + * call-seq: + * FieldDescriptor.default = default + * + * Sets this field's default value. Raises an exception when calling with + * proto syntax 3. + */ +VALUE FieldDescriptor_default_set(VALUE _self, VALUE default_value) { + DEFINE_SELF(FieldDescriptor, self, _self); + upb_fielddef* mut_def = check_field_notfrozen(self->fielddef); + + switch (upb_fielddef_type(mut_def)) { + case UPB_TYPE_FLOAT: + upb_fielddef_setdefaultfloat(mut_def, NUM2DBL(default_value)); + break; + case UPB_TYPE_DOUBLE: + upb_fielddef_setdefaultdouble(mut_def, NUM2DBL(default_value)); + break; + case UPB_TYPE_BOOL: + if (!RB_TYPE_P(default_value, T_TRUE) && + !RB_TYPE_P(default_value, T_FALSE) && + !RB_TYPE_P(default_value, T_NIL)) { + rb_raise(cTypeError, "Expected boolean for default value."); + } + + upb_fielddef_setdefaultbool(mut_def, RTEST(default_value)); + break; + case UPB_TYPE_ENUM: + case UPB_TYPE_INT32: + upb_fielddef_setdefaultint32(mut_def, NUM2INT(default_value)); + break; + case UPB_TYPE_INT64: + upb_fielddef_setdefaultint64(mut_def, NUM2INT(default_value)); + break; + case UPB_TYPE_UINT32: + upb_fielddef_setdefaultuint32(mut_def, NUM2UINT(default_value)); + break; + case UPB_TYPE_UINT64: + upb_fielddef_setdefaultuint64(mut_def, NUM2UINT(default_value)); + break; + case UPB_TYPE_STRING: + case UPB_TYPE_BYTES: + CHECK_UPB(upb_fielddef_setdefaultcstr(mut_def, StringValuePtr(default_value), + &status), + "Error setting default string"); + break; + default: + rb_raise(rb_eArgError, "Defaults not supported on field %s.%s", + upb_fielddef_fullname(mut_def), upb_fielddef_name(mut_def)); + } + + return Qnil; +} + /* * call-seq: * FieldDescriptor.label => label @@ -859,6 +1096,44 @@ VALUE FieldDescriptor_get(VALUE _self, VALUE msg_rb) { return layout_get(msg->descriptor->layout, Message_data(msg), self->fielddef); } +/* + * call-seq: + * FieldDescriptor.has?(message) => boolean + * + * Returns whether the value is set on the given message. Raises an + * exception when calling with proto syntax 3. + */ +VALUE FieldDescriptor_has(VALUE _self, VALUE msg_rb) { + DEFINE_SELF(FieldDescriptor, self, _self); + MessageHeader* msg; + TypedData_Get_Struct(msg_rb, MessageHeader, &Message_type, msg); + if (msg->descriptor->msgdef != upb_fielddef_containingtype(self->fielddef)) { + rb_raise(cTypeError, "has method called on wrong message type"); + } else if (!upb_fielddef_haspresence(self->fielddef)) { + rb_raise(rb_eArgError, "does not track presence"); + } + + return layout_has(msg->descriptor->layout, Message_data(msg), self->fielddef); +} + +/* + * call-seq: + * FieldDescriptor.clear(message) + * + * Clears the field from the message if it's set. + */ +VALUE FieldDescriptor_clear(VALUE _self, VALUE msg_rb) { + DEFINE_SELF(FieldDescriptor, self, _self); + MessageHeader* msg; + TypedData_Get_Struct(msg_rb, MessageHeader, &Message_type, msg); + if (msg->descriptor->msgdef != upb_fielddef_containingtype(self->fielddef)) { + rb_raise(cTypeError, "has method called on wrong message type"); + } + + layout_clear(msg->descriptor->layout, Message_data(msg), self->fielddef); + return Qnil; +} + /* * call-seq: * FieldDescriptor.set(message, value) @@ -1029,6 +1304,7 @@ void EnumDescriptor_register(VALUE module) { VALUE klass = rb_define_class_under( module, "EnumDescriptor", rb_cObject); rb_define_alloc_func(klass, EnumDescriptor_alloc); + rb_define_method(klass, "initialize", EnumDescriptor_initialize, 1); rb_define_method(klass, "name", EnumDescriptor_name, 0); rb_define_method(klass, "name=", EnumDescriptor_name_set, 1); rb_define_method(klass, "add_value", EnumDescriptor_add_value, 2); @@ -1036,11 +1312,41 @@ void EnumDescriptor_register(VALUE module) { rb_define_method(klass, "lookup_value", EnumDescriptor_lookup_value, 1); rb_define_method(klass, "each", EnumDescriptor_each, 0); rb_define_method(klass, "enummodule", EnumDescriptor_enummodule, 0); + rb_define_method(klass, "file_descriptor", EnumDescriptor_file_descriptor, 0); rb_include_module(klass, rb_mEnumerable); rb_gc_register_address(&cEnumDescriptor); cEnumDescriptor = klass; } +/* + * call-seq: + * Descriptor.new(file_descriptor) + * + * Initializes a new descriptor and assigns a file descriptor to it. + */ +VALUE EnumDescriptor_initialize(VALUE _self, VALUE file_descriptor_rb) { + DEFINE_SELF(EnumDescriptor, self, _self); + FileDescriptor* file_descriptor = ruby_to_FileDescriptor(file_descriptor_rb); + CHECK_UPB( + upb_filedef_addenum(file_descriptor->filedef, self->enumdef, + NULL, &status), + "Failed to associate enum to file descriptor."); + add_def_obj(file_descriptor->filedef, file_descriptor_rb); + + return Qnil; +} + +/* + * call-seq: + * Descriptor.file_descriptor + * + * Returns the FileDescriptor object this enum belongs to. + */ +VALUE EnumDescriptor_file_descriptor(VALUE _self) { + DEFINE_SELF(EnumDescriptor, self, _self); + return get_def_obj(upb_def_file(self->enumdef)); +} + /* * call-seq: * EnumDescriptor.name => name @@ -1223,34 +1529,56 @@ VALUE MessageBuilderContext_initialize(VALUE _self, return Qnil; } -static VALUE msgdef_add_field(VALUE msgdef, +static VALUE msgdef_add_field(VALUE msgdef_rb, const char* label, VALUE name, VALUE type, VALUE number, - VALUE type_class) { - VALUE fielddef = rb_class_new_instance(0, NULL, cFieldDescriptor); + VALUE type_class, + VALUE options) { + VALUE fielddef_rb = rb_class_new_instance(0, NULL, cFieldDescriptor); VALUE name_str = rb_str_new2(rb_id2name(SYM2ID(name))); - rb_funcall(fielddef, rb_intern("label="), 1, ID2SYM(rb_intern(label))); - rb_funcall(fielddef, rb_intern("name="), 1, name_str); - rb_funcall(fielddef, rb_intern("type="), 1, type); - rb_funcall(fielddef, rb_intern("number="), 1, number); + rb_funcall(fielddef_rb, rb_intern("label="), 1, ID2SYM(rb_intern(label))); + rb_funcall(fielddef_rb, rb_intern("name="), 1, name_str); + rb_funcall(fielddef_rb, rb_intern("type="), 1, type); + rb_funcall(fielddef_rb, rb_intern("number="), 1, number); if (type_class != Qnil) { - if (TYPE(type_class) != T_STRING) { - rb_raise(rb_eArgError, "Expected string for type class"); - } + Check_Type(type_class, T_STRING); + // Make it an absolute type name by prepending a dot. type_class = rb_str_append(rb_str_new2("."), type_class); - rb_funcall(fielddef, rb_intern("submsg_name="), 1, type_class); + rb_funcall(fielddef_rb, rb_intern("submsg_name="), 1, type_class); } - rb_funcall(msgdef, rb_intern("add_field"), 1, fielddef); - return fielddef; + if (options != Qnil) { + Check_Type(options, T_HASH); + + if (rb_funcall(options, rb_intern("key?"), 1, + ID2SYM(rb_intern("default"))) == Qtrue) { + Descriptor* msgdef = ruby_to_Descriptor(msgdef_rb); + if (upb_msgdef_syntax((upb_msgdef*)msgdef->msgdef) == UPB_SYNTAX_PROTO3) { + rb_raise(rb_eArgError, "Cannot set :default when using proto3 syntax."); + } + + FieldDescriptor* fielddef = ruby_to_FieldDescriptor(fielddef_rb); + if (!upb_fielddef_haspresence((upb_fielddef*)fielddef->fielddef) || + upb_fielddef_issubmsg((upb_fielddef*)fielddef->fielddef)) { + rb_raise(rb_eArgError, "Cannot set :default on this kind of field."); + } + + rb_funcall(fielddef_rb, rb_intern("default="), 1, + rb_hash_lookup(options, ID2SYM(rb_intern("default")))); + } + } + + rb_funcall(msgdef_rb, rb_intern("add_field"), 1, fielddef_rb); + return fielddef_rb; } /* * call-seq: - * MessageBuilderContext.optional(name, type, number, type_class = nil) + * MessageBuilderContext.optional(name, type, number, type_class = nil, + * options = nil) * * Defines a new optional field on this message type with the given type, tag * number, and type class (for message and enum fields). The type must be a Ruby @@ -1259,23 +1587,26 @@ static VALUE msgdef_add_field(VALUE msgdef, */ VALUE MessageBuilderContext_optional(int argc, VALUE* argv, VALUE _self) { DEFINE_SELF(MessageBuilderContext, self, _self); - VALUE name, type, number, type_class; + VALUE name, type, number; + VALUE type_class, options = Qnil; - if (argc < 3) { - rb_raise(rb_eArgError, "Expected at least 3 arguments."); + rb_scan_args(argc, argv, "32", &name, &type, &number, &type_class, &options); + + // Allow passing (name, type, number, options) or + // (name, type, number, type_class, options) + if (argc == 4 && RB_TYPE_P(type_class, T_HASH)) { + options = type_class; + type_class = Qnil; } - name = argv[0]; - type = argv[1]; - number = argv[2]; - type_class = (argc > 3) ? argv[3] : Qnil; return msgdef_add_field(self->descriptor, "optional", - name, type, number, type_class); + name, type, number, type_class, options); } /* * call-seq: - * MessageBuilderContext.required(name, type, number, type_class = nil) + * MessageBuilderContext.required(name, type, number, type_class = nil, + * options = nil) * * Defines a new required field on this message type with the given type, tag * number, and type class (for message and enum fields). The type must be a Ruby @@ -1288,18 +1619,20 @@ VALUE MessageBuilderContext_optional(int argc, VALUE* argv, VALUE _self) { */ VALUE MessageBuilderContext_required(int argc, VALUE* argv, VALUE _self) { DEFINE_SELF(MessageBuilderContext, self, _self); - VALUE name, type, number, type_class; + VALUE name, type, number; + VALUE type_class, options = Qnil; - if (argc < 3) { - rb_raise(rb_eArgError, "Expected at least 3 arguments."); + rb_scan_args(argc, argv, "32", &name, &type, &number, &type_class, &options); + + // Allow passing (name, type, number, options) or + // (name, type, number, type_class, options) + if (argc == 4 && RB_TYPE_P(type_class, T_HASH)) { + options = type_class; + type_class = Qnil; } - name = argv[0]; - type = argv[1]; - number = argv[2]; - type_class = (argc > 3) ? argv[3] : Qnil; return msgdef_add_field(self->descriptor, "required", - name, type, number, type_class); + name, type, number, type_class, options); } /* @@ -1324,7 +1657,7 @@ VALUE MessageBuilderContext_repeated(int argc, VALUE* argv, VALUE _self) { type_class = (argc > 3) ? argv[3] : Qnil; return msgdef_add_field(self->descriptor, "repeated", - name, type, number, type_class); + name, type, number, type_class, Qnil); } /* @@ -1365,9 +1698,17 @@ VALUE MessageBuilderContext_map(int argc, VALUE* argv, VALUE _self) { "type."); } + Descriptor* descriptor = ruby_to_Descriptor(self->descriptor); + if (upb_msgdef_syntax(descriptor->msgdef) == UPB_SYNTAX_PROTO2) { + rb_raise(rb_eArgError, + "Cannot add a native map field using proto2 syntax."); + } + // Create a new message descriptor for the map entry message, and create a // repeated submessage field here with that type. - mapentry_desc = rb_class_new_instance(0, NULL, cDescriptor); + VALUE file_descriptor_rb = + rb_funcall(self->descriptor, rb_intern("file_descriptor"), 0); + mapentry_desc = rb_class_new_instance(1, &file_descriptor_rb, cDescriptor); mapentry_desc_name = rb_funcall(self->descriptor, rb_intern("name"), 0); mapentry_desc_name = rb_str_cat2(mapentry_desc_name, "_MapEntry_"); mapentry_desc_name = rb_str_cat2(mapentry_desc_name, @@ -1410,8 +1751,8 @@ VALUE MessageBuilderContext_map(int argc, VALUE* argv, VALUE _self) { { // Add the map-entry message type to the current builder, and use the type // to create the map field itself. - Builder* builder_self = ruby_to_Builder(self->builder); - rb_ary_push(builder_self->pending_list, mapentry_desc); + Builder* builder = ruby_to_Builder(self->builder); + rb_ary_push(builder->pending_list, mapentry_desc); } { @@ -1514,7 +1855,8 @@ VALUE OneofBuilderContext_initialize(VALUE _self, /* * call-seq: - * OneofBuilderContext.optional(name, type, number, type_class = nil) + * OneofBuilderContext.optional(name, type, number, type_class = nil, + * default_value = nil) * * Defines a new optional field in this oneof with the given type, tag number, * and type class (for message and enum fields). The type must be a Ruby symbol @@ -1523,18 +1865,13 @@ VALUE OneofBuilderContext_initialize(VALUE _self, */ VALUE OneofBuilderContext_optional(int argc, VALUE* argv, VALUE _self) { DEFINE_SELF(OneofBuilderContext, self, _self); - VALUE name, type, number, type_class; + VALUE name, type, number; + VALUE type_class, options = Qnil; - if (argc < 3) { - rb_raise(rb_eArgError, "Expected at least 3 arguments."); - } - name = argv[0]; - type = argv[1]; - number = argv[2]; - type_class = (argc > 3) ? argv[3] : Qnil; + rb_scan_args(argc, argv, "32", &name, &type, &number, &type_class, &options); return msgdef_add_field(self->descriptor, "optional", - name, type, number, type_class); + name, type, number, type_class, options); } // ----------------------------------------------------------------------------- @@ -1604,6 +1941,112 @@ VALUE EnumBuilderContext_value(VALUE _self, VALUE name, VALUE number) { return enumdef_add_value(self->enumdesc, name, number); } + +// ----------------------------------------------------------------------------- +// FileBuilderContext. +// ----------------------------------------------------------------------------- + +DEFINE_CLASS(FileBuilderContext, + "Google::Protobuf::Internal::FileBuilderContext"); + +void FileBuilderContext_mark(void* _self) { + FileBuilderContext* self = _self; + rb_gc_mark(self->pending_list); + rb_gc_mark(self->file_descriptor); + rb_gc_mark(self->builder); +} + +void FileBuilderContext_free(void* _self) { + FileBuilderContext* self = _self; + xfree(self); +} + +VALUE FileBuilderContext_alloc(VALUE klass) { + FileBuilderContext* self = ALLOC(FileBuilderContext); + VALUE ret = TypedData_Wrap_Struct(klass, &_FileBuilderContext_type, self); + self->pending_list = Qnil; + self->file_descriptor = Qnil; + self->builder = Qnil; + return ret; +} + +void FileBuilderContext_register(VALUE module) { + VALUE klass = rb_define_class_under(module, "FileBuilderContext", rb_cObject); + rb_define_alloc_func(klass, FileBuilderContext_alloc); + rb_define_method(klass, "initialize", FileBuilderContext_initialize, 2); + rb_define_method(klass, "add_message", FileBuilderContext_add_message, 1); + rb_define_method(klass, "add_enum", FileBuilderContext_add_enum, 1); + rb_gc_register_address(&cFileBuilderContext); + cFileBuilderContext = klass; +} + +/* + * call-seq: + * FileBuilderContext.new(file_descriptor, builder) => context + * + * Create a new file builder context for the given file descriptor and + * builder context. This class is intended to serve as a DSL context to be used + * with #instance_eval. + */ +VALUE FileBuilderContext_initialize(VALUE _self, VALUE file_descriptor, + VALUE builder) { + DEFINE_SELF(FileBuilderContext, self, _self); + self->pending_list = rb_ary_new(); + self->file_descriptor = file_descriptor; + self->builder = builder; + return Qnil; +} + +/* + * call-seq: + * FileBuilderContext.add_message(name, &block) + * + * Creates a new, empty descriptor with the given name, and invokes the block in + * the context of a MessageBuilderContext on that descriptor. The block can then + * call, e.g., MessageBuilderContext#optional and MessageBuilderContext#repeated + * methods to define the message fields. + * + * This is the recommended, idiomatic way to build message definitions. + */ +VALUE FileBuilderContext_add_message(VALUE _self, VALUE name) { + DEFINE_SELF(FileBuilderContext, self, _self); + VALUE msgdef = rb_class_new_instance(1, &self->file_descriptor, cDescriptor); + VALUE args[2] = { msgdef, self->builder }; + VALUE ctx = rb_class_new_instance(2, args, cMessageBuilderContext); + VALUE block = rb_block_proc(); + rb_funcall(msgdef, rb_intern("name="), 1, name); + rb_funcall_with_block(ctx, rb_intern("instance_eval"), 0, NULL, block); + rb_ary_push(self->pending_list, msgdef); + return Qnil; +} + +/* + * call-seq: + * FileBuilderContext.add_enum(name, &block) + * + * Creates a new, empty enum descriptor with the given name, and invokes the + * block in the context of an EnumBuilderContext on that descriptor. The block + * can then call EnumBuilderContext#add_value to define the enum values. + * + * This is the recommended, idiomatic way to build enum definitions. + */ +VALUE FileBuilderContext_add_enum(VALUE _self, VALUE name) { + DEFINE_SELF(FileBuilderContext, self, _self); + VALUE enumdef = + rb_class_new_instance(1, &self->file_descriptor, cEnumDescriptor); + VALUE ctx = rb_class_new_instance(1, &enumdef, cEnumBuilderContext); + VALUE block = rb_block_proc(); + rb_funcall(enumdef, rb_intern("name="), 1, name); + rb_funcall_with_block(ctx, rb_intern("instance_eval"), 0, NULL, block); + rb_ary_push(self->pending_list, enumdef); + return Qnil; +} + +VALUE FileBuilderContext_pending_descriptors(VALUE _self) { + DEFINE_SELF(FileBuilderContext, self, _self); + return self->pending_list; +} + // ----------------------------------------------------------------------------- // Builder. // ----------------------------------------------------------------------------- @@ -1613,6 +2056,7 @@ DEFINE_CLASS(Builder, "Google::Protobuf::Internal::Builder"); void Builder_mark(void* _self) { Builder* self = _self; rb_gc_mark(self->pending_list); + rb_gc_mark(self->default_file_descriptor); } void Builder_free(void* _self) { @@ -1635,15 +2079,17 @@ VALUE Builder_alloc(VALUE klass) { klass, &_Builder_type, self); self->pending_list = Qnil; self->defs = NULL; + self->default_file_descriptor = Qnil; return ret; } void Builder_register(VALUE module) { VALUE klass = rb_define_class_under(module, "Builder", rb_cObject); - rb_define_alloc_func(klass, Builder_alloc); + rb_define_alloc_func(klass, Builder_alloc); + rb_define_method(klass, "initialize", Builder_initialize, 0); + rb_define_method(klass, "add_file", Builder_add_file, -1); rb_define_method(klass, "add_message", Builder_add_message, 1); rb_define_method(klass, "add_enum", Builder_add_enum, 1); - rb_define_method(klass, "initialize", Builder_initialize, 0); rb_define_method(klass, "finalize_to_pool", Builder_finalize_to_pool, 1); rb_gc_register_address(&cBuilder); cBuilder = klass; @@ -1651,13 +2097,40 @@ void Builder_register(VALUE module) { /* * call-seq: - * Builder.new(d) => builder + * Builder.new * - * Create a new message builder. + * Initializes a new builder. */ VALUE Builder_initialize(VALUE _self) { DEFINE_SELF(Builder, self, _self); self->pending_list = rb_ary_new(); + VALUE file_name = Qnil; + self->default_file_descriptor = + rb_class_new_instance(1, &file_name, cFileDescriptor); + return Qnil; +} + +/* + * call-seq: + * Builder.add_file(name, options = nil, &block) + * + * Creates a new, file descriptor with the given name and options and invokes + * the block in the context of a FileBuilderContext on that descriptor. The + * block can then call FileBuilderContext#add_message or + * FileBuilderContext#add_enum to define new messages or enums, respectively. + * + * This is the recommended, idiomatic way to build file descriptors. + */ +VALUE Builder_add_file(int argc, VALUE* argv, VALUE _self) { + DEFINE_SELF(Builder, self, _self); + VALUE file_descriptor = rb_class_new_instance(argc, argv, cFileDescriptor); + VALUE args[2] = { file_descriptor, _self }; + VALUE ctx = rb_class_new_instance(2, args, cFileBuilderContext); + VALUE block = rb_block_proc(); + rb_funcall_with_block(ctx, rb_intern("instance_eval"), 0, NULL, block); + + rb_ary_concat(self->pending_list, + FileBuilderContext_pending_descriptors(ctx)); return Qnil; } @@ -1665,16 +2138,17 @@ VALUE Builder_initialize(VALUE _self) { * call-seq: * Builder.add_message(name, &block) * - * Creates a new, empty descriptor with the given name, and invokes the block in - * the context of a MessageBuilderContext on that descriptor. The block can then - * call, e.g., MessageBuilderContext#optional and MessageBuilderContext#repeated - * methods to define the message fields. + * Old and deprecated way to create a new descriptor. + * See FileBuilderContext.add_message for the recommended way. * - * This is the recommended, idiomatic way to build message definitions. + * Exists for backwards compatibility to allow building descriptor pool for + * files generated by protoc which don't add messages within "add_file" block. + * Descriptors created this way get assigned to a default empty FileDescriptor. */ VALUE Builder_add_message(VALUE _self, VALUE name) { DEFINE_SELF(Builder, self, _self); - VALUE msgdef = rb_class_new_instance(0, NULL, cDescriptor); + VALUE msgdef = + rb_class_new_instance(1, &self->default_file_descriptor, cDescriptor); VALUE args[2] = { msgdef, _self }; VALUE ctx = rb_class_new_instance(2, args, cMessageBuilderContext); VALUE block = rb_block_proc(); @@ -1688,15 +2162,18 @@ VALUE Builder_add_message(VALUE _self, VALUE name) { * call-seq: * Builder.add_enum(name, &block) * - * Creates a new, empty enum descriptor with the given name, and invokes the - * block in the context of an EnumBuilderContext on that descriptor. The block - * can then call EnumBuilderContext#add_value to define the enum values. + * Old and deprecated way to create a new enum descriptor. + * See FileBuilderContext.add_enum for the recommended way. * - * This is the recommended, idiomatic way to build enum definitions. + * Exists for backwards compatibility to allow building descriptor pool for + * files generated by protoc which don't add enums within "add_file" block. + * Enum descriptors created this way get assigned to a default empty + * FileDescriptor. */ VALUE Builder_add_enum(VALUE _self, VALUE name) { DEFINE_SELF(Builder, self, _self); - VALUE enumdef = rb_class_new_instance(0, NULL, cEnumDescriptor); + VALUE enumdef = + rb_class_new_instance(1, &self->default_file_descriptor, cEnumDescriptor); VALUE ctx = rb_class_new_instance(1, &enumdef, cEnumBuilderContext); VALUE block = rb_block_proc(); rb_funcall(enumdef, rb_intern("name="), 1, name); @@ -1705,7 +2182,7 @@ VALUE Builder_add_enum(VALUE _self, VALUE name) { return Qnil; } -static void validate_msgdef(const upb_msgdef* msgdef) { +static void proto3_validate_msgdef(const upb_msgdef* msgdef) { // Verify that no required fields exist. proto3 does not support these. upb_msg_field_iter it; for (upb_msg_field_begin(&it, msgdef); @@ -1718,7 +2195,7 @@ static void validate_msgdef(const upb_msgdef* msgdef) { } } -static void validate_enumdef(const upb_enumdef* enumdef) { +static void proto3_validate_enumdef(const upb_enumdef* enumdef) { // Verify that an entry exists with integer value 0. (This is the default // value.) const char* lookup = upb_enumdef_iton(enumdef, 0); @@ -1753,10 +2230,16 @@ VALUE Builder_finalize_to_pool(VALUE _self, VALUE pool_rb) { VALUE def_rb = rb_ary_entry(self->pending_list, i); if (CLASS_OF(def_rb) == cDescriptor) { self->defs[i] = (upb_def*)ruby_to_Descriptor(def_rb)->msgdef; - validate_msgdef((const upb_msgdef*)self->defs[i]); + + if (upb_filedef_syntax(upb_def_file(self->defs[i])) == UPB_SYNTAX_PROTO3) { + proto3_validate_msgdef((const upb_msgdef*)self->defs[i]); + } } else if (CLASS_OF(def_rb) == cEnumDescriptor) { self->defs[i] = (upb_def*)ruby_to_EnumDescriptor(def_rb)->enumdef; - validate_enumdef((const upb_enumdef*)self->defs[i]); + + if (upb_filedef_syntax(upb_def_file(self->defs[i])) == UPB_SYNTAX_PROTO3) { + proto3_validate_enumdef((const upb_enumdef*)self->defs[i]); + } } } -- cgit v1.2.3