sdl4r 0.9.1 → 0.9.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +0 -0
- data/LICENSE +502 -0
- data/README +296 -1
- data/Rakefile +7 -4
- data/TODO.txt +33 -21
- data/doc/created.rid +1 -0
- data/doc/fr_class_index.html +36 -0
- data/doc/fr_file_index.html +39 -0
- data/doc/fr_method_index.html +141 -0
- data/doc/index.html +24 -0
- data/doc/rdoc-style.css +208 -0
- data/lib/sdl4r/parser.rb +1 -30
- data/lib/sdl4r/parser/reader.rb +175 -0
- data/lib/sdl4r/parser/time_span_with_zone.rb +52 -0
- data/lib/sdl4r/parser/token.rb +133 -0
- data/lib/sdl4r/parser/tokenizer.rb +506 -0
- data/lib/sdl4r/sdl.rb +126 -76
- data/lib/sdl4r/tag.rb +108 -54
- data/test/sdl4r/parser_test.rb +90 -19
- data/test/sdl4r/test.rb +93 -101
- data/test/sdl4r/test_basic_types.sdl +3 -3
- metadata +49 -37
- data/lib/scratchpad.rb +0 -49
- data/lib/sdl4r/reader.rb +0 -171
- data/lib/sdl4r/token.rb +0 -129
- data/lib/sdl4r/tokenizer.rb +0 -501
data/lib/sdl4r/tokenizer.rb
DELETED
@@ -1,501 +0,0 @@
|
|
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
|
-
require File.dirname(__FILE__) + '/reader'
|
20
|
-
require File.dirname(__FILE__) + '/token'
|
21
|
-
|
22
|
-
# Tokenizer of the SDL parser
|
23
|
-
class Tokenizer
|
24
|
-
|
25
|
-
TOKEN_TYPES = [
|
26
|
-
:IDENTIFIER,
|
27
|
-
|
28
|
-
# punctuation
|
29
|
-
:COLON, :SEMICOLON, :EQUALS, :START_BLOCK, :END_BLOCK,
|
30
|
-
|
31
|
-
# literals
|
32
|
-
:STRING, :CHARACTER, :BOOLEAN, :NUMBER, :DATE, :TIME, :BINARY, :NULL ]
|
33
|
-
|
34
|
-
|
35
|
-
# Creates an SDL tokenizer on the specified +IO+.
|
36
|
-
def initialize(io)
|
37
|
-
raise ArgumentError, "io == nil" if io.nil?
|
38
|
-
|
39
|
-
@reader = Reader.new(io)
|
40
|
-
@token_start = 0
|
41
|
-
@startEscapedQuoteLine = false
|
42
|
-
@tokens = nil
|
43
|
-
@tokenText = nil
|
44
|
-
end
|
45
|
-
|
46
|
-
# Closes this Tokenizer and its underlying +Reader+.
|
47
|
-
def close
|
48
|
-
@reader.close
|
49
|
-
end
|
50
|
-
|
51
|
-
# Close the reader and throw a SdlParseError.
|
52
|
-
def parse_error(description, line_no = nil, position = nil)
|
53
|
-
begin
|
54
|
-
@reader.close()
|
55
|
-
rescue IOError
|
56
|
-
# no recourse
|
57
|
-
end
|
58
|
-
|
59
|
-
line_no = @reader.line_no if line_no.nil?
|
60
|
-
position = @reader.pos if position.nil?
|
61
|
-
|
62
|
-
# We add one because editors typically start with line 1 and position 1
|
63
|
-
# rather than 0...
|
64
|
-
raise SdlParseError.new(description, line_no + 1, position + 1, @reader.line)
|
65
|
-
end
|
66
|
-
|
67
|
-
# Close the reader and throw a SdlParseError using the format
|
68
|
-
# Was expecting X but got Y.
|
69
|
-
#
|
70
|
-
def expecting_but_got(expecting, got, line, position)
|
71
|
-
parse_error("Was expecting #{expecting} but got #{got}", line, position)
|
72
|
-
end
|
73
|
-
|
74
|
-
# Returns the next line as tokens or nil if the end of the stream has been reached.
|
75
|
-
# This method handles line continuations both within and outside String literals.
|
76
|
-
# The line of tokens is assigned to @tokens.
|
77
|
-
#
|
78
|
-
# Returns a logical line as a list of Tokens.
|
79
|
-
#
|
80
|
-
def read_line_tokens
|
81
|
-
begin
|
82
|
-
read_line_tokens_even_if_empty()
|
83
|
-
end until @tokens.nil? or !@tokens.empty?
|
84
|
-
return @tokens
|
85
|
-
end
|
86
|
-
|
87
|
-
def line_no
|
88
|
-
@reader.line_no
|
89
|
-
end
|
90
|
-
|
91
|
-
def pos
|
92
|
-
@reader.pos
|
93
|
-
end
|
94
|
-
|
95
|
-
def line
|
96
|
-
@reader.line
|
97
|
-
end
|
98
|
-
|
99
|
-
private
|
100
|
-
|
101
|
-
# Returns the next line as tokens or nil if the end of the stream has been reached.
|
102
|
-
# This method handles line continuations both within and outside String literals.
|
103
|
-
# The line of tokens is assigned to @tokens.
|
104
|
-
#
|
105
|
-
# Returns a logical line as a list of Tokens.
|
106
|
-
# Returns an empty array if the line was empty.
|
107
|
-
#
|
108
|
-
def read_line_tokens_even_if_empty
|
109
|
-
@tokens = nil
|
110
|
-
@tokenText = nil
|
111
|
-
@token_start = nil
|
112
|
-
|
113
|
-
@reader.read_line() if @reader.end_of_line?
|
114
|
-
return @tokens unless @reader.line
|
115
|
-
|
116
|
-
@tokens = []
|
117
|
-
@token_start = @reader.pos
|
118
|
-
|
119
|
-
while not @reader.end_of_line?
|
120
|
-
if @tokenText
|
121
|
-
@tokens << Token.new(@tokenText, @reader.line_no, @token_start)
|
122
|
-
@tokenText = nil
|
123
|
-
end
|
124
|
-
|
125
|
-
c = @reader.current_char
|
126
|
-
next_c = @reader.get_line_char(@reader.pos + 1)
|
127
|
-
case c
|
128
|
-
when "\""
|
129
|
-
# handle "" style strings including line continuations
|
130
|
-
handle_double_quote_string()
|
131
|
-
|
132
|
-
when "'"
|
133
|
-
handle_character_literal()
|
134
|
-
|
135
|
-
when "{", "}", "=", ":", ";"
|
136
|
-
# handle punctuation
|
137
|
-
punctuation_token = Token.new(c, @reader.line_no, @reader.pos)
|
138
|
-
@tokenText = nil
|
139
|
-
|
140
|
-
if punctuation_token.type == :SEMICOLON
|
141
|
-
@reader.skip_char()
|
142
|
-
break
|
143
|
-
else
|
144
|
-
@tokens << punctuation_token
|
145
|
-
end
|
146
|
-
|
147
|
-
when "#"
|
148
|
-
# skip : hash comment at end of line
|
149
|
-
@reader.skip_line()
|
150
|
-
|
151
|
-
when "/"
|
152
|
-
# handle // and /**/ style comments
|
153
|
-
if next_c == "/"
|
154
|
-
# skip : // comment
|
155
|
-
@reader.skip_line()
|
156
|
-
else
|
157
|
-
handle_slash_comment()
|
158
|
-
end
|
159
|
-
|
160
|
-
when "`"
|
161
|
-
# handle multiline `` style strings
|
162
|
-
handle_back_quote_string()
|
163
|
-
|
164
|
-
when "["
|
165
|
-
# handle binary literals
|
166
|
-
handle_binary_literal()
|
167
|
-
|
168
|
-
when "\s", "\t"
|
169
|
-
@reader.skip_whitespaces()
|
170
|
-
|
171
|
-
when "\\"
|
172
|
-
# line continuations (outside a string literal)
|
173
|
-
handle_line_continuation();
|
174
|
-
|
175
|
-
when /^[0-9\-\.]$/
|
176
|
-
if c == "-" and next_c == "-"
|
177
|
-
# -- comments : ignore
|
178
|
-
@reader.skip_line()
|
179
|
-
else
|
180
|
-
# handle numbers, dates, and time spans
|
181
|
-
handle_number_date_or_time_span()
|
182
|
-
end
|
183
|
-
|
184
|
-
when /^[a-zA-Z\$_]$/
|
185
|
-
# FIXME Here, the Java code specifies isJavaIdentifierStart() but
|
186
|
-
# this is not easily implemented (at least as of Ruby 1.8).
|
187
|
-
# So, we implement a subset of these characters.
|
188
|
-
handle_identifier()
|
189
|
-
|
190
|
-
when "\n", "\r"
|
191
|
-
# end of line
|
192
|
-
@reader.skip_line()
|
193
|
-
|
194
|
-
else
|
195
|
-
parse_error("Unexpected character '#{c}'")
|
196
|
-
end
|
197
|
-
|
198
|
-
@reader.skip_char()
|
199
|
-
end
|
200
|
-
|
201
|
-
if @tokenText
|
202
|
-
@tokens << Token.new(@tokenText, @reader.line_no, @token_start)
|
203
|
-
end
|
204
|
-
|
205
|
-
return @tokens
|
206
|
-
end
|
207
|
-
|
208
|
-
# Adds the current escaped character (represented by ((|c|))) to @tokenText.
|
209
|
-
# This method assumes the previous char was a backslash.
|
210
|
-
#
|
211
|
-
def add_escaped_char_in_string(c)
|
212
|
-
case c
|
213
|
-
when "\\", "\""
|
214
|
-
@tokenText << c
|
215
|
-
when "n"
|
216
|
-
@tokenText << ?\n
|
217
|
-
when "r"
|
218
|
-
@tokenText << ?\r
|
219
|
-
when "t"
|
220
|
-
@tokenText << ?\t
|
221
|
-
else
|
222
|
-
parse_error("Illegal escape character in string literal: '#{c.chr}'.")
|
223
|
-
end
|
224
|
-
end
|
225
|
-
|
226
|
-
def handle_double_quote_string
|
227
|
-
escaped = false
|
228
|
-
@startEscapedQuoteLine = false
|
229
|
-
|
230
|
-
@tokenText = "\""
|
231
|
-
@reader.skip_char()
|
232
|
-
|
233
|
-
while not @reader.end_of_line?
|
234
|
-
c = @reader.current_char
|
235
|
-
|
236
|
-
if "\s\t".include?(c) and @startEscapedQuoteLine
|
237
|
-
# we continue
|
238
|
-
else
|
239
|
-
@startEscapedQuoteLine = false;
|
240
|
-
|
241
|
-
if escaped
|
242
|
-
add_escaped_char_in_string(c)
|
243
|
-
escaped = false
|
244
|
-
|
245
|
-
elsif c == "\\"
|
246
|
-
# check for String broken across lines
|
247
|
-
if @reader.rest_of_line =~ /^\\\s*$/
|
248
|
-
handle_escaped_double_quoted_string()
|
249
|
-
next # as we are at the beginning of a new line
|
250
|
-
else
|
251
|
-
escaped = true;
|
252
|
-
end
|
253
|
-
|
254
|
-
else
|
255
|
-
@tokenText << c
|
256
|
-
if c == "\""
|
257
|
-
# end of double-quoted string detected
|
258
|
-
@tokens << Token.new(@tokenText, @reader.line_no, @token_start)
|
259
|
-
@tokenText = nil
|
260
|
-
return
|
261
|
-
end
|
262
|
-
end
|
263
|
-
end
|
264
|
-
|
265
|
-
@reader.skip_char()
|
266
|
-
end
|
267
|
-
|
268
|
-
# detection of ill-terminated literals
|
269
|
-
if @tokenText =~ /^".*[^"]$/
|
270
|
-
parse_error(
|
271
|
-
"String literal \"#{@tokenText}\" not terminated by end quote.", @reader.line_no, @reader.line_length);
|
272
|
-
elsif @tokenText == "\""
|
273
|
-
parse_error("Orphan quote (unterminated string)", @reader.line_no, @reader.line_length);
|
274
|
-
end
|
275
|
-
end
|
276
|
-
|
277
|
-
def handle_escaped_double_quoted_string
|
278
|
-
# '\' can be followed by whitespaces
|
279
|
-
if @reader.rest_of_line =~ /^\\\s*$/
|
280
|
-
@reader.read_line()
|
281
|
-
parse_error("Escape at end of file.") if @reader.end_of_file?
|
282
|
-
|
283
|
-
@startEscapedQuoteLine = true
|
284
|
-
|
285
|
-
else
|
286
|
-
parse_error(
|
287
|
-
"Malformed string literal - escape followed by whitespace " +
|
288
|
-
"followed by non-whitespace.")
|
289
|
-
end
|
290
|
-
end
|
291
|
-
|
292
|
-
def handle_character_literal
|
293
|
-
if not @reader.more_chars_in_line?
|
294
|
-
parse_error("Got ' at end of line")
|
295
|
-
end
|
296
|
-
|
297
|
-
c2 = @reader.read_char()
|
298
|
-
|
299
|
-
if c2 == "\\"
|
300
|
-
if @reader.end_of_line?
|
301
|
-
parse_error("Got '\\ at end of line")
|
302
|
-
end
|
303
|
-
|
304
|
-
c3 = @reader.read_char()
|
305
|
-
|
306
|
-
if not @reader.more_chars_in_line?
|
307
|
-
parse_error("Got '\\#{c3} at end of line")
|
308
|
-
end
|
309
|
-
|
310
|
-
case c3
|
311
|
-
when "\\"
|
312
|
-
@tokens << Token.new("'\\'", @reader.line_no, @reader.pos)
|
313
|
-
when "'"
|
314
|
-
@tokens << Token.new("'''", @reader.line_no, @reader.pos)
|
315
|
-
when "n"
|
316
|
-
@tokens << Token.new("'\n'", @reader.line_no, @reader.pos)
|
317
|
-
when "r"
|
318
|
-
@tokens << Token.new("'\r'", @reader.line_no, @reader.pos)
|
319
|
-
when "t"
|
320
|
-
@tokens << Token.new("'\t'", @reader.line_no, @reader.pos)
|
321
|
-
else
|
322
|
-
parse_error("Illegal escape character #{@reader.current_char}")
|
323
|
-
end
|
324
|
-
|
325
|
-
@reader.skip_char()
|
326
|
-
if @reader.current_char != "'"
|
327
|
-
expecting_but_got("single quote (')", "\"#{@reader.current_char}\"")
|
328
|
-
end
|
329
|
-
else
|
330
|
-
@tokens << Token.new("'#{c2}'", @reader.line_no, @reader.pos)
|
331
|
-
if not @reader.more_chars_in_line?
|
332
|
-
parse_error("Got '#{c2} at end of line")
|
333
|
-
end
|
334
|
-
@reader.skip_char()
|
335
|
-
if @reader.current_char != "'"
|
336
|
-
expecting_but_got(
|
337
|
-
"quote (')", "\"#{@reader.current_char}\"", @reader.line_no, @reader.pos)
|
338
|
-
end
|
339
|
-
end
|
340
|
-
end
|
341
|
-
|
342
|
-
def handle_slash_comment
|
343
|
-
if not @reader.more_chars_in_line?
|
344
|
-
parse_error("Got slash (/) at end of line.")
|
345
|
-
end
|
346
|
-
|
347
|
-
if @reader.get_line_char(@reader.pos + 1) == "*"
|
348
|
-
end_index = @reader.find_next_in_line("*/")
|
349
|
-
if end_index
|
350
|
-
# handle comment on same line
|
351
|
-
@reader.skip_to(end_index + 1)
|
352
|
-
else
|
353
|
-
# handle multiline comments
|
354
|
-
loop do
|
355
|
-
@reader.read_raw_line()
|
356
|
-
if @reader.end_of_file?
|
357
|
-
parse_error("/* comment not terminated.", @reader.line_no, -2)
|
358
|
-
end
|
359
|
-
|
360
|
-
end_index = @reader.find_next_in_line("*/", 0)
|
361
|
-
|
362
|
-
if end_index
|
363
|
-
@reader.skip_to(end_index + 1)
|
364
|
-
break
|
365
|
-
end
|
366
|
-
end
|
367
|
-
end
|
368
|
-
elsif @reader.get_line_char(@reader.pos + 1) == "/"
|
369
|
-
parse_error("Got slash (/) in unexpected location.")
|
370
|
-
end
|
371
|
-
end
|
372
|
-
|
373
|
-
def handle_back_quote_string
|
374
|
-
end_index = @reader.find_next_in_line("`")
|
375
|
-
|
376
|
-
if end_index
|
377
|
-
# handle end quote on same line
|
378
|
-
@tokens << Token.new(@reader.substring(@reader.pos, end_index), @reader.line_no, @reader.pos)
|
379
|
-
@tokenText = nil
|
380
|
-
@reader.skip_to(end_index)
|
381
|
-
|
382
|
-
else
|
383
|
-
@tokenText = @reader.rest_of_line
|
384
|
-
@token_start = @reader.pos
|
385
|
-
# handle multiline quotes
|
386
|
-
loop do
|
387
|
-
@reader.read_raw_line()
|
388
|
-
if @reader.end_of_file?
|
389
|
-
parse_error("` quote not terminated.", @reader.line_no, -2)
|
390
|
-
end
|
391
|
-
|
392
|
-
end_index = @reader.find_next_in_line("`", 0)
|
393
|
-
if end_index
|
394
|
-
@tokenText << @reader.substring(0, end_index)
|
395
|
-
@reader.skip_to(end_index)
|
396
|
-
break
|
397
|
-
else
|
398
|
-
@tokenText << @reader.line
|
399
|
-
end
|
400
|
-
end
|
401
|
-
|
402
|
-
@tokens << Token.new(@tokenText, @reader.line_no, @token_start)
|
403
|
-
@tokenText = nil
|
404
|
-
end
|
405
|
-
end
|
406
|
-
|
407
|
-
def handle_binary_literal
|
408
|
-
end_index = @reader.find_next_in_line("]")
|
409
|
-
|
410
|
-
if end_index
|
411
|
-
# handle end quote on same line
|
412
|
-
@tokens << Token.new(@reader.substring(@reader.pos, end_index), @reader.line_no, @reader.pos)
|
413
|
-
@tokenText = nil
|
414
|
-
@reader.skip_to(end_index)
|
415
|
-
else
|
416
|
-
@tokenText = @reader.substring(@reader.pos)
|
417
|
-
@token_start = @reader.pos
|
418
|
-
# handle multiline quotes
|
419
|
-
loop do
|
420
|
-
@reader.read_raw_line()
|
421
|
-
if @reader.end_of_file?
|
422
|
-
parse_error("[base64] binary literal not terminated.", @reader.line_no, -2)
|
423
|
-
end
|
424
|
-
|
425
|
-
end_index = @reader.find_next_in_line("]", 0)
|
426
|
-
if end_index
|
427
|
-
@tokenText << @reader.substring(0, end_index)
|
428
|
-
@reader.skip_to(end_index)
|
429
|
-
break
|
430
|
-
else
|
431
|
-
@tokenText << @reader.line
|
432
|
-
end
|
433
|
-
end
|
434
|
-
|
435
|
-
@tokens << Token.new(@tokenText, @reader.line_no, @token_start)
|
436
|
-
@tokenText = nil
|
437
|
-
end
|
438
|
-
end
|
439
|
-
|
440
|
-
# handle a line continuation (not inside a string)
|
441
|
-
def handle_line_continuation
|
442
|
-
# backslash line continuation outside of a String literal
|
443
|
-
# can only occur at the end of a line
|
444
|
-
if not @reader.rest_of_line =~ /^\\\s*$/
|
445
|
-
parse_error("Line continuation (\\) before end of line")
|
446
|
-
else
|
447
|
-
@line = @reader.read_line()
|
448
|
-
if @line.nil?
|
449
|
-
parse_error("Line continuation at end of file.", @reader.line_no, @reader.pos)
|
450
|
-
end
|
451
|
-
end
|
452
|
-
end
|
453
|
-
|
454
|
-
def handle_number_date_or_time_span
|
455
|
-
@token_start = @reader.pos
|
456
|
-
@tokenText = ""
|
457
|
-
|
458
|
-
while not @reader.end_of_line?
|
459
|
-
c = @reader.current_char
|
460
|
-
|
461
|
-
if c =~ /[\w\.\-+:]/
|
462
|
-
@tokenText << c
|
463
|
-
elsif c == "/" and not @reader.get_line_char(@reader.pos + 1) == "*"
|
464
|
-
@tokenText << c
|
465
|
-
else
|
466
|
-
@reader.previous_char()
|
467
|
-
break
|
468
|
-
end
|
469
|
-
|
470
|
-
@reader.skip_char()
|
471
|
-
end
|
472
|
-
|
473
|
-
@tokens << Token.new(@tokenText, @reader.line_no, @token_start)
|
474
|
-
@tokenText = nil
|
475
|
-
end
|
476
|
-
|
477
|
-
def handle_identifier
|
478
|
-
@token_start = @reader.pos;
|
479
|
-
@tokenText = ""
|
480
|
-
|
481
|
-
while not @reader.end_of_line?
|
482
|
-
c = @reader.current_char
|
483
|
-
|
484
|
-
# FIXME here we are stricter than the Java version because there is no
|
485
|
-
# easy way to implement Character.isJavaIdentifierPart() in Ruby :)
|
486
|
-
if c =~ /[\w_$-]/
|
487
|
-
@tokenText << c
|
488
|
-
else
|
489
|
-
@reader.previous_char()
|
490
|
-
break
|
491
|
-
end
|
492
|
-
|
493
|
-
@reader.skip_char()
|
494
|
-
end
|
495
|
-
|
496
|
-
@tokens << Token.new(@tokenText, @reader.line_no, @token_start)
|
497
|
-
@tokenText = nil
|
498
|
-
end
|
499
|
-
|
500
|
-
end
|
501
|
-
end
|