rtext 0.5.1 → 0.7.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.
@@ -0,0 +1,42 @@
1
+ module RText
2
+
3
+ class DefaultResolver
4
+
5
+ def initialize(lang)
6
+ @lang = lang
7
+ end
8
+
9
+ def resolve_fragment(fragment)
10
+ @lang.reference_qualifier.call(fragment.unresolved_refs, fragment)
11
+ fragment.resolve_local(
12
+ :use_target_type => @lang.per_type_identifier)
13
+ end
14
+
15
+ def resolve_model(model)
16
+ @lang.reference_qualifier.call(model.unresolved_refs, model)
17
+ model.resolve(
18
+ :fragment_provider => proc {|e|
19
+ fr = @lang.fragment_ref(e)
20
+ fr && fr.fragment
21
+ },
22
+ :use_target_type => @lang.per_type_identifier)
23
+ end
24
+
25
+ def find_targets(uref, model)
26
+ @lang.reference_qualifier.call([uref], model)
27
+ identifier = uref.proxy.targetIdentifier
28
+ targets = model.index[identifier]
29
+ targets ||= []
30
+ if @lang.per_type_identifier
31
+ feature = @lang.feature_by_name(uref.element.class.ecore, uref.feature_name)
32
+ if feature
33
+ targets = targets.select{|t| t.is_a?(feature.eType.instanceClass)}
34
+ end
35
+ end
36
+ targets
37
+ end
38
+
39
+ end
40
+
41
+ end
42
+
@@ -1,7 +1,16 @@
1
+ require 'rtext/default_completer'
2
+ require 'rtext/default_resolver'
3
+
1
4
  module RText
2
5
 
3
6
  class DefaultServiceProvider
4
7
 
8
+ # Creates a DefaultServiceProvider. Options:
9
+ #
10
+ # :resolver
11
+ # a reference resolver responding to the methods provided by DefaultResolver
12
+ # default: DefaultResolver
13
+ #
5
14
  def initialize(language, fragmented_model, model_loader, options={})
6
15
  @lang = language
7
16
  @model = fragmented_model
@@ -11,6 +20,7 @@ class DefaultServiceProvider
11
20
  @model.add_fragment_change_listener(proc {|fragment, kind|
12
21
  @element_name_index = nil
13
22
  })
23
+ @resolver = options[:resolver] || DefaultResolver.new(language)
14
24
  end
15
25
 
16
26
  def language
@@ -25,17 +35,30 @@ class DefaultServiceProvider
25
35
  end
26
36
  end
27
37
 
38
+ def get_completion_options(context)
39
+ completer = RText::DefaultCompleter.new(@lang)
40
+ class << completer
41
+ attr_accessor :service_provider
42
+ def reference_options(context)
43
+ service_provider.get_reference_completion_options(context).collect {|o|
44
+ DefaultCompleter::CompletionOption.new(o.identifier, "<#{o.type}>")}
45
+ end
46
+ end
47
+ completer.service_provider = self
48
+ completer.complete(context)
49
+ end
50
+
28
51
  ReferenceCompletionOption = Struct.new(:identifier, :type)
29
- def get_reference_completion_options(reference, context)
52
+ def get_reference_completion_options(context)
30
53
  if @model.environment
31
- targets = @model.environment.find(:class => reference.eType.instanceClass)
54
+ targets = @model.environment.find(:class => context.feature.eType.instanceClass)
32
55
  else
33
- clazz = reference.eType.instanceClass
56
+ clazz = context.feature.eType.instanceClass
34
57
  targets = @model.index.values.flatten.select{|e| e.is_a?(clazz)}
35
58
  end
36
59
  index = 0
37
60
  targets.collect{|t|
38
- ident = @lang.identifier_provider.call(t, context.element, reference, index)
61
+ ident = @lang.identifier_provider.call(t, context.element, context.feature, index)
39
62
  index += 1
40
63
  if ident
41
64
  ReferenceCompletionOption.new(ident, t.class.ecore.name)
@@ -46,29 +69,34 @@ class DefaultServiceProvider
46
69
  end
47
70
 
48
71
  ReferenceTarget = Struct.new(:file, :line, :display_name)
49
- def get_reference_targets(identifier, element, feature, index)
72
+ def get_link_targets(link_descriptor)
73
+ if link_descriptor.backward
74
+ get_backward_reference_targets(link_descriptor.value,
75
+ link_descriptor.element, link_descriptor.feature, link_descriptor.index)
76
+ else
77
+ get_forward_reference_targets(link_descriptor.value,
78
+ link_descriptor.element, link_descriptor.feature, link_descriptor.index)
79
+ end
80
+ end
81
+
82
+ def get_forward_reference_targets(identifier, element, feature, index)
50
83
  result = []
51
- urefs = [
52
- RGen::Instantiator::ReferenceResolver::UnresolvedReference.new(element, feature.name,
53
- element.getGenericAsArray(feature.name)[index]) ]
54
- @lang.reference_qualifier.call(urefs, @model)
55
- identifier = urefs.first.proxy.targetIdentifier
56
- targets = @model.index[identifier]
57
- if targets && @lang.per_type_identifier
58
- if feature
59
- targets = targets.select{|t| t.is_a?(feature.eType.instanceClass)}
60
- end
61
- end
62
- targets && targets.each do |t|
63
- if @lang.fragment_ref(t)
64
- path = File.expand_path(@lang.fragment_ref(t).fragment.location)
65
- result << ReferenceTarget.new(path, @lang.line_number(t), "#{identifier} [#{t.class.ecore.name}]")
84
+ ref_value = element.getGenericAsArray(feature.name)[index]
85
+ if ref_value.is_a?(RGen::MetamodelBuilder::MMProxy)
86
+ uref = RGen::Instantiator::ReferenceResolver::UnresolvedReference.new(
87
+ element, feature.name, ref_value)
88
+ targets = @resolver.find_targets(uref, @model)
89
+ targets.each do |t|
90
+ if @lang.fragment_ref(t)
91
+ path = File.expand_path(@lang.fragment_ref(t).fragment.location)
92
+ result << ReferenceTarget.new(path, @lang.line_number(t), "#{identifier} [#{t.class.ecore.name}]")
93
+ end
66
94
  end
67
95
  end
68
96
  result
69
97
  end
70
98
 
71
- def get_referencing_elements(identifier, element, feature, index)
99
+ def get_backward_reference_targets(identifier, element, feature, index)
72
100
  result = []
73
101
  targets = @model.index[@lang.identifier_provider.call(element, nil, nil, nil)]
74
102
  if targets && @lang.per_type_identifier
@@ -160,6 +160,7 @@ def do_work
160
160
  @logger.info "connecting to #{port}" if @logger
161
161
  begin
162
162
  @socket = TCPSocket.new("127.0.0.1", port)
163
+ @socket.setsockopt(:SOCKET, :RCVBUF, 1000000)
163
164
  rescue Errno::ECONNREFUSED
164
165
  cleanup
165
166
  @connection_listener.call(:timeout) if @connection_listener
@@ -187,7 +188,7 @@ def do_work
187
188
  repeat = false
188
189
  data = nil
189
190
  begin
190
- data = @socket.read_nonblock(100000)
191
+ data = @socket.read_nonblock(1000000)
191
192
  rescue Errno::EWOULDBLOCK
192
193
  rescue IOError, EOFError, Errno::ECONNRESET
193
194
  socket_closed = true
@@ -1,10 +1,12 @@
1
1
  require 'rgen/ecore/ecore_ext'
2
2
  require 'rgen/instantiator/reference_resolver'
3
+ require 'rtext/tokenizer'
3
4
  require 'rtext/parser'
4
5
 
5
6
  module RText
6
7
 
7
8
  class Instantiator
9
+ include RText::Tokenizer
8
10
 
9
11
  # A problem found during instantiation
10
12
  # if the file is not known, it will be nil
@@ -37,8 +39,9 @@ class Instantiator
37
39
  # object which references the fragment being instantiated, will be set on model elements
38
40
  #
39
41
  # :on_progress
40
- # a proc which is called twice for each model element:
41
- # when the command token is recognized and when the element is instantiated
42
+ # a proc which is called with a number measuring the progress made since the last call;
43
+ # progress is measured by the number of command tokens recognized plus the number of
44
+ # model elements instantiated.
42
45
  #
43
46
  def instantiate(str, options={})
44
47
  @line_numbers = {}
@@ -50,10 +53,14 @@ class Instantiator
50
53
  @fragment_ref = options[:fragment_ref]
51
54
  @on_progress_proc = options[:on_progress]
52
55
  @context_class_stack = []
53
- parser = Parser.new(@lang.reference_regexp)
56
+ parser = Parser.new
54
57
  @root_elements.clear
55
58
  parser_problems = []
56
- parser.parse(str,
59
+ tokens = tokenize(str, @lang.reference_regexp,
60
+ :on_command_token => @on_progress_proc && lambda do
61
+ @on_progress_proc.call(1)
62
+ end)
63
+ parser.parse(tokens,
57
64
  :descent_visitor => lambda do |command|
58
65
  clazz = @lang.class_by_command(command.value, @context_class_stack.last)
59
66
  # in case no class is found, nil will be pushed, this will case the next command
@@ -69,9 +76,6 @@ class Instantiator
69
76
  unassociated_comments(args[3])
70
77
  end
71
78
  end,
72
- :on_command_token => @on_progress_proc && lambda do
73
- @on_progress_proc.call
74
- end,
75
79
  :problems => parser_problems)
76
80
  parser_problems.each do |p|
77
81
  problem(p.message, p.line)
@@ -88,7 +92,7 @@ class Instantiator
88
92
 
89
93
  def create_element(command, arg_list, element_list, comments, annotation, is_root)
90
94
  clazz = @context_class_stack.last
91
- @on_progress_proc.call if @on_progress_proc
95
+ @on_progress_proc.call(1) if @on_progress_proc
92
96
  if !@lang.has_command(command.value)
93
97
  problem("Unknown command '#{command.value}'", command.line)
94
98
  return
@@ -158,15 +162,21 @@ class Instantiator
158
162
  end
159
163
  if !feature.many &&
160
164
  (element.getGenericAsArray(role).size > 0 || children.size > 1)
161
- problem("Only one child allowed in role '#{role}'", line_number(children[0]))
165
+ if children.size == 1
166
+ # other child was created under another role lable with same name
167
+ problem("Only one child allowed in role '#{role}'", line_number(children[0]))
168
+ else
169
+ problem("Only one child allowed in role '#{role}'", line_number(children[1]))
170
+ end
162
171
  return
163
172
  end
164
- expected_type = @lang.concrete_types(feature.eType)
173
+ expected_type = nil
165
174
  children.each do |c|
166
- if !expected_type.include?(c.class.ecore)
167
- problem("Role '#{role}' can not take a #{c.class.ecore.name}, expected #{expected_type.name.join(", ")}", line_number(c))
168
- else
175
+ begin
169
176
  element.setOrAddGeneric(feature.name, c)
177
+ rescue StandardError
178
+ expected_type ||= @lang.concrete_types(feature.eType)
179
+ problem("Role '#{role}' can not take a #{c.class.ecore.name}, expected #{expected_type.name.join(", ")}", line_number(c))
170
180
  end
171
181
  end
172
182
  else
@@ -236,7 +246,16 @@ class Instantiator
236
246
  end
237
247
  element.setOrAddGeneric(feature.name, proxy)
238
248
  else
239
- element.setOrAddGeneric(feature.name, v.value)
249
+ begin
250
+ element.setOrAddGeneric(feature.name, v.value)
251
+ rescue StandardError
252
+ # backward compatibility for RGen versions not supporting BigDecimal
253
+ if v.value.is_a?(BigDecimal)
254
+ element.setOrAddGeneric(feature.name, v.value.to_f)
255
+ else
256
+ raise
257
+ end
258
+ end
240
259
  end
241
260
  end
242
261
  defined_args[name] = true
@@ -283,11 +302,14 @@ class Instantiator
283
302
  elsif feature.eType.is_a?(RGen::ECore::EEnum)
284
303
  [:identifier, :string]
285
304
  else
286
- { String => [:string, :identifier],
305
+ expected = { String => [:string, :identifier],
287
306
  Integer => [:integer],
288
307
  Float => [:float],
289
- RGen::MetamodelBuilder::DataTypes::Boolean => [:boolean]
308
+ RGen::MetamodelBuilder::DataTypes::Boolean => [:boolean],
309
+ Object => [:string, :identifier, :integer, :float, :boolean]
290
310
  }[feature.eType.instanceClass]
311
+ raise "unsupported EType instance class: #{feature.eType.instanceClass}" unless expected
312
+ expected
291
313
  end
292
314
  end
293
315
 
@@ -0,0 +1,31 @@
1
+ require 'json'
2
+
3
+ module RText
4
+
5
+ # this module provides an abstract JSON interface;
6
+ # it is a global configuration point for JSON conversion in RText;
7
+ # use +set_o2j_converter+ and +set_j2o_converter+ to use other json implementations
8
+ module JsonInterface
9
+
10
+ # set the o2j converter, a proc which takes an object and returns json
11
+ def self.set_o2j_converter(conv)
12
+ define_method(:object_to_json, conv)
13
+ end
14
+
15
+ # set the j2o converter, a proc which takes json and returns an object
16
+ def self.set_j2o_converter(conv)
17
+ define_method(:json_to_object, conv)
18
+ end
19
+
20
+ def object_to_json(obj)
21
+ JSON(obj)
22
+ end
23
+
24
+ def json_to_object(json)
25
+ JSON(json)
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+
@@ -29,6 +29,13 @@ class Language
29
29
  # the serializer will take care to insert quotes if the data is not a valid identifier
30
30
  # the features must also occur in :feature_provider if :feature_provider is provided
31
31
  # default: no unquoted arguments
32
+ #
33
+ # :labeled_containments
34
+ # a Proc which receives an EClass and should return the names of this EClass's containment
35
+ # references which are to be serialized with a label.
36
+ # note that labels will always automatically be used when references can't be uniquely
37
+ # derived from contained elements
38
+ # default: no additional labeled containments
32
39
  #
33
40
  # :argument_format_provider
34
41
  # a Proc which receives an EAttribute and should return a format specification string
@@ -133,6 +140,7 @@ class Language
133
140
  reject{|f| f.derived} }
134
141
  @unlabled_arguments = options[:unlabled_arguments]
135
142
  @unquoted_arguments = options[:unquoted_arguments]
143
+ @labeled_containments = options[:labeled_containments]
136
144
  @argument_format_provider = options[:argument_format_provider]
137
145
  @root_classes = options[:root_classes] || default_root_classes(root_epackage)
138
146
  command_name_provider = options[:command_name_provider] || proc{|c| c.name}
@@ -215,6 +223,11 @@ class Language
215
223
  @unquoted_arguments.call(feature.eContainingClass).include?(feature.name)
216
224
  end
217
225
 
226
+ def labeled_containment?(clazz, feature)
227
+ return false unless @labeled_containments
228
+ @labeled_containments.call(clazz).include?(feature.name)
229
+ end
230
+
218
231
  def argument_format(feature)
219
232
  @argument_format_provider && @argument_format_provider.call(feature)
220
233
  end
@@ -225,9 +238,10 @@ class Language
225
238
 
226
239
  def containments_by_target_type(clazz, type)
227
240
  map = {}
228
- clazz.eAllReferences.select{|r| r.containment}.each do |r|
241
+ containments(clazz).each do |r|
229
242
  concrete_types(r.eType).each {|t| (map[t] ||= []) << r}
230
243
  end
244
+ # the following line should be unnecessary with exception of "uniq"
231
245
  ([type]+type.eAllSuperTypes).inject([]){|m,t| m + (map[t] || []) }.uniq
232
246
  end
233
247
 
@@ -244,7 +258,11 @@ class Language
244
258
  end
245
259
 
246
260
  def fragment_ref(element)
247
- @fragment_ref_attribute && element.respond_to?(@fragment_ref_attribute) && element.send(@fragment_ref_attribute)
261
+ begin
262
+ @fragment_ref_attribute && element.send(@fragment_ref_attribute)
263
+ rescue NoMethodError
264
+ false
265
+ end
248
266
  end
249
267
 
250
268
  private
@@ -260,7 +278,7 @@ class Language
260
278
  @has_command[cmd] = true
261
279
  clazz = c.instanceClass
262
280
  @class_by_command[clazz] ||= {}
263
- c.eAllReferences.select{|r| r.containment}.collect{|r|
281
+ containments(c).collect{|r|
264
282
  [r.eType] + r.eType.eAllSubTypes}.flatten.uniq.each do |t|
265
283
  next if t.abstract
266
284
  cmw = command_name_provider.call(t)
@@ -287,11 +305,13 @@ class Language
287
305
  end
288
306
 
289
307
  # caching
290
- [ :containments,
308
+ [ :features,
309
+ :containments,
291
310
  :non_containments,
292
311
  :unlabled_arguments,
293
312
  :labled_arguments,
294
313
  :unquoted?,
314
+ :labeled_containment?,
295
315
  :argument_format,
296
316
  :concrete_types,
297
317
  :containments_by_target_type,
@@ -1,29 +1,18 @@
1
- require 'json'
1
+ require 'rtext/json_interface'
2
2
 
3
3
  module RText
4
4
 
5
5
  module MessageHelper
6
+ include JsonInterface
6
7
 
7
8
  def serialize_message(obj)
8
- each_json_object_string(obj) do |s|
9
- s.force_encoding("binary")
10
- bytes = s.bytes.to_a
11
- s.clear
12
- bytes.each do |b|
13
- if b >= 128 || b == 0x25 # %
14
- s << "%#{b.to_s(16)}".force_encoding("binary")
15
- else
16
- s << b.chr("binary")
17
- end
18
- end
19
- # there are no non ascii-7-bit characters left
20
- s.force_encoding("utf-8")
21
- end
22
- json = JSON(obj)
9
+ escape_all_strings(obj)
10
+ json = object_to_json(obj)
23
11
  # the JSON method outputs data in UTF-8 encoding
24
12
  # the RText protocol expects message lengths measured in bytes
25
13
  # there shouldn't be any non-ascii-7-bit characters, though, so json.size would also be ok
26
- "#{json.bytesize}#{json}"
14
+ json.prepend(json.bytesize.to_s)
15
+ json
27
16
  end
28
17
 
29
18
  def extract_message(data)
@@ -40,21 +29,42 @@ def extract_message(data)
40
29
  # encode from binary to utf-8 with :undef => :replace turns all non-ascii-7-bit bytes
41
30
  # into the replacement character (\uFFFD)
42
31
  json.encode!("utf-8", :undef => :replace)
43
- obj = JSON(json)
32
+ obj = json_to_object(json)
44
33
  end
45
34
  end
46
35
  if obj
47
- each_json_object_string(obj) do |s|
48
- # change encoding back to binary
49
- # there could still be replacement characters (\uFFFD), turn them into "?"
50
- s.encode!("binary", :undef => :replace)
51
- s.gsub!(/%[0-9a-fA-F][0-9a-fA-F]/){|m| m[1..2].to_i(16).chr("binary")}
52
- s.force_encoding("binary")
53
- end
36
+ unescape_all_strings(obj)
54
37
  end
55
38
  obj
56
39
  end
57
40
 
41
+ def escape_all_strings(obj)
42
+ each_json_object_string(obj) do |s|
43
+ s.force_encoding("binary")
44
+ bytes = s.bytes.to_a
45
+ s.clear
46
+ bytes.each do |b|
47
+ if b >= 128 || b == 0x25 # %
48
+ s << "%#{b.to_s(16)}".force_encoding("binary")
49
+ else
50
+ s << b.chr("binary")
51
+ end
52
+ end
53
+ # there are no non ascii-7-bit characters left
54
+ s.force_encoding("utf-8")
55
+ end
56
+ end
57
+
58
+ def unescape_all_strings(obj)
59
+ each_json_object_string(obj) do |s|
60
+ # change encoding back to binary
61
+ # there could still be replacement characters (\uFFFD), turn them into "?"
62
+ s.encode!("binary", :undef => :replace)
63
+ s.gsub!(/%[0-9a-fA-F][0-9a-fA-F]/){|m| m[1..2].to_i(16).chr("binary")}
64
+ s.force_encoding("binary")
65
+ end
66
+ end
67
+
58
68
  def each_json_object_string(object, &block)
59
69
  if object.is_a?(Hash)
60
70
  object.each_pair do |k, v|