sdl4r 0.9.1 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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