sdl4r 0.9.9 → 0.9.11
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +70 -1
- data/LICENSE +499 -497
- data/Rakefile +38 -28
- data/TODO +194 -45
- data/doc/classes/SDL4R/AbbreviationTimezoneProxy.html +151 -0
- data/doc/classes/SDL4R/ConstantTimezone.html +129 -0
- data/doc/classes/SDL4R/Element.html +148 -0
- data/doc/classes/SDL4R/Reader.html +683 -0
- data/doc/classes/SDL4R/RelativeTimezone.html +187 -0
- data/doc/classes/SDL4R/SdlBinary.html +188 -0
- data/doc/classes/SDL4R/SdlParseError.html +110 -0
- data/doc/classes/SDL4R/SdlTimeSpan.html +651 -0
- data/doc/classes/SDL4R/Serializer.html +557 -0
- data/doc/classes/SDL4R/Serializer/Ref.html +138 -0
- data/doc/classes/SDL4R/TZAbbreviationDB/Record.html +117 -0
- data/doc/classes/SDL4R/Tag.html +1274 -0
- data/doc/classes/SDL4R/Token.html +131 -0
- data/doc/created.rid +1 -0
- data/doc/files/CHANGELOG.html +290 -0
- data/doc/files/LICENSE.html +53 -0
- data/doc/files/README.html +405 -0
- data/doc/files/lib/sdl4r/abbreviation_timezone_proxy_rb.html +63 -0
- data/doc/files/lib/sdl4r/constant_timezone_rb.html +54 -0
- data/doc/files/lib/sdl4r/element_rb.html +54 -0
- data/doc/files/lib/sdl4r/reader_rb.html +68 -0
- data/doc/files/lib/sdl4r/relative_timezone_rb.html +62 -0
- data/doc/files/lib/sdl4r/sdl4r_rb.html +66 -0
- data/doc/files/lib/sdl4r/sdl4r_tzinfo_rb.html +64 -0
- data/doc/files/lib/sdl4r/sdl4r_version_rb.html +54 -0
- data/doc/files/lib/sdl4r/sdl_binary_rb.html +54 -0
- data/doc/files/lib/sdl4r/sdl_parse_error_rb.html +54 -0
- data/doc/files/lib/sdl4r/sdl_time_span_rb.html +54 -0
- data/doc/files/lib/sdl4r/serializer_rb.html +62 -0
- data/doc/files/lib/sdl4r/tag_rb.html +66 -0
- data/doc/files/lib/sdl4r/token_rb.html +54 -0
- data/doc/files/lib/sdl4r/tokenizer_rb.html +64 -0
- data/doc/files/lib/sdl4r/tz_abbreviation_db_rb.html +67 -0
- data/doc/files/lib/sdl4r_rb.html +54 -0
- data/doc/files/lib/sdl4r_tzinfo_rb.html +54 -0
- data/doc/fr_class_index.html +23 -0
- data/doc/fr_file_index.html +40 -0
- data/doc/fr_method_index.html +4711 -0
- data/doc/index.html +15 -0
- data/doc/rdoc-style.css +328 -0
- data/lib/sdl4r.rb +3 -1
- data/lib/sdl4r/abbreviation_timezone_proxy.rb +38 -0
- data/lib/sdl4r/constant_timezone.rb +58 -0
- data/{test/sdl4r/sdl_test.rb → lib/sdl4r/element.rb} +19 -14
- data/lib/sdl4r/reader.rb +772 -0
- data/lib/sdl4r/relative_timezone.rb +156 -0
- data/lib/sdl4r/sdl4r.rb +187 -45
- data/lib/sdl4r/sdl4r_tzinfo.rb +75 -0
- data/lib/sdl4r/sdl4r_version.rb +24 -0
- data/lib/sdl4r/sdl_parse_error.rb +1 -1
- data/lib/sdl4r/sdl_time_span.rb +5 -1
- data/lib/sdl4r/serializer.rb +473 -60
- data/lib/sdl4r/tag.rb +126 -51
- data/lib/sdl4r/token.rb +49 -0
- data/lib/sdl4r/tokenizer.rb +431 -0
- data/lib/sdl4r/tz_abbreviation_db.rb +266 -0
- data/read_jprof.html +16934 -0
- data/read_jprof_pull.html +14451 -0
- data/read_prof.html +4983 -0
- data/read_prof_pull.html +4896 -0
- data/test/sdl4r/parser_test.rb +577 -503
- data/test/sdl4r/read_jprof.rb +58 -0
- data/test/sdl4r/read_prof.rb +70 -0
- data/test/sdl4r/reader_test.rb +173 -0
- data/test/sdl4r/relative_timezone_test.rb +102 -0
- data/test/sdl4r/sdl4r_test.rb +611 -528
- data/test/sdl4r/sdl4r_tzinfo_test.rb +108 -0
- data/test/sdl4r/sdl_test_case.rb +60 -0
- data/test/sdl4r/serializer_test.rb +448 -11
- data/test/sdl4r/tag_test.rb +84 -5
- data/test/sdl4r/tokenizer_test.rb +128 -0
- metadata +69 -11
- data/lib/sdl4r/parser.rb +0 -648
- data/lib/sdl4r/parser/reader.rb +0 -184
- data/lib/sdl4r/parser/time_span_with_zone.rb +0 -57
- data/lib/sdl4r/parser/token.rb +0 -138
- data/lib/sdl4r/parser/tokenizer.rb +0 -507
data/lib/sdl4r/tag.rb
CHANGED
@@ -25,8 +25,8 @@ module SDL4R
|
|
25
25
|
require 'open-uri'
|
26
26
|
require 'stringio'
|
27
27
|
|
28
|
-
require
|
29
|
-
require
|
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 =
|
59
|
+
# namespace, name = to_ns_n namespace, name
|
60
60
|
# end
|
61
61
|
#
|
62
|
-
|
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 :
|
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 =
|
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(
|
290
|
-
|
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(
|
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(
|
345
|
-
|
346
|
-
|
347
|
-
|
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
|
-
|
362
|
-
|
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.
|
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,
|
528
|
-
|
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 =
|
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 =
|
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 |
|
601
|
-
qualified_name =
|
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 =
|
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=(
|
705
|
-
|
706
|
-
SDL4R.validate_identifier(
|
707
|
-
@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=(
|
716
|
-
|
717
|
-
SDL4R.validate_identifier(
|
718
|
-
@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
|
-
|
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_::
|
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 << "=\""
|
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 << "=\""
|
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!(/"/, """)
|
1054
|
+
s.gsub!(/'/, "'")
|
1055
|
+
end
|
1056
|
+
s.gsub!(/</, ">")
|
1057
|
+
s.gsub!(/>/, "<")
|
1058
|
+
s.gsub!(/&/, "&")
|
1059
|
+
s
|
1060
|
+
end
|
1061
|
+
private :value_string_to_xml
|
987
1062
|
end
|
988
1063
|
end
|
data/lib/sdl4r/token.rb
ADDED
@@ -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
|