protobuf 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +1 -1
- data/README.md +138 -126
- data/bin/rpc_server +2 -2
- data/bin/rprotoc +2 -2
- data/examples/reading_a_message.rb +3 -3
- data/examples/writing_a_message.rb +3 -3
- data/lib/protobuf.rb +3 -0
- data/lib/protobuf/compiler/nodes.rb +1 -1
- data/lib/protobuf/compiler/proto_parser.rb +16 -16
- data/lib/protobuf/compiler/visitors.rb +11 -25
- data/lib/protobuf/descriptor/descriptor_builder.rb +58 -58
- data/lib/protobuf/descriptor/field_descriptor.rb +2 -2
- data/lib/protobuf/ext/eventmachine.rb +16 -0
- data/lib/protobuf/message/decoder.rb +6 -6
- data/lib/protobuf/message/field.rb +14 -14
- data/lib/protobuf/message/message.rb +4 -4
- data/lib/protobuf/rpc/client.rb +42 -183
- data/lib/protobuf/rpc/connector.rb +19 -0
- data/lib/protobuf/rpc/connectors/base.rb +29 -0
- data/lib/protobuf/rpc/connectors/em_client.rb +227 -0
- data/lib/protobuf/rpc/connectors/eventmachine.rb +84 -0
- data/lib/protobuf/rpc/connectors/socket.rb +14 -0
- data/lib/protobuf/rpc/service.rb +4 -4
- data/lib/protobuf/version.rb +1 -1
- data/protobuf.gemspec +3 -3
- data/spec/helper/all.rb +13 -0
- data/spec/helper/server.rb +36 -0
- data/spec/helper/tolerance_matcher.rb +40 -0
- data/spec/spec_helper.rb +3 -5
- data/spec/unit/rpc/client_spec.rb +174 -0
- data/spec/unit/rpc/connector_spec.rb +36 -0
- data/spec/unit/rpc/connectors/base_spec.rb +77 -0
- data/spec/unit/rpc/connectors/eventmachine/client_spec.rb +0 -0
- data/spec/unit/rpc/connectors/eventmachine_spec.rb +0 -0
- data/spec/unit/{server_spec.rb → rpc/server_spec.rb} +0 -0
- data/spec/unit/{service_spec.rb → rpc/service_spec.rb} +0 -0
- metadata +79 -63
- data/lib/protobuf/compiler/template/rpc_bin.erb +0 -4
- data/lib/protobuf/compiler/template/rpc_client.erb +0 -18
- data/lib/protobuf/compiler/template/rpc_service.erb +0 -25
- data/lib/protobuf/rpc/client_connection.rb +0 -225
- data/spec/unit/client_spec.rb +0 -128
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
protobuf
|
2
|
-
========
|
1
|
+
# protobuf
|
3
2
|
|
4
3
|
Protobuf is an implementation of [Google's protocol buffers][google-pb] in ruby. It's a gem for managing 3 things:
|
5
4
|
|
@@ -9,85 +8,93 @@ Protobuf is an implementation of [Google's protocol buffers][google-pb] in ruby.
|
|
9
8
|
|
10
9
|
So let's dive in and see how to work with all three.
|
11
10
|
|
12
|
-
1. Compile `.proto` definitions to ruby
|
13
|
-
=======================================
|
11
|
+
## 1. Compile `.proto` definitions to ruby
|
14
12
|
|
15
13
|
Protocol Buffers are great because they allow you to clearly define data storage or data transfer packets. Google officially supports Java, C++, and Python for compilation and usage. Let's make it ruby aware!
|
16
14
|
|
17
15
|
Let's say you have a `defs.proto` file that defines a User message.
|
18
16
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
17
|
+
```
|
18
|
+
package mycompany;
|
19
|
+
message User {
|
20
|
+
required string first_name = 1;
|
21
|
+
required string last_name = 1;
|
22
|
+
}
|
24
23
|
|
25
24
|
Now let's compile that definition to ruby:
|
26
25
|
|
27
|
-
|
26
|
+
$ rprotoc defs.proto -o ./lib
|
27
|
+
```
|
28
28
|
|
29
29
|
The previous line will take whatever is defined in defs.proto and output ruby classes to the `./lib` directory, obeying the package directive. Assuming that's all defs.proto had defined, `./lib` should now look like this:
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
31
|
+
```
|
32
|
+
- lib
|
33
|
+
|- mycompany
|
34
|
+
|- defs.pb.rb
|
35
|
+
```
|
34
36
|
|
35
37
|
And `defs.pb.rb` should look like this:
|
36
38
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
39
|
+
```ruby
|
40
|
+
module Mycompany
|
41
|
+
class User
|
42
|
+
optional :string, :first_name, 1
|
43
|
+
optional :string, :last_name, 2
|
44
|
+
end
|
45
|
+
end
|
46
|
+
```
|
43
47
|
|
44
48
|
You can then use that class just like normal:
|
45
49
|
|
46
|
-
|
47
|
-
|
48
|
-
# dot notation reading/writing fields
|
49
|
-
user = Mycompany::User.new
|
50
|
-
user.first_name = "Lloyd"
|
51
|
-
user.last_name = "Christmas"
|
52
|
-
user.first_name # => "Lloyd"
|
53
|
-
|
54
|
-
# or pass in the fields as a hash to the initializer
|
55
|
-
user = Mycompany::User.new :first_name => "Lloyd", :last_name => "Christmas"
|
56
|
-
user.first_name # => Lloyd
|
57
|
-
user.last_name # => Christmas
|
50
|
+
```ruby
|
51
|
+
require 'lib/mycompany/user.pb'
|
58
52
|
|
59
|
-
|
53
|
+
# dot notation reading/writing fields
|
54
|
+
user = Mycompany::User.new
|
55
|
+
user.first_name = "Lloyd"
|
56
|
+
user.last_name = "Christmas"
|
57
|
+
user.first_name # => "Lloyd"
|
60
58
|
|
61
|
-
|
62
|
-
|
59
|
+
# or pass in the fields as a hash to the initializer
|
60
|
+
user = Mycompany::User.new :first_name => "Lloyd", :last_name => "Christmas"
|
61
|
+
user.first_name # => Lloyd
|
62
|
+
user.last_name # => Christmas
|
63
|
+
```
|
64
|
+
|
65
|
+
## 2. RPC
|
63
66
|
|
64
67
|
RPC is one of many technologies that tries to solve the problem of getting smaller pieces of data from one place to another. Many will argue for or against RPC and its usefulness, but I'm not going to do that here. Google's Protocol Buffers relies on RPC and that's why you're here.
|
65
68
|
|
66
69
|
Any discussion about RPC leads to a discussion about clients and servers and the remote procedures themselves. For our purposes, we'll talk about a `Client` (process that is calling the server/service), a `Service` (the remote procedure), and a `Server` (the process that manages one or more services). We'll start with the Service first.
|
67
70
|
|
68
|
-
|
71
|
+
### Services
|
69
72
|
|
70
73
|
Services are simply classes that have endpoint methods defined. Here's what one looks like in protobuf:
|
71
74
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
75
|
+
```
|
76
|
+
message UserRequest {
|
77
|
+
optional string email = 1;
|
78
|
+
}
|
79
|
+
message UserList {
|
80
|
+
repeated User users = 1;
|
81
|
+
}
|
82
|
+
service UserService {
|
83
|
+
rpc Find (UserRequest) returns (UserList);
|
84
|
+
}
|
85
|
+
```
|
81
86
|
|
82
87
|
And the equivalent ruby stub for the service (generated with `rprotoc`):
|
83
88
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
89
|
+
```ruby
|
90
|
+
# lib/mycompany/user_service.rb
|
91
|
+
module Mycompany
|
92
|
+
class UserService < Protobuf::Rpc::Service
|
93
|
+
rpc :find, UserRequest, UserList
|
94
|
+
end
|
95
|
+
end
|
96
|
+
```
|
97
|
+
|
91
98
|
Recognize that the extra messages would actually have gone into the `defs.pb.rb` file while the service stub would receive it's own file at `user_service.rb`.
|
92
99
|
|
93
100
|
**Important Note: The *stubbed* class here is a *stub*. You should not alter it directly in any way as it will break your definition. Read on to learn how to use this stub.**
|
@@ -96,23 +103,25 @@ Did you read the note above? Go read it. I'll wait.
|
|
96
103
|
|
97
104
|
Ok, now that you have a compiled service stub, you'll want to require it from `lib` and implement the methods. You'll notice when you compile the stub there is a large comment at the top of the file. You can use this code comment to start your real implementation. Go ahead and copy it to your services directory (probably `app/services` if we're in rails).
|
98
105
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
end
|
113
|
-
|
106
|
+
```ruby
|
107
|
+
# app/services/user_service.rb
|
108
|
+
require 'lib/mycompany/user_service'
|
109
|
+
module Mycompany
|
110
|
+
class UserService
|
111
|
+
|
112
|
+
# request -> Mycompany::UserRequest
|
113
|
+
# response -> Mycompany::UserResponse
|
114
|
+
def find
|
115
|
+
# request.email will be the unpacked string that was sent by the client request
|
116
|
+
User.find_by_email(request.email).each do |user|
|
117
|
+
# must only use a proto instance of Mycompany::User when appending to the `users` field
|
118
|
+
response.users << user.to_proto
|
114
119
|
end
|
115
120
|
end
|
121
|
+
|
122
|
+
end
|
123
|
+
end
|
124
|
+
```
|
116
125
|
|
117
126
|
Simply implement the instance method for the defined rpc. No other methods will be allowed in the class (even helpers or private methods). An implicit `request` and `response` object are provided for you, pre-instantiated, and in the case of the request, already are populated with the data that was sent by the client.
|
118
127
|
|
@@ -120,24 +129,29 @@ If you need to create your own response object (a valid case), be sure to assign
|
|
120
129
|
|
121
130
|
Triggering an error from the service is simple:
|
122
131
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
+
```ruby
|
133
|
+
#...
|
134
|
+
def find
|
135
|
+
if request.email.blank?
|
136
|
+
rpc_failed 'Unable to find user without an email'
|
137
|
+
else
|
138
|
+
# query/populate response
|
139
|
+
end
|
140
|
+
end
|
141
|
+
#...
|
142
|
+
```
|
143
|
+
|
132
144
|
This means that the client's `on_failure` callback will be invoked instead of the `on_success` callback. Read more below on client callbacks.
|
133
145
|
|
134
146
|
I find it very convenient to use a CRUD-style interface when defining certain data services, though this is certainly not always the case.
|
135
147
|
|
136
|
-
|
148
|
+
### Servers
|
137
149
|
|
138
150
|
A service is nothing without being hooked up to a socket. It's the nerdy kid waiting by the telephone for someone to call without knowing that the phone company disconnected their house. Sad and pathetic. So hook the phone lines!
|
139
151
|
|
140
|
-
|
152
|
+
```
|
153
|
+
$ rpc_server -o myserver.com -p 9939 -e production -l ./log/protobuf.log config/environment.rb
|
154
|
+
```
|
141
155
|
|
142
156
|
The previous call will start an EventMachine server running on the given host and port which will load your application into memory. You certainly don't have to run rails or any other framework, just make sure you have some kind of file that will load your services all into memory. The server doesn't know where you put your code, so tell it.
|
143
157
|
|
@@ -145,63 +159,61 @@ Be aware that server needs to be able to translate the socket stream of bytes in
|
|
145
159
|
|
146
160
|
Once the server starts, you should see it as a running process with `ps`. Sending a KILL, QUIT, or TERM signal to the pid will result in shutting the server down gracefully.
|
147
161
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
$ kill -QUIT 1234
|
152
|
-
rpc_server shutdown
|
162
|
+
```
|
163
|
+
$ ps aux | grep rpc_server
|
164
|
+
1234 ... rpc_server myservice.com:9939
|
153
165
|
|
154
|
-
|
166
|
+
$ kill -QUIT 1234
|
167
|
+
rpc_server shutdown
|
168
|
+
```
|
169
|
+
|
170
|
+
### Clients
|
155
171
|
|
156
172
|
A lot of work has gone into making the client calls simple and easy to use yet still powerful. Clients have a DSL that feels very ajax-ish, mostly because of the nature of EventMachine, but I also think it works quite well.
|
157
173
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
end
|
181
|
-
|
182
|
-
# Register a block for execution when the response
|
183
|
-
# is deemed a failure. This can be either a client-side
|
184
|
-
# or server-side failure. The object passed the to the
|
185
|
-
# block has a `message` and a `code` attribute
|
186
|
-
# to aid in logging/diagnosing the failure.
|
187
|
-
c.on_failure do |err|
|
188
|
-
puts 'It failed: ' + err.message
|
189
|
-
end
|
174
|
+
```ruby
|
175
|
+
# require the defs from the shared gem/repo
|
176
|
+
require 'sharedgem/mycompany/user.pb'
|
177
|
+
require 'sharedgem/mycompany/user_service'
|
178
|
+
|
179
|
+
# Create a request object for the method we are invoking
|
180
|
+
req = Mycompany::UserRequest.new(:email => 'jeff@gmail.com')
|
181
|
+
|
182
|
+
# Use the UserService class to generate a client, invoke the rpc method
|
183
|
+
# while passing the request object
|
184
|
+
Mycompany::UserService.client.find(req) do |c|
|
185
|
+
# This block will be executed (registering the callbacks)
|
186
|
+
# before the request actualy occurs.
|
187
|
+
# the `c` param in this block is the `.client` object
|
188
|
+
# that is generated from the call above
|
189
|
+
|
190
|
+
# Register a block for execution when the response
|
191
|
+
# is deemed successful from the service. Accepts
|
192
|
+
# the unpacked response as its only parameter
|
193
|
+
c.on_success do |response|
|
194
|
+
response.users.each do |u|
|
195
|
+
puts u.inspect
|
190
196
|
end
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
197
|
+
end
|
198
|
+
|
199
|
+
# Register a block for execution when the response
|
200
|
+
# is deemed a failure. This can be either a client-side
|
201
|
+
# or server-side failure. The object passed the to the
|
202
|
+
# block has a `message` and a `code` attribute
|
203
|
+
# to aid in logging/diagnosing the failure.
|
204
|
+
c.on_failure do |err|
|
205
|
+
puts 'It failed: ' + err.message
|
206
|
+
end
|
207
|
+
end
|
208
|
+
```
|
209
|
+
|
210
|
+
Many different options can be passed to the `.client` call above (such as `:async => true` or `:timeout => 600`). See the `lib/protobuf/rpc/client.rb` and `lib/protobuf/rpc/service.rb` files for more documentation. It should be noted that the default behavior of `UserService.client` is to return a blocking client. The nature of using Client calls within a framework like Rails demands a blocking call if the response of a web request is dependent on data returned from the service.
|
211
|
+
|
212
|
+
## 3. RPC Interop
|
198
213
|
|
199
214
|
The main reason I wrote this gem was to provide a ruby implementation to google's protobuf that worked on the RPC layer with a Java Service layer that was already running [protobuf-socket-rpc][], the supported socket rpc library for protobuf from Google. The [old gem][] did not provide a very robust RPC implementation and it most certainly did not work with the Java stack.
|
200
215
|
|
201
|
-
|
202
|
-
|
203
|
-
Accreditation & Caveats
|
204
|
-
=======================
|
216
|
+
## Accreditation & Caveats
|
205
217
|
|
206
218
|
It must be noted a large amount of the code in this library was taken from the [ruby-protobuf][old gem] gem. Its authors and I were unable to reach a communication point to be able to merge all of my RPC updates in with their master. Unfortunately I just simply couldn't use their RPC code and so I've decided to diverge from their codeset. I take no credit whatsoever for the (de)serialization and `rprotoc` code generation original work, though I have modified it slightly to be more compliant with my understanding of the pb spec. I want to say thanks to the original devs for the good work they did to get me most of the way there. The code was initially diverged at their 0.4.0 version.
|
207
219
|
|
data/bin/rpc_server
CHANGED
data/bin/rprotoc
CHANGED
@@ -9,8 +9,10 @@ else
|
|
9
9
|
gem 'protobuf'
|
10
10
|
end
|
11
11
|
require 'protobuf'
|
12
|
+
require 'protobuf/version'
|
12
13
|
require 'protobuf/compiler/compiler'
|
13
14
|
|
15
|
+
::Version = Protobuf::VERSION
|
14
16
|
|
15
17
|
options = {
|
16
18
|
:proto_path => '.',
|
@@ -22,8 +24,6 @@ opts.on('-o', '--out <OUT_DIR>', 'Specify the directory in which Ruby source fil
|
|
22
24
|
opts.on_tail('-v', '--version', 'Show version.'){ puts(opts.ver); exit }
|
23
25
|
opts.on_tail('-h', '--help', 'Show this message.'){ puts(opts.help); exit }
|
24
26
|
|
25
|
-
::Version = Protobuf::VERSION
|
26
|
-
|
27
27
|
begin
|
28
28
|
opts.order!
|
29
29
|
rescue OptionParser::ParseError
|
@@ -9,11 +9,11 @@ def list_people(address_book)
|
|
9
9
|
puts " E-mail: #{person.email}" unless person.email.empty?
|
10
10
|
person.phone.each do |phone_number|
|
11
11
|
print(case phone_number.type
|
12
|
-
when Tutorial::Person::PhoneType::MOBILE
|
12
|
+
when Tutorial::Person::PhoneType::MOBILE then
|
13
13
|
' Mobile phone #: '
|
14
|
-
when Tutorial::Person::PhoneType::HOME
|
14
|
+
when Tutorial::Person::PhoneType::HOME then
|
15
15
|
' Home phone #: '
|
16
|
-
when Tutorial::Person::PhoneType::WORK
|
16
|
+
when Tutorial::Person::PhoneType::WORK then
|
17
17
|
' Work phone #: '
|
18
18
|
end)
|
19
19
|
puts phone_number.number
|
@@ -21,11 +21,11 @@ def prompt_for_address(person)
|
|
21
21
|
print 'Is this a mobile, home, or work phone? '
|
22
22
|
person.phone.last.type =
|
23
23
|
case type = STDIN.gets.strip
|
24
|
-
when 'mobile'
|
24
|
+
when 'mobile' then
|
25
25
|
Tutorial::Person::PhoneType::MOBILE
|
26
|
-
when 'home'
|
26
|
+
when 'home' then
|
27
27
|
Tutorial::Person::PhoneType::HOME
|
28
|
-
when 'work'
|
28
|
+
when 'work' then
|
29
29
|
Tutorial::Person::PhoneType::WORK
|
30
30
|
else
|
31
31
|
puts 'Unknown phone type; leaving as default value.'
|
data/lib/protobuf.rb
CHANGED
@@ -256,10 +256,10 @@ module Racc
|
|
256
256
|
}
|
257
257
|
if code
|
258
258
|
case code
|
259
|
-
when 1 # yyerror
|
259
|
+
when 1 then # yyerror
|
260
260
|
@racc_user_yyerror = true # user_yyerror
|
261
261
|
return -reduce_n
|
262
|
-
when 2 # yyaccept
|
262
|
+
when 2 then # yyaccept
|
263
263
|
return shift_n
|
264
264
|
else
|
265
265
|
raise '[Racc Bug] unknown jump code'
|
@@ -278,12 +278,12 @@ module Racc
|
|
278
278
|
# error
|
279
279
|
#
|
280
280
|
case @racc_error_status
|
281
|
-
when 0
|
281
|
+
when 0 then
|
282
282
|
unless arg[21] # user_yyerror
|
283
283
|
nerr += 1
|
284
284
|
on_error @racc_t, @racc_val, @racc_vstack
|
285
285
|
end
|
286
|
-
when 3
|
286
|
+
when 3 then
|
287
287
|
if @racc_t == 0 # is $
|
288
288
|
throw :racc_end_parse, nil
|
289
289
|
end
|
@@ -482,30 +482,30 @@ module_eval <<'..end lib/protobuf/compiler/proto.y modeval..id110d2bf917', 'lib/
|
|
482
482
|
def scan
|
483
483
|
until @scanner.eos?
|
484
484
|
case
|
485
|
-
when match(/\s+/, /\/\/.*/)
|
485
|
+
when match(/\s+/, /\/\/.*/) then
|
486
486
|
# skip
|
487
|
-
when match(/\/\*/)
|
487
|
+
when match(/\/\*/) then
|
488
488
|
# C-like comment
|
489
489
|
raise 'EOF inside block comment' until @scanner.scan_until(/\*\//)
|
490
|
-
when match(/(?:required|optional|repeated|import|package|option|message|extend|enum|service|rpc|returns|group|default|extensions|to|max|double|float|int32|int64|uint32|uint64|sint32|sint64|fixed32|fixed64|sfixed32|sfixed64|bool|string|bytes)\b/)
|
490
|
+
when match(/(?:required|optional|repeated|import|package|option|message|extend|enum|service|rpc|returns|group|default|extensions|to|max|double|float|int32|int64|uint32|uint64|sint32|sint64|fixed32|fixed64|sfixed32|sfixed64|bool|string|bytes)\b/) then
|
491
491
|
yield [@token, @token.to_sym]
|
492
|
-
when match(/[+-]?\d*\.\d+([Ee][\+-]?\d+)?/)
|
492
|
+
when match(/[+-]?\d*\.\d+([Ee][\+-]?\d+)?/) then
|
493
493
|
yield [:FLOAT_LITERAL, @token.to_f]
|
494
|
-
when match(/[+-]?[1-9]\d*(?!\.)/, /0(?![.xX0-9])/)
|
494
|
+
when match(/[+-]?[1-9]\d*(?!\.)/, /0(?![.xX0-9])/) then
|
495
495
|
yield [:DEC_INTEGER, @token.to_i]
|
496
|
-
when match(/0[xX]([A-Fa-f0-9])+/)
|
496
|
+
when match(/0[xX]([A-Fa-f0-9])+/) then
|
497
497
|
yield [:HEX_INTEGER, @token.to_i(0)]
|
498
|
-
when match(/0[0-7]+/)
|
498
|
+
when match(/0[0-7]+/) then
|
499
499
|
yield [:OCT_INTEGER, @token.to_i(0)]
|
500
|
-
when match(/(true|false)\b/)
|
500
|
+
when match(/(true|false)\b/) then
|
501
501
|
yield [:BOOLEAN_LITERAL, @token == 'true']
|
502
|
-
when match(/"(?:[^"\\]+|\\.)*"/, /'(?:[^'\\]+|\\.)*'/)
|
502
|
+
when match(/"(?:[^"\\]+|\\.)*"/, /'(?:[^'\\]+|\\.)*'/) then
|
503
503
|
yield [:STRING_LITERAL, eval(@token)]
|
504
|
-
when match(/[a-zA-Z_]\w*/)
|
504
|
+
when match(/[a-zA-Z_]\w*/) then
|
505
505
|
yield [:IDENT, @token.to_sym]
|
506
|
-
when match(/[A-Z]\w*/)
|
506
|
+
when match(/[A-Z]\w*/) then
|
507
507
|
yield [:CAMEL_IDENT, @token.to_sym]
|
508
|
-
when match(/./)
|
508
|
+
when match(/./) then
|
509
509
|
yield [@token, @token]
|
510
510
|
else
|
511
511
|
raise "parse error around #{@scanner.string[@scanner.pos, 32].inspect}"
|