rtext 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ =0.2.0
2
+
3
+ * First public release
4
+
@@ -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
+
@@ -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
+
@@ -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
+