rtext 0.4.0 → 0.5.0

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