rtext 0.8.1 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +120 -89
- data/Project.yaml +15 -0
- data/RText_Protocol +47 -4
- data/lib/rtext/context_builder.rb +49 -8
- data/lib/rtext/default_completer.rb +212 -163
- data/lib/rtext/default_service_provider.rb +3 -3
- data/lib/rtext/frontend/connector.rb +130 -56
- data/lib/rtext/instantiator.rb +11 -3
- data/lib/rtext/language.rb +5 -5
- data/lib/rtext/serializer.rb +3 -3
- data/lib/rtext/service.rb +281 -253
- data/lib/rtext/tokenizer.rb +2 -2
- metadata +33 -33
- data/Rakefile +0 -46
- data/test/completer_test.rb +0 -606
- data/test/context_builder_test.rb +0 -948
- data/test/frontend/context_test.rb +0 -301
- data/test/instantiator_test.rb +0 -1704
- data/test/integration/backend.out +0 -13
- data/test/integration/crash_on_request_editor.rb +0 -12
- data/test/integration/ecore_editor.rb +0 -50
- data/test/integration/frontend.log +0 -38203
- data/test/integration/model/invalid_encoding.invenc +0 -2
- data/test/integration/model/test.crash_on_request +0 -18
- data/test/integration/model/test.crashing_backend +0 -18
- data/test/integration/model/test.dont_open_socket +0 -0
- data/test/integration/model/test.invalid_cmd_line +0 -0
- data/test/integration/model/test.not_in_rtext +0 -0
- data/test/integration/model/test_large_with_errors.ect3 +0 -43523
- data/test/integration/model/test_metamodel.ect +0 -24
- data/test/integration/model/test_metamodel2.ect +0 -5
- data/test/integration/model/test_metamodel3.ect4 +0 -7
- data/test/integration/model/test_metamodel_error.ect2 +0 -3
- data/test/integration/model/test_metamodel_ok.ect2 +0 -18
- data/test/integration/test.rb +0 -966
- data/test/link_detector_test.rb +0 -287
- data/test/message_helper_test.rb +0 -118
- data/test/rtext_test.rb +0 -11
- data/test/serializer_test.rb +0 -1004
- data/test/tokenizer_test.rb +0 -173
@@ -21,18 +21,25 @@ def initialize(config, options={})
|
|
21
21
|
@outfile_provider = options[:outfile_provider]
|
22
22
|
@keep_outfile = options[:keep_outfile]
|
23
23
|
@connection_timeout = options[:connection_timeout] || 10
|
24
|
+
@process_id = nil
|
24
25
|
end
|
25
26
|
|
26
27
|
def execute_command(obj, options={})
|
27
|
-
timeout = options[:timeout] ||
|
28
|
+
timeout = options[:timeout] || 10
|
28
29
|
@busy = false if @busy_start_time && (Time.now > @busy_start_time + timeout)
|
29
30
|
if @busy
|
30
31
|
do_work
|
31
|
-
:backend_busy
|
32
|
-
|
32
|
+
return :backend_busy
|
33
|
+
end
|
34
|
+
unless connected?
|
35
|
+
connect unless connecting?
|
36
|
+
do_work
|
37
|
+
end
|
38
|
+
if connected?
|
33
39
|
obj["invocation_id"] = @invocation_id
|
34
40
|
obj["type"] = "request"
|
35
41
|
@socket.send(serialize_message(obj), 0)
|
42
|
+
@logger.debug('Sent request') if @logger
|
36
43
|
result = nil
|
37
44
|
@busy = true
|
38
45
|
@busy_start_time = Time.now
|
@@ -66,9 +73,8 @@ def execute_command(obj, options={})
|
|
66
73
|
result
|
67
74
|
end
|
68
75
|
else
|
69
|
-
|
70
|
-
|
71
|
-
:connecting
|
76
|
+
@logger.debug('connecting')
|
77
|
+
:connecting
|
72
78
|
end
|
73
79
|
end
|
74
80
|
|
@@ -83,27 +89,72 @@ def stop
|
|
83
89
|
end
|
84
90
|
if connected?
|
85
91
|
execute_command({"type" => "request", "command" => "stop"})
|
86
|
-
while do_work
|
92
|
+
while do_work
|
87
93
|
sleep(0.1)
|
88
94
|
end
|
89
95
|
end
|
96
|
+
ensure_process_cleanup(@process_id, @keep_outfile ? nil : @out_file, 10)
|
97
|
+
@process_id = nil
|
90
98
|
end
|
91
99
|
|
92
100
|
private
|
93
101
|
|
102
|
+
def wait_for_process_to_exit(process_id, timeout)
|
103
|
+
with_timeout timeout do
|
104
|
+
begin
|
105
|
+
waitpid(process_id, Process::WNOHANG)
|
106
|
+
process_id = nil
|
107
|
+
true
|
108
|
+
rescue Errno::ECHILD => _
|
109
|
+
false
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def ensure_process_cleanup(process_id, out_file, timeout)
|
115
|
+
Thread.new do
|
116
|
+
begin
|
117
|
+
unless process_id.nil?
|
118
|
+
process_id = nil if wait_for_process_to_exit(process_id, timeout)
|
119
|
+
end
|
120
|
+
ensure
|
121
|
+
unless process_id.nil?
|
122
|
+
begin
|
123
|
+
Process.kill('QUIT', process_id)
|
124
|
+
rescue Errno::ESRCH => _
|
125
|
+
end
|
126
|
+
end
|
127
|
+
File.unlink(out_file) if !out_file.nil? && File.exist?(out_file)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def with_timeout(timeout, sleep_time = 0.1, &block)
|
133
|
+
started = Time.now
|
134
|
+
while true do
|
135
|
+
return true if block.call
|
136
|
+
if Time.now > started + timeout
|
137
|
+
return false
|
138
|
+
end
|
139
|
+
sleep(sleep_time)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
|
94
144
|
def connected?
|
95
|
-
@state == :
|
145
|
+
!@process_id.nil? && @state == :read_from_socket && backend_running?
|
96
146
|
end
|
97
147
|
|
98
148
|
def connecting?
|
99
|
-
@state == :
|
149
|
+
!@process_id.nil? && (@state == :wait_for_file || @state == :wait_for_port)
|
100
150
|
end
|
101
151
|
|
102
152
|
def backend_running?
|
103
153
|
if @process_id
|
104
154
|
begin
|
105
|
-
|
106
|
-
|
155
|
+
waitpid(@process_id, Process::WNOHANG)
|
156
|
+
return true
|
157
|
+
rescue Errno::ECHILD => _
|
107
158
|
end
|
108
159
|
end
|
109
160
|
false
|
@@ -121,66 +172,87 @@ def tempfile_name
|
|
121
172
|
end
|
122
173
|
|
123
174
|
def connect
|
124
|
-
|
175
|
+
return if connected?
|
125
176
|
@connect_start_time = Time.now
|
126
177
|
|
127
178
|
@logger.info @config.command if @logger
|
128
179
|
|
129
180
|
if @outfile_provider
|
130
|
-
@out_file = @outfile_provider.call
|
181
|
+
@out_file = File.expand_path(@outfile_provider.call)
|
131
182
|
else
|
132
|
-
@out_file = tempfile_name
|
183
|
+
@out_file = File.expand_path(tempfile_name)
|
133
184
|
end
|
134
|
-
File.unlink(@out_file) if File.exist?(@out_file)
|
135
185
|
|
136
|
-
|
137
|
-
@
|
186
|
+
if @process_id.nil?
|
187
|
+
File.unlink(@out_file) if File.exist?(@out_file)
|
188
|
+
Dir.chdir(File.dirname(@config.file)) do
|
189
|
+
@logger.debug(@config.command.strip + " > #{@out_file} 2>&1") if @logger
|
190
|
+
@process_id = spawn(@config.command.strip + " > #{@out_file} 2>&1")
|
191
|
+
@state = :wait_for_file
|
192
|
+
end
|
138
193
|
end
|
139
|
-
@work_state = :wait_for_file
|
140
194
|
end
|
141
195
|
|
142
196
|
def do_work
|
143
|
-
|
144
|
-
|
145
|
-
if
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
197
|
+
unless @process_id
|
198
|
+
@state = :off
|
199
|
+
@logger.debug('No process id') if @logger
|
200
|
+
return false
|
201
|
+
end
|
202
|
+
if @state == :wait_for_port && !File.exist?(@out_file)
|
203
|
+
@state = :wait_for_file
|
204
|
+
end
|
205
|
+
if @state == :wait_for_file && File.exist?(@out_file)
|
206
|
+
@state = :wait_for_port
|
207
|
+
end
|
208
|
+
if @state == :wait_for_file
|
209
|
+
while true
|
210
|
+
if Time.now > @connect_start_time + @connection_timeout
|
211
|
+
cleanup
|
212
|
+
@connection_listener.call(:timeout) if @connection_listener
|
213
|
+
@state = :off
|
214
|
+
@logger.warn "process didn't startup (connection timeout)" if @logger
|
215
|
+
return false
|
216
|
+
end
|
217
|
+
sleep(0.1)
|
218
|
+
if File.exist?(@out_file)
|
219
|
+
@state = :wait_for_port
|
220
|
+
break
|
221
|
+
end
|
154
222
|
end
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
@
|
163
|
-
|
164
|
-
|
223
|
+
end
|
224
|
+
if @state == :wait_for_port
|
225
|
+
while true
|
226
|
+
break unless File.exist?(@out_file)
|
227
|
+
output = File.read(@out_file)
|
228
|
+
if output =~ /^RText service, listening on port (\d+)/
|
229
|
+
port = $1.to_i
|
230
|
+
@logger.info "connecting to #{port}" if @logger
|
231
|
+
begin
|
232
|
+
@socket = TCPSocket.new("127.0.0.1", port)
|
233
|
+
@socket.setsockopt(:SOCKET, :RCVBUF, 1000000)
|
234
|
+
rescue Errno::ECONNREFUSED
|
235
|
+
cleanup
|
236
|
+
@connection_listener.call(:timeout) if @connection_listener
|
237
|
+
@state = :off
|
238
|
+
@logger.warn "could not connect socket (connection timeout)" if @logger
|
239
|
+
return false
|
240
|
+
end
|
241
|
+
@state = :read_from_socket
|
242
|
+
@connection_listener.call(:connected) if @connection_listener
|
243
|
+
break
|
244
|
+
end
|
245
|
+
if Time.now > @connect_start_time + @connection_timeout
|
165
246
|
cleanup
|
166
247
|
@connection_listener.call(:timeout) if @connection_listener
|
167
|
-
@work_state = :done
|
168
248
|
@state = :off
|
169
249
|
@logger.warn "could not connect socket (connection timeout)" if @logger
|
250
|
+
return false
|
170
251
|
end
|
171
|
-
|
172
|
-
@work_state = :read_from_socket
|
173
|
-
@connection_listener.call(:connected) if @connection_listener
|
174
|
-
end
|
175
|
-
if Time.now > @connect_start_time + @connection_timeout
|
176
|
-
cleanup
|
177
|
-
@connection_listener.call(:timeout) if @connection_listener
|
178
|
-
@work_state = :done
|
179
|
-
@state = :off
|
180
|
-
@logger.warn "could not connect socket (connection timeout)" if @logger
|
252
|
+
sleep(0.1)
|
181
253
|
end
|
182
|
-
|
183
|
-
|
254
|
+
end
|
255
|
+
if @state == :read_from_socket
|
184
256
|
repeat = true
|
185
257
|
socket_closed = false
|
186
258
|
response_data = ""
|
@@ -195,6 +267,7 @@ def do_work
|
|
195
267
|
@logger.info "server socket closed (end of file)" if @logger
|
196
268
|
end
|
197
269
|
if data
|
270
|
+
@logger.debug('Got data') if @logger
|
198
271
|
repeat = true
|
199
272
|
response_data.concat(data)
|
200
273
|
while obj = extract_message(response_data)
|
@@ -208,13 +281,13 @@ def do_work
|
|
208
281
|
end
|
209
282
|
elsif !backend_running? || socket_closed
|
210
283
|
cleanup
|
211
|
-
@
|
284
|
+
@state = :off
|
285
|
+
@logger.debug("backend_running?: #{backend_running?}, socket_closed: #{socket_closed}") if @logger
|
212
286
|
return false
|
213
287
|
end
|
214
288
|
end
|
215
|
-
true
|
216
289
|
end
|
217
|
-
|
290
|
+
true
|
218
291
|
end
|
219
292
|
|
220
293
|
def cleanup
|
@@ -224,7 +297,8 @@ def cleanup
|
|
224
297
|
break unless backend_running?
|
225
298
|
sleep(0.1)
|
226
299
|
end
|
227
|
-
|
300
|
+
ensure_process_cleanup(@process_id, @keep_outfile ? nil : @out_file, 10)
|
301
|
+
@process_id = nil
|
228
302
|
end
|
229
303
|
|
230
304
|
end
|
data/lib/rtext/instantiator.rb
CHANGED
@@ -247,7 +247,15 @@ class Instantiator
|
|
247
247
|
element.setOrAddGeneric(feature.name, proxy)
|
248
248
|
else
|
249
249
|
begin
|
250
|
-
|
250
|
+
v_value = v.value
|
251
|
+
feature_instance_class = feature.eType.instanceClass
|
252
|
+
if feature_instance_class == String && (v_value.is_a?(Float) || v_value.is_a?(Fixnum))
|
253
|
+
element.setOrAddGeneric(feature.name, v_value.to_s)
|
254
|
+
elsif feature_instance_class == Float && v_value.is_a?(Fixnum)
|
255
|
+
element.setOrAddGeneric(feature.name, v_value.to_f)
|
256
|
+
else
|
257
|
+
element.setOrAddGeneric(feature.name, v_value)
|
258
|
+
end
|
251
259
|
rescue StandardError
|
252
260
|
# backward compatibility for RGen versions not supporting BigDecimal
|
253
261
|
if v.value.is_a?(BigDecimal)
|
@@ -302,9 +310,9 @@ class Instantiator
|
|
302
310
|
elsif feature.eType.is_a?(RGen::ECore::EEnum)
|
303
311
|
[:identifier, :string]
|
304
312
|
else
|
305
|
-
expected = { String => [:string, :identifier],
|
313
|
+
expected = { String => [:string, :identifier, :integer, :float],
|
306
314
|
Integer => [:integer],
|
307
|
-
Float => [:float],
|
315
|
+
Float => [:float, :integer],
|
308
316
|
RGen::MetamodelBuilder::DataTypes::Boolean => [:boolean],
|
309
317
|
Object => [:string, :identifier, :integer, :float, :boolean]
|
310
318
|
}[feature.eType.instanceClass]
|
data/lib/rtext/language.rb
CHANGED
@@ -260,7 +260,7 @@ class Language
|
|
260
260
|
end
|
261
261
|
|
262
262
|
def concrete_types(clazz)
|
263
|
-
([clazz] + clazz.eAllSubTypes).select{|c|
|
263
|
+
([clazz] + clazz.eAllSubTypes).select{|c| c.concrete}
|
264
264
|
end
|
265
265
|
|
266
266
|
def containments_by_target_type(clazz, type)
|
@@ -299,7 +299,7 @@ class Language
|
|
299
299
|
@command_by_class = {}
|
300
300
|
@has_command = {}
|
301
301
|
root_epackage.eAllClasses.each do |c|
|
302
|
-
next
|
302
|
+
next unless c.concrete
|
303
303
|
cmd = command_name_provider.call(c)
|
304
304
|
@command_by_class[c.instanceClass] = cmd
|
305
305
|
@has_command[cmd] = true
|
@@ -307,7 +307,7 @@ class Language
|
|
307
307
|
@class_by_command[clazz] ||= {}
|
308
308
|
containments(c).collect{|r|
|
309
309
|
[r.eType] + r.eType.eAllSubTypes}.flatten.uniq.each do |t|
|
310
|
-
next
|
310
|
+
next unless t.concrete
|
311
311
|
cmw = command_name_provider.call(t)
|
312
312
|
raise "ambiguous command name #{cmw}" if @class_by_command[clazz][cmw]
|
313
313
|
@class_by_command[clazz][cmw] = t.instanceClass
|
@@ -315,7 +315,7 @@ class Language
|
|
315
315
|
end
|
316
316
|
@class_by_command[nil] = {}
|
317
317
|
@root_classes.each do |c|
|
318
|
-
next
|
318
|
+
next unless c.concrete
|
319
319
|
cmw = command_name_provider.call(c)
|
320
320
|
raise "ambiguous command name #{cmw}" if @class_by_command[nil][cmw]
|
321
321
|
@class_by_command[nil][cmw] = c.instanceClass
|
@@ -323,7 +323,7 @@ class Language
|
|
323
323
|
end
|
324
324
|
|
325
325
|
def default_root_classes(root_package)
|
326
|
-
root_epackage.eAllClasses.select{|c|
|
326
|
+
root_epackage.eAllClasses.select{|c| c.concrete &&
|
327
327
|
!c.eAllReferences.any?{|r| r.eOpposite && r.eOpposite.containment}}
|
328
328
|
end
|
329
329
|
|
data/lib/rtext/serializer.rb
CHANGED
@@ -152,7 +152,7 @@ class Serializer
|
|
152
152
|
result << v.to_s
|
153
153
|
end
|
154
154
|
elsif feature.eType.instanceClass == String
|
155
|
-
if @lang.unquoted?(feature) && v.to_s =~ /^[a-zA-Z_]\w*$/ && v.to_s != "true" && v.to_s != "false"
|
155
|
+
if @lang.unquoted?(feature) && v.to_s =~ /^[a-zA-Z_]\w*$/m && v.to_s != "true" && v.to_s != "false"
|
156
156
|
result << v.to_s
|
157
157
|
else
|
158
158
|
result << "\"#{v.gsub("\\","\\\\\\\\").gsub("\"","\\\"").gsub("\n","\\n").
|
@@ -166,7 +166,7 @@ class Serializer
|
|
166
166
|
# formatting not available for BigDecimals
|
167
167
|
else
|
168
168
|
if arg_format
|
169
|
-
|
169
|
+
result << sprintf(arg_format, v)
|
170
170
|
else
|
171
171
|
result << v.to_s
|
172
172
|
end
|
@@ -179,7 +179,7 @@ class Serializer
|
|
179
179
|
result << v.to_s
|
180
180
|
end
|
181
181
|
elsif feature.eType.instanceClass == Object
|
182
|
-
if v.to_s =~
|
182
|
+
if v.to_s =~ /^-?\d+(\.\d+)?$|^\w+$|^true$|^false$/
|
183
183
|
result << v.to_s
|
184
184
|
else
|
185
185
|
result << "\"#{v.to_s.gsub("\\","\\\\\\\\").gsub("\"","\\\"").gsub("\n","\\n").
|