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
data/CHANGELOG
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Martin Thiede
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
== RText - Ruby Textual Modelling
|
2
|
+
|
3
|
+
RText can be used to derive textual languages from an RGen metamodel with very little effort.
|
4
|
+
|
5
|
+
RText features include:
|
6
|
+
* Generic textual syntax
|
7
|
+
* Generic instantiator (parser) and serializer
|
8
|
+
* Consistency checks derived from the metamodel
|
9
|
+
* Designed to work with lightweight editor plugins providing
|
10
|
+
- Syntax Highlighting
|
11
|
+
- Auto Completion on language commands and model references
|
12
|
+
- Error Annotations
|
13
|
+
- Model Navigation
|
14
|
+
|
15
|
+
|
16
|
+
= Installation
|
17
|
+
|
18
|
+
> gem install rtext
|
19
|
+
|
20
|
+
|
21
|
+
= Documentation
|
22
|
+
|
23
|
+
|
24
|
+
= Examples
|
25
|
+
|
26
|
+
|
27
|
+
= License
|
28
|
+
|
29
|
+
RGen is released under the MIT license.
|
@@ -0,0 +1,247 @@
|
|
1
|
+
=RText Plugin Implementation Guide
|
2
|
+
|
3
|
+
RText editors consist of a frontend and a backend. Most of the logic is implemented
|
4
|
+
within the backend while the frontend is kept as thin as possible. This should simplify
|
5
|
+
the task of writing a frontend and promote development of RText frontends for different
|
6
|
+
editing environments.
|
7
|
+
|
8
|
+
This guide explains how to build a RText frontend which will typically be a plugin for an
|
9
|
+
existing editor.
|
10
|
+
|
11
|
+
|
12
|
+
==Plugin Responsibilies
|
13
|
+
|
14
|
+
Here is a rough overview of what a RText plugin has to do:
|
15
|
+
|
16
|
+
* find the .rtext config file for a given RText model file
|
17
|
+
* start the corresponding backend process if not started already
|
18
|
+
* provide a way to reload the model and issue the refresh command to the backend
|
19
|
+
* implement syntax highlighting for the generic RText syntax
|
20
|
+
* forward auto completion requests to the backend, let the user choose from the returned options
|
21
|
+
and insert the option chosen
|
22
|
+
* forward element search requests to the backend, let the user choose from the matched elements
|
23
|
+
and jump to the element chosen
|
24
|
+
* forward reference target requests to the backend, let the user choose from the targets found
|
25
|
+
and jump to the target chosen
|
26
|
+
* query the backend for model problems and display them to the user
|
27
|
+
* stop the backend process when it's no longer needed
|
28
|
+
|
29
|
+
|
30
|
+
==Plugin Implementation Hints
|
31
|
+
|
32
|
+
The plugin implementation could include the following entities.
|
33
|
+
|
34
|
+
===Command
|
35
|
+
|
36
|
+
A command issued by the plugin and sent to the backend service. See the section about the
|
37
|
+
Frontend/Backend Communication Protocol below for details about available commands.
|
38
|
+
|
39
|
+
|
40
|
+
===Connector
|
41
|
+
|
42
|
+
A connector represents the connection to a running backend process.
|
43
|
+
Each connector/process is associated with a specific .rtext file and a specific service specification
|
44
|
+
within this file. See the RText Users Guide for details about the .rtext config file.
|
45
|
+
|
46
|
+
When the backend service is started, it outputs the UDP port it is listening on. The connector
|
47
|
+
should use this port to send commands to the backend.
|
48
|
+
|
49
|
+
The connector should send commands to the backend. It should assign invocation ids and keep the
|
50
|
+
invocation until a response is received or a timeout has occured.
|
51
|
+
|
52
|
+
When the backend responds, the connector should associate the response packages with the previous
|
53
|
+
request using the invocation id. It should also join fragmented response packages and send an
|
54
|
+
internal notification when a response has been fully received.
|
55
|
+
|
56
|
+
|
57
|
+
===Connector Manager
|
58
|
+
|
59
|
+
The connector manager should be able to return a connector for a given RText model file.
|
60
|
+
|
61
|
+
It needs to identify the backend process to which to connect by means of the .rtext config file and
|
62
|
+
a contained service specification (one .rtext files may contain service specifications for starting
|
63
|
+
different backend services depending on the model file name).
|
64
|
+
|
65
|
+
For this, it needs to find the first .rtext config file containing a service specification matching
|
66
|
+
the given RText model file name. See the RText Users guide for details about the .rtext config file
|
67
|
+
and the way how they are found in the filesystem.
|
68
|
+
|
69
|
+
Once the .rtext file/service specification is found it can be used as a key for a lookup for
|
70
|
+
already established connections. The plugin should avoid starting the same process twice. All
|
71
|
+
RText model files with the same file extension and belonging to the same .rtext file should
|
72
|
+
use the same process.
|
73
|
+
|
74
|
+
The process should be kept running as long as editing is in progress. This will greatly
|
75
|
+
reduce response times as the model can be kept in memory and only the changed parts need
|
76
|
+
to be reloaded.
|
77
|
+
|
78
|
+
When the plugin lifecycle ends, the connector manager should send the "stop" command to the
|
79
|
+
backend processes.
|
80
|
+
|
81
|
+
|
82
|
+
==Frontend/Backend Communication Protocol
|
83
|
+
|
84
|
+
Communication takes place via plain text transmitted via UDP. There are requests by
|
85
|
+
the frontend and responses by the backend. In both cases the text is interpreted as
|
86
|
+
a set of lines (separated by \n or \r\n).
|
87
|
+
|
88
|
+
|
89
|
+
===Request
|
90
|
+
|
91
|
+
The request consists of a command id, an invocation id and the command parameters.
|
92
|
+
The invocation id is repeated within the response to allow proper association with the
|
93
|
+
request. Command parameters are command specific (see below).
|
94
|
+
|
95
|
+
Requests can not span more than one UDP package and thus are limited to the UDP payload size.
|
96
|
+
|
97
|
+
line 1: <command id>
|
98
|
+
line 2: <invocation id>
|
99
|
+
line 3..n: <command parameters>
|
100
|
+
|
101
|
+
|
102
|
+
===Response
|
103
|
+
|
104
|
+
Each response contains the invocation id of the request in the first line.
|
105
|
+
Since it could be splitted over several UDP packages it contains an indication
|
106
|
+
in the second line if more packages are following ("more") or this is the last package
|
107
|
+
("last"). The rest of the response is a fragment of the command result data. All fragments
|
108
|
+
of a set of response packages with the same invocation id need to be joined.
|
109
|
+
|
110
|
+
Note that there are no mechanisms to protect agains package loss or reordering since
|
111
|
+
this protocol is intended to be used on local socket connections only.
|
112
|
+
|
113
|
+
line 1: <invocation id>
|
114
|
+
line 2: "more" | "last"
|
115
|
+
line 3..n: <command result data>
|
116
|
+
|
117
|
+
|
118
|
+
===Command: Refresh
|
119
|
+
|
120
|
+
The refresh command tells the backend to reload the model from the file system. Frontends
|
121
|
+
could issue this command after a model file has been changed in the file system or on
|
122
|
+
an explicit user request.
|
123
|
+
|
124
|
+
Request Format:
|
125
|
+
command id: "refresh"
|
126
|
+
no parameters
|
127
|
+
|
128
|
+
Response Format:
|
129
|
+
no result data
|
130
|
+
|
131
|
+
|
132
|
+
===Command: Complete
|
133
|
+
|
134
|
+
This command is a request by the frontend to show auto completion options at a given
|
135
|
+
location within a file. The location is expressed using a set of context lines and the cursor
|
136
|
+
column position in the current line.
|
137
|
+
|
138
|
+
Context lines are lines from an RText file which contain a (context) command and all
|
139
|
+
the parent commands wrapped around it. Any sibling commands can be omitted as well as
|
140
|
+
any lines containing closing braces and brackets. The order of lines is the same as in the RText file.
|
141
|
+
|
142
|
+
Here is an example. Consider the following RText file with the cursor in the line of "Command3" at
|
143
|
+
the time when the auto completion command is issued.
|
144
|
+
|
145
|
+
Command1 {
|
146
|
+
Command2 {
|
147
|
+
role1: [
|
148
|
+
Command3 <== cursor in this line
|
149
|
+
Command4
|
150
|
+
]
|
151
|
+
}
|
152
|
+
Command5
|
153
|
+
}
|
154
|
+
|
155
|
+
The context lines in this case would be the following.
|
156
|
+
|
157
|
+
Command1 {
|
158
|
+
Command2 {
|
159
|
+
role1: [
|
160
|
+
Command3
|
161
|
+
|
162
|
+
The current line is always the last of the context lines.
|
163
|
+
|
164
|
+
Note that all siblings of the command and parent commands have been stripped off, as well as
|
165
|
+
any closing braces or brackets.
|
166
|
+
|
167
|
+
The purpose of this special context line format is to keep the task of extracting the
|
168
|
+
context in the frontend simple and the amount of data transmitted to the backend low.
|
169
|
+
It's also a way to keep the parsing time of the context low in the backend and thus to minimize
|
170
|
+
the user noticable delay.
|
171
|
+
|
172
|
+
Request Format:
|
173
|
+
command id: "complete"
|
174
|
+
param line 1: cursor column position in the current line
|
175
|
+
param line 2..n: context lines
|
176
|
+
|
177
|
+
Response Format:
|
178
|
+
result line 1..n: <completion string>;<extra info string>
|
179
|
+
|
180
|
+
|
181
|
+
===Command: Show Problems
|
182
|
+
|
183
|
+
This command is a request by the frontend to determine and return all problems present in the current model.
|
184
|
+
The command implicitly reloads the model from the filesystem before checking for problems.
|
185
|
+
|
186
|
+
Request Format:
|
187
|
+
command id: "show_problems"
|
188
|
+
no parameters
|
189
|
+
|
190
|
+
Response Format:
|
191
|
+
result line n: <file name>
|
192
|
+
result line n+1..n+m <line number>;<message>
|
193
|
+
|
194
|
+
Note that the file name is repeated only once for all problems within a file to reduce the amout of
|
195
|
+
result data which needs to be transmitted.
|
196
|
+
|
197
|
+
|
198
|
+
===Command: Reference Targets
|
199
|
+
|
200
|
+
This command is used to retrieve the targets of a given reference. The reference location is expressed
|
201
|
+
by means of a set of context lines and the cursor column position in the current line. The cursor
|
202
|
+
column position must be within the string representing the reference. The format of the context lines
|
203
|
+
is the same as the one described with the "Complete" command (see above).
|
204
|
+
|
205
|
+
Note that the service provider is free to define how a reference is represented. In particular it
|
206
|
+
can interpret the command identifier as a reference and use this mechanism to show incoming references
|
207
|
+
(reverse references).
|
208
|
+
|
209
|
+
As references can be ambiguous, the result is a list of possible targets. Each target consists of the
|
210
|
+
filename and line number of the target element as well as the text to be displayed to the user in case
|
211
|
+
there are more than one targets to choose from.
|
212
|
+
|
213
|
+
Request Format:
|
214
|
+
command id: "get_reference_targets"
|
215
|
+
param line 1: cursor column position in the current line
|
216
|
+
param line 2..n: context lines
|
217
|
+
|
218
|
+
Response Format:
|
219
|
+
result line 1..n: <file name>;<line number>;<display name>
|
220
|
+
|
221
|
+
|
222
|
+
===Command: Find Elements
|
223
|
+
|
224
|
+
This command returns a set of model elements matching a search pattern. The format of the
|
225
|
+
pattern depends on the service provider. In a simple case, for example, the pattern could be the
|
226
|
+
beginning of the element names.
|
227
|
+
|
228
|
+
Request Format:
|
229
|
+
command id: "get_elements"
|
230
|
+
param line 1: <seach pattern>
|
231
|
+
|
232
|
+
Response Format:
|
233
|
+
result line 1..n: <display name>;<file name>;<line number>
|
234
|
+
|
235
|
+
|
236
|
+
===Command: Stop
|
237
|
+
|
238
|
+
This command is normally invoked when the frontend terminates or otherwise needs to terminate
|
239
|
+
the backend service. When receiving this command, the backend will terminate.
|
240
|
+
|
241
|
+
Request Format:
|
242
|
+
command id: "stop"
|
243
|
+
no parameters
|
244
|
+
|
245
|
+
Response Format:
|
246
|
+
no result data
|
247
|
+
|
data/RText_Users_Guide
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
=RText Users Guide
|
2
|
+
|
3
|
+
== The .rtext config file
|
4
|
+
|
5
|
+
A .rtext config file is used to specify which backend service is to be started for a given
|
6
|
+
RText model file.
|
7
|
+
|
8
|
+
In order to be found by an RText plugin, the .rtext file must be located in the same directory
|
9
|
+
as the RText model file being edited or in any of its parent directories.
|
10
|
+
|
11
|
+
Here is the grammar for the config file syntax:
|
12
|
+
|
13
|
+
rtext_config ::= <service_spec>+
|
14
|
+
service_spec ::= <file_pattern_list>:\n<command line string>\n
|
15
|
+
file_pattern_list ::= <file_pattern> | <file_pattern>, <file_pattern_list>
|
16
|
+
file_pattern ::= <filename without path> | *.<extension>
|
17
|
+
|
18
|
+
Here is an example:
|
19
|
+
|
20
|
+
*.ext1:
|
21
|
+
ruby service1.rb
|
22
|
+
*.ext2, *.ext3:
|
23
|
+
ruby service2.rb *.ext2 *.ext3
|
24
|
+
noext:
|
25
|
+
service3.sh
|
26
|
+
|
27
|
+
This example contains specifications for three different backend services. service1.rb is started
|
28
|
+
when a model file with the extension "ext1" is being edited. service2.rb is started when a
|
29
|
+
model file with extension "ext2" or "ext3" is edited. service2.rb also receives additional command
|
30
|
+
line parameters. service3.sh is started whenever a file named "noext" is edited.
|
31
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'rake/gempackagetask'
|
2
|
+
require 'rake/rdoctask'
|
3
|
+
|
4
|
+
DocFiles = [
|
5
|
+
"README", "CHANGELOG", "MIT-LICENSE",
|
6
|
+
"RText_Users_Guide",
|
7
|
+
"RText_Plugin_Implementation_Guide"]
|
8
|
+
|
9
|
+
RTextGemSpec = Gem::Specification.new do |s|
|
10
|
+
s.name = %q{rtext}
|
11
|
+
s.version = "0.2.0"
|
12
|
+
s.date = Time.now.strftime("%Y-%m-%d")
|
13
|
+
s.summary = %q{Ruby Textual Modelling}
|
14
|
+
s.email = %q{martin dot thiede at gmx de}
|
15
|
+
s.homepage = %q{http://ruby-gen.org}
|
16
|
+
s.description = %q{RText can be used to derive textual languages from an RGen metamodel with very little effort.}
|
17
|
+
s.authors = ["Martin Thiede"]
|
18
|
+
s.add_dependency('rgen', '>= 0.6.0')
|
19
|
+
gemfiles = Rake::FileList.new
|
20
|
+
gemfiles.include("{lib,test}/**/*")
|
21
|
+
gemfiles.include(DocFiles)
|
22
|
+
gemfiles.include("Rakefile")
|
23
|
+
gemfiles.exclude(/\b\.bak\b/)
|
24
|
+
s.files = gemfiles
|
25
|
+
s.rdoc_options = ["--main", "README", "-x", "test"]
|
26
|
+
s.extra_rdoc_files = DocFiles
|
27
|
+
end
|
28
|
+
|
29
|
+
Rake::RDocTask.new do |rd|
|
30
|
+
rd.main = "README"
|
31
|
+
rd.rdoc_files.include(DocFiles)
|
32
|
+
rd.rdoc_files.include("lib/**/*.rb")
|
33
|
+
rd.rdoc_dir = "doc"
|
34
|
+
end
|
35
|
+
|
36
|
+
RTextPackageTask = Rake::GemPackageTask.new(RTextGemSpec) do |p|
|
37
|
+
p.need_zip = false
|
38
|
+
end
|
39
|
+
|
40
|
+
task :prepare_package_rdoc => :rdoc do
|
41
|
+
RTextPackageTask.package_files.include("doc/**/*")
|
42
|
+
end
|
43
|
+
|
44
|
+
task :release => [:prepare_package_rdoc, :package]
|
45
|
+
|
46
|
+
task :clobber => [:clobber_rdoc, :clobber_package]
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'rgen/ecore/ecore_ext'
|
2
|
+
|
3
|
+
module RText
|
4
|
+
|
5
|
+
class Completer
|
6
|
+
|
7
|
+
CompletionOption = Struct.new(:text, :extra)
|
8
|
+
|
9
|
+
# Creates a completer for RText::Language +language+.
|
10
|
+
#
|
11
|
+
def initialize(language)
|
12
|
+
@lang = language
|
13
|
+
end
|
14
|
+
|
15
|
+
# Provides completion options
|
16
|
+
#
|
17
|
+
# :linestart
|
18
|
+
# the content of the current line before the cursor
|
19
|
+
#
|
20
|
+
# :prev_line_provider
|
21
|
+
# is a proc which must return lines above the current line
|
22
|
+
# it receives an index parameter in the range 1..n
|
23
|
+
# 1 is the line just above the current one, 2 is the second line above, etc.
|
24
|
+
# the proc must return the line as a string or nil if there is no more line
|
25
|
+
#
|
26
|
+
# :ref_completion_option_provider
|
27
|
+
# a proc which receives a EReference and should return
|
28
|
+
# the possible completion options as CompletionOption objects
|
29
|
+
# note, that the context element may be nil if this information is unavailable
|
30
|
+
#
|
31
|
+
def complete(line, linepos, prev_line_provider, ref_completion_option_provider=nil)
|
32
|
+
linestart = line[0..linepos-1]
|
33
|
+
# command
|
34
|
+
if linestart =~ /^\s*(\w*)$/
|
35
|
+
prefix = $1
|
36
|
+
classes = completion_classes(prev_line_provider)
|
37
|
+
classes = classes.select{|c| c.name.index(prefix) == 0} if prefix
|
38
|
+
classes.sort{|a,b| a.name <=> b.name}.collect do |c|
|
39
|
+
uargs = @lang.unlabled_arguments(c).collect{|a| "<#{a.name}>"}.join(", ")
|
40
|
+
CompletionOption.new(c.name, uargs)
|
41
|
+
end
|
42
|
+
# attribute
|
43
|
+
elsif linestart =~ /^\s*(\w+)\s+(?:[^,]+,)*\s*(\w*)$/
|
44
|
+
command, prefix = $1, $2
|
45
|
+
clazz = @lang.class_by_command(command)
|
46
|
+
if clazz
|
47
|
+
features = @lang.labled_arguments(clazz.ecore)
|
48
|
+
features = features.select{|f| f.name.index(prefix) == 0} if prefix
|
49
|
+
features.sort{|a,b| a.name <=> b.name}.collect do |f|
|
50
|
+
CompletionOption.new("#{f.name}:", "<#{f.eType.name}>")
|
51
|
+
end
|
52
|
+
else
|
53
|
+
[]
|
54
|
+
end
|
55
|
+
# value
|
56
|
+
elsif linestart =~ /\s*(\w+)\s+(?:[^,]+,)*\s*(\w+):\s*(\S*)$/
|
57
|
+
command, fn, prefix = $1, $2, $3
|
58
|
+
clazz = @lang.class_by_command(command)
|
59
|
+
feature = clazz && @lang.non_containments(clazz.ecore).find{|f| f.name == fn}
|
60
|
+
if feature
|
61
|
+
if feature.is_a?(RGen::ECore::EReference)
|
62
|
+
if ref_completion_option_provider
|
63
|
+
ref_completion_option_provider.call(feature)
|
64
|
+
else
|
65
|
+
[]
|
66
|
+
end
|
67
|
+
elsif feature.eType.is_a?(RGen::ECore::EEnum)
|
68
|
+
feature.eType.eLiterals.collect do |l|
|
69
|
+
CompletionOption.new("#{l.name}")
|
70
|
+
end
|
71
|
+
elsif feature.eType.instanceClass == String
|
72
|
+
[ CompletionOption.new("\"\"") ]
|
73
|
+
elsif feature.eType.instanceClass == Integer
|
74
|
+
(0..4).collect{|i| CompletionOption.new("#{i}") }
|
75
|
+
elsif feature.eType.instanceClass == Float
|
76
|
+
(0..4).collect{|i| CompletionOption.new("#{i}.0") }
|
77
|
+
elsif feature.eType.instanceClass == RGen::MetamodelBuilder::DataTypes::Boolean
|
78
|
+
[true, false].collect{|b| CompletionOption.new("#{b}") }
|
79
|
+
else
|
80
|
+
[]
|
81
|
+
end
|
82
|
+
else
|
83
|
+
[]
|
84
|
+
end
|
85
|
+
else
|
86
|
+
[]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def completion_classes(prev_line_provider)
|
93
|
+
clazz, feature = context(prev_line_provider)
|
94
|
+
if clazz
|
95
|
+
if feature
|
96
|
+
@lang.concrete_types(feature.eType)
|
97
|
+
else
|
98
|
+
refs_by_class = {}
|
99
|
+
clazz.eAllReferences.select{|r| r.containment}.each do |r|
|
100
|
+
@lang.concrete_types(r.eType).each { |c| (refs_by_class[c] ||= []) << r }
|
101
|
+
end
|
102
|
+
refs_by_class.keys.select{|c| refs_by_class[c].size == 1}
|
103
|
+
end
|
104
|
+
else
|
105
|
+
@lang.root_epackage.eAllClasses.select{|c| !c.abstract &&
|
106
|
+
!c.eAllReferences.any?{|r| r.eOpposite && r.eOpposite.containment}}
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def context(prev_line_provider)
|
111
|
+
command, role = parse_context(prev_line_provider)
|
112
|
+
clazz = command && @lang.root_epackage.eAllClasses.find{|c| c.name == command}
|
113
|
+
feature = role && clazz && clazz.eAllReferences.find{|r| r.containment && r.name == role}
|
114
|
+
[clazz, feature]
|
115
|
+
end
|
116
|
+
|
117
|
+
def parse_context(prev_line_provider)
|
118
|
+
block_nesting = 0
|
119
|
+
array_nesting = 0
|
120
|
+
non_empty_lines = 0
|
121
|
+
role = nil
|
122
|
+
i = 0
|
123
|
+
while line = prev_line_provider.call(i+=1)
|
124
|
+
# empty or comment
|
125
|
+
next if line =~ /^\s*$/ || line =~ /^\s*#/
|
126
|
+
# role
|
127
|
+
if line =~ /^\s*(\w+):\s*$/
|
128
|
+
role = $1 if non_empty_lines == 0
|
129
|
+
# block open
|
130
|
+
elsif line =~ /^\s*(\S+).*\{\s*$/
|
131
|
+
block_nesting -= 1
|
132
|
+
return [$1, role] if block_nesting < 0
|
133
|
+
# block close
|
134
|
+
elsif line =~ /^\s*\}\s*$/
|
135
|
+
block_nesting += 1
|
136
|
+
# array open
|
137
|
+
elsif line =~ /^\s*(\w+):\s*\[\s*$/
|
138
|
+
array_nesting -= 1
|
139
|
+
role = $1 if array_nesting < 0
|
140
|
+
# array close
|
141
|
+
elsif line =~ /^\s*\]\s*$/
|
142
|
+
array_nesting += 1
|
143
|
+
end
|
144
|
+
non_empty_lines += 1
|
145
|
+
end
|
146
|
+
[nil, nil]
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
|