rtext 0.2.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,190 @@
1
+ require 'rtext/language'
2
+
3
+ module RText
4
+
5
+ class Serializer
6
+
7
+ # Creates a serializer for RText::Language +language+.
8
+ #
9
+ def initialize(language)
10
+ @lang = language
11
+ end
12
+
13
+ # Serialize +elements+ to +writer+. Options:
14
+ #
15
+ # :set_line_number
16
+ # if set to true, the serializer will try to update the line number attribute of model
17
+ # elements, while they are serialized, given that they have the line_number_attribute
18
+ # specified in the RText::Language
19
+ # default: don't set line number
20
+ #
21
+ # :fragment_ref
22
+ # an object referencing a fragment, this will be set on all model elements while they
23
+ # are serialized, given that they have the fragment_ref_attribute specified in the
24
+ # RText::Language
25
+ # default: don't set fragment reference
26
+ #
27
+ def serialize(elements, writer, options={})
28
+ @writer = writer
29
+ @set_line_number = options[:set_line_number]
30
+ @fragment_ref = options[:fragment_ref]
31
+ @line_number = 1
32
+ @indent = 0
33
+ if elements.is_a?(Array)
34
+ serialize_elements(elements)
35
+ else
36
+ serialize_elements([elements])
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def serialize_elements(elements)
43
+ elements.each do |e|
44
+ serialize_element(e)
45
+ end
46
+ end
47
+
48
+ def serialize_element(element)
49
+ set_fragment_ref(element)
50
+ set_line_number(element, @line_number) if @set_line_number
51
+ clazz = element.class.ecore
52
+
53
+ # the comment provider may modify the element
54
+ comment = @lang.comment_provider && @lang.comment_provider.call(element)
55
+ if comment
56
+ comment.split(/\r?\n/).each do |l|
57
+ write("##{l}")
58
+ end
59
+ end
60
+ headline = @lang.command_by_class(clazz.instanceClass)
61
+ raise "no command name for class #{clazz.instanceClass.to_s}" unless headline
62
+ args = []
63
+ @lang.unlabled_arguments(clazz).each do |f|
64
+ values = serialize_values(element, f)
65
+ args << values if values
66
+ end
67
+ @lang.labled_arguments(clazz).each do |f|
68
+ values = serialize_values(element, f)
69
+ args << "#{f.name}: #{values}" if values
70
+ end
71
+ headline += " "+args.join(", ") if args.size > 0
72
+ contained_elements = {}
73
+ @lang.containments(clazz).each do |f|
74
+ contained_elements[f] = element.getGenericAsArray(f.name)
75
+ end
76
+ if contained_elements.values.any?{|v| v.size > 0}
77
+ headline += " {"
78
+ write(headline)
79
+ iinc
80
+ @lang.containments(clazz).each do |f|
81
+ childs = contained_elements[f]
82
+ if childs.size > 0
83
+ if @lang.containments_by_target_type(f.eContainingClass, f.eType).size > 1
84
+ if childs.size > 1
85
+ write("#{f.name}: [")
86
+ iinc
87
+ serialize_elements(childs)
88
+ idec
89
+ write("]")
90
+ else
91
+ write("#{f.name}:")
92
+ iinc
93
+ serialize_elements(childs)
94
+ idec
95
+ end
96
+ else
97
+ serialize_elements(childs)
98
+ end
99
+ end
100
+ end
101
+ idec
102
+ write("}")
103
+ else
104
+ write(headline)
105
+ end
106
+ end
107
+
108
+ def serialize_values(element, feature)
109
+ values = element.getGenericAsArray(feature.name).compact
110
+ result = []
111
+ arg_format = @lang.argument_format(feature)
112
+ values.each do |v|
113
+ if feature.eType.instanceClass == Integer
114
+ if arg_format
115
+ result << sprintf(arg_format, v)
116
+ else
117
+ result << v.to_s
118
+ end
119
+ elsif feature.eType.instanceClass == String
120
+ if @lang.unquoted?(feature) && v.to_s =~ /^[a-zA-Z_]\w*$/ && v.to_s != "true" && v.to_s != "false"
121
+ result << v.to_s
122
+ else
123
+ result << "\"#{v.gsub("\\","\\\\\\\\").gsub("\"","\\\"").gsub("\n","\\n").
124
+ gsub("\r","\\r").gsub("\t","\\t").gsub("\f","\\f").gsub("\b","\\b")}\""
125
+ end
126
+ elsif feature.eType.instanceClass == RGen::MetamodelBuilder::DataTypes::Boolean
127
+ result << v.to_s
128
+ elsif feature.eType.instanceClass == Float
129
+ if arg_format
130
+ result << sprintf(arg_format, v)
131
+ else
132
+ result << v.to_s
133
+ end
134
+ elsif feature.eType.is_a?(RGen::ECore::EEnum)
135
+ if v.to_s =~ /\W/
136
+ result << "\"#{v.to_s.gsub("\\","\\\\\\\\").gsub("\"","\\\"").gsub("\n","\\n").
137
+ gsub("\r","\\r").gsub("\t","\\t").gsub("\f","\\f").gsub("\b","\\b")}\""
138
+ else
139
+ result << v.to_s
140
+ end
141
+ elsif feature.eType.instanceClass == Object
142
+ if v.to_s =~ /^\d+(\.\d+)?$|^\w+$|^true$|^false$/
143
+ result << v.to_s
144
+ else
145
+ result << "\"#{v.to_s.gsub("\\","\\\\\\\\").gsub("\"","\\\"").gsub("\n","\\n").
146
+ gsub("\r","\\r").gsub("\t","\\t").gsub("\f","\\f").gsub("\b","\\b")}\""
147
+ end
148
+ elsif feature.is_a?(RGen::ECore::EReference)
149
+ result << @lang.identifier_provider.call(v, element)
150
+ end
151
+ end
152
+ if result.size > 1
153
+ "[#{result.join(", ")}]"
154
+ elsif result.size == 1
155
+ result.first
156
+ else
157
+ nil
158
+ end
159
+ end
160
+
161
+ def set_line_number(element, line)
162
+ if @lang.line_number_attribute && element.respond_to?("#{@lang.line_number_attribute}=")
163
+ element.send("#{@lang.line_number_attribute}=", line)
164
+ end
165
+ end
166
+
167
+ def set_fragment_ref(element)
168
+ if @fragment_ref &&
169
+ @lang.fragment_ref_attribute && element.respond_to?("#{@lang.fragment_ref_attribute}=")
170
+ element.send("#{@lang.fragment_ref_attribute}=", @fragment_ref)
171
+ end
172
+ end
173
+
174
+ def write(str)
175
+ @writer.write(@lang.indent_string * @indent + str + "\n")
176
+ @line_number += 1
177
+ end
178
+
179
+ def iinc
180
+ @indent += 1
181
+ end
182
+
183
+ def idec
184
+ @indent -= 1
185
+ end
186
+ end
187
+
188
+ end
189
+
190
+
@@ -0,0 +1,182 @@
1
+ require 'socket'
2
+ require 'rtext/completer'
3
+ require 'rtext/context_element_builder'
4
+
5
+ module RText
6
+
7
+ class Service
8
+ PortRangeStart = 9001
9
+ PortRangeEnd = 9100
10
+
11
+ FlushInterval = 1
12
+
13
+ # Creates an RText backend service. Options:
14
+ #
15
+ # :timeout
16
+ # idle time in seconds after which the service will terminate itelf
17
+ #
18
+ # :logger
19
+ # a logger object on which the service will write its logging output
20
+ #
21
+ def initialize(lang, service_provider, options={})
22
+ @lang = lang
23
+ @service_provider = service_provider
24
+ @completer = RText::Completer.new(lang)
25
+ @timeout = options[:timeout] || 60
26
+ @logger = options[:logger]
27
+ end
28
+
29
+ def run
30
+ socket = create_socket
31
+ puts "RText service, listening on port #{socket.addr[1]}"
32
+ $stdout.flush
33
+
34
+ last_access_time = Time.now
35
+ last_flush_time = Time.now
36
+ stop_requested = false
37
+ while !stop_requested
38
+ begin
39
+ msg, from = socket.recvfrom_nonblock(65000)
40
+ rescue Errno::EWOULDBLOCK
41
+ sleep(0.01)
42
+ if (Time.now - last_access_time) > @timeout
43
+ @logger.info("RText service, stopping now (timeout)") if @logger
44
+ break
45
+ end
46
+ retry
47
+ end
48
+ if Time.now > last_flush_time + FlushInterval
49
+ $stdout.flush
50
+ last_flush_time = Time.now
51
+ end
52
+ last_access_time = Time.now
53
+ lines = msg.split(/\r?\n/)
54
+ cmd = lines.shift
55
+ invocation_id = lines.shift
56
+ response = nil
57
+ case cmd
58
+ when "refresh"
59
+ response = refresh(lines)
60
+ when "complete"
61
+ response = complete(lines)
62
+ when "show_problems"
63
+ response = get_problems(lines)
64
+ when "get_reference_targets"
65
+ response = get_reference_targets(lines)
66
+ when "get_elements"
67
+ response = get_open_element_choices(lines)
68
+ when "stop"
69
+ response = []
70
+ @logger.info("RText service, stopping now (stop requested)") if @logger
71
+ stop_requested = true
72
+ else
73
+ @logger.debug("unknown command #{cmd}") if @logger
74
+ response = []
75
+ end
76
+ send_response(response, invocation_id, socket, from)
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ def send_response(response, invocation_id, socket, from)
83
+ @logger.debug(response.inspect) if @logger
84
+ loop do
85
+ packet_lines = []
86
+ size = 0
87
+ while response.size > 0 && size + response.first.size < 65000
88
+ size += response.first.size
89
+ packet_lines << response.shift
90
+ end
91
+ if response.size > 0
92
+ packet_lines.unshift("more\n")
93
+ else
94
+ packet_lines.unshift("last\n")
95
+ end
96
+ packet_lines.unshift("#{invocation_id}\n")
97
+ socket.send(packet_lines.join, 0, from[2], from[1])
98
+ break if response.size == 0
99
+ end
100
+ end
101
+
102
+ def create_socket
103
+ socket = UDPSocket.new
104
+ port = PortRangeStart
105
+ begin
106
+ socket.bind("localhost", port)
107
+ rescue Errno::EADDRINUSE
108
+ port += 1
109
+ retry if port <= PortRangeEnd
110
+ raise
111
+ end
112
+ socket
113
+ end
114
+
115
+ def refresh(lines)
116
+ @service_provider.load_model
117
+ []
118
+ end
119
+
120
+ def complete(lines)
121
+ linepos = lines.shift.to_i
122
+ context = ContextElementBuilder.build_context_element(@lang, lines, linepos)
123
+ @logger.debug("context element: #{@lang.identifier_provider.call(context, nil)}") if @logger
124
+ current_line = lines.pop
125
+ current_line ||= ""
126
+ options = @completer.complete(current_line, linepos,
127
+ proc {|i| lines[-i]},
128
+ proc {|ref|
129
+ @service_provider.get_reference_completion_options(ref, context).collect {|o|
130
+ Completer::CompletionOption.new(o.identifier, "<#{o.type}>")}
131
+ })
132
+ options.collect { |o|
133
+ "#{o.text};#{o.extra}\n"
134
+ }
135
+ end
136
+
137
+ def get_problems(lines)
138
+ # TODO: severity
139
+ result = []
140
+ @service_provider.get_problems.each do |fp|
141
+ result << fp.file+"\n"
142
+ fp.problems.each do |p|
143
+ result << "#{p.line};#{p.message}\n"
144
+ end
145
+ end
146
+ result
147
+ end
148
+
149
+ def get_reference_targets(lines)
150
+ linepos = lines.shift.to_i
151
+ context = ContextElementBuilder.build_context_element(@lang, lines, linepos)
152
+ current_line = lines.last
153
+ result = []
154
+ if current_line[linepos..linepos] =~ /[\w\/]/
155
+ ident_start = (current_line.rindex(/[^\w\/]/, linepos) || -1)+1
156
+ ident_end = (current_line.index(/[^\w\/]/, linepos) || current_line.size)-1
157
+ ident = current_line[ident_start..ident_end]
158
+ result << "#{ident_start};#{ident_end}\n"
159
+ if current_line[0..linepos+1] =~ /^\s*\w+$/
160
+ @service_provider.get_referencing_elements(ident, context).each do |t|
161
+ result << "#{t.file};#{t.line};#{t.display_name}\n"
162
+ end
163
+ else
164
+ @service_provider.get_reference_targets(ident, context, lines, linepos).each do |t|
165
+ result << "#{t.file};#{t.line};#{t.display_name}\n"
166
+ end
167
+ end
168
+ end
169
+ result
170
+ end
171
+
172
+ def get_open_element_choices(lines)
173
+ pattern = lines.shift
174
+ @service_provider.get_open_element_choices(pattern).collect do |c|
175
+ "#{c.display_name};#{c.file};#{c.line}\n"
176
+ end
177
+ end
178
+
179
+ end
180
+
181
+ end
182
+
@@ -0,0 +1,59 @@
1
+ module RTextPlugin
2
+
3
+ class ConnectorManager
4
+ def start
5
+ end
6
+
7
+ def stop
8
+ end
9
+
10
+ def trigger
11
+ end
12
+
13
+ private
14
+
15
+ def find_rtext_config_file(file)
16
+ end
17
+
18
+ def extract_file_pattern(file)
19
+ ext = File.extname
20
+ if ext.size > 0
21
+ "*.#{ext}"
22
+ else
23
+ File.basename(file)
24
+ end
25
+ end
26
+
27
+ ServiceSpec = Struct.new(:config_file, :pattern, :command)
28
+
29
+ def parse_rtext_config_file(file)
30
+ expect = :pattern
31
+ line = 1
32
+ pattern = nil
33
+ specs = []
34
+ File.open(file) do |f|
35
+ f.readlines.each do |l|
36
+ case expect
37
+ when :pattern
38
+ if l =~ /(.*):\s*$/
39
+ pattern = $1.split(",").collect{|s| s.strip}
40
+ expect = :command
41
+ else
42
+ raise "expected file pattern in line #{line}"
43
+ end
44
+ when :command
45
+ if l =~ /[^:]\s*$/
46
+ specs << ServiceSpec.new(file, pattern, l)
47
+ else
48
+ raise "expected command in line #{line}"
49
+ end
50
+ end
51
+ line += 1
52
+ end
53
+ end
54
+ specs
55
+ end
56
+
57
+ end
58
+
59
+ end
@@ -0,0 +1,931 @@
1
+ # coding: binary
2
+ $:.unshift File.join(File.dirname(__FILE__),"..","lib")
3
+
4
+ require 'test/unit'
5
+ require 'rgen/environment'
6
+ require 'rgen/metamodel_builder'
7
+ require 'rtext/instantiator'
8
+ require 'rtext/language'
9
+
10
+ class RTextInstantiatorTest < Test::Unit::TestCase
11
+
12
+ module TestMM
13
+ extend RGen::MetamodelBuilder::ModuleExtension
14
+ class TestNode < RGen::MetamodelBuilder::MMBase
15
+ SomeEnum = RGen::MetamodelBuilder::DataTypes::Enum.new([:A, :B, :'non-word*chars'])
16
+ has_attr 'text', String
17
+ has_attr 'integer', Integer
18
+ has_attr 'boolean', Boolean
19
+ has_attr 'enum', SomeEnum
20
+ has_many_attr 'nums', Integer
21
+ has_attr 'float', Float
22
+ has_one 'related', TestNode
23
+ has_many 'others', TestNode
24
+ contains_many 'childs', TestNode, 'parent'
25
+ end
26
+ class SubNode < TestNode
27
+ end
28
+ end
29
+
30
+ module TestMM2
31
+ extend RGen::MetamodelBuilder::ModuleExtension
32
+ class TestNode < RGen::MetamodelBuilder::MMBase
33
+ contains_one 'singleChild', TestNode, 'parent'
34
+ end
35
+ class TestNode2 < RGen::MetamodelBuilder::MMBase
36
+ end
37
+ class TestNode3 < RGen::MetamodelBuilder::MMBase
38
+ end
39
+ class TestNode4 < TestNode
40
+ end
41
+ TestNode.contains_one 'singleChild2a', TestNode2, 'parentA'
42
+ TestNode.contains_one 'singleChild2b', TestNode2, 'parentB'
43
+ end
44
+
45
+ module TestMMLinenoFilename
46
+ extend RGen::MetamodelBuilder::ModuleExtension
47
+ class TestNode < RGen::MetamodelBuilder::MMBase
48
+ has_attr 'text', String
49
+ has_attr 'lineno', Integer
50
+ has_attr 'filename', String
51
+ contains_many 'childs', TestNode, 'parent'
52
+ end
53
+ end
54
+
55
+ module TestMMAbstract
56
+ extend RGen::MetamodelBuilder::ModuleExtension
57
+ class TestNode < RGen::MetamodelBuilder::MMBase
58
+ abstract
59
+ end
60
+ end
61
+
62
+ module TestMMData
63
+ extend RGen::MetamodelBuilder::ModuleExtension
64
+ # class "Data" exists in the standard Ruby namespace
65
+ class Data < RGen::MetamodelBuilder::MMBase
66
+ has_attr 'notTheBuiltin', String
67
+ end
68
+ end
69
+
70
+ module TestMMSubpackage
71
+ extend RGen::MetamodelBuilder::ModuleExtension
72
+ module SubPackage
73
+ extend RGen::MetamodelBuilder::ModuleExtension
74
+ class TestNodeSub < RGen::MetamodelBuilder::MMBase
75
+ has_attr 'text', String
76
+ end
77
+ class Data < RGen::MetamodelBuilder::MMBase
78
+ has_attr 'notTheBuiltin', String
79
+ end
80
+ end
81
+ end
82
+
83
+ def test_simple
84
+ env, problems = instantiate(%Q(
85
+ TestNode text: "some text", nums: [1,2] {
86
+ TestNode text: "child"
87
+ TestNode text: "child2"
88
+ }
89
+ ), TestMM)
90
+ assert_no_problems(problems)
91
+ assert_model_simple(env, :with_nums)
92
+ end
93
+
94
+ def test_multiple_roots
95
+ env, problems = instantiate(%Q(
96
+ TestNode
97
+ TestNode
98
+ ), TestMM)
99
+ assert_no_problems(problems)
100
+ assert_equal 2, env.elements.size
101
+ end
102
+
103
+ def test_comment
104
+ env, problems = instantiate(%Q(
105
+ # comment 1
106
+ TestNode text: "some text" {# comment 1.1
107
+ childs: [ # comment 1.2
108
+ # comment 2
109
+ TestNode text: "child" # comment 2.1
110
+ # comment 3
111
+ TestNode text: "child2" #comment 3.1
112
+ # unassociated
113
+ ] # comment 1.3
114
+ # unassociated
115
+ } # comment 1.4
116
+ #comment 1
117
+ TestNode { #comment 1.1
118
+ childs: # comment 1.2
119
+ TestNode text: "child" #comment2
120
+ # unassociated
121
+ }# comment 1.3
122
+ # unassociated
123
+ ), TestMM)
124
+ assert_no_problems(problems)
125
+ assert_model_simple(env)
126
+ end
127
+
128
+ def test_comment_only
129
+ env, problems = instantiate(%Q(
130
+ # comment 1
131
+ ), TestMM)
132
+ assert_no_problems(problems)
133
+ end
134
+
135
+ def test_empty
136
+ env, problems = instantiate("", TestMM)
137
+ assert_no_problems(problems)
138
+ end
139
+
140
+ #
141
+ # options
142
+ #
143
+
144
+ def test_line_number_setter
145
+ env, problems = instantiate(%q(
146
+ TestNode text: "node1" {
147
+ TestNode text: "node2"
148
+
149
+ #some comment
150
+ TestNode text: "node3"
151
+ }
152
+ TestNode text: "node4"
153
+ ), TestMMLinenoFilename, :line_number_attribute => "lineno")
154
+ assert_no_problems(problems)
155
+ assert_equal 2, env.find(:text => "node1").first.lineno
156
+ assert_equal 3, env.find(:text => "node2").first.lineno
157
+ assert_equal 6, env.find(:text => "node3").first.lineno
158
+ assert_equal 8, env.find(:text => "node4").first.lineno
159
+ end
160
+
161
+ def test_root_elements
162
+ root_elements = []
163
+ env, problems = instantiate(%Q(
164
+ TestNode text: A
165
+ TestNode text: B
166
+ TestNode text: C
167
+ ), TestMM, :root_elements => root_elements)
168
+ assert_no_problems(problems)
169
+ assert_equal ["A", "B", "C"], root_elements.text
170
+ end
171
+
172
+ def test_file_name_option
173
+ env, problems = instantiate(%Q(
174
+ TestNode text: A
175
+ TestNode text: B
176
+ TestNode a problem here
177
+ ), TestMM, :file_name => "some_file")
178
+ assert_equal ["some_file"], problems.collect{|p| p.file}
179
+ end
180
+
181
+ def test_file_name_setter
182
+ env, problems = instantiate(%Q(
183
+ TestNode text: A
184
+ ), TestMMLinenoFilename, :file_name => "some_file", :file_name_attribute => "filename")
185
+ assert_equal "some_file", env.elements.first.filename
186
+ end
187
+
188
+ #
189
+ # children with role
190
+ #
191
+
192
+ def test_child_role
193
+ env, problems = instantiate(%Q(
194
+ TestNode text: "some text" {
195
+ TestNode text: "child"
196
+ childs:
197
+ TestNode text: "child2"
198
+ }
199
+ ), TestMM)
200
+ assert_no_problems(problems)
201
+ assert_model_simple(env)
202
+ end
203
+
204
+ def test_child_role2
205
+ env, problems = instantiate(%Q(
206
+ TestNode text: "some text" {
207
+ childs: [
208
+ TestNode text: "child"
209
+ TestNode text: "child2"
210
+ ]
211
+ }
212
+ ), TestMM)
213
+ assert_no_problems(problems)
214
+ assert_model_simple(env)
215
+ end
216
+
217
+ def test_child_role3
218
+ env, problems = instantiate(%Q(
219
+ TestNode text: "some text" {
220
+ childs:
221
+ TestNode text: "child"
222
+ childs:
223
+ TestNode text: "child2"
224
+ }
225
+ ), TestMM)
226
+ assert_no_problems(problems)
227
+ assert_model_simple(env)
228
+ end
229
+
230
+ def test_child_role4
231
+ env, problems = instantiate(%Q(
232
+ TestNode text: "some text" {
233
+ childs: [
234
+ TestNode text: "child"
235
+ ]
236
+ childs: [
237
+ TestNode text: "child2"
238
+ ]
239
+ }
240
+ ), TestMM)
241
+ assert_no_problems(problems)
242
+ assert_model_simple(env)
243
+ end
244
+
245
+ #
246
+ # whitespace
247
+ #
248
+
249
+ def test_whitespace1
250
+ env, problems = instantiate(%Q(
251
+ TestNode text: "some text" , nums: [ 1 , 2 ] {
252
+
253
+ # comment
254
+
255
+ TestNode text: "child"
256
+
257
+ TestNode text: "child2"
258
+
259
+ }
260
+ ), TestMM)
261
+ assert_no_problems(problems)
262
+ assert_model_simple(env, :with_nums)
263
+ end
264
+
265
+ def test_whitespace2
266
+ env, problems = instantiate(%Q(
267
+ # comment1
268
+
269
+ # comment2
270
+
271
+ TestNode text: "some text" {
272
+
273
+ childs:
274
+
275
+ # comment
276
+
277
+ TestNode text: "child"
278
+
279
+ childs: [
280
+
281
+ TestNode text: "child2"
282
+
283
+ ]
284
+
285
+ }
286
+ ), TestMM)
287
+ assert_no_problems(problems)
288
+ assert_model_simple(env)
289
+ end
290
+
291
+ #
292
+ # references
293
+ #
294
+
295
+ def test_references
296
+ unresolved_refs = []
297
+ env, problems = instantiate(%Q(
298
+ TestNode text: "root" {
299
+ TestNode related: /
300
+ TestNode related: //
301
+ TestNode related: /some
302
+ TestNode related: //some
303
+ TestNode related: /some/
304
+ TestNode related: some/
305
+ TestNode related: some//
306
+ TestNode related: some
307
+ TestNode related: /some/reference
308
+ TestNode related: /some/reference/
309
+ TestNode related: some/reference/
310
+ TestNode related: some/reference
311
+ }
312
+ ), TestMM, :unresolved_refs => unresolved_refs)
313
+ assert_no_problems(problems)
314
+ ref_targets = [
315
+ "/",
316
+ "//",
317
+ "/some",
318
+ "//some",
319
+ "/some/",
320
+ "some/",
321
+ "some//",
322
+ "some",
323
+ "/some/reference",
324
+ "/some/reference/",
325
+ "some/reference/",
326
+ "some/reference"
327
+ ]
328
+ assert_equal ref_targets, env.find(:text => "root").first.childs.collect{|c| c.related.targetIdentifier}
329
+ assert_equal ref_targets, unresolved_refs.collect{|ur| ur.proxy.targetIdentifier}
330
+ end
331
+
332
+ def test_references_many
333
+ env, problems = instantiate(%Q(
334
+ TestNode text: "root" {
335
+ TestNode others: /other
336
+ TestNode others: [ /other ]
337
+ TestNode others: [ /other1, /other2 ]
338
+ }
339
+ ), TestMM)
340
+ assert_no_problems(problems)
341
+ assert_equal [
342
+ [ "/other" ],
343
+ [ "/other" ],
344
+ [ "/other1", "/other2" ],
345
+ ], env.find(:text => "root").first.childs.collect{|c| c.others.collect{|p| p.targetIdentifier}}
346
+ end
347
+
348
+ def test_reference_regexp
349
+ env, problems = instantiate(%Q(
350
+ TestNode text: "root" {
351
+ TestNode related: some
352
+ TestNode related: ::some
353
+ TestNode related: some::reference
354
+ TestNode related: ::some::reference
355
+ }
356
+ ), TestMM, :reference_regexp => /\A\w*(::\w*)+/)
357
+ assert_no_problems(problems)
358
+ assert_equal [
359
+ "some",
360
+ "::some",
361
+ "some::reference",
362
+ "::some::reference"
363
+ ], env.find(:text => "root").first.childs.collect{|c| c.related.targetIdentifier}
364
+ end
365
+
366
+ #
367
+ # unlabled arguments
368
+ #
369
+
370
+ def test_unlabled_arguments
371
+ env, problems = instantiate(%Q(
372
+ TestNode "some text", [1,2] {
373
+ TestNode "child"
374
+ TestNode "child2"
375
+ }
376
+ ), TestMM, :unlabled_arguments => proc {|clazz| ["text", "nums"]})
377
+ assert_no_problems(problems)
378
+ assert_model_simple(env, :with_nums)
379
+ end
380
+
381
+ def test_unlabled_arguments_not_in_front
382
+ env, problems = instantiate(%Q(
383
+ TestNode nums: [1,2], "some text" {
384
+ TestNode "child"
385
+ TestNode "child2"
386
+ }
387
+ ), TestMM, :unlabled_arguments => proc {|clazz| ["text", "nums"]})
388
+ assert_no_problems(problems)
389
+ assert_model_simple(env, :with_nums)
390
+ end
391
+
392
+ def test_unlabled_arguments_using_labled
393
+ env, problems = instantiate(%Q(
394
+ TestNode text: "some text", nums: [1,2] {
395
+ TestNode text: "child"
396
+ TestNode text: "child2"
397
+ }
398
+ ), TestMM, :unlabled_arguments => proc {|clazz| ["text", "nums"]})
399
+ assert_no_problems(problems)
400
+ assert_model_simple(env, :with_nums)
401
+ end
402
+
403
+ def test_unlabled_arguments_subclass
404
+ env, problems = instantiate(%Q(
405
+ SubNode "some text", [1, 2] {
406
+ TestNode text: "child"
407
+ TestNode text: "child2"
408
+ }
409
+ ), TestMM, :unlabled_arguments => proc {|clazz| ["text", "nums"]})
410
+ assert_no_problems(problems)
411
+ assert_model_simple(env, :with_nums)
412
+ end
413
+
414
+ #
415
+ # problems
416
+ #
417
+
418
+ def test_unexpected_end_of_file
419
+ env, problems = instantiate(%Q(
420
+ TestNode text: "some text" {
421
+ ), TestMM)
422
+ assert_problems([/unexpected end of file, expected \}/i], problems)
423
+ end
424
+
425
+ def test_unknown_command
426
+ env, problems = instantiate(%Q(
427
+ NotDefined
428
+ ), TestMM)
429
+ assert_problems([/unknown command 'NotDefined'/i], problems)
430
+ end
431
+
432
+ def test_unknown_command_abstract
433
+ env, problems = instantiate(%Q(
434
+ TestNode
435
+ ), TestMMAbstract)
436
+ assert_problems([/unknown command 'TestNode'/i], problems)
437
+ end
438
+
439
+ def test_unexpected_unlabled_argument
440
+ env, problems = instantiate(%Q(
441
+ TestNode "more text"
442
+ ), TestMM)
443
+ assert_problems([/unexpected unlabled argument, 0 unlabled arguments expected/i], problems)
444
+ end
445
+
446
+ def test_unknown_child_role
447
+ env, problems = instantiate(%Q(
448
+ TestNode {
449
+ notdefined:
450
+ TestNode
451
+ }
452
+ ), TestMM)
453
+ assert_problems([/unknown child role 'notdefined'/i], problems)
454
+ end
455
+
456
+ def test_not_a_child_role
457
+ env, problems = instantiate(%Q(
458
+ TestNode {
459
+ text:
460
+ TestNode
461
+ others:
462
+ TestNode
463
+ }
464
+ ), TestMM)
465
+ assert_problems([
466
+ /role 'text' can not take child elements/i,
467
+ /role 'others' can not take child elements/i
468
+ ], problems)
469
+ end
470
+
471
+ def test_not_a_single_child
472
+ env, problems = instantiate(%Q(
473
+ TestNode {
474
+ singleChild: [
475
+ TestNode
476
+ TestNode
477
+ ]
478
+ }
479
+ ), TestMM2)
480
+ assert_problems([
481
+ /only one child allowed in role 'singleChild'/i,
482
+ ], problems)
483
+ end
484
+
485
+ def test_not_a_single_child2
486
+ env, problems = instantiate(%Q(
487
+ TestNode {
488
+ singleChild:
489
+ TestNode
490
+ singleChild:
491
+ TestNode
492
+ }
493
+ ), TestMM2)
494
+ assert_problems([
495
+ /only one child allowed in role 'singleChild'/i,
496
+ ], problems)
497
+ end
498
+
499
+ def test_wrong_child_role
500
+ env, problems = instantiate(%Q(
501
+ TestNode {
502
+ singleChild:
503
+ TestNode2
504
+ }
505
+ ), TestMM2)
506
+ assert_problems([
507
+ /role 'singleChild' can not take a TestNode2, expected TestNode/i,
508
+ ], problems)
509
+ end
510
+
511
+ def test_wrong_child
512
+ env, problems = instantiate(%Q(
513
+ TestNode {
514
+ TestNode3
515
+ }
516
+ ), TestMM2)
517
+ assert_problems([
518
+ /this kind of element can not be contained here/i,
519
+ ], problems)
520
+ end
521
+
522
+ def test_ambiguous_child_role
523
+ env, problems = instantiate(%Q(
524
+ TestNode {
525
+ TestNode2
526
+ }
527
+ ), TestMM2)
528
+ assert_problems([
529
+ /role of element is ambiguous, use a role label/i,
530
+ ], problems)
531
+ end
532
+
533
+ def test_non_ambiguous_child_role_subclass
534
+ env, problems = instantiate(%Q(
535
+ TestNode {
536
+ TestNode4
537
+ }
538
+ ), TestMM2)
539
+ assert_no_problems(problems)
540
+ end
541
+
542
+ def test_not_a_single_child3
543
+ env, problems = instantiate(%Q(
544
+ TestNode {
545
+ TestNode
546
+ TestNode
547
+ }
548
+ ), TestMM2)
549
+ assert_problems([
550
+ /only one child allowed in role 'singleChild'/i,
551
+ ], problems)
552
+ end
553
+
554
+ def test_unknown_argument
555
+ env, problems = instantiate(%Q(
556
+ TestNode unknown: "some text"
557
+ ), TestMM)
558
+ assert_problems([/unknown argument 'unknown'/i], problems)
559
+ end
560
+
561
+ def test_attribute_in_child_reference
562
+ env, problems = instantiate(%Q(
563
+ TestNode singleChild: "some text"
564
+ ), TestMM2)
565
+ assert_problems([/argument 'singleChild' can only take child elements/i], problems)
566
+ end
567
+
568
+ def test_arguments_duplicate
569
+ env, problems = instantiate(%Q(
570
+ TestNode text: "some text", text: "more text"
571
+ ), TestMM)
572
+ assert_problems([/argument 'text' already defined/i], problems)
573
+ end
574
+
575
+ def test_unlabled_arguments_duplicate
576
+ env, problems = instantiate(%Q(
577
+ TestNode text: "some text", "more text"
578
+ ), TestMM, :unlabled_arguments => proc {|c| ["text"]})
579
+ assert_problems([/argument 'text' already defined/i], problems)
580
+ end
581
+
582
+ def test_multiple_arguments_in_non_many_attribute
583
+ env, problems = instantiate(%Q(
584
+ TestNode text: ["text1", "text2"]
585
+ ), TestMM)
586
+ assert_problems([/argument 'text' can take only one value/i], problems)
587
+ end
588
+
589
+ def test_wrong_argument_type
590
+ env, problems = instantiate(%Q(
591
+ TestNode text: 1
592
+ TestNode integer: "text"
593
+ TestNode integer: true
594
+ TestNode integer: 1.2
595
+ TestNode integer: a
596
+ TestNode integer: /a
597
+ TestNode enum: 1
598
+ TestNode enum: x
599
+ TestNode related: 1
600
+ ), TestMM)
601
+ assert_problems([
602
+ /argument 'text' can not take a integer, expected string/i,
603
+ /argument 'integer' can not take a string, expected integer/i,
604
+ /argument 'integer' can not take a boolean, expected integer/i,
605
+ /argument 'integer' can not take a float, expected integer/i,
606
+ /argument 'integer' can not take a identifier, expected integer/i,
607
+ /argument 'integer' can not take a reference, expected integer/i,
608
+ /argument 'enum' can not take a integer, expected identifier/i,
609
+ /argument 'enum' can not take value x, expected A, B/i,
610
+ /argument 'related' can not take a integer, expected reference, identifier/i
611
+ ], problems)
612
+ end
613
+
614
+ def test_missing_opening_brace
615
+ env, problems = instantiate(%Q(
616
+ TestNode
617
+ }
618
+ ), TestMM)
619
+ assert_problems([/unexpected \}, expected identifier/i], problems)
620
+ end
621
+
622
+ #
623
+ # command name provider
624
+ #
625
+
626
+ def test_command_name_provider
627
+ env, problems = instantiate(%Q(
628
+ TestNodeX text: "some text", nums: [1,2] {
629
+ TestNodeX text: "child"
630
+ TestNodeX text: "child2"
631
+ }
632
+ ), TestMM, :command_name_provider => proc do |c|
633
+ c.name + "X"
634
+ end)
635
+ assert_no_problems(problems)
636
+ assert_model_simple(env, :with_nums)
637
+ end
638
+
639
+ def test_command_name_provider_ambiguous
640
+ begin
641
+ env, problems = instantiate(%Q(
642
+ TestNode
643
+ ), TestMM, :command_name_provider => proc do |c|
644
+ "Fixed"
645
+ end)
646
+ assert false
647
+ rescue RuntimeError => e
648
+ assert e.message =~ /ambiguous command name/
649
+ end
650
+ end
651
+
652
+ #
653
+ # comment handler
654
+ #
655
+
656
+ def test_comment_handler
657
+ proc_calls = 0
658
+ env, problems = instantiate(%Q(
659
+ #comment
660
+ TestNode text: "node1"
661
+ #comment
662
+ # multiline
663
+ TestNode text: "node2"
664
+ TestNode text: "node3" #comment
665
+ #above
666
+ TestNode text: "node4" {#right1
667
+ childs: [ #right2
668
+ #unassociated1
669
+ ] #right3
670
+ #unassociated2
671
+ } #below
672
+ #above1
673
+ #above2
674
+ TestNode text: "node5" { #right1
675
+ childs: #right2
676
+ TestNode
677
+ }#below
678
+ #comment without
679
+ #an element following
680
+ ), TestMM, :comment_handler => proc {|c,k,e,env|
681
+ proc_calls += 1
682
+ if e.nil?
683
+ case proc_calls
684
+ when 4
685
+ assert_equal "unassociated1", c
686
+ assert_equal :unassociated, k
687
+ when 5
688
+ assert_equal "unassociated2", c
689
+ assert_equal :unassociated, k
690
+ when 15
691
+ assert_equal "comment without\nan element following", c
692
+ assert_equal :unassociated, k
693
+ end
694
+ elsif e.text == "node1"
695
+ assert_equal "comment", c
696
+ assert_equal :above, k
697
+ elsif e.text == "node2"
698
+ assert_equal "comment\n multiline", c
699
+ assert_equal :above, k
700
+ elsif e.text == "node3"
701
+ assert_equal "comment", c
702
+ assert_equal :eol, k
703
+ elsif e.text == "node4"
704
+ case proc_calls
705
+ when 6
706
+ assert_equal "above", c
707
+ assert_equal :above, k
708
+ when 7
709
+ assert_equal "right1", c
710
+ assert_equal :eol, k
711
+ when 8
712
+ assert_equal "right2", c
713
+ assert_equal :eol, k
714
+ when 9
715
+ assert_equal "right3", c
716
+ assert_equal :eol, k
717
+ when 10
718
+ assert_equal "below", c
719
+ assert_equal :eol, k
720
+ end
721
+ elsif e.text == "node5"
722
+ case proc_calls
723
+ when 11
724
+ assert_equal "above1\nabove2", c
725
+ assert_equal :above, k
726
+ when 12
727
+ assert_equal "right1", c
728
+ assert_equal :eol, k
729
+ when 13
730
+ assert_equal "right2", c
731
+ assert_equal :eol, k
732
+ when 14
733
+ assert_equal "below", c
734
+ assert_equal :eol, k
735
+ end
736
+ else
737
+ assert false, "unexpected element in comment handler"
738
+ end
739
+ true
740
+ })
741
+ assert_no_problems(problems)
742
+ assert_equal 15, proc_calls
743
+ end
744
+
745
+ def test_comment_handler_comment_not_allowed
746
+ env, problems = instantiate(%Q(
747
+ #comment
748
+ TestNode
749
+ ), TestMM, :comment_handler => proc {|c,k,e,env|
750
+ false
751
+ })
752
+ assert_problems([/element can not take this comment/], problems)
753
+ end
754
+
755
+ def test_comment_handler_comment_not_allowed_unassociated
756
+ env, problems = instantiate(%Q(
757
+ #comment
758
+ ), TestMM, :comment_handler => proc {|c,k,e,env|
759
+ false
760
+ })
761
+ assert_problems([/Unassociated comment not allowed/], problems)
762
+ end
763
+
764
+ #
765
+ # subpackages
766
+ #
767
+
768
+ def test_subpackage
769
+ env, problems = instantiate(%q(
770
+ TestNodeSub text: "something"
771
+ ), TestMMSubpackage)
772
+ assert_no_problems(problems)
773
+ assert_equal "something", env.elements.first.text
774
+ end
775
+
776
+ def test_subpackage_no_shortname_opt
777
+ env, problems = instantiate(%q(
778
+ TestNodeSub text: "something"
779
+ ), TestMMSubpackage, :short_class_names => false)
780
+ assert_problems([/Unknown command 'TestNodeSub'/], problems)
781
+ end
782
+
783
+ #
784
+ # values
785
+ #
786
+
787
+ def test_escapes
788
+ env, problems = instantiate(%q(
789
+ TestNode text: "some \" \\\\ \\\\\" text \r xx \n xx \r\n xx \t xx \b xx \f"
790
+ ), TestMM)
791
+ assert_no_problems(problems)
792
+ assert_equal %Q(some " \\ \\" text \r xx \n xx \r\n xx \t xx \b xx \f), env.elements.first.text
793
+ end
794
+
795
+ def test_escape_single_backslash
796
+ env, problems = instantiate(%q(
797
+ TestNode text: "a single \\ will be just itself"
798
+ ), TestMM)
799
+ assert_no_problems(problems)
800
+ assert_equal %q(a single \\ will be just itself), env.elements.first.text
801
+ end
802
+
803
+ def test_string_umlauts
804
+ env, problems = instantiate(%q(
805
+ TestNode text: "ä, ö, ü"
806
+ ), TestMM)
807
+ assert_no_problems(problems)
808
+ assert_equal %q(ä, ö, ü), env.elements.first.text
809
+ end
810
+
811
+ def test_integer
812
+ env, problems = instantiate(%q(
813
+ TestNode integer: 7
814
+ ), TestMM)
815
+ assert_no_problems(problems)
816
+ assert_equal 7, env.elements.first.integer
817
+ end
818
+
819
+ def test_integer_hex
820
+ env, problems = instantiate(%q(
821
+ TestNode text: root {
822
+ TestNode integer: 0x7
823
+ TestNode integer: 0X7
824
+ TestNode integer: 0x007
825
+ TestNode integer: 0x77
826
+ TestNode integer: 0xabCDEF
827
+ }
828
+ ), TestMM)
829
+ assert_no_problems(problems)
830
+ assert_equal [7, 7, 7, 0x77, 0xABCDEF], env.find(:text => "root").first.childs.collect{|c| c.integer}
831
+ end
832
+
833
+ def test_float
834
+ env, problems = instantiate(%q(
835
+ TestNode float: 1.23
836
+ TestNode float: 1.23e-08
837
+ TestNode float: 1.23e+10
838
+ ), TestMM)
839
+ assert_no_problems(problems)
840
+ assert_equal 1.23, env.elements[0].float
841
+ assert_equal 1.23e-08, env.elements[1].float
842
+ assert_equal 1.23e+10, env.elements[2].float
843
+ end
844
+
845
+ def test_boolean
846
+ env, problems = instantiate(%q(
847
+ TestNode text: root {
848
+ TestNode boolean: true
849
+ TestNode boolean: false
850
+ }
851
+ ), TestMM)
852
+ assert_no_problems(problems)
853
+ assert_equal [true, false], env.find(:text => "root").first.childs.collect{|c| c.boolean}
854
+ end
855
+
856
+ def test_enum
857
+ env, problems = instantiate(%q(
858
+ TestNode text: root {
859
+ TestNode enum: A
860
+ TestNode enum: B
861
+ TestNode enum: "non-word*chars"
862
+ }
863
+ ), TestMM)
864
+ assert_no_problems(problems)
865
+ assert_equal [:A, :B, :'non-word*chars'], env.find(:text => "root").first.childs.collect{|c| c.enum}
866
+ end
867
+
868
+ #
869
+ # conflicts with builtins
870
+ #
871
+
872
+ def test_conflict_builtin
873
+ env, problems = instantiate(%q(
874
+ Data notTheBuiltin: "for sure"
875
+ ), TestMMData)
876
+ assert_no_problems(problems)
877
+ assert_equal "for sure", env.elements.first.notTheBuiltin
878
+ end
879
+
880
+ def test_builtin_in_subpackage
881
+ env, problems = instantiate(%q(
882
+ Data notTheBuiltin: "for sure"
883
+ ), TestMMSubpackage)
884
+ assert_no_problems(problems)
885
+ assert_equal "for sure", env.elements.first.notTheBuiltin
886
+ end
887
+
888
+ private
889
+
890
+ def instantiate(text, mm, options={})
891
+ env = RGen::Environment.new
892
+ lang = RText::Language.new(mm.ecore, options)
893
+ inst = RText::Instantiator.new(lang)
894
+ problems = []
895
+ inst.instantiate(text, options.merge({:env => env, :problems => problems}))
896
+ return env, problems
897
+ end
898
+
899
+ def assert_no_problems(problems)
900
+ assert problems.empty?, problems.collect{|p| "#{p.message}, line: #{p.line}"}.join("\n")
901
+ end
902
+
903
+ def assert_problems(expected, problems)
904
+ remaining = problems.dup
905
+ probs = []
906
+ expected.each do |e|
907
+ p = problems.find{|p| p.message =~ e}
908
+ probs << "expected problem not present: #{e}" if !p
909
+ remaining.delete(p)
910
+ end
911
+ remaining.each do |p|
912
+ probs << "unexpected problem: #{p.message}, line: #{p.line}"
913
+ end
914
+ assert probs.empty?, probs.join("\n")
915
+ end
916
+
917
+ def assert_model_simple(env, *opts)
918
+ raise "unknown options" unless (opts - [:with_nums]).empty?
919
+ root = env.find(:class => TestMM::TestNode, :text => "some text").first
920
+ assert_not_nil root
921
+ assert_equal 2, root.childs.size
922
+ assert_equal [TestMM::TestNode, TestMM::TestNode], root.childs.collect{|c| c.class}
923
+ assert_equal ["child", "child2"], root.childs.text
924
+ if opts.include?(:with_nums)
925
+ assert_equal [1, 2], root.nums
926
+ end
927
+ end
928
+
929
+ end
930
+
931
+