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