rtext 0.2.0

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