sdl4r 0.9.9 → 0.9.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. data/CHANGELOG +70 -1
  2. data/LICENSE +499 -497
  3. data/Rakefile +38 -28
  4. data/TODO +194 -45
  5. data/doc/classes/SDL4R/AbbreviationTimezoneProxy.html +151 -0
  6. data/doc/classes/SDL4R/ConstantTimezone.html +129 -0
  7. data/doc/classes/SDL4R/Element.html +148 -0
  8. data/doc/classes/SDL4R/Reader.html +683 -0
  9. data/doc/classes/SDL4R/RelativeTimezone.html +187 -0
  10. data/doc/classes/SDL4R/SdlBinary.html +188 -0
  11. data/doc/classes/SDL4R/SdlParseError.html +110 -0
  12. data/doc/classes/SDL4R/SdlTimeSpan.html +651 -0
  13. data/doc/classes/SDL4R/Serializer.html +557 -0
  14. data/doc/classes/SDL4R/Serializer/Ref.html +138 -0
  15. data/doc/classes/SDL4R/TZAbbreviationDB/Record.html +117 -0
  16. data/doc/classes/SDL4R/Tag.html +1274 -0
  17. data/doc/classes/SDL4R/Token.html +131 -0
  18. data/doc/created.rid +1 -0
  19. data/doc/files/CHANGELOG.html +290 -0
  20. data/doc/files/LICENSE.html +53 -0
  21. data/doc/files/README.html +405 -0
  22. data/doc/files/lib/sdl4r/abbreviation_timezone_proxy_rb.html +63 -0
  23. data/doc/files/lib/sdl4r/constant_timezone_rb.html +54 -0
  24. data/doc/files/lib/sdl4r/element_rb.html +54 -0
  25. data/doc/files/lib/sdl4r/reader_rb.html +68 -0
  26. data/doc/files/lib/sdl4r/relative_timezone_rb.html +62 -0
  27. data/doc/files/lib/sdl4r/sdl4r_rb.html +66 -0
  28. data/doc/files/lib/sdl4r/sdl4r_tzinfo_rb.html +64 -0
  29. data/doc/files/lib/sdl4r/sdl4r_version_rb.html +54 -0
  30. data/doc/files/lib/sdl4r/sdl_binary_rb.html +54 -0
  31. data/doc/files/lib/sdl4r/sdl_parse_error_rb.html +54 -0
  32. data/doc/files/lib/sdl4r/sdl_time_span_rb.html +54 -0
  33. data/doc/files/lib/sdl4r/serializer_rb.html +62 -0
  34. data/doc/files/lib/sdl4r/tag_rb.html +66 -0
  35. data/doc/files/lib/sdl4r/token_rb.html +54 -0
  36. data/doc/files/lib/sdl4r/tokenizer_rb.html +64 -0
  37. data/doc/files/lib/sdl4r/tz_abbreviation_db_rb.html +67 -0
  38. data/doc/files/lib/sdl4r_rb.html +54 -0
  39. data/doc/files/lib/sdl4r_tzinfo_rb.html +54 -0
  40. data/doc/fr_class_index.html +23 -0
  41. data/doc/fr_file_index.html +40 -0
  42. data/doc/fr_method_index.html +4711 -0
  43. data/doc/index.html +15 -0
  44. data/doc/rdoc-style.css +328 -0
  45. data/lib/sdl4r.rb +3 -1
  46. data/lib/sdl4r/abbreviation_timezone_proxy.rb +38 -0
  47. data/lib/sdl4r/constant_timezone.rb +58 -0
  48. data/{test/sdl4r/sdl_test.rb → lib/sdl4r/element.rb} +19 -14
  49. data/lib/sdl4r/reader.rb +772 -0
  50. data/lib/sdl4r/relative_timezone.rb +156 -0
  51. data/lib/sdl4r/sdl4r.rb +187 -45
  52. data/lib/sdl4r/sdl4r_tzinfo.rb +75 -0
  53. data/lib/sdl4r/sdl4r_version.rb +24 -0
  54. data/lib/sdl4r/sdl_parse_error.rb +1 -1
  55. data/lib/sdl4r/sdl_time_span.rb +5 -1
  56. data/lib/sdl4r/serializer.rb +473 -60
  57. data/lib/sdl4r/tag.rb +126 -51
  58. data/lib/sdl4r/token.rb +49 -0
  59. data/lib/sdl4r/tokenizer.rb +431 -0
  60. data/lib/sdl4r/tz_abbreviation_db.rb +266 -0
  61. data/read_jprof.html +16934 -0
  62. data/read_jprof_pull.html +14451 -0
  63. data/read_prof.html +4983 -0
  64. data/read_prof_pull.html +4896 -0
  65. data/test/sdl4r/parser_test.rb +577 -503
  66. data/test/sdl4r/read_jprof.rb +58 -0
  67. data/test/sdl4r/read_prof.rb +70 -0
  68. data/test/sdl4r/reader_test.rb +173 -0
  69. data/test/sdl4r/relative_timezone_test.rb +102 -0
  70. data/test/sdl4r/sdl4r_test.rb +611 -528
  71. data/test/sdl4r/sdl4r_tzinfo_test.rb +108 -0
  72. data/test/sdl4r/sdl_test_case.rb +60 -0
  73. data/test/sdl4r/serializer_test.rb +448 -11
  74. data/test/sdl4r/tag_test.rb +84 -5
  75. data/test/sdl4r/tokenizer_test.rb +128 -0
  76. metadata +69 -11
  77. data/lib/sdl4r/parser.rb +0 -648
  78. data/lib/sdl4r/parser/reader.rb +0 -184
  79. data/lib/sdl4r/parser/time_span_with_zone.rb +0 -57
  80. data/lib/sdl4r/parser/token.rb +0 -138
  81. data/lib/sdl4r/parser/tokenizer.rb +0 -507
@@ -19,24 +19,29 @@
19
19
  #++
20
20
 
21
21
  module SDL4R
22
+ # Used internally by Reader for keeping track of its state.
23
+ # It shouldn't be used directly as it is subject to changes as Reader is modified.
24
+ class Element
22
25
 
23
- require 'test/unit'
24
- require "rexml/document"
26
+ attr_accessor :self_closing
27
+ attr_reader :name, :prefix, :attributes, :values
25
28
 
26
- require 'sdl4r/tag'
27
-
28
- class SDLTest < Test::Unit::TestCase
29
+ def initialize(prefix, name)
30
+ @prefix = prefix
31
+ @name = name
32
+ @attributes = []
33
+ @values = []
34
+ @self_closing = false
35
+ end
29
36
 
30
- def test_coerce_or_fail
31
- # Most basic types are considered to be tested in other tests (like ParserTest)
32
- tag = Tag.new "tag1"
33
- assert_raise ArgumentError do tag.add_value(Object.new) end
34
- assert_raise ArgumentError do tag.add_value([1, 2, 3]) end
35
- assert_raise ArgumentError do tag.add_value({"a" => "b"}) end
37
+ def add_attribute(prefix, name, value)
38
+ @attributes << [[prefix, name], value]
39
+ end
36
40
 
37
- # check translation of Rational
38
- tag.add_value(Rational(3, 10))
39
- assert_equal [0.3], tag.values
41
+ def add_value(value)
42
+ @values << value
40
43
  end
44
+
45
+ self.freeze
41
46
  end
42
47
  end
@@ -0,0 +1,772 @@
1
+ #!/usr/bin/env ruby -w
2
+ # encoding: UTF-8
3
+
4
+ #--
5
+ # Simple Declarative Language (SDL) for Ruby
6
+ # Copyright 2005 Ikayzo, inc.
7
+ #
8
+ # This program is free software. You can distribute or modify it under the
9
+ # terms of the GNU Lesser General Public License version 2.1 as published by
10
+ # the Free Software Foundation.
11
+ #
12
+ # This program is distributed AS IS and WITHOUT WARRANTY. OF ANY KIND,
13
+ # INCLUDING MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
14
+ # See the GNU Lesser General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Lesser General Public License
17
+ # along with this program; if not, contact the Free Software Foundation, Inc.,
18
+ # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
+ #++
20
+
21
+ module SDL4R
22
+
23
+ require 'stringio'
24
+ require 'date'
25
+
26
+ require 'sdl4r/sdl4r'
27
+ require 'sdl4r/sdl_time_span'
28
+ require 'sdl4r/sdl_binary'
29
+ require 'sdl4r/tokenizer'
30
+ require 'sdl4r/element'
31
+
32
+ # Implementation of a pull parser for SDL designed after the model of Nokogiri::XML::Reader.
33
+ #
34
+ class Reader
35
+
36
+ TYPE_ELEMENT = :ELEMENT
37
+ TYPE_END_ELEMENT = :END_ELEMENT
38
+
39
+ # @private
40
+ def self.add_values_handler(map, handler)
41
+ map[:NULL] = handler
42
+ map[:INTEGER] = handler
43
+ map[:FLOAT] = handler
44
+ map[:BOOLEAN] = handler
45
+ map[:CHARACTER] = handler
46
+ map[:INLINE_BACKQUOTE_STRING] = handler
47
+ map[:INLINE_DOUBLE_QUOTE_STRING] = handler
48
+ map[:MULTILINE_BACKQUOTE_STRING_START] = handler
49
+ map[:MULTILINE_DOUBLE_QUOTE_STRING_START] = handler
50
+ map[:INLINE_BINARY] = handler
51
+ map[:MULTILINE_BINARY_START] = handler
52
+ map[:DATE] = handler
53
+ map[:TIME_OR_TIMESPAN] = handler
54
+ end
55
+
56
+ # @private
57
+ @@SKIP_PROC = lambda { |reader| false } # skips current token
58
+ # @private
59
+ @@ON_SELF_CLOSING_TAG_PROG = lambda { |reader| reader.on_self_closing_tag }
60
+
61
+ # @private
62
+ @@comment_handler_set = {
63
+ :INLINE_COMMENT => lambda { |reader| reader.on_simple_comment },
64
+ :ONE_LINE_COMMENT => lambda { |reader| reader.on_simple_comment },
65
+ :MULTILINE_COMMENT_START => lambda { |reader| reader.on_multiline_comment },
66
+ }
67
+
68
+ # Handlers that work the same at the top level or in any normal tag body.
69
+ # @private
70
+ @@common_tag_set = {
71
+ :WHITESPACE => @@SKIP_PROC,
72
+ :EOL => @@SKIP_PROC,
73
+ :SEMICOLON => @@SKIP_PROC,
74
+ :IDENTIFIER => lambda { |reader| reader.on_tag_start },
75
+ }
76
+
77
+ # The handlers are object with #call() (like Proc, etc) that should return false if the
78
+ # corresponding token is ignored, true otherwise.
79
+ # @private
80
+ #
81
+ @@handler_sets = {}
82
+
83
+ @@handler_sets[:top] = {
84
+ :EOF => lambda { |reader| reader.on_eof },
85
+ }
86
+ @@handler_sets[:top].merge!(@@common_tag_set)
87
+ @@handler_sets[:top].merge!(@@comment_handler_set)
88
+ add_values_handler(@@handler_sets[:top], lambda { |reader| reader.on_anonymous_value })
89
+
90
+ @@handler_sets[:tag_values] = {
91
+ :WHITESPACE => @@SKIP_PROC,
92
+ :LINE_CONTINUATION => @@SKIP_PROC,
93
+ :IDENTIFIER => lambda { |reader| reader.on_attribute },
94
+ :EOL => @@ON_SELF_CLOSING_TAG_PROG,
95
+ :SEMICOLON => @@ON_SELF_CLOSING_TAG_PROG,
96
+ :BLOCK_START => lambda { |reader| reader.on_tag_body_start },
97
+ :EOF => @@ON_SELF_CLOSING_TAG_PROG,
98
+ }
99
+ @@handler_sets[:tag_values].merge!(@@comment_handler_set)
100
+ add_values_handler(@@handler_sets[:tag_values], lambda { |reader| reader.on_value })
101
+
102
+ @@handler_sets[:tag_attributes] = {
103
+ :WHITESPACE => @@SKIP_PROC,
104
+ :LINE_CONTINUATION => @@SKIP_PROC,
105
+ :IDENTIFIER => lambda { |reader| reader.on_attribute },
106
+ :EOL => @@ON_SELF_CLOSING_TAG_PROG,
107
+ :SEMICOLON => @@ON_SELF_CLOSING_TAG_PROG,
108
+ :BLOCK_START => lambda { |reader| reader.on_tag_body_start },
109
+ :EOF => @@ON_SELF_CLOSING_TAG_PROG,
110
+ }
111
+ @@handler_sets[:tag_attributes].merge!(@@comment_handler_set)
112
+
113
+ @@handler_sets[:tag_body] = {
114
+ :BLOCK_END => lambda { |reader| reader.on_tag_body_end },
115
+ }
116
+ @@handler_sets[:tag_body].merge!(@@common_tag_set)
117
+ @@handler_sets[:tag_body].merge!(@@comment_handler_set)
118
+ add_values_handler(@@handler_sets[:tag_body], lambda { |reader| reader.on_anonymous_value })
119
+
120
+ @@handler_sets[:eof] = {}
121
+
122
+ # @private
123
+ @@value_handlers = {
124
+ :NULL => lambda { |s, reader| nil },
125
+ :INTEGER => lambda { |s, reader| reader.parse_integer(s) },
126
+ :FLOAT => lambda { |s, reader| reader.parse_float(s) },
127
+ :BOOLEAN => lambda { |s, reader| (s =~ /\A(?:true|on)\Z/) ? true : false },
128
+ :CHARACTER => lambda { |s, reader| reader.parse_character(s) },
129
+ :INLINE_BACKQUOTE_STRING => lambda { |s, reader| s },
130
+ :INLINE_DOUBLE_QUOTE_STRING => lambda { |s, reader| reader.parse_double_quote_string(s) },
131
+ :MULTILINE_BACKQUOTE_STRING_START =>
132
+ lambda { |s, reader| reader.parse_multiline_backquote_string(s) },
133
+ :MULTILINE_DOUBLE_QUOTE_STRING_START =>
134
+ lambda { |s, reader| reader.parse_multiline_double_quote_string(s) },
135
+ :INLINE_BINARY => lambda { |s, reader| SdlBinary.decode64(s) },
136
+ :MULTILINE_BINARY_START => lambda { |s, reader| reader.parse_multiline_binary(s) },
137
+ :DATE => lambda { |s, reader| reader.parse_date(s) },
138
+ :TIME_OR_TIMESPAN => lambda { |s, reader| reader.parse_time_span(s) },
139
+ }
140
+
141
+ # Type of the traversed SDL node (e.g. TYPE_ELEMENT).
142
+ attr_reader :node_type
143
+
144
+ # Prefix (namespace) of the traversed SDL node.
145
+ attr_reader :prefix
146
+
147
+ # Name of the traversed SDL node.
148
+ attr_reader :name
149
+
150
+ # Depth of the current SDL node. Depth of top nodes is 1 (0 would be the root that the Reader
151
+ # doesn't traverse).
152
+ attr_reader :depth
153
+
154
+ def initialize(io)
155
+ raise ArgumentError, "io == nil" if io.nil?
156
+ raise ArgumentError, "io is not an IO" unless io.respond_to?(:gets)
157
+
158
+ @io = io
159
+ @tokenizer = Tokenizer.new(@io)
160
+ @element = nil
161
+ @element_pool = []
162
+ @depth = 1
163
+
164
+ clear_node()
165
+ set_mode(:top)
166
+ end
167
+
168
+ # @return [Array] an array of the attributes structured as follows:
169
+ # <code>[ [["ns1", "attr1"], 123], [["", "attr2"], true] ]</code>
170
+ def attributes
171
+ @element ? @element.attributes.clone : nil
172
+ end
173
+
174
+ # @return the value of the specified attribute.
175
+ #
176
+ # @overload attribute(name)
177
+ # @overload attribute (prefix, name)
178
+ def attribute(prefix, name = nil)
179
+ return nil unless @element
180
+
181
+ if name
182
+ prefix, name = prefix.to_s, name.to_s
183
+ else
184
+ prefix, name = '', prefix.to_s
185
+ end
186
+
187
+ @element.attributes.each do |attr|
188
+ return attr[1] if attr[0][0] == prefix && attr[0][1] == name
189
+ end
190
+ return nil
191
+ end
192
+
193
+ # @return the value of the attribute at the specified index.
194
+ def attribute_at(index)
195
+ if @element
196
+ @element.attributes[index]
197
+ else
198
+ nil
199
+ end
200
+ end
201
+
202
+ # @return [Integer] number of attributes in the current element
203
+ def attribute_count
204
+ @element ? @element.attributes.size : 0
205
+ end
206
+
207
+ # @return [boolean] whether the current element has attributes.
208
+ def attributes?
209
+ @element && @element.attributes.size > 0
210
+ end
211
+
212
+ # Calls the given block for each encountered Tag. The block is called when the Tag definition
213
+ # is complete.
214
+ #
215
+ # @param [boolean] only_top_tags if true only top Tags are enumerated
216
+ # @yield [Tag] called at each Tag
217
+ #
218
+ def each_tag(only_top_tags = false)
219
+ stack = []
220
+ tag = nil # Only used during definition (values + attributes)
221
+
222
+ while node = read
223
+ case node.node_type
224
+
225
+ when TYPE_ELEMENT
226
+ tag = Tag.new @element.prefix, @element.name
227
+ node.attributes.each do |attribute|
228
+ tag.set_attribute(attribute[0][0], attribute[0][1], attribute[1])
229
+ end
230
+ values = node.values
231
+ tag.values = values if values
232
+ stack.last.add_child(tag) unless stack.empty?
233
+
234
+ if node.self_closing?
235
+ yield tag if !only_top_tags or @depth <= 1
236
+ else
237
+ stack << tag
238
+ end
239
+
240
+ tag = nil # definition ended here
241
+
242
+ when TYPE_END_ELEMENT
243
+ tag = stack.pop
244
+ yield tag if !only_top_tags or depth <= 1
245
+ end
246
+ end
247
+ end
248
+
249
+ def self_closing?
250
+ @element ? @element.self_closing : false
251
+ end
252
+
253
+ def clear_node
254
+ @node_type = nil
255
+ @prefix = nil
256
+ @name = nil
257
+ end
258
+ private :clear_node
259
+
260
+ # @return the values of the current node, nil if there are none.
261
+ def values
262
+ if @element
263
+ values = @element.values
264
+ values.empty? ? nil : values.clone
265
+ else
266
+ @value
267
+ end
268
+ end
269
+ alias_method :value, :values
270
+
271
+ def values?
272
+ if @element
273
+ !@element.values.empty?
274
+ else
275
+ !@value.nil?
276
+ end
277
+ end
278
+ alias_method :value?, :values?
279
+
280
+ def self.from_io(io)
281
+ self.new(io)
282
+ end
283
+
284
+ def self.from_memory(s)
285
+ self.new(StringIO.new(s))
286
+ end
287
+
288
+ # Enumerates all the parsed nodes and calls the given block.
289
+ #
290
+ # @yield [Reader] the current node
291
+ #
292
+ # @example
293
+ # open("sample.sdl") do |io|
294
+ # SDL4R::Reader.from_io(io).each do |node]
295
+ # puts node.node_type
296
+ # end
297
+ # end
298
+ #
299
+ def each(&block)
300
+ while node = self.read
301
+ block.call(node)
302
+ end
303
+ end
304
+
305
+ # Reads the next node in the SDL structure.
306
+ #
307
+ # @example
308
+ # open("sample.sdl") do |io|
309
+ # reader = SDL4R::Reader.from_io(io)
310
+ # while node = reader.read
311
+ # puts node.node_type
312
+ # end
313
+ # end
314
+ #
315
+ # @return [Reader] returns a Reader if a new node has been reached or +nil+ if the end of file
316
+ # has been reached.
317
+ def read
318
+ clear_node
319
+
320
+ node = nil
321
+
322
+ while @tokenizer.read
323
+ handler = @handler_set[@tokenizer.token_type]
324
+ unless handler
325
+ raise_unexpected_token
326
+ end
327
+ if handler.call(self)
328
+ node = self if @node_type # otherwise, we reached the end of the file
329
+ break
330
+ end
331
+ end
332
+
333
+ node
334
+ end
335
+
336
+ # @private
337
+ def raise_unexpected_token
338
+ @tokenizer.raise_parse_error(
339
+ "unexpected token #{@tokenizer.token_type} #{@tokenizer.token.inspect}",
340
+ @tokenizer.token_line_no,
341
+ @tokenizer.token_pos)
342
+ end
343
+
344
+ def set_mode(mode)
345
+ handler_set = @@handler_sets[mode]
346
+ raise ArgumentError, "unknown mode #{mode.to_s}" unless handler_set
347
+ @mode = mode
348
+ @handler_set = handler_set
349
+ end
350
+ protected :set_mode
351
+
352
+ # Creates and returns the object representing a datetime (calls SDL4R#new_time by default).
353
+ # Can be overriden.
354
+ #
355
+ # def new_time(year, month, day, hour, min, sec, msec, timezone_code)
356
+ # Time.utc(year, month, day, hour, min, sec, msec, timezone_code)
357
+ # end
358
+ #
359
+ def new_time(year, month, day, hour, min, sec, msec, timezone_code)
360
+ SDL4R::new_time(year, month, day, hour, min, sec, msec, timezone_code)
361
+ end
362
+
363
+ # @private
364
+ def on_simple_comment # :nodoc:
365
+ # @node_type = TYPE_COMMENT
366
+ false
367
+ end
368
+
369
+ # @private
370
+ def on_eof # :nodoc:
371
+ @node_type = nil
372
+ true
373
+ end
374
+
375
+ # @private
376
+ def on_multiline_comment # :nodoc:
377
+ # @node_type = TYPE_COMMENT
378
+ @value = @tokenizer.token
379
+
380
+ while @tokenizer.read
381
+ case @tokenizer.token_type
382
+ when :EOL
383
+ @value << ?\n
384
+ when :MULTILINE_COMMENT_PART
385
+ @value << @tokenizer.token
386
+ when :MULTILINE_COMMENT_END
387
+ @value << @tokenizer.token
388
+ break
389
+ else
390
+ raise_unexpected_token
391
+ end
392
+ end
393
+
394
+ false
395
+ end
396
+
397
+ # @private
398
+ def on_tag_start # :nodoc:
399
+ read_name
400
+ set_mode :tag_values
401
+ @element = Element.new @prefix, @name
402
+
403
+ false
404
+ end
405
+
406
+ # @private
407
+ def on_self_closing_tag # :nodoc:
408
+ @node_type = TYPE_ELEMENT
409
+ @prefix = @element.prefix
410
+ @name = @element.name
411
+ @element.self_closing = true
412
+ set_mode(@depth <= 1 ? :top : :tag_body)
413
+ end
414
+
415
+ # @private
416
+ def on_attribute # :nodoc:
417
+ read_name
418
+ read_equal
419
+ read_value
420
+ set_mode :tag_attributes
421
+ @element.add_attribute(@prefix, @name, @value)
422
+
423
+ false
424
+ end
425
+
426
+ # @private
427
+ def on_value # :nodoc:
428
+ read_value
429
+ set_mode :tag_values
430
+ @element.add_value(@value)
431
+
432
+ false
433
+ end
434
+
435
+ # Should only be called from :top or :tag_body modes.
436
+ # @private
437
+ def on_anonymous_value # :nodoc:
438
+ set_mode :tag_values
439
+ @element = Element.new '', SDL4R::ANONYMOUS_TAG_NAME
440
+
441
+ on_value
442
+ end
443
+
444
+ # @private
445
+ def on_tag_body_start # :nodoc:
446
+ @node_type = TYPE_ELEMENT
447
+ @prefix = @element.prefix
448
+ @name = @element.name
449
+ @depth += 1
450
+ set_mode :tag_body
451
+
452
+ true
453
+ end
454
+
455
+ # @private
456
+ def on_tag_body_end # :nodoc:
457
+ if @depth <= 1
458
+ raise "unexpected end of tag"
459
+ end
460
+
461
+ clear_node
462
+ @node_type = TYPE_END_ELEMENT
463
+ @depth -= 1
464
+ set_mode(@depth <= 1 ? :top : :tag_body)
465
+
466
+ true
467
+ end
468
+
469
+ # @private
470
+ def parse_double_quote_string(s)
471
+ return s if s.empty?
472
+
473
+ string = ""
474
+ escaped = false
475
+
476
+ s.each_char do |c|
477
+ if escaped
478
+ escaped = false
479
+
480
+ case c
481
+ when "\\", "\""
482
+ string << c
483
+ when "n"
484
+ string << ?\n
485
+ when "r"
486
+ string << ?\r
487
+ when "t"
488
+ string << ?\t
489
+ else
490
+ @tokenizer.raise_parse_error("Illegal escape character in string literal: '#{c}'.")
491
+ end
492
+
493
+ elsif c == "\\"
494
+ escaped = true
495
+
496
+ else
497
+ string << c
498
+ end
499
+ end
500
+
501
+ @tokenizer.raise_parse_error("orphan backslash") if escaped
502
+
503
+ string
504
+ end
505
+
506
+ # @private
507
+ def parse_multiline_string(first_string, part_token_type, end_token_type)
508
+ string = ""
509
+ string << first_string
510
+
511
+ loop do
512
+ case @tokenizer.read
513
+ when :EOL
514
+ # skip
515
+ when part_token_type
516
+ string << @tokenizer.token
517
+ when end_token_type
518
+ string << @tokenizer.token
519
+ break
520
+ else
521
+ raise_unexpected_token
522
+ end
523
+ end
524
+
525
+ string
526
+ end
527
+ private :parse_multiline_string
528
+
529
+ # @private
530
+ def parse_multiline_backquote_string(s)
531
+ parse_multiline_string s, :MULTILINE_BACKQUOTE_STRING_PART, :MULTILINE_BACKQUOTE_STRING_END
532
+ end
533
+
534
+ # @private
535
+ def parse_multiline_double_quote_string(s)
536
+ parse_double_quote_string(
537
+ parse_multiline_string(
538
+ s, :MULTILINE_DOUBLE_QUOTE_STRING_PART, :MULTILINE_DOUBLE_QUOTE_STRING_END))
539
+ end
540
+
541
+ # @private
542
+ def parse_multiline_binary(s)
543
+ literal = parse_multiline_string s, :MULTILINE_BINARY_PART, :MULTILINE_BINARY_END
544
+ return SdlBinary.decode64(literal)
545
+ end
546
+
547
+ # @private
548
+ def parse_character(s)
549
+ case s
550
+ when /\A.\Z/
551
+ s
552
+ when "\\\\"
553
+ "\\"
554
+ when "\\'"
555
+ "'"
556
+ when "\\n"
557
+ "\n"
558
+ when "\\r"
559
+ "\r"
560
+ when "\\t"
561
+ "\t"
562
+ else
563
+ raise "illegal character literal #{s.inspect}"
564
+ end
565
+ end
566
+
567
+ # @private
568
+ def parse_integer(s)
569
+ if s =~ /\A([^L]+)L\Z/i
570
+ return Integer($1)
571
+ else
572
+ return Integer(s)
573
+ end
574
+ end
575
+
576
+ # @private
577
+ def parse_float(s)
578
+ if s =~ /\A([^BDF]+)BD\Z/i
579
+ return BigDecimal($1)
580
+ elsif s =~ /\A([^BDF]+)[FD]\Z/i
581
+ return Float($1) rescue @tokenizer.raise_parse_error("not a float '#{$1}'")
582
+ else
583
+ return Float(s) rescue @tokenizer.raise_parse_error("not a float '#{s}'")
584
+ end
585
+ end
586
+
587
+ # Parses the +literal+ into a returned Date object.
588
+ #
589
+ # Raises an ArgumentError if +literal+ has a bad format.
590
+ #
591
+ # @private
592
+ def parse_date(literal)
593
+ # here, we're being stricter than strptime() alone as we forbid trailing chars (also faster)
594
+ if literal =~ /\A(-?\d+)\/(\d+)\/(\d+)\Z/
595
+ date_year = $1.to_i
596
+ date_month = $2.to_i
597
+ date_day = $3.to_i
598
+
599
+ skip_whitespaces(false)
600
+
601
+ # Check whether the next tag is the time part
602
+ if @tokenizer.token_type == :TIME_OR_TIMESPAN
603
+ # Is it a time or timespan?
604
+ day, hour, min, sec, msec, zone =
605
+ parse_time_span_and_time_zone(@tokenizer.token, true, true)
606
+
607
+ if day
608
+ @tokenizer.unread
609
+ return Date.civil(date_year, date_month, date_day)
610
+ else
611
+ return new_time(date_year, date_month, date_day, hour, min, sec, msec, zone)
612
+ end
613
+
614
+ else
615
+ @tokenizer.unread
616
+ return Date.civil(date_year, date_month, date_day)
617
+ end
618
+
619
+ else
620
+ raise ArgumentError, "Malformed Date <#{literal}>"
621
+ end
622
+ end
623
+
624
+ # Parses +literal+ (String) into the corresponding SDLTimeSpan, which is then
625
+ # returned.
626
+ #
627
+ # Raises an ArgumentError if the literal is not a correct timespan literal.
628
+ #
629
+ # @private
630
+ def parse_time_span(literal)
631
+ days, hours, minutes, seconds, milliseconds, zone_code =
632
+ parse_time_span_and_time_zone(literal, true, false)
633
+
634
+ if zone_code
635
+ @tokenizer.raise_parse_error("got a time when expecting a timespan: \"#{literal}\"")
636
+ end
637
+
638
+ return SDL4R::SdlTimeSpan.new(days || 0, hours, minutes, seconds, milliseconds)
639
+ end
640
+
641
+ private
642
+
643
+ # Parses the given literal into a returned array
644
+ # [days, hours, minutes, seconds, milliseconds, zone_code].
645
+ # 'days', 'hours', 'minutes', 'seconds', 'milliseconds' are integers.
646
+ # 'days' is +nil+ if not specified in +literal+.
647
+ # 'seconds' and 'milliseconds' are equal to 0 if they're not specified in +literal+.
648
+ # 'zone_code' (string) is equal to nil if not specified.
649
+ #
650
+ # +allowDays+ indicates whether the specification of days is allowed
651
+ # in +literal+
652
+ # +allowTimeZone+ indicates whether the specification of the timeZone is
653
+ # allowed in +literal+
654
+ #
655
+ # All components are returned disregarding the values of +allowDays+ and
656
+ # +allowTimeZone+.
657
+ #
658
+ # Raises an ArgumentError if +literal+ has a bad format.
659
+ def parse_time_span_and_time_zone(literal, allowDays, allowTimeZone)
660
+ overall_sign = (literal =~ /^-/)? -1 : +1
661
+
662
+ if literal =~ /\A(([+\-]?\d+)d:)/
663
+ if allowDays
664
+ days = Integer($2)
665
+ time_part = literal[($1.length)..-1]
666
+ else
667
+ # detected a day specification in a pure time literal
668
+ raise ArgumentError, "unexpected day specification in #{literal}"
669
+ end
670
+ else
671
+ days = nil
672
+ time_part = literal
673
+ end
674
+
675
+ # We have to parse the string ourselves because AFAIK :
676
+ # - strptime() can't parse milliseconds
677
+ # - strptime() can't parse the time zone custom offset (CET+02:30)
678
+ # - strptime() accepts trailing chars
679
+ # (e.g. "12:24-xyz@" ==> "xyz@" is obviously wrong but strptime()
680
+ # won't mind)
681
+ if /\A([+-]?\d+):(\d+)(?::(\d+)(?:\.(\d+))?)?
682
+ (?:-([a-zA-Z0-9\/_]+(?:[+\-]\d+(?::\d+)?)?))?\Z/ix =~ time_part
683
+ hours = $1.to_i
684
+ minutes = $2.to_i
685
+ seconds = $3 ? $3.to_i : 0
686
+ milliseconds =
687
+ if $4
688
+ millisecond_part = ($4)? $4.ljust(3, '0') : nil
689
+ millisecond_part.to_i
690
+ else
691
+ 0
692
+ end
693
+
694
+ if $5 and not allowTimeZone
695
+ raise ArgumentError, "unexpected time zone specification in #{literal}"
696
+ end
697
+
698
+ zone_code = $5 # might be nil
699
+
700
+ if not allowDays and $1 =~ /\A[+-]/
701
+ # unexpected timeSpan syntax
702
+ raise ArgumentError, "unexpected sign on hours : #{literal}"
703
+ end
704
+
705
+ # take the sign into account
706
+ if overall_sign == -1
707
+ hours = -hours if days # otherwise the sign is already applied to the hours
708
+ minutes = -minutes
709
+ seconds = -seconds
710
+ milliseconds = -milliseconds
711
+ end
712
+
713
+ return [ days, hours, minutes, seconds, milliseconds, zone_code ]
714
+
715
+ else
716
+ raise ArgumentError, "bad time component : #{literal}"
717
+ end
718
+ end
719
+
720
+ def skip_whitespaces(allow_line_continuations = false)
721
+ while token_type = @tokenizer.read
722
+ case token_type
723
+ when :WHITESPACE
724
+ # skip
725
+ when :LINE_CONTINUATION
726
+ break unless allow_line_continuations
727
+ else
728
+ break
729
+ end
730
+ end
731
+ end
732
+
733
+ # @private
734
+ def read_equal
735
+ skip_whitespaces(false)
736
+ unless @tokenizer.token_type == :EQUAL
737
+ raise_unexpected_token
738
+ end
739
+ skip_whitespaces(true)
740
+ end
741
+
742
+ # @private
743
+ def read_name
744
+ @name = @tokenizer.token
745
+
746
+ if @tokenizer.read == :COLON
747
+ # Namespace + Name (except syntax error)
748
+ if @tokenizer.read == :IDENTIFIER
749
+ @prefix = @name
750
+ @name = @tokenizer.token
751
+
752
+ else
753
+ raise_unexpected_token
754
+ end
755
+
756
+ else # Just a Name, it seems
757
+ @tokenizer.unread
758
+ @prefix = ''
759
+ end
760
+
761
+ @name
762
+ end
763
+
764
+ # @private
765
+ def read_value
766
+ handler = @@value_handlers[@tokenizer.token_type]
767
+ raise_unexpected_token unless handler
768
+ @value = handler.call(@tokenizer.token, self)
769
+ @value
770
+ end
771
+ end
772
+ end