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.
- data/CHANGELOG +4 -0
- data/MIT-LICENSE +20 -0
- data/README +29 -0
- data/RText_Plugin_Implementation_Guide +247 -0
- data/RText_Users_Guide +31 -0
- data/Rakefile +46 -0
- data/lib/rtext/completer.rb +152 -0
- data/lib/rtext/context_element_builder.rb +112 -0
- data/lib/rtext/default_loader.rb +137 -0
- data/lib/rtext/default_service_provider.rb +153 -0
- data/lib/rtext/instantiator.rb +284 -0
- data/lib/rtext/language.rb +257 -0
- data/lib/rtext/parser.rb +251 -0
- data/lib/rtext/serializer.rb +190 -0
- data/lib/rtext/service.rb +182 -0
- data/lib/rtext_plugin/connection_manager.rb +59 -0
- data/test/instantiator_test.rb +931 -0
- data/test/rtext_test.rb +5 -0
- data/test/serializer_test.rb +418 -0
- metadata +84 -0
@@ -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
|
+
|