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/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
|