rtext 0.5.1 → 0.7.0

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