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,112 @@
|
|
1
|
+
module RText
|
2
|
+
|
3
|
+
# ContextElementBuilder build a partial model from a set of context lines.
|
4
|
+
#
|
5
|
+
# Context lines are lines from an RText file which contain a (context) command and all
|
6
|
+
# the parent commands wrapped around it. Any sibling commands can be omitted as well as
|
7
|
+
# any lines containing closing braces and brackets.
|
8
|
+
#
|
9
|
+
# The resulting partial model contains a (context) model element and all its parent
|
10
|
+
# elements. Further references are not resolved.
|
11
|
+
#
|
12
|
+
module ContextElementBuilder
|
13
|
+
|
14
|
+
class << self
|
15
|
+
|
16
|
+
# Instantiates the context element based on a set of +content_lines+. Content lines
|
17
|
+
# are the RText lines containing the nested command headers in the original order.
|
18
|
+
# The last line of +context_lines+ is the one which will create the context element.
|
19
|
+
# +position_in_line+ is the cursor column position within the last line
|
20
|
+
def build_context_element(language, context_lines, position_in_line)
|
21
|
+
context_info = fix_context(context_lines)
|
22
|
+
return nil unless context_info
|
23
|
+
element = instantiate_context_element(language, context_info)
|
24
|
+
unless element
|
25
|
+
fix_current_line(context_info, position_in_line)
|
26
|
+
element = instantiate_context_element(language, context_info)
|
27
|
+
end
|
28
|
+
element
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def instantiate_context_element(language, context_info)
|
34
|
+
root_elements = []
|
35
|
+
problems = []
|
36
|
+
Instantiator.new(language).instantiate(context_info.lines.join("\n"),
|
37
|
+
:root_elements => root_elements, :problems => problems)
|
38
|
+
if root_elements.size > 0
|
39
|
+
find_leaf_child(root_elements.first, context_info.num_elements-1)
|
40
|
+
else
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def find_leaf_child(element, num_required_children)
|
46
|
+
childs = element.class.ecore.eAllReferences.select{|r| r.containment}.collect{|r|
|
47
|
+
element.getGenericAsArray(r.name)}.flatten
|
48
|
+
if childs.size > 0
|
49
|
+
find_leaf_child(childs.first, num_required_children-1)
|
50
|
+
elsif num_required_children == 0
|
51
|
+
element
|
52
|
+
else
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
ContextInfo = Struct.new(:lines, :num_elements, :pos_leaf_element)
|
58
|
+
|
59
|
+
# extend +context_lines+ into a set of lines which can be processed by the RText
|
60
|
+
# instantiator: cut of curly brace from current line if present and add missing
|
61
|
+
# closing curly braces and square brackets
|
62
|
+
# returns a ContextInfo containing the new set of lines, the number of model elements
|
63
|
+
# contained in this model snipped and the number of the line containing the leaf element
|
64
|
+
def fix_context(context_lines)
|
65
|
+
context_lines = context_lines.dup
|
66
|
+
line = context_lines.last
|
67
|
+
return nil if line.nil? || is_non_element_line(line)
|
68
|
+
context_lines << strip_curly_brace(context_lines.pop)
|
69
|
+
pos_leaf_element = context_lines.size-1
|
70
|
+
num_elements = 1
|
71
|
+
context_lines.reverse.each do |l|
|
72
|
+
if l =~ /\{\s*$/
|
73
|
+
context_lines << "}"
|
74
|
+
num_elements += 1
|
75
|
+
elsif l =~ /\[\s*$/
|
76
|
+
context_lines << "]"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
ContextInfo.new(context_lines, num_elements, pos_leaf_element)
|
80
|
+
end
|
81
|
+
|
82
|
+
def is_non_element_line(line)
|
83
|
+
line = line.strip
|
84
|
+
line == "" || line == "}" || line == "]" || line =~ /^#/ || line =~ /^\w+:$/
|
85
|
+
end
|
86
|
+
|
87
|
+
def strip_curly_brace(line)
|
88
|
+
line.sub(/\{\s*$/,'')
|
89
|
+
end
|
90
|
+
|
91
|
+
def fix_current_line(context_info, pos_in_line)
|
92
|
+
context_info.lines[context_info.pos_leaf_element] =
|
93
|
+
cut_current_argument(context_info.lines[context_info.pos_leaf_element], pos_in_line)
|
94
|
+
end
|
95
|
+
|
96
|
+
def cut_current_argument(line, pos_in_line)
|
97
|
+
left_comma_pos = line.rindex(",", pos_in_line-1)
|
98
|
+
if left_comma_pos
|
99
|
+
line[0..left_comma_pos-1]
|
100
|
+
elsif line =~ /^\s*\w+/
|
101
|
+
$&
|
102
|
+
else
|
103
|
+
""
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'rgen/environment'
|
2
|
+
require 'rgen/util/file_change_detector'
|
3
|
+
require 'rgen/fragment/model_fragment'
|
4
|
+
require 'rtext/instantiator'
|
5
|
+
|
6
|
+
module RText
|
7
|
+
|
8
|
+
# Loads RText files into a FragmentedModel.
|
9
|
+
#
|
10
|
+
# A glob pattern or file provider specifies the files which should be loaded. The load method
|
11
|
+
# can be called to load and to reload the model. Only changed files will be reloaded.
|
12
|
+
#
|
13
|
+
# Optionally, the loader can use a fragment cache to speed up loading.
|
14
|
+
#
|
15
|
+
class DefaultLoader
|
16
|
+
|
17
|
+
# Create a default loader for +language+, loading into +fragmented_model+.
|
18
|
+
# It will find files either by evaluating the glob pattern given with +:pattern+
|
19
|
+
# (see Dir.glob) or by means of a +file_provider+. Options:
|
20
|
+
#
|
21
|
+
# :pattern
|
22
|
+
# a glob pattern or an array of glob patterns
|
23
|
+
# alternatively, a +:file_provider+ may be specified
|
24
|
+
#
|
25
|
+
# :file_provider
|
26
|
+
# a proc which is called without any arguments and should return the files to load
|
27
|
+
# this is an alternative to providing +:pattern+
|
28
|
+
#
|
29
|
+
# :cache
|
30
|
+
# a fragment cache to be used for loading
|
31
|
+
#
|
32
|
+
def initialize(language, fragmented_model, options={})
|
33
|
+
@lang = language
|
34
|
+
@model = fragmented_model
|
35
|
+
@change_detector = RGen::Util::FileChangeDetector.new(
|
36
|
+
:file_added => method(:file_added),
|
37
|
+
:file_removed => method(:file_removed),
|
38
|
+
:file_changed => method(:file_changed))
|
39
|
+
@cache = options[:cache]
|
40
|
+
@fragment_by_file = {}
|
41
|
+
pattern = options[:pattern]
|
42
|
+
@file_provider = options[:file_provider] || proc { Dir.glob(pattern) }
|
43
|
+
end
|
44
|
+
|
45
|
+
# Loads or reloads model fragments from files using the file patterns or file provider
|
46
|
+
# specified in the constructor. Options:
|
47
|
+
#
|
48
|
+
# :before_load
|
49
|
+
# a proc which is called before a file is actually loaded, receives the fragment to load
|
50
|
+
# into and a symbol indicating the kind of loading: :load, :load_cached, :load_update_cache
|
51
|
+
# default: no before load proc
|
52
|
+
#
|
53
|
+
def load(options={})
|
54
|
+
@before_load_proc = options[:before_load]
|
55
|
+
files = @file_provider.call
|
56
|
+
@change_detector.check_files(files)
|
57
|
+
@model.resolve(:fragment_provider => method(:fragment_provider),
|
58
|
+
:use_target_type => @lang.per_type_identifier)
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def file_added(file)
|
64
|
+
fragment = RGen::Fragment::ModelFragment.new(file,
|
65
|
+
:identifier_provider => @lang.identifier_provider)
|
66
|
+
load_fragment_cached(fragment)
|
67
|
+
@model.add_fragment(fragment)
|
68
|
+
@fragment_by_file[file] = fragment
|
69
|
+
end
|
70
|
+
|
71
|
+
def file_removed(file)
|
72
|
+
@model.remove_fragment(@fragment_by_file[file])
|
73
|
+
@fragment_by_file.delete(file)
|
74
|
+
end
|
75
|
+
|
76
|
+
def file_changed(file)
|
77
|
+
file_removed(file)
|
78
|
+
file_added(file)
|
79
|
+
end
|
80
|
+
|
81
|
+
def fragment_provider(element)
|
82
|
+
fr = @lang.fragment_ref(element)
|
83
|
+
fr && fr.fragment
|
84
|
+
end
|
85
|
+
|
86
|
+
def load_fragment_cached(fragment)
|
87
|
+
if @cache
|
88
|
+
begin
|
89
|
+
result = @cache.load(fragment)
|
90
|
+
rescue ArgumentError => e
|
91
|
+
# Marshal#load raises an ArgumentError if required classes are not present
|
92
|
+
if e.message =~ /undefined class\/module/
|
93
|
+
result = :invalid
|
94
|
+
else
|
95
|
+
raise
|
96
|
+
end
|
97
|
+
end
|
98
|
+
if result == :invalid
|
99
|
+
@before_load_proc && @before_load_proc.call(fragment, :load_update_cache)
|
100
|
+
load_fragment(fragment)
|
101
|
+
@cache.store(fragment)
|
102
|
+
else
|
103
|
+
@before_load_proc && @before_load_proc.call(fragment, :load_cached)
|
104
|
+
end
|
105
|
+
else
|
106
|
+
@before_load_proc && @before_load_proc.call(fragment, :load)
|
107
|
+
load_fragment(fragment)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def load_fragment(fragment)
|
112
|
+
env = RGen::Environment.new
|
113
|
+
urefs = []
|
114
|
+
problems = []
|
115
|
+
root_elements = []
|
116
|
+
inst = RText::Instantiator.new(@lang)
|
117
|
+
File.open(fragment.location) do |f|
|
118
|
+
inst.instantiate(f.read,
|
119
|
+
:env => env,
|
120
|
+
:unresolved_refs => urefs,
|
121
|
+
:problems => problems,
|
122
|
+
:root_elements => root_elements,
|
123
|
+
:fragment_ref => fragment.fragment_ref,
|
124
|
+
:file_name => fragment.location)
|
125
|
+
end
|
126
|
+
fragment.data = {:problems => problems}
|
127
|
+
fragment.set_root_elements(root_elements,
|
128
|
+
:unresolved_refs => urefs,
|
129
|
+
:elements => env.elements)
|
130
|
+
fragment.build_index
|
131
|
+
fragment.resolve_local(:use_target_type => @lang.per_type_identifier)
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
|
@@ -0,0 +1,153 @@
|
|
1
|
+
module RText
|
2
|
+
|
3
|
+
class DefaultServiceProvider
|
4
|
+
|
5
|
+
def initialize(language, fragmented_model, model_loader)
|
6
|
+
@lang = language
|
7
|
+
@model = fragmented_model
|
8
|
+
@loader = model_loader
|
9
|
+
@element_name_index = nil
|
10
|
+
@model.add_fragment_change_listener(proc {|fragment, kind|
|
11
|
+
@element_name_index = nil
|
12
|
+
})
|
13
|
+
end
|
14
|
+
|
15
|
+
def load_model
|
16
|
+
@loader.load
|
17
|
+
end
|
18
|
+
|
19
|
+
ReferenceCompletionOption = Struct.new(:identifier, :type)
|
20
|
+
def get_reference_completion_options(reference, context)
|
21
|
+
if @model.environment
|
22
|
+
targets = @model.environment.find(:class => reference.eType.instanceClass)
|
23
|
+
else
|
24
|
+
clazz = reference.eType.instanceClass
|
25
|
+
targets = @model.index.values.flatten.select{|e| e.is_a?(clazz)}
|
26
|
+
end
|
27
|
+
targets.collect{|t|
|
28
|
+
ident = @lang.identifier_provider.call(t, context)
|
29
|
+
if ident
|
30
|
+
ReferenceCompletionOption.new(ident, t.class.ecore.name)
|
31
|
+
else
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
}.compact.sort{|a,b| a.identifier <=> b.identifier}
|
35
|
+
end
|
36
|
+
|
37
|
+
ReferenceTarget = Struct.new(:file, :line, :display_name)
|
38
|
+
def get_reference_targets(identifier, context, lines, linepos)
|
39
|
+
result = []
|
40
|
+
identifier = @lang.qualify_reference(identifier, context)
|
41
|
+
targets = @model.index[identifier]
|
42
|
+
if targets && @lang.per_type_identifier
|
43
|
+
current_line = lines.last
|
44
|
+
linestart = current_line[0..linepos-1]
|
45
|
+
if linestart =~ /\s*(\w+)\s+(?:[^,]+,)*\s*(\w+):\s*(\S*)$/
|
46
|
+
command, fn, prefix = $1, $2, $3
|
47
|
+
clazz = @lang.class_by_command(command)
|
48
|
+
feature = clazz && @lang.non_containments(clazz.ecore).find{|f| f.name == fn}
|
49
|
+
if feature
|
50
|
+
targets = targets.select{|t| t.is_a?(feature.eType.instanceClass)}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
targets && targets.each do |t|
|
55
|
+
if @lang.fragment_ref(t)
|
56
|
+
path = File.expand_path(@lang.fragment_ref(t).fragment.location)
|
57
|
+
result << ReferenceTarget.new(path, @lang.line_number(t), "#{identifier} [#{t.class.ecore.name}]")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
result
|
61
|
+
end
|
62
|
+
|
63
|
+
def get_referencing_elements(identifier, context)
|
64
|
+
result = []
|
65
|
+
targets = @model.index[@lang.identifier_provider.call(context, nil)]
|
66
|
+
if targets && @lang.per_type_identifier
|
67
|
+
targets = targets.select{|t| t.class == context.class}
|
68
|
+
end
|
69
|
+
if targets && targets.size == 1
|
70
|
+
target = targets.first
|
71
|
+
elements = target.class.ecore.eAllReferences.select{|r|
|
72
|
+
r.eOpposite && !r.containment && !r.eOpposite.containment}.collect{|r|
|
73
|
+
target.getGenericAsArray(r.name)}.flatten
|
74
|
+
elements.each do |e|
|
75
|
+
if @lang.fragment_ref(e)
|
76
|
+
path = File.expand_path(@lang.fragment_ref(e).fragment.location)
|
77
|
+
display_name = ""
|
78
|
+
ident = @lang.identifier_provider.call(e, nil)
|
79
|
+
display_name += "#{ident} " if ident
|
80
|
+
display_name += "[#{e.class.ecore.name}]"
|
81
|
+
result << ReferenceTarget.new(path, @lang.line_number(e), display_name)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
result
|
86
|
+
end
|
87
|
+
|
88
|
+
FileProblems = Struct.new(:file, :problems)
|
89
|
+
Problem = Struct.new(:severity, :line, :message)
|
90
|
+
def get_problems
|
91
|
+
load_model
|
92
|
+
result = []
|
93
|
+
@model.fragments.sort{|a,b| a.location <=> b.location}.each do |fragment|
|
94
|
+
problems = []
|
95
|
+
if fragment.data && fragment.data[:problems]
|
96
|
+
fragment.data[:problems].each do |p|
|
97
|
+
problems << Problem.new("Error", p.line, p.message)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
fragment.unresolved_refs.each do |ur|
|
101
|
+
# TODO: where do these proxies come from?
|
102
|
+
next unless ur.proxy.targetIdentifier
|
103
|
+
problems << Problem.new("Error", @lang.line_number(ur.element), "unresolved reference #{ur.proxy.targetIdentifier}")
|
104
|
+
end
|
105
|
+
if problems.size > 0
|
106
|
+
result << FileProblems.new(File.expand_path(fragment.location), problems)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
result
|
110
|
+
end
|
111
|
+
|
112
|
+
OpenElementChoice = Struct.new(:display_name, :file, :line)
|
113
|
+
def get_open_element_choices(pattern)
|
114
|
+
result = []
|
115
|
+
return result unless pattern
|
116
|
+
sub_index = element_name_index[pattern[0..0].downcase]
|
117
|
+
sub_index && sub_index.each_pair do |ident, elements|
|
118
|
+
if ident.split(/\W/).last.downcase.index(pattern.downcase) == 0
|
119
|
+
elements.each do |e|
|
120
|
+
if @lang.fragment_ref(e)
|
121
|
+
non_word_index = ident.rindex(/\W/)
|
122
|
+
if non_word_index
|
123
|
+
name = ident[non_word_index+1..-1]
|
124
|
+
scope = ident[0..non_word_index-1]
|
125
|
+
else
|
126
|
+
name = ident
|
127
|
+
scope = ""
|
128
|
+
end
|
129
|
+
display_name = "#{name} [#{e.class.ecore.name}]"
|
130
|
+
display_name += " - #{scope}" if scope.size > 0
|
131
|
+
path = File.expand_path(@lang.fragment_ref(e).fragment.location)
|
132
|
+
result << OpenElementChoice.new(display_name, path, @lang.line_number(e))
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
result.sort{|a,b| a.display_name <=> b.display_name}
|
138
|
+
end
|
139
|
+
|
140
|
+
def element_name_index
|
141
|
+
return @element_name_index if @element_name_index
|
142
|
+
@element_name_index = {}
|
143
|
+
@model.index.each_pair do |ident, elements|
|
144
|
+
key = ident.split(/\W/).last[0..0].downcase
|
145
|
+
@element_name_index[key] ||= {}
|
146
|
+
@element_name_index[key][ident] = elements
|
147
|
+
end
|
148
|
+
@element_name_index
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
@@ -0,0 +1,284 @@
|
|
1
|
+
require 'rgen/ecore/ecore_ext'
|
2
|
+
require 'rgen/instantiator/reference_resolver'
|
3
|
+
require 'rtext/parser'
|
4
|
+
|
5
|
+
module RText
|
6
|
+
|
7
|
+
class Instantiator
|
8
|
+
|
9
|
+
# A problem found during instantiation
|
10
|
+
# if the file is not known, it will be nil
|
11
|
+
InstantiatorProblem = Struct.new(:message, :file, :line)
|
12
|
+
|
13
|
+
# Creates an instantiator for RText::Language +language+
|
14
|
+
#
|
15
|
+
def initialize(language)
|
16
|
+
@lang = language
|
17
|
+
end
|
18
|
+
|
19
|
+
# instantiate +str+, +options+ include:
|
20
|
+
#
|
21
|
+
# :env
|
22
|
+
# environment to which model elements will be added
|
23
|
+
#
|
24
|
+
# :problems
|
25
|
+
# an array to which problems will be appended
|
26
|
+
#
|
27
|
+
# :unresolved_refs
|
28
|
+
# an array to which unresolved references will be appended
|
29
|
+
#
|
30
|
+
# :root_elements
|
31
|
+
# an array which will hold the root elements
|
32
|
+
#
|
33
|
+
# :file_name
|
34
|
+
# name of the file being instantiated, will be set on model elements
|
35
|
+
#
|
36
|
+
# :fragment_ref
|
37
|
+
# object which references the fragment being instantiated, will be set on model elements
|
38
|
+
#
|
39
|
+
def instantiate(str, options={})
|
40
|
+
@line_numbers = {}
|
41
|
+
@env = options[:env]
|
42
|
+
@problems = options[:problems] || []
|
43
|
+
@unresolved_refs = options[:unresolved_refs]
|
44
|
+
@root_elements = options[:root_elements] || []
|
45
|
+
@file_name = options[:file_name]
|
46
|
+
@fragment_ref = options[:fragment_ref]
|
47
|
+
parser = Parser.new(@lang.reference_regexp)
|
48
|
+
begin
|
49
|
+
@root_elements.clear
|
50
|
+
parser.parse(str) do |*args|
|
51
|
+
if args[0]
|
52
|
+
create_element(*args)
|
53
|
+
else
|
54
|
+
unassociated_comments(args[3])
|
55
|
+
end
|
56
|
+
end
|
57
|
+
rescue Parser::Error => e
|
58
|
+
problem(e.message, e.line)
|
59
|
+
@unresolved_refs.clear if @unresolved_refs
|
60
|
+
@root_elements.clear
|
61
|
+
end
|
62
|
+
if @unresolved_refs
|
63
|
+
@unresolved_refs.each do |ur|
|
64
|
+
ur.proxy.targetIdentifier = @lang.qualify_reference(ur.proxy.targetIdentifier, ur.element)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def unassociated_comments(comments)
|
72
|
+
comments.each do |c|
|
73
|
+
handle_comment(c, nil)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def create_element(command, arg_list, element_list, comments, is_root)
|
78
|
+
clazz = @lang.class_by_command(command.value)
|
79
|
+
if !clazz
|
80
|
+
problem("Unknown command '#{command.value}'", command.line)
|
81
|
+
return
|
82
|
+
end
|
83
|
+
if clazz.ecore.abstract
|
84
|
+
problem("Unknown command '#{command.value}' (metaclass is abstract)", command.line)
|
85
|
+
return
|
86
|
+
end
|
87
|
+
element = clazz.new
|
88
|
+
@env << element if @env
|
89
|
+
@root_elements << element if is_root
|
90
|
+
unlabled_args = @lang.unlabled_arguments(clazz.ecore).name
|
91
|
+
di_index = 0
|
92
|
+
defined_args = {}
|
93
|
+
arg_list.each do |a|
|
94
|
+
if is_labeled(a)
|
95
|
+
set_argument(element, a[0].value, a[1], defined_args, command.line)
|
96
|
+
else
|
97
|
+
if di_index < unlabled_args.size
|
98
|
+
set_argument(element, unlabled_args[di_index], a, defined_args, command.line)
|
99
|
+
di_index += 1
|
100
|
+
else
|
101
|
+
problem("Unexpected unlabled argument, #{unlabled_args.size} unlabled arguments expected", command.line)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
element_list.each do |e|
|
106
|
+
if is_labeled(e)
|
107
|
+
add_children(element, e[1], e[0].value, e[0].line)
|
108
|
+
else
|
109
|
+
add_children(element, e, nil, nil)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
set_line_number(element, command.line)
|
113
|
+
set_file_name(element)
|
114
|
+
set_fragment_ref(element)
|
115
|
+
comments.each do |c|
|
116
|
+
handle_comment(c, element)
|
117
|
+
end
|
118
|
+
element
|
119
|
+
end
|
120
|
+
|
121
|
+
def add_children(element, children, role, role_line)
|
122
|
+
if role
|
123
|
+
feature = @lang.feature_by_name(element.class.ecore, role)
|
124
|
+
if !feature
|
125
|
+
problem("Unknown child role '#{role}'", role_line)
|
126
|
+
return
|
127
|
+
end
|
128
|
+
if !feature.is_a?(RGen::ECore::EReference) || !feature.containment
|
129
|
+
problem("Role '#{role}' can not take child elements", role_line)
|
130
|
+
return
|
131
|
+
end
|
132
|
+
children = [children] unless children.is_a?(Array)
|
133
|
+
children.compact!
|
134
|
+
if children.size == 0
|
135
|
+
return
|
136
|
+
end
|
137
|
+
if !feature.many &&
|
138
|
+
(element.getGenericAsArray(role).size > 0 || children.size > 1)
|
139
|
+
problem("Only one child allowed in role '#{role}'", line_number(children[0]))
|
140
|
+
return
|
141
|
+
end
|
142
|
+
expected_type = @lang.concrete_types(feature.eType)
|
143
|
+
children.each do |c|
|
144
|
+
if !expected_type.include?(c.class.ecore)
|
145
|
+
problem("Role '#{role}' can not take a #{c.class.ecore.name}, expected #{expected_type.name.join(", ")}", line_number(c))
|
146
|
+
else
|
147
|
+
element.setOrAddGeneric(feature.name, c)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
else
|
151
|
+
raise "if there is no role, children must not be an Array" if children.is_a?(Array)
|
152
|
+
child = children
|
153
|
+
return if child.nil?
|
154
|
+
feature = @lang.containments_by_target_type(element.class.ecore, child.class.ecore)
|
155
|
+
if feature.size == 0
|
156
|
+
problem("This kind of element can not be contained here", line_number(child))
|
157
|
+
return
|
158
|
+
end
|
159
|
+
if feature.size > 1
|
160
|
+
problem("Role of element is ambiguous, use a role label", line_number(child))
|
161
|
+
return
|
162
|
+
end
|
163
|
+
feature = feature[0]
|
164
|
+
if element.getGenericAsArray(feature.name).size > 0 && !feature.many
|
165
|
+
problem("Only one child allowed in role '#{feature.name}'", line_number(child))
|
166
|
+
return
|
167
|
+
end
|
168
|
+
element.setOrAddGeneric(feature.name, child)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def set_argument(element, name, value, defined_args, line)
|
173
|
+
feature = @lang.feature_by_name(element.class.ecore, name)
|
174
|
+
if !feature
|
175
|
+
problem("Unknown argument '#{name}'", line)
|
176
|
+
return
|
177
|
+
end
|
178
|
+
if feature.is_a?(RGen::ECore::EReference) && feature.containment
|
179
|
+
problem("Argument '#{name}' can only take child elements", line)
|
180
|
+
return
|
181
|
+
end
|
182
|
+
if defined_args[name]
|
183
|
+
problem("Argument '#{name}' already defined", line)
|
184
|
+
return
|
185
|
+
end
|
186
|
+
value = [value] unless value.is_a?(Array)
|
187
|
+
if value.size > 1 && !feature.many
|
188
|
+
problem("Argument '#{name}' can take only one value", line)
|
189
|
+
return
|
190
|
+
end
|
191
|
+
expected_kind = expected_token_kind(feature)
|
192
|
+
value.each do |v|
|
193
|
+
if !expected_kind.include?(v.kind)
|
194
|
+
problem("Argument '#{name}' can not take a #{v.kind}, expected #{expected_kind.join(", ")}", line)
|
195
|
+
elsif feature.eType.is_a?(RGen::ECore::EEnum)
|
196
|
+
if !feature.eType.eLiterals.name.include?(v.value)
|
197
|
+
problem("Argument '#{name}' can not take value #{v.value}, expected #{feature.eType.eLiterals.name.join(", ")}", line)
|
198
|
+
else
|
199
|
+
element.setOrAddGeneric(feature.name, v.value.to_sym)
|
200
|
+
end
|
201
|
+
elsif feature.is_a?(RGen::ECore::EReference)
|
202
|
+
proxy = RGen::MetamodelBuilder::MMProxy.new(v.value)
|
203
|
+
if @unresolved_refs
|
204
|
+
@unresolved_refs <<
|
205
|
+
RGen::Instantiator::ReferenceResolver::UnresolvedReference.new(element, feature.name, proxy)
|
206
|
+
end
|
207
|
+
element.setOrAddGeneric(feature.name, proxy)
|
208
|
+
else
|
209
|
+
element.setOrAddGeneric(feature.name, v.value)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
defined_args[name] = true
|
213
|
+
end
|
214
|
+
|
215
|
+
def handle_comment(comment_desc, element)
|
216
|
+
if @lang.comment_handler
|
217
|
+
kind = comment_desc[1]
|
218
|
+
if kind == :eol
|
219
|
+
comment = comment_desc[0].value
|
220
|
+
else
|
221
|
+
comment = comment_desc[0].collect{|c| c.value}.join("\n")
|
222
|
+
end
|
223
|
+
success = @lang.comment_handler.call(comment, kind, element, @env)
|
224
|
+
if !success
|
225
|
+
if element.nil?
|
226
|
+
problem("Unassociated comment not allowed", comment_desc[0][0].line)
|
227
|
+
else
|
228
|
+
problem("This kind of element can not take this comment", line_number(element))
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def is_labeled(a)
|
235
|
+
a.is_a?(Array) && a[0].respond_to?(:kind) && a[0].kind == :label
|
236
|
+
end
|
237
|
+
|
238
|
+
def expected_token_kind(feature)
|
239
|
+
if feature.is_a?(RGen::ECore::EReference)
|
240
|
+
[:reference, :identifier]
|
241
|
+
elsif feature.eType.is_a?(RGen::ECore::EEnum)
|
242
|
+
[:identifier, :string]
|
243
|
+
else
|
244
|
+
{ String => [:string, :identifier],
|
245
|
+
Integer => [:integer],
|
246
|
+
Float => [:float],
|
247
|
+
RGen::MetamodelBuilder::DataTypes::Boolean => [:boolean]
|
248
|
+
}[feature.eType.instanceClass]
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def set_line_number(element, line)
|
253
|
+
@line_numbers[element] = line
|
254
|
+
if @lang.line_number_attribute && element.respond_to?("#{@lang.line_number_attribute}=")
|
255
|
+
element.send("#{@lang.line_number_attribute}=", line)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
def set_file_name(element)
|
260
|
+
if @file_name &&
|
261
|
+
@lang.file_name_attribute && element.respond_to?("#{@lang.file_name_attribute}=")
|
262
|
+
element.send("#{@lang.file_name_attribute}=", @file_name)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
def set_fragment_ref(element)
|
267
|
+
if @fragment_ref &&
|
268
|
+
@lang.fragment_ref_attribute && element.respond_to?("#{@lang.fragment_ref_attribute}=")
|
269
|
+
element.send("#{@lang.fragment_ref_attribute}=", @fragment_ref)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
def line_number(e)
|
274
|
+
@line_numbers[e]
|
275
|
+
end
|
276
|
+
|
277
|
+
def problem(msg, line)
|
278
|
+
@problems << InstantiatorProblem.new(msg, @file_name, line)
|
279
|
+
end
|
280
|
+
|
281
|
+
end
|
282
|
+
|
283
|
+
end
|
284
|
+
|