sdl4r 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,129 @@
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__) + '/parser'
20
+
21
+ # An SDL token.
22
+ #
23
+ # @author Daniel Leuck, Philippe Vosges
24
+ #
25
+ class Token
26
+
27
+ def initialize(text, line = -1, position = -1)
28
+ @text = text
29
+ @line = line
30
+ @pos = position
31
+ @size = text.length
32
+
33
+ begin
34
+ @type = nil
35
+ @object = nil
36
+
37
+ if text =~ /^["`]/
38
+ @type = :STRING
39
+ @object = Parser.parse_string(text)
40
+
41
+ elsif text =~ /^'/
42
+ @type = :CHARACTER
43
+ @object = text[1...-1]
44
+
45
+ elsif text == "null"
46
+ @type = :NULL
47
+ @object = nil
48
+
49
+ elsif text =~ /^true$|^on$/
50
+ @type = :BOOLEAN
51
+ @object = true
52
+
53
+ elsif text =~ /^false$|^off$/
54
+ @type = :BOOLEAN
55
+ @object = false
56
+
57
+ elsif text =~ /^\[/
58
+ @type=:BINARY
59
+ @object = Parser.parse_binary(text)
60
+
61
+ elsif text =~ /^\d+\/\d+\/\d+$/
62
+ @type = :DATE;
63
+ @object = Parser.parse_date_time(text)
64
+
65
+ elsif text =~ /^-?\d+d?:\d+/
66
+ @type = :TIME
67
+ @object = parse_time_span_with_zone(text)
68
+
69
+ elsif text =~ /^[\d\-\.]/
70
+ @type = :NUMBER
71
+ @object = Parser.parse_number(text)
72
+
73
+ else
74
+ case text[0]
75
+ when ?{
76
+ @type = :START_BLOCK
77
+ when ?}
78
+ @type = :END_BLOCK
79
+ when ?=
80
+ @type = :EQUALS
81
+ when ?:
82
+ @type = :COLON
83
+ when ?;
84
+ @type = :SEMICOLON
85
+ end
86
+ end
87
+
88
+ rescue ArgumentError
89
+ raise SdlParseError.new($!.message, @line, @pos)
90
+ end
91
+
92
+ @type = :IDENTIFIER if @type.nil? # if all hope is lost, it's an identifier
93
+
94
+ @punctuation =
95
+ @type == :COLON || @type == :SEMICOLON || @type == :EQUALS ||
96
+ @type == :START_BLOCK || @type == :END_BLOCK
97
+ @literal = @type != :IDENTIFIER && !@punctuation
98
+ end
99
+
100
+ attr_reader :text, :type, :line, :position
101
+
102
+ def literal?
103
+ @literal
104
+ end
105
+
106
+ # Returns the Ruby object corresponding to this literal (or nil if it is
107
+ # not a literal).
108
+ def object_for_literal
109
+ return @object
110
+ end
111
+
112
+ def to_s
113
+ @type.to_s + " " + @text + " pos:" + @pos.to_s
114
+ end
115
+
116
+ # This special parse method is used only by the Token class for
117
+ # tokens which are ambiguously either a TimeSpan or the time component
118
+ # of a date/time type
119
+ def parse_time_span_with_zone(literal)
120
+ raise ArgumentError("time span or date literal is nil") if literal.nil?
121
+
122
+ days, hours, minutes, seconds, time_zone_offset =
123
+ Parser.parse_time_span_and_time_zone(literal, true, true)
124
+
125
+ return Parser::TimeSpanWithZone.new(days, hours, minutes, seconds, time_zone_offset)
126
+ end
127
+ end
128
+
129
+ end
@@ -0,0 +1,501 @@
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