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