chrisk-protopuffs 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ }