rtext 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +20 -0
- data/{README → README.rdoc} +5 -1
- data/RText_Protocol +444 -0
- data/Rakefile +10 -10
- data/lib/rtext/completer.rb +32 -26
- data/lib/rtext/context_builder.rb +113 -59
- data/lib/rtext/default_loader.rb +73 -8
- data/lib/rtext/default_service_provider.rb +30 -14
- data/lib/rtext/frontend/config.rb +58 -0
- data/lib/rtext/frontend/connector.rb +233 -0
- data/lib/rtext/frontend/connector_manager.rb +81 -0
- data/lib/rtext/frontend/context.rb +56 -0
- data/lib/rtext/generic.rb +13 -0
- data/lib/rtext/instantiator.rb +30 -7
- data/lib/rtext/language.rb +54 -27
- data/lib/rtext/link_detector.rb +57 -0
- data/lib/rtext/message_helper.rb +77 -0
- data/lib/rtext/parser.rb +19 -68
- data/lib/rtext/serializer.rb +18 -3
- data/lib/rtext/service.rb +182 -118
- data/lib/rtext/tokenizer.rb +102 -0
- data/test/completer_test.rb +327 -70
- data/test/context_builder_test.rb +671 -91
- data/test/instantiator_test.rb +153 -0
- data/test/integration/backend.out +10 -0
- data/test/integration/crash_on_request_editor.rb +12 -0
- data/test/integration/ecore_editor.rb +50 -0
- data/test/integration/frontend.log +25138 -0
- data/test/integration/model/invalid_encoding.invenc +2 -0
- data/test/integration/model/test.crash_on_request +18 -0
- data/test/integration/model/test.crashing_backend +18 -0
- data/test/integration/model/test.dont_open_socket +0 -0
- data/test/integration/model/test.invalid_cmd_line +0 -0
- data/test/integration/model/test.not_in_rtext +0 -0
- data/test/integration/model/test_large_with_errors.ect3 +43523 -0
- data/test/integration/model/test_metamodel.ect +18 -0
- data/test/integration/model/test_metamodel2.ect +5 -0
- data/test/integration/model/test_metamodel_error.ect2 +3 -0
- data/test/integration/model/test_metamodel_ok.ect2 +18 -0
- data/test/integration/test.rb +684 -0
- data/test/link_detector_test.rb +276 -0
- data/test/message_helper_test.rb +118 -0
- data/test/rtext_test.rb +4 -1
- data/test/serializer_test.rb +96 -1
- data/test/tokenizer_test.rb +125 -0
- metadata +36 -10
- data/RText_Plugin_Implementation_Guide +0 -268
- data/lib/rtext_plugin/connection_manager.rb +0 -59
@@ -0,0 +1,56 @@
|
|
1
|
+
module RText
|
2
|
+
module Frontend
|
3
|
+
|
4
|
+
module Context
|
5
|
+
|
6
|
+
# lines; all lines from the beginning up to and including the current line
|
7
|
+
def self.extract(lines)
|
8
|
+
non_ignored_lines = 0
|
9
|
+
array_nesting = 0
|
10
|
+
block_nesting = 0
|
11
|
+
last_element_line = 0
|
12
|
+
result = []
|
13
|
+
lines.reverse.each_with_index do |l, i|
|
14
|
+
if i == 0
|
15
|
+
result.unshift(l)
|
16
|
+
else
|
17
|
+
l = l.strip
|
18
|
+
if l.size == 0 || l[0..0] == "#"
|
19
|
+
# ignore empty lines and comments
|
20
|
+
else
|
21
|
+
non_ignored_lines += 1
|
22
|
+
case l[-1..-1]
|
23
|
+
when "{"
|
24
|
+
if block_nesting > 0
|
25
|
+
block_nesting -= 1
|
26
|
+
elsif block_nesting == 0
|
27
|
+
result.unshift(l)
|
28
|
+
last_element_line = non_ignored_lines
|
29
|
+
end
|
30
|
+
when "}"
|
31
|
+
block_nesting += 1
|
32
|
+
when "["
|
33
|
+
if array_nesting > 0
|
34
|
+
array_nesting -= 1
|
35
|
+
elsif array_nesting == 0
|
36
|
+
result.unshift(l)
|
37
|
+
end
|
38
|
+
when "]"
|
39
|
+
array_nesting += 1
|
40
|
+
when ":"
|
41
|
+
# lable directly above element
|
42
|
+
if non_ignored_lines == last_element_line + 1
|
43
|
+
result.unshift(l)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
result
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
data/lib/rtext/instantiator.rb
CHANGED
@@ -36,6 +36,10 @@ class Instantiator
|
|
36
36
|
# :fragment_ref
|
37
37
|
# object which references the fragment being instantiated, will be set on model elements
|
38
38
|
#
|
39
|
+
# :on_progress
|
40
|
+
# a proc which is called twice for each model element:
|
41
|
+
# when the command token is recognized and when the element is instantiated
|
42
|
+
#
|
39
43
|
def instantiate(str, options={})
|
40
44
|
@line_numbers = {}
|
41
45
|
@env = options[:env]
|
@@ -44,6 +48,7 @@ class Instantiator
|
|
44
48
|
@root_elements = options[:root_elements] || []
|
45
49
|
@file_name = options[:file_name]
|
46
50
|
@fragment_ref = options[:fragment_ref]
|
51
|
+
@on_progress_proc = options[:on_progress]
|
47
52
|
@context_class_stack = []
|
48
53
|
parser = Parser.new(@lang.reference_regexp)
|
49
54
|
@root_elements.clear
|
@@ -64,15 +69,13 @@ class Instantiator
|
|
64
69
|
unassociated_comments(args[3])
|
65
70
|
end
|
66
71
|
end,
|
72
|
+
:on_command_token => @on_progress_proc && lambda do
|
73
|
+
@on_progress_proc.call
|
74
|
+
end,
|
67
75
|
:problems => parser_problems)
|
68
76
|
parser_problems.each do |p|
|
69
77
|
problem(p.message, p.line)
|
70
78
|
end
|
71
|
-
if @unresolved_refs
|
72
|
-
@unresolved_refs.each do |ur|
|
73
|
-
ur.proxy.targetIdentifier = @lang.qualify_reference(ur.proxy.targetIdentifier, ur.element)
|
74
|
-
end
|
75
|
-
end
|
76
79
|
end
|
77
80
|
|
78
81
|
private
|
@@ -83,8 +86,9 @@ class Instantiator
|
|
83
86
|
end
|
84
87
|
end
|
85
88
|
|
86
|
-
def create_element(command, arg_list, element_list, comments, is_root)
|
89
|
+
def create_element(command, arg_list, element_list, comments, annotation, is_root)
|
87
90
|
clazz = @context_class_stack.last
|
91
|
+
@on_progress_proc.call if @on_progress_proc
|
88
92
|
if !@lang.has_command(command.value)
|
89
93
|
problem("Unknown command '#{command.value}'", command.line)
|
90
94
|
return
|
@@ -132,6 +136,7 @@ class Instantiator
|
|
132
136
|
comments.each do |c|
|
133
137
|
handle_comment(c, element)
|
134
138
|
end
|
139
|
+
handle_annotation(annotation, element) if annotation
|
135
140
|
element
|
136
141
|
end
|
137
142
|
|
@@ -209,7 +214,13 @@ class Instantiator
|
|
209
214
|
end
|
210
215
|
expected_kind = expected_token_kind(feature)
|
211
216
|
value.each do |v|
|
212
|
-
if
|
217
|
+
if v.kind == :generic
|
218
|
+
if @lang.generics_enabled
|
219
|
+
element.setOrAddGeneric(feature.name, v.value)
|
220
|
+
else
|
221
|
+
problem("Generic value not allowed", line)
|
222
|
+
end
|
223
|
+
elsif !expected_kind.include?(v.kind)
|
213
224
|
problem("Argument '#{name}' can not take a #{v.kind}, expected #{expected_kind.join(", ")}", line)
|
214
225
|
elsif feature.eType.is_a?(RGen::ECore::EEnum)
|
215
226
|
if !feature.eType.eLiterals.name.include?(v.value)
|
@@ -250,6 +261,18 @@ class Instantiator
|
|
250
261
|
end
|
251
262
|
end
|
252
263
|
|
264
|
+
def handle_annotation(annotation_desc, element)
|
265
|
+
if @lang.annotation_handler
|
266
|
+
annotation = annotation_desc.collect{|c| c.value}.join("\n")
|
267
|
+
success = @lang.annotation_handler.call(annotation, element, @env)
|
268
|
+
if !success
|
269
|
+
problem("Annotation not allowed", line_number(element))
|
270
|
+
end
|
271
|
+
else
|
272
|
+
problem("Annotation not allowed", line_number(element))
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
253
276
|
def is_labeled(a)
|
254
277
|
a.is_a?(Array) && a[0].respond_to?(:kind) && a[0].kind == :label
|
255
278
|
end
|
data/lib/rtext/language.rb
CHANGED
@@ -44,12 +44,14 @@ class Language
|
|
44
44
|
# default: word characters separated by at least one slash (/)
|
45
45
|
#
|
46
46
|
# :identifier_provider
|
47
|
-
# a Proc which receives an element
|
48
|
-
# the
|
47
|
+
# a Proc which receives an element, its containing element, the feature through which the
|
48
|
+
# element is referenced and the index position of the reference within the feature's values.
|
49
|
+
# the latter 3 argumnets may be nil. it should return the element's identifier as a string.
|
49
50
|
# the identifier must be unique for the element unless "per_type_identifier" is set to true,
|
50
|
-
# in which case they must be unique for each element of the same type
|
51
|
-
# identifiers may be relative to the given containing element
|
52
|
-
#
|
51
|
+
# in which case they must be unique for each element of the same type.
|
52
|
+
# identifiers may be relative to the given containing element, depending on the given
|
53
|
+
# feature and index position. in this case a globally unique
|
54
|
+
# identifier must be resonstructed by the proc specified using the :reference_qualifier option.
|
53
55
|
# if the containing element is nil, the identifier returned must be globally unique.
|
54
56
|
# default: identifiers calculated by QualifiedNameProvider
|
55
57
|
# in this case options to QualifiedNameProvider may be provided and will be passed through
|
@@ -59,16 +61,11 @@ class Language
|
|
59
61
|
# default: false
|
60
62
|
#
|
61
63
|
# :reference_qualifier
|
62
|
-
# a Proc which receives
|
63
|
-
#
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
67
|
-
# is that this element may need to be constructed using only partially parsable data.
|
68
|
-
# it is garantueed though that the element's chain of containing elements is complete and
|
69
|
-
# that (non-containment) references are resolved as far as possible.
|
70
|
-
# default: no reference qualifier, i.e. all identifiers returned by the identifier provider
|
71
|
-
# must be globally unique
|
64
|
+
# a Proc which receives RGen unresolved references and either a FragmentedModel or a ModelFragment.
|
65
|
+
# it should modify the unresolved references' targetIdentifiers to make them globally unique.
|
66
|
+
# the Proc is called for each fragment after it as been loaded and for the overall model.
|
67
|
+
# this can be used to qualify context specific references returned by the identifier provider.
|
68
|
+
# default: no reference qualifier
|
72
69
|
#
|
73
70
|
# :root_classes
|
74
71
|
# an Array of EClass objects representing classes which can be used on root level
|
@@ -98,6 +95,18 @@ class Language
|
|
98
95
|
# the Proc may also modify the element to remove information already part of the comment
|
99
96
|
# default: no comments
|
100
97
|
#
|
98
|
+
# :annotation_handler
|
99
|
+
# a Proc which will be invoked when a new element has been instantiated. receives
|
100
|
+
# the annotation as a string, the element and the environment to which the element has been added to.
|
101
|
+
# the environment may be nil. it may change the model or otherwise use the annotated information.
|
102
|
+
# if the element can take no annotation, it should return false, otherwise true.
|
103
|
+
# default: no handling of annotations
|
104
|
+
#
|
105
|
+
# :annotation_provider
|
106
|
+
# a Proc which receives an element and should return this element's annotation as a string or nil.
|
107
|
+
# the Proc may also modify the element to remove information already part of the annotation.
|
108
|
+
# default: no annotations
|
109
|
+
#
|
101
110
|
# :indent_string
|
102
111
|
# the string representing one indent, could be a tab or spaces
|
103
112
|
# default: 2 spaces
|
@@ -106,10 +115,22 @@ class Language
|
|
106
115
|
# a Proc which receives an EClass object and should return an RText command name
|
107
116
|
# default: class name
|
108
117
|
#
|
118
|
+
# :backward_ref_attribute
|
119
|
+
# a Proc which receives an EClass object and should return the name of this class's
|
120
|
+
# feature which is used to represent backward references (i.e. for following the backward
|
121
|
+
# reference, the user must click on a value of this attribute)
|
122
|
+
# a value of nil means that the command name is used to follow the backward reference
|
123
|
+
# default: nil (command name)
|
124
|
+
#
|
125
|
+
# :enable_generics
|
126
|
+
# if set to true, generics (<value>) are allowed, otherwise forbidden
|
127
|
+
# default: false
|
128
|
+
#
|
109
129
|
def initialize(root_epackage, options={})
|
110
130
|
@root_epackage = root_epackage
|
111
131
|
@feature_provider = options[:feature_provider] ||
|
112
|
-
proc { |c| RGen::Serializer::OppositeReferenceFilter.call(c.eAllStructuralFeatures)
|
132
|
+
proc { |c| RGen::Serializer::OppositeReferenceFilter.call(c.eAllStructuralFeatures).
|
133
|
+
reject{|f| f.derived} }
|
113
134
|
@unlabled_arguments = options[:unlabled_arguments]
|
114
135
|
@unquoted_arguments = options[:unquoted_arguments]
|
115
136
|
@argument_format_provider = options[:argument_format_provider]
|
@@ -118,31 +139,45 @@ class Language
|
|
118
139
|
setup_commands(root_epackage, command_name_provider)
|
119
140
|
@reference_regexp = options[:reference_regexp] || /\A\w*(\/\w*)+/
|
120
141
|
@identifier_provider = options[:identifier_provider] ||
|
121
|
-
proc { |element, context|
|
142
|
+
proc { |element, context, feature, index|
|
122
143
|
@qualified_name_provider ||= RGen::Serializer::QualifiedNameProvider.new(options)
|
123
|
-
|
144
|
+
name_attribute = options[:attribute_name] || "name"
|
145
|
+
if element.is_a?(RGen::MetamodelBuilder::MMProxy) || element.respond_to?(name_attribute)
|
146
|
+
@qualified_name_provider.identifier(element)
|
147
|
+
else
|
148
|
+
nil
|
149
|
+
end
|
124
150
|
}
|
125
|
-
@reference_qualifier = options[:reference_qualifier]
|
151
|
+
@reference_qualifier = options[:reference_qualifier] || proc{|urefs, model_or_fragment| }
|
126
152
|
@line_number_attribute = options[:line_number_attribute]
|
127
153
|
@file_name_attribute = options[:file_name_attribute]
|
128
154
|
@fragment_ref_attribute = options[:fragment_ref_attribute]
|
129
155
|
@comment_handler = options[:comment_handler]
|
130
156
|
@comment_provider = options[:comment_provider]
|
157
|
+
@annotation_handler = options[:annotation_handler]
|
158
|
+
@annotation_provider = options[:annotation_provider]
|
131
159
|
@indent_string = options[:indent_string] || " "
|
132
160
|
@per_type_identifier = options[:per_type_identifier]
|
161
|
+
@backward_ref_attribute = options[:backward_ref_attribute] || proc{|c| nil}
|
162
|
+
@generics_enabled = options[:enable_generics]
|
133
163
|
end
|
134
164
|
|
135
165
|
attr_reader :root_epackage
|
136
166
|
attr_reader :root_classes
|
137
167
|
attr_reader :reference_regexp
|
138
168
|
attr_reader :identifier_provider
|
169
|
+
attr_reader :reference_qualifier
|
139
170
|
attr_reader :line_number_attribute
|
140
171
|
attr_reader :file_name_attribute
|
141
172
|
attr_reader :fragment_ref_attribute
|
142
173
|
attr_reader :comment_handler
|
143
174
|
attr_reader :comment_provider
|
175
|
+
attr_reader :annotation_handler
|
176
|
+
attr_reader :annotation_provider
|
144
177
|
attr_reader :indent_string
|
145
178
|
attr_reader :per_type_identifier
|
179
|
+
attr_reader :backward_ref_attribute
|
180
|
+
attr_reader :generics_enabled
|
146
181
|
|
147
182
|
def class_by_command(command, context_class)
|
148
183
|
map = @class_by_command[context_class]
|
@@ -212,14 +247,6 @@ class Language
|
|
212
247
|
@fragment_ref_attribute && element.respond_to?(@fragment_ref_attribute) && element.send(@fragment_ref_attribute)
|
213
248
|
end
|
214
249
|
|
215
|
-
def qualify_reference(identifier, element)
|
216
|
-
if @reference_qualifier
|
217
|
-
@reference_qualifier.call(identifier, element)
|
218
|
-
else
|
219
|
-
identifier
|
220
|
-
end
|
221
|
-
end
|
222
|
-
|
223
250
|
private
|
224
251
|
|
225
252
|
def setup_commands(root_epackage, command_name_provider)
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'rtext/tokenizer'
|
2
|
+
require 'rtext/context_builder'
|
3
|
+
|
4
|
+
module RText
|
5
|
+
|
6
|
+
class LinkDetector
|
7
|
+
include RText::Tokenizer
|
8
|
+
|
9
|
+
def initialize(lang)
|
10
|
+
@lang = lang
|
11
|
+
end
|
12
|
+
|
13
|
+
LinkDesc = Struct.new(:element, :feature, :index, :backward, :value, :scol, :ecol)
|
14
|
+
|
15
|
+
# column numbers start at 1
|
16
|
+
def detect(lines, column)
|
17
|
+
# make sure there is a space at the end of the line
|
18
|
+
# otherwise an important attribute value (e.g. the local name) might be missing in the
|
19
|
+
# context model since the context builder removes it as "just being completed"
|
20
|
+
lines.last.concat(" ")
|
21
|
+
current_line = lines.last
|
22
|
+
context = ContextBuilder.build_context(@lang, lines, lines.last.size)
|
23
|
+
tokens = tokenize(lines.last, @lang.reference_regexp)
|
24
|
+
token = tokens.find{|t| t.scol && t.scol <= column && t.ecol && t.ecol >= column}
|
25
|
+
if context && context.element && token &&
|
26
|
+
[:reference, :integer, :string, :identifier].include?(token.kind)
|
27
|
+
if column > 1
|
28
|
+
line_prefix = lines.last[0..column-2]
|
29
|
+
else
|
30
|
+
line_prefix = ""
|
31
|
+
end
|
32
|
+
context2 = ContextBuilder.build_context(@lang, lines, line_prefix.size)
|
33
|
+
# for command prefixes nested below a lable, the context element and feature are
|
34
|
+
# the parent element and feature, this is not what's expected here
|
35
|
+
if line_prefix =~ /^\s*\w*$/
|
36
|
+
context2.element = nil
|
37
|
+
context2.feature = nil
|
38
|
+
end
|
39
|
+
bwref_attr = @lang.backward_ref_attribute.call(context.element.class.ecore)
|
40
|
+
if bwref_attr
|
41
|
+
is_backward = (context2.feature && context2.feature.name == bwref_attr)
|
42
|
+
else
|
43
|
+
is_backward = (token.kind == :identifier && line_prefix =~ /^\s*\w*$/)
|
44
|
+
end
|
45
|
+
is_backward = is_backward ? true : false
|
46
|
+
if context2.element && context2.feature
|
47
|
+
index = context2.element.getGenericAsArray(context2.feature.name).size
|
48
|
+
end
|
49
|
+
LinkDesc.new(context.element, context2.feature, index, is_backward, token.value, token.scol, token.ecol)
|
50
|
+
else
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module RText
|
4
|
+
|
5
|
+
module MessageHelper
|
6
|
+
|
7
|
+
def serialize_message(obj)
|
8
|
+
each_json_object_string(obj) do |s|
|
9
|
+
s.force_encoding("binary")
|
10
|
+
bytes = s.bytes.to_a
|
11
|
+
s.clear
|
12
|
+
bytes.each do |b|
|
13
|
+
if b >= 128 || b == 0x25 # %
|
14
|
+
s << "%#{b.to_s(16)}".force_encoding("binary")
|
15
|
+
else
|
16
|
+
s << b.chr("binary")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
# there are no non ascii-7-bit characters left
|
20
|
+
s.force_encoding("utf-8")
|
21
|
+
end
|
22
|
+
json = JSON(obj)
|
23
|
+
# the JSON method outputs data in UTF-8 encoding
|
24
|
+
# the RText protocol expects message lengths measured in bytes
|
25
|
+
# there shouldn't be any non-ascii-7-bit characters, though, so json.size would also be ok
|
26
|
+
"#{json.bytesize}#{json}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def extract_message(data)
|
30
|
+
# interpret input data as binary
|
31
|
+
data.force_encoding("binary")
|
32
|
+
obj = nil
|
33
|
+
if data =~ /^(\d+)\{/
|
34
|
+
length_length = $1.size
|
35
|
+
length = $1.to_i
|
36
|
+
if data.size >= length_length + length
|
37
|
+
data.slice!(0..(length_length-1))
|
38
|
+
json = data.slice!(0..length-1)
|
39
|
+
# there shouldn't be any non ascii-7-bit characters, transcode just to be sure
|
40
|
+
# encode from binary to utf-8 with :undef => :replace turns all non-ascii-7-bit bytes
|
41
|
+
# into the replacement character (\uFFFD)
|
42
|
+
json.encode!("utf-8", :undef => :replace)
|
43
|
+
obj = JSON(json)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
if obj
|
47
|
+
each_json_object_string(obj) do |s|
|
48
|
+
# change encoding back to binary
|
49
|
+
# there could still be replacement characters (\uFFFD), turn them into "?"
|
50
|
+
s.encode!("binary", :undef => :replace)
|
51
|
+
s.gsub!(/%[0-9a-fA-F][0-9a-fA-F]/){|m| m[1..2].to_i(16).chr("binary")}
|
52
|
+
s.force_encoding("binary")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
obj
|
56
|
+
end
|
57
|
+
|
58
|
+
def each_json_object_string(object, &block)
|
59
|
+
if object.is_a?(Hash)
|
60
|
+
object.each_pair do |k, v|
|
61
|
+
# don't call block for hash keys
|
62
|
+
each_json_object_string(v, &block)
|
63
|
+
end
|
64
|
+
elsif object.is_a?(Array)
|
65
|
+
object.each do |v|
|
66
|
+
each_json_object_string(v, &block)
|
67
|
+
end
|
68
|
+
elsif object.is_a?(String)
|
69
|
+
yield(object)
|
70
|
+
else
|
71
|
+
# ignore
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
data/lib/rtext/parser.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
|
+
require 'rtext/generic'
|
2
|
+
require 'rtext/tokenizer'
|
3
|
+
|
1
4
|
module RText
|
2
5
|
|
3
6
|
class Parser
|
7
|
+
include RText::Tokenizer
|
4
8
|
|
5
9
|
Problem = Struct.new(:message, :line)
|
6
10
|
|
@@ -14,7 +18,7 @@ class Parser
|
|
14
18
|
@problems = options[:problems] || []
|
15
19
|
@non_consume_count = 0
|
16
20
|
@consume_problem_reported = false
|
17
|
-
@tokens = tokenize(str, @reference_regexp)
|
21
|
+
@tokens = tokenize(str, @reference_regexp, :on_command_token => options[:on_command_token])
|
18
22
|
@last_line = @tokens.last && @tokens.last.line
|
19
23
|
#@debug = true
|
20
24
|
begin
|
@@ -29,6 +33,7 @@ class Parser
|
|
29
33
|
def parse_statement(is_root=false, allow_unassociated_comment=false)
|
30
34
|
comments = []
|
31
35
|
comment = parse_comment
|
36
|
+
annotation = parse_annotation
|
32
37
|
if (next_token && next_token == :identifier) || !allow_unassociated_comment
|
33
38
|
comments << [ comment, :above] if comment
|
34
39
|
command = consume(:identifier)
|
@@ -43,7 +48,7 @@ class Parser
|
|
43
48
|
eol_comment = parse_eol_comment
|
44
49
|
comments << [ eol_comment, :eol ] if eol_comment
|
45
50
|
consume(:newline)
|
46
|
-
@asc_visitor.call(command, arg_list, element_list, comments, is_root)
|
51
|
+
@asc_visitor.call(command, arg_list, element_list, comments, annotation, is_root)
|
47
52
|
else
|
48
53
|
discard_until(:newline)
|
49
54
|
nil
|
@@ -51,7 +56,7 @@ class Parser
|
|
51
56
|
elsif comment
|
52
57
|
# if there is no statement, the comment is non-optional
|
53
58
|
comments << [ comment, :unassociated ]
|
54
|
-
@asc_visitor.call(nil, nil, nil, comments, nil)
|
59
|
+
@asc_visitor.call(nil, nil, nil, comments, nil, nil)
|
55
60
|
nil
|
56
61
|
else
|
57
62
|
# die expecting an identifier (next token is not an identifier)
|
@@ -71,6 +76,16 @@ class Parser
|
|
71
76
|
result
|
72
77
|
end
|
73
78
|
|
79
|
+
def parse_annotation
|
80
|
+
result = nil
|
81
|
+
while next_token == :annotation
|
82
|
+
result ||= []
|
83
|
+
result << consume(:annotation)
|
84
|
+
consume(:newline)
|
85
|
+
end
|
86
|
+
result
|
87
|
+
end
|
88
|
+
|
74
89
|
def parse_eol_comment
|
75
90
|
if next_token == :comment
|
76
91
|
consume(:comment)
|
@@ -169,7 +184,7 @@ class Parser
|
|
169
184
|
result
|
170
185
|
end
|
171
186
|
|
172
|
-
AnyValue = [:identifier, :integer, :float, :string, :boolean, :reference]
|
187
|
+
AnyValue = [:identifier, :integer, :float, :string, :boolean, :reference, :generic]
|
173
188
|
|
174
189
|
def parse_value
|
175
190
|
consume(*AnyValue)
|
@@ -238,70 +253,6 @@ class Parser
|
|
238
253
|
end
|
239
254
|
end
|
240
255
|
|
241
|
-
Token = Struct.new(:kind, :value, :line)
|
242
|
-
|
243
|
-
def tokenize(str, reference_regexp)
|
244
|
-
result = []
|
245
|
-
str.split(/\r?\n/).each_with_index do |str, idx|
|
246
|
-
idx += 1
|
247
|
-
if str =~ /^\s*#(.*)/
|
248
|
-
result << Token.new(:comment, $1, idx)
|
249
|
-
else
|
250
|
-
until str.empty?
|
251
|
-
case str
|
252
|
-
when reference_regexp
|
253
|
-
str = $'
|
254
|
-
result << Token.new(:reference, $&, idx)
|
255
|
-
when /\A[-+]?\d+\.\d+(?:e[+-]\d+)?\b/
|
256
|
-
str = $'
|
257
|
-
result << Token.new(:float, $&.to_f, idx)
|
258
|
-
when /\A0[xX][0-9a-fA-F]+\b/
|
259
|
-
str = $'
|
260
|
-
result << Token.new(:integer, $&.to_i(16), idx)
|
261
|
-
when /\A[-+]?\d+\b/
|
262
|
-
str = $'
|
263
|
-
result << Token.new(:integer, $&.to_i, idx)
|
264
|
-
when /\A"((?:[^"\\]|\\.)*)"/
|
265
|
-
str = $'
|
266
|
-
result << Token.new(:string, $1.
|
267
|
-
gsub('\\\\','\\').
|
268
|
-
gsub('\\"','"').
|
269
|
-
gsub('\\n',"\n").
|
270
|
-
gsub('\\r',"\r").
|
271
|
-
gsub('\\t',"\t").
|
272
|
-
gsub('\\f',"\f").
|
273
|
-
gsub('\\b',"\b"), idx)
|
274
|
-
when /\A(?:true|false)\b/
|
275
|
-
str = $'
|
276
|
-
result << Token.new(:boolean, $& == "true", idx)
|
277
|
-
when /\A([a-zA-Z_]\w*)\b(?:\s*:)?/
|
278
|
-
str = $'
|
279
|
-
if $&[-1] == ?:
|
280
|
-
result << Token.new(:label, $1, idx)
|
281
|
-
else
|
282
|
-
result << Token.new(:identifier, $&, idx)
|
283
|
-
end
|
284
|
-
when /\A[\{\}\[\]:,]/
|
285
|
-
str = $'
|
286
|
-
result << Token.new($&, nil, idx)
|
287
|
-
when /\A#(.*)/
|
288
|
-
str = ""
|
289
|
-
result << Token.new(:comment, $1, idx)
|
290
|
-
when /\A\s+/
|
291
|
-
str = $'
|
292
|
-
# ignore
|
293
|
-
when /\A\S+/
|
294
|
-
str = $'
|
295
|
-
result << Token.new(:error, $&, idx)
|
296
|
-
end
|
297
|
-
end
|
298
|
-
end
|
299
|
-
result << Token.new(:newline, nil, idx) \
|
300
|
-
unless result.empty? || result.last.kind == :newline
|
301
|
-
end
|
302
|
-
result
|
303
|
-
end
|
304
|
-
|
305
256
|
end
|
306
257
|
|
307
258
|
end
|