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
@@ -25,8 +25,8 @@ module SDL4R
25
25
  require 'open-uri'
26
26
  require 'stringio'
27
27
 
28
- require File.dirname(__FILE__) + '/sdl4r'
29
- require File.dirname(__FILE__) + '/parser'
28
+ require 'sdl4r/sdl4r'
29
+ require 'sdl4r/reader'
30
30
 
31
31
  # SDL documents are made of Tags.
32
32
  #
@@ -56,18 +56,54 @@ module SDL4R
56
56
  #
57
57
  # Use at the beginning of a method in order to have correctly defined parameters:
58
58
  # def foo(namespace, name = nil)
59
- # namespace, name = to_nns namespace, name
59
+ # namespace, name = to_ns_n namespace, name
60
60
  # end
61
61
  #
62
- def to_nns(namespace, name)
62
+ # Also converts Symbols to Strings.
63
+ #
64
+ def to_ns_n(namespace, name)
65
+ namespace = namespace.id2name if namespace.is_a? Symbol
66
+ name = name.id2name if name.is_a? Symbol
67
+
63
68
  if name.nil? and not namespace.nil?
64
69
  name = namespace
65
70
  namespace = ""
66
71
  end
67
72
  return namespace, name
68
73
  end
69
- private :to_nns
70
-
74
+ private :to_ns_n
75
+
76
+ # Returns [recursive, namespace, name] based on the parameters.
77
+ # Allows:
78
+ # to_rec_ns_n()
79
+ # to_rec_ns_n(recursive)
80
+ # to_rec_ns_n(recursive, name)
81
+ # to_rec_ns_n(name)
82
+ # to_rec_ns_n(namespace, name)
83
+ # to_rec_ns_n(recursive, namespace, name)
84
+ #
85
+ def to_rec_ns_n(*args)
86
+ raise ArgumentError, "too many arguments: #{args.length}" if args.length > 3
87
+
88
+ recursive = false
89
+ if args.length > 0 and (args[0].is_a? TrueClass or args[0].is_a? FalseClass)
90
+ recursive = args.shift
91
+ elsif args.length > 2
92
+ raise ArgumentError, "too many or wrong arguments: #{args.length}"
93
+ end
94
+
95
+ namespace = nil # we look at any namespace by default
96
+ namespace = args.shift if args.length == 2
97
+ namespace = namespace.id2name if namespace.is_a? Symbol
98
+
99
+ name = nil
100
+ name = args.shift if args.length == 1
101
+ name = name.id2name if name.is_a? Symbol
102
+
103
+ return recursive, namespace, name
104
+ end
105
+ private :to_rec_ns_n
106
+
71
107
  # Creates an empty tag in the given namespace. If the +namespace+ is nil
72
108
  # it will be coerced to an empty String.
73
109
  #
@@ -104,7 +140,8 @@ module SDL4R
104
140
  # and is not a legal SDL identifier.
105
141
  #
106
142
  def initialize(namespace, name = nil, &block)
107
- namespace, name = to_nns namespace, name
143
+ namespace, name = to_ns_n namespace, name
144
+
108
145
 
109
146
  raise ArgumentError, "tag namespace must be a String" unless namespace.is_a? String
110
147
  raise ArgumentError, "tag name must be a String" unless name.is_a? String
@@ -165,6 +202,7 @@ module SDL4R
165
202
  # Returns the added child.
166
203
  #
167
204
  def add_child(child)
205
+ raise ArgumentError, "child is nil" unless child
168
206
  @children.push(child)
169
207
  return child
170
208
  end
@@ -200,6 +238,7 @@ module SDL4R
200
238
  add_child(o)
201
239
  elsif o.is_a?(Hash)
202
240
  o.each_pair { |key, value|
241
+ key = key.to_s if key.is_a? Symbol
203
242
  namespace, key = key.split(/:/) if key.match(/:/)
204
243
  namespace ||= ""
205
244
  set_attribute(namespace, key, value)
@@ -286,11 +325,8 @@ module SDL4R
286
325
  # tag.children(false, "name") # => children of name "name"
287
326
  # tag.children(false, "ns", nil) # => children of namespace "ns"
288
327
  #
289
- def children(recursive = false, namespace = nil, name = :DEFAULT, &block) # :yields: child
290
- if name == :DEFAULT
291
- name = namespace
292
- namespace = nil
293
- end
328
+ def children(*args, &block) # :yields: child
329
+ recursive, namespace, name = to_rec_ns_n(*args)
294
330
 
295
331
  if block_given?
296
332
  each_child(recursive, namespace, name, &block)
@@ -316,6 +352,8 @@ module SDL4R
316
352
  #
317
353
  # _name_:: if nil, all children are considered (nil by default).
318
354
  def children_values(name = nil)
355
+ name = name.id2name if name.is_a? Symbol
356
+
319
357
  children_values = []
320
358
  each_child(false, name) { |child|
321
359
  case child.values.size
@@ -330,9 +368,11 @@ module SDL4R
330
368
  return children_values
331
369
  end
332
370
 
333
- # child
334
- # child(name)
335
- # child(recursive, name)
371
+ # child # => first child
372
+ # child(name) # => first child of specified name (any namespace)
373
+ # child(true, name) # => first child (or descendant) of specified name (any namspace)
374
+ # child(namespace, name) # => first child of specified namespace and name
375
+ # child(recursive, namespace, name)
336
376
  #
337
377
  # Get the first child with the given name, optionally using a recursive search.
338
378
  #
@@ -341,16 +381,13 @@ module SDL4R
341
381
  #
342
382
  # Returns the first child tag having the given name or +nil+ if no such child exists
343
383
  #
344
- def child(recursive = false, name = nil)
345
- if name.nil?
346
- name = recursive
347
- recursive = false
348
- end
349
-
350
- unless name
384
+ def child(*args)
385
+ recursive, namespace, name = to_rec_ns_n(*args)
386
+
387
+ if namespace.nil? and name.nil?
351
388
  return @children.first
352
389
  else
353
- each_child(recursive, name) { |child| return child }
390
+ each_child(recursive, namespace, name) { |child| return child }
354
391
  end
355
392
  end
356
393
 
@@ -358,8 +395,16 @@ module SDL4R
358
395
  #
359
396
  # _name_:: name of the searched child Tag
360
397
  #
361
- def has_child?(name)
362
- !child(name).nil?
398
+ # has_child?(name)
399
+ # has_child?(namespace, name)
400
+ #
401
+ def has_child?(namespace, name = :DEFAULT)
402
+ if name == :DEFAULT
403
+ name = namespace
404
+ namespace = nil
405
+ end
406
+
407
+ !child(namespace, name).nil?
363
408
  end
364
409
 
365
410
  # Indicates whether there are children Tag.
@@ -492,7 +537,7 @@ module SDL4R
492
537
  #
493
538
  def values=(someValues)
494
539
  @values.clear()
495
- someValues.to_a.each { |v|
540
+ someValues.each { |v|
496
541
  # this is required to ensure validation of types
497
542
  add_value(v)
498
543
  }
@@ -519,13 +564,14 @@ module SDL4R
519
564
  #
520
565
  def set_attribute(namespace, key, value = :default)
521
566
  if value == :default
522
- value = key
523
- key = namespace
524
- namespace = ""
567
+ value, key, namespace = key, namespace, ""
525
568
  end
569
+ namespace = namespace.id2name if namespace.is_a? Symbol
570
+ key = key.id2name if key.is_a? Symbol
526
571
 
527
- raise ArgumentError, "attribute namespace must be a String" unless namespace.is_a? String
528
- raise ArgumentError, "attribute key must be a String" unless key.is_a? String
572
+ raise ArgumentError,
573
+ "attribute namespace must be a String or a Symbol" unless namespace.is_a? String
574
+ raise ArgumentError, "attribute key must be a String or a Symbol" unless key.is_a? String
529
575
  raise ArgumentError, "attribute key cannot be empty" if key.empty?
530
576
 
531
577
  SDL4R.validate_identifier(namespace) unless namespace.empty?
@@ -548,7 +594,7 @@ module SDL4R
548
594
  #
549
595
  #
550
596
  def attribute(namespace, key = nil)
551
- namespace, key = to_nns namespace, key
597
+ namespace, key = to_ns_n namespace, key
552
598
  attributes = @attributesByNamespace[namespace]
553
599
  return attributes.nil? ? nil : attributes[key]
554
600
  end
@@ -561,7 +607,7 @@ module SDL4R
561
607
  # has_attribute?(namespace, key)
562
608
  #
563
609
  def has_attribute?(namespace = nil, key = nil)
564
- namespace, key = to_nns namespace, key
610
+ namespace, key = to_ns_n namespace, key
565
611
 
566
612
  if namespace or key
567
613
  attributes = @attributesByNamespace[namespace]
@@ -590,6 +636,8 @@ module SDL4R
590
636
  # qualified names (e.g. "meat:color"). If "", attributes of the default namespace are returned.
591
637
  #
592
638
  def attributes(namespace = nil, &block) # :yields: namespace, key, value
639
+ namespace = namespace.id2name if namespace.is_a? Symbol
640
+
593
641
  if block_given?
594
642
  each_attribute(namespace, &block)
595
643
 
@@ -597,8 +645,8 @@ module SDL4R
597
645
  if namespace.nil?
598
646
  hash = {}
599
647
 
600
- each_attribute do | namespace, key, value |
601
- qualified_name = namespace.empty? ? key : namespace + ':' + key
648
+ each_attribute do | attr_namespace, key, value |
649
+ qualified_name = attr_namespace.empty? ? key : attr_namespace + ':' + key
602
650
  hash[qualified_name] = value
603
651
  end
604
652
 
@@ -621,7 +669,7 @@ module SDL4R
621
669
  # Returns the value of the removed attribute or +nil+ if it didn't exist.
622
670
  #
623
671
  def remove_attribute(namespace, key = nil)
624
- namespace, key = to_nns namespace, key
672
+ namespace, key = to_ns_n namespace, key
625
673
  attributes = @attributesByNamespace[namespace]
626
674
  return attributes.nil? ? nil : attributes.delete(key)
627
675
  end
@@ -633,6 +681,7 @@ module SDL4R
633
681
  if namespace.nil?
634
682
  @attributesByNamespace.clear
635
683
  else
684
+ namespace = namespace.id2name if namespace.is_a? Symbol
636
685
  @attributesByNamespace.delete(namespace)
637
686
  end
638
687
  end
@@ -671,9 +720,9 @@ module SDL4R
671
720
  #
672
721
  def set_attributes(namespace, attribute_hash = nil)
673
722
  if attribute_hash.nil?
674
- attribute_hash = namespace
675
- namespace = ""
723
+ attribute_hash, namespace = namespace, ""
676
724
  end
725
+ namespace = namespace.id2name if namespace.is_a? Symbol
677
726
 
678
727
  raise ArgumentError, "namespace can't be nil" if namespace.nil?
679
728
  raise ArgumentError, "attribute_hash should be a Hash" unless attribute_hash.is_a? Hash
@@ -701,10 +750,10 @@ module SDL4R
701
750
  # Raises +ArgumentError+ if the name is not a legal SDL identifier
702
751
  # (see SDL4R#validate_identifier).
703
752
  #
704
- def name=(a_name)
705
- a_name = a_name.to_s
706
- SDL4R.validate_identifier(a_name)
707
- @name = a_name
753
+ def name=(name)
754
+ name = name.id2name if name.is_a? Symbol
755
+ SDL4R.validate_identifier(name)
756
+ @name = name
708
757
  end
709
758
 
710
759
  # The namespace to set. +nil+ will be coerced to the empty string.
@@ -712,10 +761,10 @@ module SDL4R
712
761
  # Raises +ArgumentError+ if the namespace is non-blank and is not
713
762
  # a legal SDL identifier (see SDL4R#validate_identifier)
714
763
  #
715
- def namespace=(a_namespace)
716
- a_namespace = a_namespace.to_s
717
- SDL4R.validate_identifier(a_namespace) unless a_namespace.empty?
718
- @namespace = a_namespace
764
+ def namespace=(namespace)
765
+ namespace = namespace.id2name if namespace.is_a? Symbol
766
+ SDL4R.validate_identifier(namespace) unless namespace.empty?
767
+ @namespace = namespace
719
768
  end
720
769
 
721
770
  # Adds all the tags specified in the given IO, String, Pathname or URI to this Tag.
@@ -745,7 +794,7 @@ module SDL4R
745
794
  io = yield
746
795
 
747
796
  begin
748
- Parser.new(io).parse.each do |tag|
797
+ SDL4R::Reader.from_io(io).each_tag(true) do |tag|
749
798
  add_child(tag)
750
799
  end
751
800
 
@@ -867,7 +916,7 @@ module SDL4R
867
916
 
868
917
  # Returns a string representation of the children tags.
869
918
  #
870
- # _linePrefix_:: A prefix to insert before every line.
919
+ # _linePrefix_:: a prefix to insert before every line.
871
920
  # _s_:: a String that receives the string representation
872
921
  #
873
922
  # TODO: break up long lines using the backslash
@@ -947,12 +996,18 @@ module SDL4R
947
996
  end
948
997
  end
949
998
  end
950
-
999
+
951
1000
  # output values
952
1001
  unless @values.empty?
953
1002
  i = 0
954
1003
  @values.each do |value|
955
- s << " _val" << i.to_s << "=\"" << SDL4R.format(value, false) << "\""
1004
+ s << " _val" << i.to_s << "=\""
1005
+ if value.is_a? String
1006
+ s << value_string_to_xml(value, true)
1007
+ else
1008
+ s << SDL4R.format(value, false)
1009
+ end
1010
+ s << "\""
956
1011
  i += 1
957
1012
  end
958
1013
  end
@@ -964,7 +1019,13 @@ module SDL4R
964
1019
  unless omit_null_attributes and attribute_value.nil?
965
1020
  s << " "
966
1021
  s << "#{attribute_namespace}:" unless attribute_namespace.empty?
967
- s << attribute_name << "=\"" << SDL4R.format(attribute_value, false) << ?"
1022
+ s << attribute_name << "=\""
1023
+ if attribute_value.is_a? String
1024
+ s << value_string_to_xml(attribute_value, true)
1025
+ else
1026
+ s << SDL4R.format(attribute_value, false)
1027
+ end
1028
+ s << ?"
968
1029
  end
969
1030
  end
970
1031
  end
@@ -984,5 +1045,19 @@ module SDL4R
984
1045
 
985
1046
  return s
986
1047
  end
1048
+
1049
+ # @private
1050
+ def value_string_to_xml(s, in_quotes = true)
1051
+ s = s.clone
1052
+ if in_quotes
1053
+ s.gsub!(/"/, "&quot;")
1054
+ s.gsub!(/'/, "&apos;")
1055
+ end
1056
+ s.gsub!(/</, "&gt;")
1057
+ s.gsub!(/>/, "&lt;")
1058
+ s.gsub!(/&/, "&amp;")
1059
+ s
1060
+ end
1061
+ private :value_string_to_xml
987
1062
  end
988
1063
  end
@@ -0,0 +1,49 @@
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
+ # Used internally by Tokenizer for keeping track of its state.
23
+ # Don't use directly.
24
+ # @private
25
+ class Token
26
+
27
+ def initialize(text, type, matcher, line_no, pos)
28
+ @text = text
29
+ @type = type
30
+ @matcher = matcher
31
+ @line_no = line_no
32
+ @pos = pos
33
+ end
34
+
35
+ # Line number of the token
36
+ attr_accessor :line_no
37
+ # Position of the token in the line
38
+ attr_accessor :pos
39
+ # Type of token (e.g. :WHITESPACE)
40
+ attr_accessor :type
41
+ # Matcher object associated that discovered this Token
42
+ attr_accessor :matcher
43
+ # The token text
44
+ attr_accessor :text
45
+
46
+ self.freeze
47
+ end
48
+ end
49
+
@@ -0,0 +1,431 @@
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 'strscan'
24
+
25
+ require 'sdl4r/sdl_parse_error'
26
+ require 'sdl4r/token'
27
+
28
+ # Tokenizer for SDL.
29
+ #
30
+ # As Ruby's IO standard libraries are not so much low-level, this class works on lines. This
31
+ # means that some token types reflect this line-oriented tokenizing.
32
+ #
33
+ # The other solution would be to implement a proper tokenizer natively, which I don't feel like
34
+ # doing right now.
35
+ #
36
+ #--
37
+ # FIXME: implement a way of stacking the errors without raising an error immediately
38
+ #++
39
+ #
40
+ class Tokenizer # :nodoc: all
41
+
42
+ class Matcher # :nodoc: all
43
+ def initialize(token_type, regex, options = {}, &block)
44
+ options = {
45
+ :next_mode => nil,
46
+ :push_back_eol => false,
47
+ :error => nil,
48
+ }.merge(options)
49
+
50
+ @token_type = token_type
51
+ @regex = regex
52
+ @next_mode = options[:next_mode]
53
+ @push_back_eol = options[:push_back_eol]
54
+ @error = options[:error]
55
+
56
+ if block_given?
57
+ instance_eval(&block)
58
+ end
59
+ end
60
+ attr_reader :token_type, :regex, :next_mode
61
+
62
+ # Indicates whether the matched token tends to match the end of line character and whether
63
+ # it should be pushed back in this cases.
64
+ attr_reader :push_back_eol
65
+
66
+ # If +nil+, this Matcher is normal, otherwise it is meant to detect errors and this
67
+ # returns a message.
68
+ attr_reader :error
69
+
70
+ # Called when a token is found in order to remove meaningless characters, etc.
71
+ def process_token(token)
72
+ token
73
+ end
74
+
75
+ self.freeze
76
+ end
77
+
78
+ # A string used at the end of each line in order to trigger the EOL token.
79
+ # @private
80
+ @@EOL_STRING = "\n"
81
+
82
+ # @private
83
+ @@matcher_sets = {
84
+ :top => [
85
+ Matcher.new(:EOL, /\A\n/),
86
+ Matcher.new(:WHITESPACE, /\A\s+/, :push_back_eol => true),
87
+ Matcher.new(:SEMICOLON, /\A;/),
88
+ Matcher.new(:COLON, /\A:/),
89
+ Matcher.new(:EQUAL, /\A=/),
90
+ Matcher.new(:BLOCK_START, /\A\{/),
91
+ Matcher.new(:BLOCK_END, /\A\}/),
92
+ Matcher.new(:BOOLEAN, /\Atrue|false|on|off/),
93
+ Matcher.new(:NULL, /\Anull/),
94
+ Matcher.new(:ONE_LINE_COMMENT, /\A(?:#|--|\/\/).*\Z/, :push_back_eol => true) do
95
+ def process_token(token)
96
+ token.gsub!(/\A(?:#|--|\/\/)/, "")
97
+ end
98
+ end,
99
+ Matcher.new(:INLINE_COMMENT, /\A\/\*[\s\S]*?\*\//) do
100
+ def process_token(token)
101
+ token.gsub!(/\A\/\*|\*\/\Z/, "")
102
+ end
103
+ end,
104
+ Matcher.new(
105
+ :MULTILINE_COMMENT_START,
106
+ /\A\/\*.*\Z/,
107
+ :next_mode => :multiline_comment,
108
+ :push_back_eol => true) do
109
+ def process_token(token)
110
+ token.gsub!(/\A\/\*/, "")
111
+ end
112
+ end,
113
+ Matcher.new(:CHARACTER, /\A'(?:[^\\']|\\.)'/) do
114
+ def process_token(token)
115
+ token.gsub!(/\A'|'\Z/, "")
116
+ end
117
+ end,
118
+ Matcher.new(:INLINE_BACKQUOTE_STRING, /\A`[^`]*`/, :is_node => true) do
119
+ def process_token(token)
120
+ token.gsub!(/\A`|`\Z/, "")
121
+ end
122
+ end,
123
+ Matcher.new(:INLINE_DOUBLE_QUOTE_STRING, /\A"(?:[^\\"]|\\.)*"/) do
124
+ def process_token(token)
125
+ token.gsub!(/\A"|"\Z/, "")
126
+ end
127
+ end,
128
+ Matcher.new(
129
+ :MULTILINE_BACKQUOTE_STRING_START,
130
+ /\A`[^`]*\Z/,
131
+ :next_mode => :multiline_backquote_string,
132
+ :is_node => true) do
133
+ def process_token(token)
134
+ token.gsub!(/\A`/, "")
135
+ end
136
+ end,
137
+ Matcher.new(
138
+ :MULTILINE_DOUBLE_QUOTE_STRING_START,
139
+ /\A"(?:[^\\"]|\\\S)*\\\s*\Z/,
140
+ :next_mode => :multiline_double_quote_string,
141
+ :push_back_eol => true) do
142
+ def process_token(token)
143
+ token.gsub!(/\A"|\\\s*\Z/, "")
144
+ end
145
+ end,
146
+ Matcher.new(:INLINE_BINARY, /\A\[[\sA-Za-z0-9\/=\+]*\]/) do
147
+ def process_token(token)
148
+ token.gsub!(/\A\[|\s+|\]\Z/, "")
149
+ end
150
+ end,
151
+ Matcher.new(
152
+ :MULTILINE_BINARY_START, /\A\[[\sA-Za-z0-9\/=\+]*\Z/,
153
+ :next_mode => :multiline_binary,
154
+ :push_back_eol => true) do
155
+ def process_token(token)
156
+ token.gsub!(/\A\[|\s+/, "")
157
+ end
158
+ end,
159
+ Matcher.new(
160
+ :IDENTIFIER, /\A#{SDL4R::IDENTIFIER_START_CLASS}#{SDL4R::IDENTIFIER_PART_CLASS}*/),
161
+ Matcher.new(:DATE, /\A-?\d+\/\d+\/\d+/, :is_node => true),
162
+ Matcher.new(
163
+ :TIME_OR_TIMESPAN,
164
+ /\A(?:-?\d+d:)?-?\d+:\d+(?::\d+(?:\.\d+)?)?
165
+ (?:-[a-zA-Z\/]+(?:[+-]\d+(?::\d+)?)?)?/ix),
166
+ Matcher.new(:INTEGER, /\A[\+\-]?\d+L/i), # takes precedence on floats
167
+ # the float regex is meant to also catch bad syntaxed floats like "1.2.2" (otherwise, we
168
+ # would not detect this kind of errors easily).
169
+ Matcher.new(
170
+ :FLOAT, /\A[\+\-]?(?:\d+(?:F|D|BD)|\d*\.[\d\.]+(?:F|D|BD)?)/i),
171
+ Matcher.new(:INTEGER, /\A[\+\-]?\d+L?/i),
172
+ Matcher.new(:LINE_CONTINUATION, /\A\\\s*\Z/), # outside of comments, strings, etc
173
+ Matcher.new(
174
+ :UNCLOSED_DOUBLE_QUOTE_STRING,
175
+ /\A"(?:[^\\"]|\\\S)*/,
176
+ :error => "unclosed string"),
177
+ ],
178
+
179
+ :multiline_comment => [
180
+ Matcher.new(:EOL, /\A\n/),
181
+ Matcher.new(:MULTILINE_COMMENT_END, /\A[\s\S]*?\*\//, :next_mode => :top) do
182
+ def process_token(token)
183
+ token.gsub!(/\*\/\Z/, "")
184
+ end
185
+ end,
186
+ Matcher.new(:MULTILINE_COMMENT_PART, /\A.+\Z/, :push_back_eol => true)
187
+ ],
188
+
189
+ :multiline_backquote_string => [
190
+ Matcher.new(:EOL, /\A\n/),
191
+ Matcher.new(:MULTILINE_BACKQUOTE_STRING_END, /\A[^`]*`/, :next_mode => :top) do
192
+ def process_token(token)
193
+ token.gsub!(/`\Z/, "")
194
+ end
195
+ end,
196
+ Matcher.new(:MULTILINE_BACKQUOTE_STRING_PART, /\A[^`]*\Z/)
197
+ ],
198
+
199
+ :multiline_double_quote_string => [
200
+ Matcher.new(:EOL, /\A\n/),
201
+ Matcher.new(
202
+ :MULTILINE_DOUBLE_QUOTE_STRING_END, /\A(?:[^\\"]|\\\S)*"/, :next_mode => :top) do
203
+ def process_token(token)
204
+ token.gsub!(/\A\s+|"\Z/, "")
205
+ end
206
+ end,
207
+ Matcher.new(
208
+ :MULTILINE_DOUBLE_QUOTE_STRING_PART,
209
+ /\A(?:[^\\"]|\\\S)*\\\s*\Z/,
210
+ :push_back_eol => true) do
211
+ def process_token(token)
212
+ token.gsub!(/\A\s+|\\\s*\Z/, "")
213
+ end
214
+ end,
215
+ Matcher.new(
216
+ :UNCLOSED_DOUBLE_QUOTE_STRING,
217
+ /\A(?:[^\\"]|\\\S)*\Z/,
218
+ :error => "unclosed multiline string")
219
+ ],
220
+
221
+ :multiline_binary => [
222
+ Matcher.new(:EOL, /\A\n/),
223
+ Matcher.new(:MULTILINE_BINARY_END, /\A[\sA-Za-z0-9\/=\+]*\]/, :next_mode => :top) do
224
+ def process_token(token)
225
+ token.gsub!(/\s+|\]\Z/, "")
226
+ end
227
+ end,
228
+ Matcher.new(:MULTILINE_BINARY_PART, /\A[\sA-Za-z0-9\/=\+]*\Z/, :push_back_eol => true) do
229
+ def process_token(token)
230
+ token.gsub!(/\s+/, "")
231
+ end
232
+ end
233
+ ]
234
+ }
235
+
236
+ # @param [IO] the IO to read from
237
+ # @raise [ArgumentError] if +io+ is +nil+.
238
+ def initialize io
239
+ raise ArgumentError, 'io' unless io
240
+ @io = io
241
+ @scanner = nil
242
+ @line_no = -1
243
+ set_mode(:top)
244
+
245
+ @token = nil
246
+ @pushed_back_token = nil
247
+ @previous_token = nil
248
+
249
+ @token_pool = [] # a pool of reusable Tokens
250
+ end
251
+
252
+ # @return [String] text of the current token.
253
+ def token
254
+ @token.text
255
+ end
256
+
257
+ # @return [Symbol] type of the current token (e.g. +:WHITESPACE+)
258
+ def token_type
259
+ @token.type
260
+ end
261
+
262
+ # @return [Integer] position of the current token (only meant for error tracking for the time
263
+ # being)
264
+ def token_line_no
265
+ @token.line_no
266
+ end
267
+
268
+ # @return [Integer] position of the current token (only meant for error tracking for the time
269
+ # being)
270
+ def token_pos
271
+ @token.pos
272
+ end
273
+
274
+ # Sets the current working mode of this Tokenizer.
275
+ #
276
+ # @param [Symbol] new mode
277
+ # * +:top+ (normal default mode)
278
+ # * +:multiline_comment+
279
+ # * +:multiline_backquote_string+
280
+ # * +:multiline_double_quote_string+
281
+ # * +:multiline_binary+
282
+ #
283
+ # @return [self]
284
+ # @raise [ArgumentError] if the given mode is unknown.
285
+ #
286
+ def set_mode(mode)
287
+ ms = @@matcher_sets[mode]
288
+ raise ArgumentError, "unknown tokenizer mode #{mode.to_s}" unless ms
289
+ @matcher_set = ms
290
+ self
291
+ end
292
+
293
+ # Reads a token from the pushed back ones.
294
+ def read_pushed_back
295
+ record_previous_token
296
+
297
+ # Set the current state
298
+ @token = @pushed_back_token
299
+ @pushed_back_token = nil
300
+
301
+ if @token.matcher
302
+ next_mode = @token.matcher.next_mode
303
+ set_mode(next_mode) if next_mode
304
+ end
305
+ end
306
+ private :read_pushed_back
307
+
308
+ # Goes to the next token.
309
+ #
310
+ # @return [Symbol] +nil+ if eof has been reached, the current token type otherwise.
311
+ #
312
+ def read
313
+ if @pushed_back_token
314
+ read_pushed_back
315
+ return @token.type
316
+ end
317
+
318
+ record_previous_token
319
+ @token = nil
320
+
321
+ if @line_no < 0 or @scanner.eos? # fetch a line if beginning or at end of line
322
+ unless read_line
323
+ if previous_token_type == :EOF
324
+ return nil
325
+ else
326
+ @token = Token.new(nil, :EOF, nil, @line_no, @scanner ? @scanner.pos : 0)
327
+ return @token.type
328
+ end
329
+ end
330
+ end
331
+
332
+ pos = @scanner.pos
333
+ @matcher_set.each do |matcher|
334
+ if token_text = @scanner.scan(matcher.regex)
335
+ error = matcher.error
336
+ if error
337
+ raise_parse_error(error)
338
+
339
+ else
340
+ set_matcher_token(matcher, token_text, pos)
341
+ if matcher.push_back_eol and @scanner.eos?
342
+ @scanner.pos = @scanner.pos - @@EOL_STRING.size
343
+ end
344
+ end
345
+ break
346
+ end
347
+ end
348
+
349
+ raise_unexpected_char unless @token
350
+
351
+ return @token.type
352
+ end
353
+
354
+ def record_previous_token
355
+ @previous_token = @token
356
+ end
357
+ private :record_previous_token
358
+
359
+ # Sets the current Token using the Matcher that detected it
360
+ def set_matcher_token(matcher, token_text, pos)
361
+ @token = Token.new(
362
+ matcher.process_token(token_text), matcher.token_type, matcher, @line_no, pos)
363
+
364
+ next_mode = matcher.next_mode
365
+ set_mode(next_mode) if next_mode
366
+ end
367
+ private :set_matcher_token
368
+
369
+ # @return [Symbol] the type of the previous Token.
370
+ def previous_token_type
371
+ @previous_token ? @previous_token.type : nil
372
+ end
373
+
374
+ # Unreads the current token.
375
+ # The previous token becomes the current one
376
+ #
377
+ # @raise if #unread has been called twice in a row (no call to #read)
378
+ def unread
379
+ if @pushed_back_token
380
+ raise "only one token can be pushed back"
381
+ else
382
+ @pushed_back_token = @token
383
+ @token = @previous_token
384
+
385
+ # We have no memory of what happened before
386
+ @previous_token = nil
387
+
388
+ if @token.matcher
389
+ next_mode = @token.matcher.next_mode
390
+ set_mode(next_mode) if next_mode
391
+ end
392
+ end
393
+ end
394
+
395
+ # Raises a standard "unexpected character" error.
396
+ def raise_unexpected_char(msg = "unexpected char")
397
+ raise_parse_error "#{msg}: <#{@scanner.peek(1)}>"
398
+ end
399
+
400
+ def raise_parse_error(msg = "parse error", line_no = @line_no, pos = @scanner.pos)
401
+ line = (line_no == @line_no)? @scanner.string : nil
402
+ raise SdlParseError.new(msg, line_no + 1, pos + 1, line)
403
+ end
404
+
405
+ private
406
+
407
+ # Reads the next line of the IO.
408
+ # All lines are normalized to end with a single '\n'.
409
+ #
410
+ # @return [String] the new read line.
411
+ #
412
+ def read_line
413
+ line = @io.gets
414
+
415
+ if line
416
+ # Clean the line of its end characters
417
+ line.gsub!(/(?:\n|\r\n|\r)\Z/, @@EOL_STRING)
418
+
419
+ @line_no += 1
420
+
421
+ if @scanner
422
+ @scanner.string = line
423
+ else
424
+ @scanner = StringScanner.new(line)
425
+ end
426
+ end
427
+
428
+ line
429
+ end
430
+ end
431
+ end