rtext 0.8.1 → 0.10.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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +120 -89
  3. data/Project.yaml +15 -0
  4. data/RText_Protocol +47 -4
  5. data/lib/rtext/context_builder.rb +49 -8
  6. data/lib/rtext/default_completer.rb +212 -163
  7. data/lib/rtext/default_service_provider.rb +3 -3
  8. data/lib/rtext/frontend/connector.rb +130 -56
  9. data/lib/rtext/instantiator.rb +11 -3
  10. data/lib/rtext/language.rb +5 -5
  11. data/lib/rtext/serializer.rb +3 -3
  12. data/lib/rtext/service.rb +281 -253
  13. data/lib/rtext/tokenizer.rb +2 -2
  14. metadata +33 -33
  15. data/Rakefile +0 -46
  16. data/test/completer_test.rb +0 -606
  17. data/test/context_builder_test.rb +0 -948
  18. data/test/frontend/context_test.rb +0 -301
  19. data/test/instantiator_test.rb +0 -1704
  20. data/test/integration/backend.out +0 -13
  21. data/test/integration/crash_on_request_editor.rb +0 -12
  22. data/test/integration/ecore_editor.rb +0 -50
  23. data/test/integration/frontend.log +0 -38203
  24. data/test/integration/model/invalid_encoding.invenc +0 -2
  25. data/test/integration/model/test.crash_on_request +0 -18
  26. data/test/integration/model/test.crashing_backend +0 -18
  27. data/test/integration/model/test.dont_open_socket +0 -0
  28. data/test/integration/model/test.invalid_cmd_line +0 -0
  29. data/test/integration/model/test.not_in_rtext +0 -0
  30. data/test/integration/model/test_large_with_errors.ect3 +0 -43523
  31. data/test/integration/model/test_metamodel.ect +0 -24
  32. data/test/integration/model/test_metamodel2.ect +0 -5
  33. data/test/integration/model/test_metamodel3.ect4 +0 -7
  34. data/test/integration/model/test_metamodel_error.ect2 +0 -3
  35. data/test/integration/model/test_metamodel_ok.ect2 +0 -18
  36. data/test/integration/test.rb +0 -966
  37. data/test/link_detector_test.rb +0 -287
  38. data/test/message_helper_test.rb +0 -118
  39. data/test/rtext_test.rb +0 -11
  40. data/test/serializer_test.rb +0 -1004
  41. 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] || 5
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
- elsif connected?
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
- connect unless connecting?
70
- do_work
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 == :connected && backend_running?
145
+ !@process_id.nil? && @state == :read_from_socket && backend_running?
96
146
  end
97
147
 
98
148
  def connecting?
99
- @state == :connecting
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
- return true unless waitpid(@process_id, Process::WNOHANG)
106
- rescue Errno::ECHILD
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
- @state = :connecting
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
- Dir.chdir(File.dirname(@config.file)) do
137
- @process_id = spawn(@config.command.strip + " > #{@out_file} 2>&1")
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
- case @work_state
144
- when :wait_for_file
145
- if File.exist?(@out_file)
146
- @work_state = :wait_for_port
147
- end
148
- if Time.now > @connect_start_time + @connection_timeout
149
- cleanup
150
- @connection_listener.call(:timeout) if @connection_listener
151
- @work_state = :done
152
- @state = :off
153
- @logger.warn "process didn't startup (connection timeout)" if @logger
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
- true
156
- when :wait_for_port
157
- output = File.read(@out_file)
158
- if output =~ /^RText service, listening on port (\d+)/
159
- port = $1.to_i
160
- @logger.info "connecting to #{port}" if @logger
161
- begin
162
- @socket = TCPSocket.new("127.0.0.1", port)
163
- @socket.setsockopt(:SOCKET, :RCVBUF, 1000000)
164
- rescue Errno::ECONNREFUSED
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
- @state = :connected
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
- true
183
- when :read_from_socket
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
- @work_state = :done
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
- File.unlink(@out_file) unless @keep_outfile
300
+ ensure_process_cleanup(@process_id, @keep_outfile ? nil : @out_file, 10)
301
+ @process_id = nil
228
302
  end
229
303
 
230
304
  end
@@ -247,7 +247,15 @@ class Instantiator
247
247
  element.setOrAddGeneric(feature.name, proxy)
248
248
  else
249
249
  begin
250
- element.setOrAddGeneric(feature.name, v.value)
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]
@@ -260,7 +260,7 @@ class Language
260
260
  end
261
261
 
262
262
  def concrete_types(clazz)
263
- ([clazz] + clazz.eAllSubTypes).select{|c| !c.abstract}
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 if c.abstract
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 if t.abstract
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 if c.abstract
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| !c.abstract &&
326
+ root_epackage.eAllClasses.select{|c| c.concrete &&
327
327
  !c.eAllReferences.any?{|r| r.eOpposite && r.eOpposite.containment}}
328
328
  end
329
329
 
@@ -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
- result << sprintf(arg_format, v)
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 =~ /^\d+(\.\d+)?$|^\w+$|^true$|^false$/
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").