sdl4r 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +3 -0
- data/Rakefile +45 -0
- data/TODO.txt +117 -0
- data/lib/scratchpad.rb +49 -0
- data/lib/sdl4r/parser.rb +678 -0
- data/lib/sdl4r/reader.rb +171 -0
- data/lib/sdl4r/sdl.rb +242 -0
- data/lib/sdl4r/sdl_binary.rb +78 -0
- data/lib/sdl4r/sdl_parse_error.rb +44 -0
- data/lib/sdl4r/sdl_time_span.rb +301 -0
- data/lib/sdl4r/tag.rb +949 -0
- data/lib/sdl4r/token.rb +129 -0
- data/lib/sdl4r/tokenizer.rb +501 -0
- data/test/sdl4r/parser_test.rb +295 -0
- data/test/sdl4r/test.rb +541 -0
- data/test/sdl4r/test_basic_types.sdl +138 -0
- data/test/sdl4r/test_structures.sdl +180 -0
- metadata +81 -0
data/lib/sdl4r/reader.rb
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
# Simple Declarative Language (SDL) for Ruby
|
2
|
+
# Copyright 2005 Ikayzo, inc.
|
3
|
+
#
|
4
|
+
# This program is free software. You can distribute or modify it under the
|
5
|
+
# terms of the GNU Lesser General Public License version 2.1 as published by
|
6
|
+
# the Free Software Foundation.
|
7
|
+
#
|
8
|
+
# This program is distributed AS IS and WITHOUT WARRANTY. OF ANY KIND,
|
9
|
+
# INCLUDING MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
|
10
|
+
# See the GNU Lesser General Public License for more details.
|
11
|
+
#
|
12
|
+
# You should have received a copy of the GNU Lesser General Public License
|
13
|
+
# along with this program; if not, contact the Free Software Foundation, Inc.,
|
14
|
+
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
15
|
+
|
16
|
+
|
17
|
+
module SDL4R
|
18
|
+
|
19
|
+
# Gives access to the characters read to the Parser.
|
20
|
+
# This class was designed to gather the handling of the UTF-8 issues in one place and shield the
|
21
|
+
# Parser class from these problems.
|
22
|
+
class Reader
|
23
|
+
|
24
|
+
RUBY_1_8_OR_LESS = require 'jcode'
|
25
|
+
|
26
|
+
|
27
|
+
# +io+ an open IO from which the characters are read.
|
28
|
+
def initialize(io)
|
29
|
+
raise ArgumentError, "io == nil" if io.nil?
|
30
|
+
|
31
|
+
@io = io
|
32
|
+
@line = nil
|
33
|
+
@line_chars = nil
|
34
|
+
@line_no = 0
|
35
|
+
@pos = 0
|
36
|
+
end
|
37
|
+
|
38
|
+
attr_reader :line_no, :pos, :line;
|
39
|
+
|
40
|
+
def line_length
|
41
|
+
return @line_chars.nil? ? 0 : @line_chars.length
|
42
|
+
end
|
43
|
+
|
44
|
+
# Reads next line in stream skipping comment lines and blank lines.
|
45
|
+
#
|
46
|
+
# Returns the next line or nil at the end of the file.
|
47
|
+
def read_line
|
48
|
+
@line_chars = nil
|
49
|
+
|
50
|
+
while @line = read_raw_line()
|
51
|
+
# Skip empty and commented lines
|
52
|
+
break unless @line.empty? or @line =~ /^#/
|
53
|
+
end
|
54
|
+
|
55
|
+
return @line
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns the string that goes from the current position of this Reader to the end of the line
|
59
|
+
# or nil if the current position doesn't allow that.
|
60
|
+
def rest_of_line
|
61
|
+
return @line[@pos..-1]
|
62
|
+
end
|
63
|
+
|
64
|
+
# Indicates whether the end of file has been reached.
|
65
|
+
def end_of_file?
|
66
|
+
return @line.nil?
|
67
|
+
end
|
68
|
+
|
69
|
+
# Indicates whether there are more characters in the current line
|
70
|
+
def more_chars_in_line?
|
71
|
+
return @pos < line_length - 1
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns whether the end of the current +line+ as been reached.
|
75
|
+
def end_of_line?
|
76
|
+
return @pos >= line_length
|
77
|
+
end
|
78
|
+
|
79
|
+
# Skips the current line by going just after its end.
|
80
|
+
def skip_line
|
81
|
+
@pos = line_length
|
82
|
+
end
|
83
|
+
|
84
|
+
# Skips the whitespaces that follow the current position.
|
85
|
+
def skip_whitespaces
|
86
|
+
while (@pos + 1) < line_length and
|
87
|
+
(@line[@pos + 1] == ?\s or @line[@pos + 1] == ?\t)
|
88
|
+
@pos += 1
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Returns the character at position +pos+ in the current line.
|
93
|
+
# Returns nil if there is no current line or if +pos+ is after the end of the line.
|
94
|
+
def get_line_char(pos)
|
95
|
+
if @line_chars and pos < line_length
|
96
|
+
return @line_chars[pos]
|
97
|
+
else
|
98
|
+
return nil
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Returns the character at the current position or nil after end-of-line or end-of -file.
|
103
|
+
def current_char
|
104
|
+
return get_line_char(@pos)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Go to the next character in the stream.
|
108
|
+
def skip_char
|
109
|
+
@pos += 1 if @pos < line_length
|
110
|
+
end
|
111
|
+
|
112
|
+
# Go to the next character and returns it (or nil if end-of-line or -file has been reached).
|
113
|
+
def read_char
|
114
|
+
skip_char()
|
115
|
+
return current_char
|
116
|
+
end
|
117
|
+
|
118
|
+
# Returns to the previous char if possible.
|
119
|
+
def previous_char
|
120
|
+
@pos -= 1 if @pos >= -1
|
121
|
+
end
|
122
|
+
|
123
|
+
# Returns the next index of the expression (string, regexp, fixnum) in the current line, starting
|
124
|
+
# from after the current position if no position is specified.
|
125
|
+
def find_next_in_line(searched, start_pos = nil)
|
126
|
+
start_pos = @pos + 1 unless start_pos
|
127
|
+
return @line.index(searched, start_pos)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Skips the specified position in the current line.
|
131
|
+
def skip_to(new_pos)
|
132
|
+
@pos = new_pos
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
# Returns a subpart of the current line starting from +from+ and stopping at +to+ (excluded).
|
137
|
+
def substring(from, to = -1)
|
138
|
+
return @line[from..to]
|
139
|
+
end
|
140
|
+
|
141
|
+
# Reads and returns a "raw" line including lines with comments and blank lines.
|
142
|
+
#
|
143
|
+
# Returns the next line or nil if at the end of the file.
|
144
|
+
#
|
145
|
+
# This method changes the value of @line, @lineNo and @pos.
|
146
|
+
def read_raw_line
|
147
|
+
@line = @io.gets()
|
148
|
+
|
149
|
+
# We ensure that only \n is used as an end-of-line by replacing \r and \r\n.
|
150
|
+
if @line
|
151
|
+
if not @line.gsub!(/\r\n/m, "\n")
|
152
|
+
@line.gsub!(/\r/m, "\n")
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
@pos = 0;
|
157
|
+
@line_chars = nil
|
158
|
+
if @line
|
159
|
+
@line_no += 1
|
160
|
+
@line_chars = @line.scan(/./m)
|
161
|
+
end
|
162
|
+
return @line
|
163
|
+
end
|
164
|
+
|
165
|
+
# Closes this Reader and its underlying +IO+.
|
166
|
+
def close
|
167
|
+
@io.close
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
data/lib/sdl4r/sdl.rb
ADDED
@@ -0,0 +1,242 @@
|
|
1
|
+
# Simple Declarative Language (SDL) for Ruby
|
2
|
+
# Copyright 2005 Ikayzo, inc.
|
3
|
+
#
|
4
|
+
# This program is free software. You can distribute or modify it under the
|
5
|
+
# terms of the GNU Lesser General Public License version 2.1 as published by
|
6
|
+
# the Free Software Foundation.
|
7
|
+
#
|
8
|
+
# This program is distributed AS IS and WITHOUT WARRANTY. OF ANY KIND,
|
9
|
+
# INCLUDING MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
|
10
|
+
# See the GNU Lesser General Public License for more details.
|
11
|
+
#
|
12
|
+
# You should have received a copy of the GNU Lesser General Public License
|
13
|
+
# along with this program; if not, contact the Free Software Foundation, Inc.,
|
14
|
+
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
15
|
+
|
16
|
+
|
17
|
+
require 'jcode'
|
18
|
+
require 'base64'
|
19
|
+
require 'rational'
|
20
|
+
require 'date'
|
21
|
+
|
22
|
+
# Various SDL related utility methods
|
23
|
+
#
|
24
|
+
module SDL4R
|
25
|
+
|
26
|
+
MAX_INTEGER_32 = 2**31 - 1
|
27
|
+
MIN_INTEGER_32 = -(2**31)
|
28
|
+
|
29
|
+
MAX_INTEGER_64 = 2**63 - 1
|
30
|
+
MIN_INTEGER_64 = -(2**63)
|
31
|
+
|
32
|
+
|
33
|
+
# Returns the milliseonds part of a DateTime (by translating the +sec_fraction+ part) as an
|
34
|
+
# integer.
|
35
|
+
def self.get_date_milliseconds(date)
|
36
|
+
sec_fraction = date.sec_fraction() # in days
|
37
|
+
# 86400000 is the number of milliseconds in a day
|
38
|
+
return (sec_fraction * 86400000).round()
|
39
|
+
end
|
40
|
+
|
41
|
+
BASE64_WRAP_LINE_LENGTH = 72
|
42
|
+
|
43
|
+
# Creates an SDL string representation for a given object and returns it.
|
44
|
+
#
|
45
|
+
# +o+:: the object to format
|
46
|
+
# +add_quotes+:: indicates whether quotes will be added to Strings and characters
|
47
|
+
# (true by default)
|
48
|
+
# +line_prefix+:: the line prefix to use ("" by default)
|
49
|
+
# +indent+:: the indent string to use ("\t" by default)
|
50
|
+
#
|
51
|
+
def self.format(o, add_quotes = true, line_prefix = "", indent = "\t")
|
52
|
+
if o.is_a?(String)
|
53
|
+
if add_quotes
|
54
|
+
o_length = 0
|
55
|
+
o.scan(/./m) { o_length += 1 } # counts the number of chars (as opposed of bytes)
|
56
|
+
if o_length == 1
|
57
|
+
return "'" + escape(o, "'") + "'"
|
58
|
+
else
|
59
|
+
return '"' + escape(o, '"') + '"'
|
60
|
+
end
|
61
|
+
else
|
62
|
+
return escape(o)
|
63
|
+
end
|
64
|
+
|
65
|
+
elsif o.is_a?(Bignum)
|
66
|
+
return o.to_s + "BD"
|
67
|
+
|
68
|
+
elsif o.is_a?(Integer)
|
69
|
+
if MIN_INTEGER_32 <= o and o <= MAX_INTEGER_32
|
70
|
+
return o.to_s
|
71
|
+
elsif MIN_INTEGER_64 <= o and o <= MAX_INTEGER_64
|
72
|
+
return o.to_s + "L"
|
73
|
+
else
|
74
|
+
return o.to_s + "BD"
|
75
|
+
end
|
76
|
+
|
77
|
+
elsif o.is_a?(Float)
|
78
|
+
return o.to_s + "F"
|
79
|
+
|
80
|
+
elsif o.is_a?(Rational)
|
81
|
+
return o.to_f.to_s + "F"
|
82
|
+
|
83
|
+
elsif defined? Flt::DecNum and o.is_a?(Flt::DecNum)
|
84
|
+
return o.to_s + "BD"
|
85
|
+
|
86
|
+
elsif o.nil?
|
87
|
+
return "null"
|
88
|
+
|
89
|
+
elsif o.is_a?(SdlBinary)
|
90
|
+
encoded_o = Base64.encode64(o.bytes)
|
91
|
+
encoded_o.gsub!(/[\r\n]/m, "") # Remove the EOL inserted every 60 chars
|
92
|
+
|
93
|
+
if add_quotes
|
94
|
+
if encoded_o.length > BASE64_WRAP_LINE_LENGTH
|
95
|
+
# FIXME: we should a constant or some parameter instead of hardcoded spaces
|
96
|
+
wrap_lines_in_ascii(encoded_o, BASE64_WRAP_LINE_LENGTH, "#{line_prefix}#{indent}")
|
97
|
+
encoded_o.insert(0, "[#{$/}")
|
98
|
+
encoded_o << "#{$/}#{line_prefix}]"
|
99
|
+
else
|
100
|
+
encoded_o.insert(0, "[")
|
101
|
+
encoded_o << "]"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
return encoded_o
|
106
|
+
|
107
|
+
# Below, we use "#{o.year}" instead of "%Y" because "%Y" always emit 4 chars at least even if
|
108
|
+
# the date is before 1000.
|
109
|
+
elsif o.is_a?(DateTime) || o.is_a?(Time)
|
110
|
+
milliseconds = o.is_a?(DateTime) ? get_date_milliseconds(o) : (o.usec / 10).to_i
|
111
|
+
|
112
|
+
if milliseconds == 0
|
113
|
+
if o.zone
|
114
|
+
return o.strftime("#{o.year}/%m/%d %H:%M:%S%Z")
|
115
|
+
else
|
116
|
+
return o.strftime("#{o.year}/%m/%d %H:%M:%S")
|
117
|
+
end
|
118
|
+
else
|
119
|
+
if o.zone
|
120
|
+
return o.strftime("#{o.year}/%m/%d %H:%M:%S." + milliseconds.to_s.ljust(3, '0') + "%Z")
|
121
|
+
else
|
122
|
+
return o.strftime("#{o.year}/%m/%d %H:%M:%S." + milliseconds.to_s.ljust(3, '0'))
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
elsif o.is_a?(Date)
|
127
|
+
return o.strftime("#{o.year}/%m/%d")
|
128
|
+
|
129
|
+
else
|
130
|
+
return o.to_s
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Wraps lines in "s" (by modifying it). This method only supports 1-byte character strings.
|
135
|
+
#
|
136
|
+
def self.wrap_lines_in_ascii(s, line_length, line_prefix = nil)
|
137
|
+
# We could use such code if it supported any value for "line_prefix": unfortunately it is capped
|
138
|
+
# at 64 in the regular expressions.
|
139
|
+
#
|
140
|
+
# return "#{line_prefix}" + encoded_o.scan(/.{1,#{line_prefix}}/).join("#{$/}#{line_prefix}")
|
141
|
+
|
142
|
+
eol_size = "#{$/}".size
|
143
|
+
|
144
|
+
i = 0
|
145
|
+
while i < s.size
|
146
|
+
if i > 0
|
147
|
+
s.insert(i, $/)
|
148
|
+
i += eol_size
|
149
|
+
end
|
150
|
+
|
151
|
+
if line_prefix
|
152
|
+
s.insert(i, line_prefix)
|
153
|
+
i += line_prefix.size
|
154
|
+
end
|
155
|
+
|
156
|
+
i += line_length
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
ESCAPED_QUOTES = {
|
161
|
+
"\"" => "\\\"",
|
162
|
+
"'" => "\\'",
|
163
|
+
"`" => "\\`",
|
164
|
+
}
|
165
|
+
|
166
|
+
ESCAPED_CHARS = {
|
167
|
+
"\\" => "\\\\",
|
168
|
+
"\t" => "\\t",
|
169
|
+
"\r" => "\\r",
|
170
|
+
"\n" => "\\n",
|
171
|
+
}
|
172
|
+
ESCAPED_CHARS.merge!(ESCAPED_QUOTES)
|
173
|
+
|
174
|
+
# Returns an escaped version of +s+ (i.e. where characters which need to be
|
175
|
+
# escaped, are escaped).
|
176
|
+
#
|
177
|
+
def self.escape(s, quote_char = nil)
|
178
|
+
escaped_s = ""
|
179
|
+
|
180
|
+
s.each_char { |c|
|
181
|
+
escaped_char = ESCAPED_CHARS[c]
|
182
|
+
if escaped_char
|
183
|
+
if ESCAPED_QUOTES.has_key?(c)
|
184
|
+
if quote_char && c == quote_char
|
185
|
+
escaped_s << escaped_char
|
186
|
+
else
|
187
|
+
escaped_s << c
|
188
|
+
end
|
189
|
+
else
|
190
|
+
escaped_s << escaped_char
|
191
|
+
end
|
192
|
+
else
|
193
|
+
escaped_s << c
|
194
|
+
end
|
195
|
+
}
|
196
|
+
|
197
|
+
return escaped_s
|
198
|
+
end
|
199
|
+
|
200
|
+
# This method was kept from the Java code but it is not sure if it should have a usefulness yet.
|
201
|
+
#
|
202
|
+
def self.coerce_or_fail(o)
|
203
|
+
return o
|
204
|
+
end
|
205
|
+
|
206
|
+
# Validates an SDL identifier String. SDL Identifiers must start with a
|
207
|
+
# Unicode letter or underscore (_) and contain only unicode letters,
|
208
|
+
# digits, underscores (_), and dashes(-).
|
209
|
+
#
|
210
|
+
# @param identifier The identifier to validate
|
211
|
+
# @throws IllegalArgumentException if the identifier is not legal
|
212
|
+
#
|
213
|
+
# TODO: support UTF-8 identifiers
|
214
|
+
#
|
215
|
+
def self.validate_identifier(identifier)
|
216
|
+
if identifier.nil? or identifier.empty?
|
217
|
+
raise ArgumentError, "SDL identifiers cannot be null or empty."
|
218
|
+
end
|
219
|
+
|
220
|
+
# in Java, was if(!Character.isJavaIdentifierStart(identifier.charAt(0)))
|
221
|
+
unless identifier =~ /^[a-zA-Z_]/
|
222
|
+
raise ArgumentError,
|
223
|
+
"'" + identifier[0..0] +
|
224
|
+
"' is not a legal first character for an SDL identifier. " +
|
225
|
+
"SDL Identifiers must start with a unicode letter or " +
|
226
|
+
"an underscore (_)."
|
227
|
+
end
|
228
|
+
|
229
|
+
unless identifier.length == 1 or identifier =~ /^[a-zA-Z_][a-zA-Z_0-9\-]*$/
|
230
|
+
for i in 1..identifier.length
|
231
|
+
unless identifier[i..i] =~ /^[a-zA-Z_0-9\-]$/
|
232
|
+
raise ArgumentError,
|
233
|
+
"'" + identifier[i..i] +
|
234
|
+
"' is not a legal character for an SDL identifier. " +
|
235
|
+
"SDL Identifiers must start with a unicode letter or " +
|
236
|
+
"underscore (_) followed by 0 or more unicode " +
|
237
|
+
"letters, digits, underscores (_), or dashes (-)"
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# Simple Declarative Language (SDL) for Ruby
|
2
|
+
# Copyright 2005 Ikayzo, inc.
|
3
|
+
#
|
4
|
+
# This program is free software. You can distribute or modify it under the
|
5
|
+
# terms of the GNU Lesser General Public License version 2.1 as published by
|
6
|
+
# the Free Software Foundation.
|
7
|
+
#
|
8
|
+
# This program is distributed AS IS and WITHOUT WARRANTY. OF ANY KIND,
|
9
|
+
# INCLUDING MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
|
10
|
+
# See the GNU Lesser General Public License for more details.
|
11
|
+
#
|
12
|
+
# You should have received a copy of the GNU Lesser General Public License
|
13
|
+
# along with this program; if not, contact the Free Software Foundation, Inc.,
|
14
|
+
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
15
|
+
|
16
|
+
|
17
|
+
module SDL4R
|
18
|
+
|
19
|
+
#
|
20
|
+
# Represents a binary value.
|
21
|
+
# This class was introduced to avoid the confusion between a Ruby String and a binary literal.
|
22
|
+
#
|
23
|
+
class SdlBinary
|
24
|
+
|
25
|
+
attr_accessor :bytes
|
26
|
+
|
27
|
+
# +value+: a String containing the bytes
|
28
|
+
def initialize(bytes)
|
29
|
+
@bytes = bytes
|
30
|
+
end
|
31
|
+
|
32
|
+
def ==(o)
|
33
|
+
return true if self.equal?(o)
|
34
|
+
return false if not o.instance_of?(self.class)
|
35
|
+
return self.bytes == o.bytes
|
36
|
+
end
|
37
|
+
|
38
|
+
def eql?(o)
|
39
|
+
self == o
|
40
|
+
end
|
41
|
+
|
42
|
+
def hash
|
43
|
+
return bytes.hash
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns the bytes base64-encoded.
|
47
|
+
def to_s
|
48
|
+
return Base64.encode64(bytes)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Decodes the specified base-64 encoded string and returns a corresponding SdlBinary
|
52
|
+
# instance.
|
53
|
+
# +s+ might not include the conventional starting and ending square brackets.
|
54
|
+
def self.decode64(s)
|
55
|
+
s = s.delete("\n\r\t ")
|
56
|
+
|
57
|
+
binary = Base64.decode64(s)
|
58
|
+
|
59
|
+
if binary.empty? and not s.empty?
|
60
|
+
raise ArgumentError, "bad binary literal"
|
61
|
+
end
|
62
|
+
|
63
|
+
return SdlBinary.new(binary)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Try to coerce 'o' into a SdlBinary.
|
68
|
+
# Raise an ArgumentError if it fails.
|
69
|
+
def self.SdlBinary(o)
|
70
|
+
if o.kind_of? SdlBinary
|
71
|
+
return o
|
72
|
+
elsif o.kind_of? String
|
73
|
+
return SdlBinary.new(o)
|
74
|
+
else
|
75
|
+
raise ArgumentError, "can't coerce argument"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|