rtext 0.4.0 → 0.5.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.
Files changed (48) hide show
  1. data/CHANGELOG +20 -0
  2. data/{README → README.rdoc} +5 -1
  3. data/RText_Protocol +444 -0
  4. data/Rakefile +10 -10
  5. data/lib/rtext/completer.rb +32 -26
  6. data/lib/rtext/context_builder.rb +113 -59
  7. data/lib/rtext/default_loader.rb +73 -8
  8. data/lib/rtext/default_service_provider.rb +30 -14
  9. data/lib/rtext/frontend/config.rb +58 -0
  10. data/lib/rtext/frontend/connector.rb +233 -0
  11. data/lib/rtext/frontend/connector_manager.rb +81 -0
  12. data/lib/rtext/frontend/context.rb +56 -0
  13. data/lib/rtext/generic.rb +13 -0
  14. data/lib/rtext/instantiator.rb +30 -7
  15. data/lib/rtext/language.rb +54 -27
  16. data/lib/rtext/link_detector.rb +57 -0
  17. data/lib/rtext/message_helper.rb +77 -0
  18. data/lib/rtext/parser.rb +19 -68
  19. data/lib/rtext/serializer.rb +18 -3
  20. data/lib/rtext/service.rb +182 -118
  21. data/lib/rtext/tokenizer.rb +102 -0
  22. data/test/completer_test.rb +327 -70
  23. data/test/context_builder_test.rb +671 -91
  24. data/test/instantiator_test.rb +153 -0
  25. data/test/integration/backend.out +10 -0
  26. data/test/integration/crash_on_request_editor.rb +12 -0
  27. data/test/integration/ecore_editor.rb +50 -0
  28. data/test/integration/frontend.log +25138 -0
  29. data/test/integration/model/invalid_encoding.invenc +2 -0
  30. data/test/integration/model/test.crash_on_request +18 -0
  31. data/test/integration/model/test.crashing_backend +18 -0
  32. data/test/integration/model/test.dont_open_socket +0 -0
  33. data/test/integration/model/test.invalid_cmd_line +0 -0
  34. data/test/integration/model/test.not_in_rtext +0 -0
  35. data/test/integration/model/test_large_with_errors.ect3 +43523 -0
  36. data/test/integration/model/test_metamodel.ect +18 -0
  37. data/test/integration/model/test_metamodel2.ect +5 -0
  38. data/test/integration/model/test_metamodel_error.ect2 +3 -0
  39. data/test/integration/model/test_metamodel_ok.ect2 +18 -0
  40. data/test/integration/test.rb +684 -0
  41. data/test/link_detector_test.rb +276 -0
  42. data/test/message_helper_test.rb +118 -0
  43. data/test/rtext_test.rb +4 -1
  44. data/test/serializer_test.rb +96 -1
  45. data/test/tokenizer_test.rb +125 -0
  46. metadata +36 -10
  47. data/RText_Plugin_Implementation_Guide +0 -268
  48. 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
+
@@ -0,0 +1,13 @@
1
+ require 'rgen/metamodel_builder'
2
+
3
+ module RText
4
+
5
+ class Generic < RGen::MetamodelBuilder::MMGeneric
6
+ attr_reader :string
7
+ def initialize(string)
8
+ @string = string
9
+ end
10
+ end
11
+
12
+ end
13
+
@@ -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 !expected_kind.include?(v.kind)
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
@@ -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 and its containing element or nil and should return
48
- # the element's identifier as a string
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. in this case a globally unique
52
- # identifer must be resonstructed by the proc specified using the :reference_qualifier option.
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 an element identifier as returned by the identifier provider and
63
- # another element which uses this identifier to reference the element.
64
- # it must return the globally unique version of the identifier.
65
- # in case the received identifier is already globally unique, it must be returned as is.
66
- # the received element might only be similar to the original referencing element. the reason
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
- @qualified_name_provider.identifier(element)
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
@@ -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