chrisk-protopuffs 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/LICENSE.txt ADDED
@@ -0,0 +1,18 @@
1
+ Permission is hereby granted, free of charge, to any person obtaining
2
+ a copy of this software and associated documentation files (the
3
+ "Software"), to deal in the Software without restriction, including
4
+ without limitation the rights to use, copy, modify, merge, publish,
5
+ distribute, sublicense, and/or sell copies of the Software, and to
6
+ permit persons to whom the Software is furnished to do so, subject to
7
+ the following conditions:
8
+
9
+ The above copyright notice and this permission notice shall be
10
+ included in all copies or substantial portions of the Software.
11
+
12
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,3 @@
1
+ = Protopuffs!
2
+
3
+ A new implementation of Protocol Buffers in Ruby.
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 2
4
+ :patch: 0
@@ -0,0 +1,90 @@
1
+ module Protopuffs
2
+ module Message
3
+
4
+ class Base
5
+
6
+ class << self
7
+ attr_reader :fields
8
+
9
+ def define_message_class(name, fields)
10
+ name = name.capitalize
11
+ self.check_fields_for_errors(name, fields)
12
+ Message.send(:remove_const, name) if Message.const_defined?(name)
13
+ klass = Message.const_set(name, Class.new(self))
14
+ klass.instance_variable_set(:@fields, fields)
15
+ fields.each do |field|
16
+ klass.send(:attr_accessor, field.identifier)
17
+ end
18
+ klass
19
+ end
20
+
21
+ def check_fields_for_errors(name, fields)
22
+ tags = fields.map { |field| field.tag }
23
+ if tags.uniq.size != fields.size
24
+ raise ParseError, "A tag was reused in the descriptor for message #{name}. Please check that each tag is unique."
25
+ end
26
+
27
+ if tags.any? { |tag| tag < 1 || tag > 536_870_911 }
28
+ raise ParseError, "A tag is out of range in the descriptor for message #{name}. Please check that each tag is in the range 1 <= x <= 536,870,911."
29
+ end
30
+
31
+ if tags.any? { |tag| (19000..19999).include?(tag) }
32
+ raise ParseError, "A tag is invalid in the descriptor for message #{name}. Please check that each tag is not in the reserved range 19,000 <= x <= 19,999."
33
+ end
34
+ end
35
+ end
36
+
37
+ attr_reader :buffer
38
+
39
+ def initialize
40
+ if self.class == Base
41
+ raise "#{self.class} should not be instantiated directly. Use the factory #{self.class}.define_message_class instead."
42
+ end
43
+ @buffer = StringIO.new
44
+ end
45
+
46
+ def to_wire_format
47
+ self.class.fields.each do |field|
48
+ value = send(field.identifier)
49
+ @buffer.write field.to_wire_format_with_value(value) unless value.nil?
50
+ end
51
+ @buffer.string
52
+ end
53
+
54
+ def from_wire_format(buffer)
55
+ @buffer = buffer
56
+ until @buffer.eof?
57
+ tag, value_bytes = MessageField.shift_tag_and_value_bytes(@buffer)
58
+ field = self.class.fields.find { |field| field.tag == tag }
59
+ next if field.nil?
60
+
61
+ value = field.decode(value_bytes)
62
+ if field.repeated? && send(field.identifier).nil?
63
+ send("#{field.identifier}=", [value])
64
+ elsif field.repeated?
65
+ send(field.identifier) << value
66
+ else
67
+ send("#{field.identifier}=", value)
68
+ end
69
+ end
70
+ set_values_for_missing_optional_fields
71
+ end
72
+
73
+ def set_values_for_missing_optional_fields
74
+ self.class.fields.select { |field| field.optional? }.each do |field|
75
+ send("#{field.identifier}=", field.default) if send(field.identifier).nil?
76
+ end
77
+ end
78
+
79
+ def ==(other)
80
+ return false if self.class != other.class
81
+ self.class.fields.each do |field|
82
+ return false if send(field.identifier) != other.send(field.identifier)
83
+ end
84
+ true
85
+ end
86
+
87
+ end
88
+
89
+ end
90
+ end
@@ -0,0 +1,226 @@
1
+ module Protopuffs
2
+
3
+ class MessageField
4
+ attr_reader :modifier, :type, :identifier, :tag, :default
5
+
6
+ def initialize(modifier, type, identifier, tag, default = nil)
7
+ @modifier = modifier
8
+ @type = type
9
+ @identifier = identifier
10
+ @tag = tag
11
+ @default = default
12
+
13
+ set_default_for_type if optional? && @default.nil?
14
+ end
15
+
16
+ def set_default_for_type
17
+ if numeric?
18
+ @default = 0
19
+ elsif @type == "string"
20
+ @default = ""
21
+ elsif @type == "bool"
22
+ @default = false
23
+ end
24
+ end
25
+
26
+ def wire_type
27
+ case @type
28
+ when "int32", "int64", "uint32", "uint64", "bool" then WireType::VARINT
29
+ when "double", "fixed64" then WireType::FIXED64
30
+ when "string", "bytes" then WireType::LENGTH_DELIMITED
31
+ when "float", "fixed32" then WireType::FIXED32
32
+ else WireType::LENGTH_DELIMITED # embedded messages
33
+ end
34
+ end
35
+
36
+ def key
37
+ (@tag << 3) | wire_type
38
+ end
39
+
40
+ def repeated?
41
+ @modifier == "repeated"
42
+ end
43
+
44
+ def optional?
45
+ @modifier == "optional"
46
+ end
47
+
48
+ def numeric?
49
+ %w(double float int32 int64 uint32 unit64 sint32 sint64 fixed32 fixed64
50
+ sfixed32 sfixed64).include?(@type)
51
+ end
52
+
53
+ def embedded_message?
54
+ wire_type == WireType::LENGTH_DELIMITED && @type != "string" && @type != "bytes"
55
+ end
56
+
57
+ def to_wire_format_with_value(value)
58
+ field_encoder = lambda { |val| self.class.varint_encode(key) + encode(val) }
59
+
60
+ if repeated?
61
+ value.map(&field_encoder).join
62
+ else
63
+ field_encoder.call(value)
64
+ end
65
+ end
66
+
67
+ def encode(value)
68
+ case wire_type
69
+ when WireType::VARINT
70
+ value = (value ? 1 : 0) if @type == "bool"
71
+ value_bytes = self.class.varint_encode(value)
72
+ when WireType::LENGTH_DELIMITED
73
+ if value.respond_to?(:to_wire_format)
74
+ embedded_bytes = value.to_wire_format
75
+ value_bytes = self.class.varint_encode(embedded_bytes.size) + embedded_bytes
76
+ else
77
+ value_bytes = self.class.varint_encode(value.size)
78
+ value_bytes += self.class.string_encode(value) if @type == "string"
79
+ value_bytes += value if @type == "bytes"
80
+ end
81
+ when WireType::FIXED32
82
+ value_bytes = self.class.float_encode(value) if @type == "float"
83
+ value_bytes = self.class.fixed32_encode(value) if @type == "fixed32"
84
+ when WireType::FIXED64
85
+ value_bytes = self.class.double_encode(value) if @type == "double"
86
+ value_bytes = self.class.fixed64_encode(value) if @type == "fixed64"
87
+ end
88
+ value_bytes
89
+ end
90
+
91
+
92
+ def self.varint_encode(value)
93
+ return [0].pack('C') if value.zero?
94
+ bytes = []
95
+ until value.zero?
96
+ byte = 0
97
+ 7.times do |i|
98
+ byte |= (value & 1) << i
99
+ value >>= 1
100
+ end
101
+ byte |= 0b10000000
102
+ bytes << byte
103
+ end
104
+ bytes[-1] &= 0b01111111
105
+ bytes.pack('C*')
106
+ end
107
+
108
+ def self.string_encode(value)
109
+ value.unpack('U*').pack('C*')
110
+ end
111
+
112
+ def self.float_encode(value)
113
+ [value].pack('e')
114
+ end
115
+
116
+ def self.double_encode(value)
117
+ [value].pack('E')
118
+ end
119
+
120
+ def self.fixed64_encode(value)
121
+ [value].pack('Q')
122
+ end
123
+
124
+ def self.fixed32_encode(value)
125
+ [value].pack('V')
126
+ end
127
+
128
+ # note: returns two values
129
+ def self.shift_tag_and_value_bytes(buffer)
130
+ bits = 0
131
+ bytes = shift_varint(buffer)
132
+ bytes.each_with_index do |byte, index|
133
+ byte &= 0b01111111
134
+ bits |= byte << (7 * index)
135
+ end
136
+ wire_type = bits & 0b00000111
137
+ tag = bits >> 3
138
+
139
+ case wire_type
140
+ when WireType::VARINT
141
+ value_bytes = shift_varint(buffer)
142
+ when WireType::LENGTH_DELIMITED
143
+ value_bytes = shift_length_delimited(buffer)
144
+ when WireType::FIXED32
145
+ value_bytes = shift_fixed32(buffer)
146
+ when WireType::FIXED64
147
+ value_bytes = shift_fixed64(buffer)
148
+ end
149
+
150
+ [tag, value_bytes]
151
+ end
152
+
153
+ def self.shift_varint(buffer)
154
+ bytes = []
155
+ begin
156
+ byte = buffer.readchar
157
+ bytes << (byte & 0b01111111)
158
+ end while byte >> 7 == 1
159
+ bytes
160
+ end
161
+
162
+ def self.shift_length_delimited(buffer)
163
+ bytes = shift_varint(buffer)
164
+ value_length = self.varint_decode(bytes)
165
+ buffer.read(value_length)
166
+ end
167
+
168
+ def self.shift_fixed32(buffer)
169
+ buffer.read(4)
170
+ end
171
+
172
+ def self.shift_fixed64(buffer)
173
+ buffer.read(8)
174
+ end
175
+
176
+ def decode(value_bytes)
177
+ case wire_type
178
+ when WireType::VARINT
179
+ value = self.class.varint_decode(value_bytes)
180
+ if @type == "bool"
181
+ value = true if value == 1
182
+ value = false if value == 0
183
+ end
184
+ when WireType::LENGTH_DELIMITED
185
+ if embedded_message?
186
+ value = Message.const_get(@type).new
187
+ value.from_wire_format(StringIO.new(value_bytes))
188
+ else
189
+ value = value_bytes
190
+ end
191
+ when WireType::FIXED32
192
+ value = self.class.float_decode(value_bytes) if @type == "float"
193
+ value = self.class.fixed32_decode(value_bytes) if @type == "fixed32"
194
+ when WireType::FIXED64
195
+ value = self.class.double_decode(value_bytes) if @type == "double"
196
+ value = self.class.fixed64_decode(value_bytes) if @type == "fixed64"
197
+ end
198
+ value
199
+ end
200
+
201
+ def self.varint_decode(bytes)
202
+ value = 0
203
+ bytes.each_with_index do |byte, index|
204
+ value |= byte << (7 * index)
205
+ end
206
+ value
207
+ end
208
+
209
+ def self.float_decode(bytes)
210
+ bytes.unpack('e').first
211
+ end
212
+
213
+ def self.double_decode(bytes)
214
+ bytes.unpack('E').first
215
+ end
216
+
217
+ def self.fixed64_decode(bytes)
218
+ bytes.unpack('Q').first
219
+ end
220
+
221
+ def self.fixed32_decode(bytes)
222
+ bytes.unpack('V').first
223
+ end
224
+ end
225
+
226
+ end
@@ -0,0 +1,10 @@
1
+ module Protopuffs
2
+ class WireType
3
+ VARINT = 0
4
+ FIXED64 = 1
5
+ LENGTH_DELIMITED = 2
6
+ START_GROUP = 3
7
+ END_GROUP = 4
8
+ FIXED32 = 5
9
+ end
10
+ end
@@ -0,0 +1,24 @@
1
+ module Protopuffs
2
+
3
+ # The Protopuffs::Parser module holds wrapper classes that parse input
4
+ # strings using Treetop. They also set off the building of an abstract
5
+ # syntax tree from Treetop's parse tree.
6
+ module Parser
7
+
8
+ class ProtocolBufferDescriptor
9
+ def initialize
10
+ Treetop.load "lib/protopuffs/parser/protocol_buffer"
11
+ @parser = Protopuffs::ProtocolBufferParser.new
12
+ end
13
+
14
+ def parse(input)
15
+ parse_tree = @parser.parse(input)
16
+ parse_tree.build
17
+ parse_tree
18
+ rescue
19
+ raise ParseError
20
+ end
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,124 @@
1
+ module Protopuffs
2
+ grammar ProtocolBuffer
3
+
4
+ rule proto
5
+ s? proto_entries s? {
6
+ def messages
7
+ proto_entries.entries.select { |p| p.respond_to?(:message?) }
8
+ end
9
+
10
+ def build
11
+ messages.each { |message| message.build }
12
+ end
13
+ }
14
+ end
15
+
16
+ rule proto_entries
17
+ proto_entry rest:(s? proto_entry)* {
18
+ def entries
19
+ rest.elements.inject([proto_entry]) { |a, s_and_pe| a << s_and_pe.proto_entry }
20
+ end
21
+ }
22
+ end
23
+
24
+ rule proto_entry
25
+ message {
26
+ def message?
27
+ true
28
+ end
29
+ }
30
+ end
31
+
32
+ rule message
33
+ "message" s identifier s "{" s? body:message_body? s? "}" {
34
+ def name
35
+ identifier.text_value
36
+ end
37
+
38
+ def build
39
+ Message::Base.define_message_class(name, body.empty? ? [] : body.build)
40
+ end
41
+ }
42
+ end
43
+
44
+ rule message_body
45
+ field rest:(s? field)* {
46
+ def fields
47
+ rest.elements.inject([field]) { |a, s_and_field| a << s_and_field.field }
48
+ end
49
+
50
+ def build
51
+ fields.map { |field| field.build }
52
+ end
53
+ }
54
+ end
55
+
56
+ rule field
57
+ modifier s type s identifier s "=" s integer s? option:field_option? ";" {
58
+ def default
59
+ option.empty? ? nil : option.literal.obj
60
+ end
61
+
62
+ def build
63
+ MessageField.new(modifier.text_value, type.text_value, identifier.text_value,
64
+ integer.text_value.to_i, default)
65
+ end
66
+ }
67
+ end
68
+
69
+ rule field_option
70
+ "[" s? "default" s? "=" s? literal s? "]"
71
+ end
72
+
73
+ rule modifier
74
+ "required" / "optional" / "repeated"
75
+ end
76
+
77
+ rule type
78
+ "double" / "float" / "int32" / "int64" / "uint32" / "uint64" /
79
+ "sint32" / "sint64" / "fixed32" / "fixed64" / "sfixed32" /
80
+ "sfixed64" / "bool" / "string" / "bytes" / identifier
81
+ end
82
+
83
+ rule literal
84
+ integer / string
85
+ end
86
+
87
+ rule integer
88
+ [\d]+ {
89
+ def obj
90
+ text_value.to_i
91
+ end
92
+ }
93
+ end
94
+
95
+ rule string
96
+ quote chars:[\w]* quote {
97
+ def obj
98
+ chars.text_value
99
+ end
100
+ }
101
+ end
102
+
103
+ rule quote
104
+ ["']
105
+ end
106
+
107
+ rule identifier
108
+ [A-Za-z_] [\w_]*
109
+ end
110
+
111
+ rule s
112
+ (whitespace / comment_to_eol)+
113
+ end
114
+
115
+ rule comment_to_eol
116
+ '//' (!"\n" .)*
117
+ end
118
+
119
+ rule whitespace
120
+ [ \t\n\r]
121
+ end
122
+
123
+ end
124
+ end
data/lib/protopuffs.rb ADDED
@@ -0,0 +1,27 @@
1
+ require "protopuffs/message/base"
2
+ require "protopuffs/message/field"
3
+ require "protopuffs/message/wire_type"
4
+ require "protopuffs/parser/parser"
5
+
6
+
7
+ module Protopuffs
8
+ class ParseError < StandardError; end
9
+
10
+ def self.proto_load_path
11
+ @proto_load_path ||= []
12
+ end
13
+
14
+ def self.proto_load_path=(paths)
15
+ @proto_load_path = paths
16
+ end
17
+
18
+ def self.load_message_classes
19
+ parser = Protopuffs::Parser::ProtocolBufferDescriptor.new
20
+
21
+ @proto_load_path.each do |path|
22
+ Dir.glob(File.join(path, "**/*.proto")) do |descriptor_path|
23
+ parser.parse(File.read(descriptor_path))
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,142 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class AbstractSyntaxTreeTest < Test::Unit::TestCase
4
+
5
+ context "a protocol buffer descriptor" do
6
+ setup do
7
+ @parser = Protopuffs::Parser::ProtocolBufferDescriptor.new
8
+ end
9
+
10
+ context "with an empty Person message" do
11
+ setup do
12
+ @descriptor = "message Person {}"
13
+ end
14
+
15
+ should "create one MessageDescriptor" do
16
+ Protopuffs::Message::Base.expects(:define_message_class).once.with("Person", [])
17
+ @parser.parse(@descriptor)
18
+ end
19
+
20
+ should "not create any MessageFields" do
21
+ Protopuffs::MessageField.expects(:new).never
22
+ @parser.parse(@descriptor)
23
+ end
24
+ end
25
+
26
+
27
+ context "with two empty messages Apple and Orange" do
28
+ setup do
29
+ @descriptor = <<-proto
30
+ message Apple {}
31
+ message Orange {}
32
+ proto
33
+ end
34
+
35
+ should "create two MessageDescriptors" do
36
+ Protopuffs::Message::Base.expects(:define_message_class).once.with("Apple", []).in_sequence
37
+ Protopuffs::Message::Base.expects(:define_message_class).once.with("Orange", []).in_sequence
38
+ @parser.parse(@descriptor)
39
+ end
40
+
41
+ should "not create any MessageFields" do
42
+ Protopuffs::MessageField.expects(:new).never
43
+ @parser.parse(@descriptor)
44
+ end
45
+ end
46
+
47
+
48
+ context "with a Person message including a name field" do
49
+ setup do
50
+ @descriptor = <<-proto
51
+ message Person {
52
+ required string name = 1;
53
+ }
54
+ proto
55
+ end
56
+
57
+ should "create one 'Person' MessageDescriptor with one field" do
58
+ Protopuffs::Message::Base.expects(:define_message_class).once.with("Person", responds_with(:size, 1))
59
+ @parser.parse(@descriptor)
60
+ end
61
+
62
+ should "create one MessageField with correct options" do
63
+ Protopuffs::MessageField.expects(:new).once.with("required", "string", "name", 1, nil).returns(stub(:tag => 1, :identifier => "name"))
64
+ @parser.parse(@descriptor)
65
+ end
66
+ end
67
+
68
+
69
+ context "with a Person message including three fields" do
70
+ setup do
71
+ @descriptor = <<-proto
72
+ message Person {
73
+ required string name = 1;
74
+ required int32 id = 2;
75
+ optional string email = 3;
76
+ }
77
+ proto
78
+ end
79
+
80
+ should "create one 'Person' MessageDescriptor with three fields" do
81
+ Protopuffs::Message::Base.expects(:define_message_class).once.with("Person", responds_with(:size, 3))
82
+ @parser.parse(@descriptor)
83
+ end
84
+
85
+ should "create three MessageFields with correct options" do
86
+ Protopuffs::MessageField.expects(:new).once.with("required", "string", "name", 1, nil).in_sequence.returns(stub(:tag => 1, :identifier => "name"))
87
+ Protopuffs::MessageField.expects(:new).once.with("required", "int32", "id", 2, nil).in_sequence.returns(stub(:tag => 2, :identifier => "id"))
88
+ Protopuffs::MessageField.expects(:new).once.with("optional", "string", "email", 3, nil).in_sequence.returns(stub(:tag => 3, :identifier => "email"))
89
+ @parser.parse(@descriptor)
90
+ end
91
+ end
92
+
93
+
94
+ context "with a message including two fields with defaults and one without" do
95
+ setup do
96
+ @descriptor = <<-proto
97
+ message Person {
98
+ required string name = 1;
99
+ optional string language = 2 [default = "en"];
100
+ optional int32 account_code = 3 [default = 0];
101
+ }
102
+ proto
103
+ end
104
+
105
+ should "create one 'Person' MessageDescriptor with three fields" do
106
+ Protopuffs::Message::Base.expects(:define_message_class).once.with("Person", responds_with(:size, 3))
107
+ @parser.parse(@descriptor)
108
+ end
109
+
110
+ should "create three MessageFields with correct options" do
111
+ Protopuffs::MessageField.expects(:new).once.with("required", "string", "name", 1, nil).in_sequence.returns(stub(:tag => 1, :identifier => "name"))
112
+ Protopuffs::MessageField.expects(:new).once.with("optional", "string", "language", 2, "en").in_sequence.returns(stub(:tag => 2, :identifier => "language"))
113
+ Protopuffs::MessageField.expects(:new).once.with("optional", "int32", "account_code", 3, 0).in_sequence.returns(stub(:tag => 3, :identifier => "account_code"))
114
+ @parser.parse(@descriptor)
115
+ end
116
+ end
117
+
118
+
119
+ context "with a message including a user-typed field" do
120
+ setup do
121
+ @descriptor = <<-proto
122
+ message Person {
123
+ required string name = 1;
124
+ repeated Address addresses = 2;
125
+ }
126
+ proto
127
+ end
128
+
129
+ should "create one 'Person' MessageDescriptor with two fields" do
130
+ Protopuffs::Message::Base.expects(:define_message_class).once.with("Person", responds_with(:size, 2))
131
+ @parser.parse(@descriptor)
132
+ end
133
+
134
+ should "create two MessageFields with correct options" do
135
+ Protopuffs::MessageField.expects(:new).once.with("required", "string", "name", 1, nil).in_sequence.returns(stub(:tag => 1, :identifier => "name"))
136
+ Protopuffs::MessageField.expects(:new).once.with("repeated", "Address", "addresses", 2, nil).in_sequence.returns(stub(:tag => 2, :identifier => "addresses"))
137
+ @parser.parse(@descriptor)
138
+ end
139
+ end
140
+ end
141
+
142
+ end
@@ -0,0 +1,5 @@
1
+ message Person {
2
+ required string name = 1;
3
+ required int32 id = 2;
4
+ optional string email = 3;
5
+ }