motion-strscan 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +63 -0
- data/lib/motion-strscan/string.rb +20 -0
- data/lib/motion-strscan/strscan.rb +676 -0
- data/lib/motion-strscan/version.rb +3 -0
- data/lib/motion-strscan.rb +8 -0
- data/spec/helpers/it_behaves_like.rb +27 -0
- data/spec/string_spec/_shared/slice.rb +244 -0
- data/spec/string_spec/byteslice_spec.rb +21 -0
- data/spec/strscan_spec/_shared/bol.rb +25 -0
- data/spec/strscan_spec/_shared/concat.rb +31 -0
- data/spec/strscan_spec/_shared/eos.rb +17 -0
- data/spec/strscan_spec/_shared/extract_range.rb +22 -0
- data/spec/strscan_spec/_shared/extract_range_matched.rb +22 -0
- data/spec/strscan_spec/_shared/get_byte.rb +28 -0
- data/spec/strscan_spec/_shared/matched_size.rb +21 -0
- data/spec/strscan_spec/_shared/peek.rb +44 -0
- data/spec/strscan_spec/_shared/pos.rb +83 -0
- data/spec/strscan_spec/_shared/rest_size.rb +18 -0
- data/spec/strscan_spec/_shared/terminate.rb +17 -0
- data/spec/strscan_spec/append_spec.rb +7 -0
- data/spec/strscan_spec/beginning_of_line_spec.rb +3 -0
- data/spec/strscan_spec/bol_spec.rb +3 -0
- data/spec/strscan_spec/check_spec.rb +13 -0
- data/spec/strscan_spec/check_until_spec.rb +14 -0
- data/spec/strscan_spec/clear_spec.rb +21 -0
- data/spec/strscan_spec/concat_spec.rb +7 -0
- data/spec/strscan_spec/dup_spec.rb +36 -0
- data/spec/strscan_spec/element_reference_spec.rb +46 -0
- data/spec/strscan_spec/empty_spec.rb +21 -0
- data/spec/strscan_spec/eos_spec.rb +3 -0
- data/spec/strscan_spec/exist_spec.rb +21 -0
- data/spec/strscan_spec/get_byte_spec.rb +3 -0
- data/spec/strscan_spec/getbyte_spec.rb +23 -0
- data/spec/strscan_spec/getch_spec.rb +41 -0
- data/spec/strscan_spec/initialize_spec.rb +25 -0
- data/spec/strscan_spec/inspect_spec.rb +17 -0
- data/spec/strscan_spec/match_spec.rb +25 -0
- data/spec/strscan_spec/matched_size_spec.rb +3 -0
- data/spec/strscan_spec/matched_spec.rb +37 -0
- data/spec/strscan_spec/matchedsize_spec.rb +23 -0
- data/spec/strscan_spec/must_C_version_spec.rb +5 -0
- data/spec/strscan_spec/peek_spec.rb +4 -0
- data/spec/strscan_spec/peep_spec.rb +21 -0
- data/spec/strscan_spec/pointer_spec.rb +7 -0
- data/spec/strscan_spec/pos_spec.rb +7 -0
- data/spec/strscan_spec/post_match_spec.rb +24 -0
- data/spec/strscan_spec/pre_match_spec.rb +37 -0
- data/spec/strscan_spec/reset_spec.rb +12 -0
- data/spec/strscan_spec/rest_size_spec.rb +3 -0
- data/spec/strscan_spec/rest_spec.rb +44 -0
- data/spec/strscan_spec/restsize_spec.rb +21 -0
- data/spec/strscan_spec/scan_full_spec.rb +27 -0
- data/spec/strscan_spec/scan_spec.rb +50 -0
- data/spec/strscan_spec/scan_until_spec.rb +20 -0
- data/spec/strscan_spec/search_full_spec.rb +27 -0
- data/spec/strscan_spec/skip_spec.rb +15 -0
- data/spec/strscan_spec/skip_until_spec.rb +15 -0
- data/spec/strscan_spec/string_spec.rb +37 -0
- data/spec/strscan_spec/terminate_spec.rb +3 -0
- data/spec/strscan_spec/unscan_spec.rb +26 -0
- metadata +172 -0
@@ -0,0 +1,676 @@
|
|
1
|
+
# RubyMotion (at least as of 3.6) does not include an implementation of
|
2
|
+
# StringScanner (strscan.rb). So an implementation was borrowed from MacRuby:
|
3
|
+
# https://github.com/MacRuby/MacRuby/blob/d1e47668d4345c78871f49b06849f3c7e4355943/lib/strscan.rb
|
4
|
+
#
|
5
|
+
# However, that implementation treats the pointer as a character pointer, instead
|
6
|
+
# of a byte pointer, as specified in the MRI. It was therefore having difficulties
|
7
|
+
# with multibyte strings, and with libraries that expect the pointer to be byte-based.
|
8
|
+
#
|
9
|
+
# The code has been modifed to use byte-based positioning and sizing, and will now
|
10
|
+
# return the same sizes and values as Ruby 1.9 and 2.0
|
11
|
+
#------------------------------------------------------------------------------
|
12
|
+
|
13
|
+
|
14
|
+
# (modified) MacRuby implementation of strscan.
|
15
|
+
#
|
16
|
+
# This file is covered by the Ruby license. See COPYING for more details.
|
17
|
+
#
|
18
|
+
# Copyright (C) 2012, The MacRuby Team. All rights reserved.
|
19
|
+
# Copyright (C) 2009-2011, Apple Inc. All rights reserved.
|
20
|
+
|
21
|
+
class ScanError < StandardError; end
|
22
|
+
|
23
|
+
# StringScanner provides for lexical scanning operations on a String. Here is
|
24
|
+
# an example of its usage:
|
25
|
+
#
|
26
|
+
# s = StringScanner.new('This is an example string')
|
27
|
+
# s.eos? # -> false
|
28
|
+
#
|
29
|
+
# p s.scan(/\w+/) # -> "This"
|
30
|
+
# p s.scan(/\w+/) # -> nil
|
31
|
+
# p s.scan(/\s+/) # -> " "
|
32
|
+
# p s.scan(/\s+/) # -> nil
|
33
|
+
# p s.scan(/\w+/) # -> "is"
|
34
|
+
# s.eos? # -> false
|
35
|
+
#
|
36
|
+
# p s.scan(/\s+/) # -> " "
|
37
|
+
# p s.scan(/\w+/) # -> "an"
|
38
|
+
# p s.scan(/\s+/) # -> " "
|
39
|
+
# p s.scan(/\w+/) # -> "example"
|
40
|
+
# p s.scan(/\s+/) # -> " "
|
41
|
+
# p s.scan(/\w+/) # -> "string"
|
42
|
+
# s.eos? # -> true
|
43
|
+
#
|
44
|
+
# p s.scan(/\s+/) # -> nil
|
45
|
+
# p s.scan(/\w+/) # -> nil
|
46
|
+
#
|
47
|
+
# Scanning a string means remembering the position of a <i>scan pointer</i>,
|
48
|
+
# which is just an index. The point of scanning is to move forward a bit at
|
49
|
+
# a time, so matches are sought after the scan pointer; usually immediately
|
50
|
+
# after it.
|
51
|
+
#
|
52
|
+
# Given the string "test string", here are the pertinent scan pointer
|
53
|
+
# positions:
|
54
|
+
#
|
55
|
+
# t e s t s t r i n g
|
56
|
+
# 0 1 2 ... 1
|
57
|
+
# 0
|
58
|
+
#
|
59
|
+
# When you #scan for a pattern (a regular expression), the match must occur
|
60
|
+
# at the character after the scan pointer. If you use #scan_until, then the
|
61
|
+
# match can occur anywhere after the scan pointer. In both cases, the scan
|
62
|
+
# pointer moves <i>just beyond</i> the last character of the match, ready to
|
63
|
+
# scan again from the next character onwards. This is demonstrated by the
|
64
|
+
# example above.
|
65
|
+
#
|
66
|
+
# == Method Categories
|
67
|
+
#
|
68
|
+
# There are other methods besides the plain scanners. You can look ahead in
|
69
|
+
# the string without actually scanning. You can access the most recent match.
|
70
|
+
# You can modify the string being scanned, reset or terminate the scanner,
|
71
|
+
# find out or change the position of the scan pointer, skip ahead, and so on.
|
72
|
+
#
|
73
|
+
# === Advancing the Scan Pointer
|
74
|
+
#
|
75
|
+
# - #getch
|
76
|
+
# - #get_byte
|
77
|
+
# - #scan
|
78
|
+
# - #scan_until
|
79
|
+
# - #skip
|
80
|
+
# - #skip_until
|
81
|
+
#
|
82
|
+
# === Looking Ahead
|
83
|
+
#
|
84
|
+
# - #check
|
85
|
+
# - #check_until
|
86
|
+
# - #exist?
|
87
|
+
# - #match?
|
88
|
+
# - #peek
|
89
|
+
#
|
90
|
+
# === Finding Where we Are
|
91
|
+
#
|
92
|
+
# - #beginning_of_line? (#bol?)
|
93
|
+
# - #eos?
|
94
|
+
# - #rest?
|
95
|
+
# - #rest_size
|
96
|
+
# - #pos
|
97
|
+
#
|
98
|
+
# === Setting Where we Are
|
99
|
+
#
|
100
|
+
# - #reset
|
101
|
+
# - #terminate
|
102
|
+
# - #pos=
|
103
|
+
#
|
104
|
+
# === Match Data
|
105
|
+
#
|
106
|
+
# - #matched
|
107
|
+
# - #matched?
|
108
|
+
# - #matched_size
|
109
|
+
# - []
|
110
|
+
# - #pre_match
|
111
|
+
# - #post_match
|
112
|
+
#
|
113
|
+
# === Miscellaneous
|
114
|
+
#
|
115
|
+
# - <<
|
116
|
+
# - #concat
|
117
|
+
# - #string
|
118
|
+
# - #string=
|
119
|
+
# - #unscan
|
120
|
+
#
|
121
|
+
# There are aliases to several of the methods.
|
122
|
+
#
|
123
|
+
class StringScanner
|
124
|
+
|
125
|
+
# string <String>:: The string to scan
|
126
|
+
# pos <Integer>:: The position of the scan pointer. In the 'reset' position, this
|
127
|
+
# value is zero. In the 'terminated' position (i.e. the string is exhausted),
|
128
|
+
# this value is the length of the string.
|
129
|
+
#
|
130
|
+
# In short, it's a 0-based index into the string.
|
131
|
+
#
|
132
|
+
# s = StringScanner.new('test string')
|
133
|
+
# s.pos # -> 0
|
134
|
+
# s.scan_until /str/ # -> "test str"
|
135
|
+
# s.pos # -> 8
|
136
|
+
# s.terminate # -> #<StringScanner fin>
|
137
|
+
# s.pos # -> 11
|
138
|
+
#
|
139
|
+
# match <String>:: Matched string
|
140
|
+
#
|
141
|
+
attr_reader :string, :char_pos, :byte_pos
|
142
|
+
|
143
|
+
# This method is defined for backward compatibility.
|
144
|
+
#
|
145
|
+
def self.must_C_version
|
146
|
+
self
|
147
|
+
end
|
148
|
+
|
149
|
+
# StringScanner.new(string, dup = false)
|
150
|
+
#
|
151
|
+
# Creates a new StringScanner object to scan over the given +string+.
|
152
|
+
# +dup+ argument is obsolete and not used now.
|
153
|
+
#
|
154
|
+
def initialize(string, dup = false)
|
155
|
+
begin
|
156
|
+
@string = string.to_str
|
157
|
+
rescue
|
158
|
+
raise TypeError, "can't convert #{string.class.name} into String"
|
159
|
+
end
|
160
|
+
@byte_pos = 0
|
161
|
+
end
|
162
|
+
|
163
|
+
# Duplicates a StringScanner object when dup or clone are called on the
|
164
|
+
# object.
|
165
|
+
#
|
166
|
+
def initialize_copy(orig)
|
167
|
+
@string = orig.string
|
168
|
+
@byte_pos = orig.byte_pos
|
169
|
+
@match = orig.instance_variable_get("@match")
|
170
|
+
end
|
171
|
+
|
172
|
+
# Reset the scan pointer (index 0) and clear matching data.
|
173
|
+
#
|
174
|
+
def reset
|
175
|
+
@byte_pos = 0
|
176
|
+
@match = nil
|
177
|
+
self
|
178
|
+
end
|
179
|
+
|
180
|
+
# Set the scan pointer to the end of the string and clear matching data.
|
181
|
+
#
|
182
|
+
def terminate
|
183
|
+
@match = nil
|
184
|
+
@byte_pos = @string.bytesize
|
185
|
+
self
|
186
|
+
end
|
187
|
+
|
188
|
+
# Equivalent to #terminate.
|
189
|
+
# This method is obsolete; use #terminate instead.
|
190
|
+
#
|
191
|
+
def clear
|
192
|
+
warn "StringScanner#clear is obsolete; use #terminate instead" if $VERBOSE
|
193
|
+
terminate
|
194
|
+
end
|
195
|
+
|
196
|
+
# Changes the string being scanned to +str+ and resets the scanner.
|
197
|
+
# Returns +str+.
|
198
|
+
#
|
199
|
+
def string=(str)
|
200
|
+
reset
|
201
|
+
begin
|
202
|
+
@string = str.to_str
|
203
|
+
rescue
|
204
|
+
raise TypeError, "can't convert #{str.class.name} into String"
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# Appends +str+ to the string being scanned.
|
209
|
+
# This method does not affect scan pointer.
|
210
|
+
#
|
211
|
+
# s = StringScanner.new("Fri Dec 12 1975 14:39")
|
212
|
+
# s.scan(/Fri /)
|
213
|
+
# s << " +1000 GMT"
|
214
|
+
# s.string # -> "Fri Dec 12 1975 14:39 +1000 GMT"
|
215
|
+
# s.scan(/Dec/) # -> "Dec"
|
216
|
+
#
|
217
|
+
def concat(str)
|
218
|
+
begin
|
219
|
+
@string << str.to_str
|
220
|
+
rescue
|
221
|
+
raise TypeError, "can't convert #{str.class.name} into String"
|
222
|
+
end
|
223
|
+
self
|
224
|
+
end
|
225
|
+
alias :<< :concat
|
226
|
+
|
227
|
+
# Returns the byte postition of the scan pointer (not the character position)
|
228
|
+
#------------------------------------------------------------------------------
|
229
|
+
def pos
|
230
|
+
@byte_pos
|
231
|
+
end
|
232
|
+
|
233
|
+
# Modify the scan pointer.
|
234
|
+
#
|
235
|
+
# s = StringScanner.new('test string')
|
236
|
+
# s.pos = 7 # -> 7
|
237
|
+
# s.rest # -> "ring"
|
238
|
+
#
|
239
|
+
def pos=(n)
|
240
|
+
n = (n + @string.bytesize) if (n < 0)
|
241
|
+
raise RangeError, "index out of range" if (n < 0 || (@string && n > @string.bytesize))
|
242
|
+
@byte_pos = n
|
243
|
+
end
|
244
|
+
|
245
|
+
alias :pointer :pos
|
246
|
+
alias :pointer= :pos=
|
247
|
+
|
248
|
+
# Scans one byte and returns it.
|
249
|
+
# This method is not multibyte sensitive.
|
250
|
+
# See also: #getch.
|
251
|
+
#
|
252
|
+
# s = StringScanner.new('ab')
|
253
|
+
# s.get_byte # => "a"
|
254
|
+
# s.get_byte # => "b"
|
255
|
+
# s.get_byte # => nil
|
256
|
+
#
|
257
|
+
# # encoding: EUC-JP
|
258
|
+
# s = StringScanner.new("\244\242")
|
259
|
+
# s.get_byte # => "244"
|
260
|
+
# s.get_byte # => "242"
|
261
|
+
# s.get_byte # => nil
|
262
|
+
#
|
263
|
+
def get_byte
|
264
|
+
# temp hack
|
265
|
+
# TODO replace by a solution that will work with UTF-8
|
266
|
+
scan(/./mn)
|
267
|
+
end
|
268
|
+
|
269
|
+
# Equivalent to #get_byte.
|
270
|
+
# This method is obsolete; use #get_byte instead.
|
271
|
+
#
|
272
|
+
def getbyte
|
273
|
+
warn "StringScanner#getbyte is obsolete; use #get_byte instead" if $VERBOSE
|
274
|
+
get_byte
|
275
|
+
end
|
276
|
+
|
277
|
+
# Tries to match with +pattern+ at the current position. If there's a match,
|
278
|
+
# the scanner advances the "scan pointer" and returns the matched string.
|
279
|
+
# Otherwise, the scanner returns +nil+.
|
280
|
+
#
|
281
|
+
# s = StringScanner.new('test string')
|
282
|
+
# p s.scan(/\w+/) # -> "test"
|
283
|
+
# p s.scan(/\w+/) # -> nil
|
284
|
+
# p s.scan(/\s+/) # -> " "
|
285
|
+
# p s.scan(/\w+/) # -> "string"
|
286
|
+
# p s.scan(/./) # -> nil
|
287
|
+
#
|
288
|
+
def scan(pattern)
|
289
|
+
_scan(pattern, true, true, true)
|
290
|
+
end
|
291
|
+
|
292
|
+
# Scans the string _until_ the +pattern+ is matched. Returns the substring up
|
293
|
+
# to and including the end of the match, advancing the scan pointer to that
|
294
|
+
# location. If there is no match, +nil+ is returned.
|
295
|
+
#
|
296
|
+
# s = StringScanner.new("Fri Dec 12 1975 14:39")
|
297
|
+
# s.scan_until(/1/) # -> "Fri Dec 1"
|
298
|
+
# s.pre_match # -> "Fri Dec "
|
299
|
+
# s.scan_until(/XYZ/) # -> nil
|
300
|
+
#
|
301
|
+
def scan_until(pattern)
|
302
|
+
_scan(pattern, true, true, false)
|
303
|
+
end
|
304
|
+
|
305
|
+
# Tests whether the given +pattern+ is matched from the current scan pointer.
|
306
|
+
# Returns the matched string if +return_string_p+ is true.
|
307
|
+
# Advances the scan pointer if +advance_pointer_p+ is true.
|
308
|
+
# The match register is affected.
|
309
|
+
#
|
310
|
+
# "full" means "#scan with full parameters".
|
311
|
+
#
|
312
|
+
def scan_full(pattern, succptr, getstr)
|
313
|
+
_scan(pattern, succptr, getstr, true)
|
314
|
+
end
|
315
|
+
|
316
|
+
# Scans the string _until_ the +pattern+ is matched.
|
317
|
+
# Returns the matched string if +return_string_p+ is true, otherwise
|
318
|
+
# returns the number of bytes advanced.
|
319
|
+
# Advances the scan pointer if +advance_pointer_p+, otherwise not.
|
320
|
+
# This method does affect the match register.
|
321
|
+
#
|
322
|
+
def search_full(pattern, succptr, getstr)
|
323
|
+
_scan(pattern, succptr, getstr, false)
|
324
|
+
end
|
325
|
+
|
326
|
+
# Scans one character and returns it.
|
327
|
+
# This method is multibyte character sensitive.
|
328
|
+
#
|
329
|
+
# s = StringScanner.new("ab")
|
330
|
+
# s.getch # => "a"
|
331
|
+
# s.getch # => "b"
|
332
|
+
# s.getch # => nil
|
333
|
+
#
|
334
|
+
def getch
|
335
|
+
scan(/./m)
|
336
|
+
end
|
337
|
+
|
338
|
+
# Returns +true+ if the scan pointer is at the end of the string.
|
339
|
+
#
|
340
|
+
# s = StringScanner.new('test string')
|
341
|
+
# p s.eos? # => false
|
342
|
+
# s.scan(/test/)
|
343
|
+
# p s.eos? # => false
|
344
|
+
# s.terminate
|
345
|
+
# p s.eos? # => true
|
346
|
+
#
|
347
|
+
def eos?
|
348
|
+
@byte_pos >= @string.bytesize
|
349
|
+
end
|
350
|
+
|
351
|
+
# Equivalent to #eos?.
|
352
|
+
# This method is obsolete, use #eos? instead.
|
353
|
+
#
|
354
|
+
def empty?
|
355
|
+
warn "StringScanner#empty? is obsolete; use #eos? instead" if $VERBOSE
|
356
|
+
eos?
|
357
|
+
end
|
358
|
+
|
359
|
+
# Returns true iff there is more data in the string. See #eos?.
|
360
|
+
# This method is obsolete; use #eos? instead.
|
361
|
+
#
|
362
|
+
# s = StringScanner.new('test string')
|
363
|
+
# s.eos? # These two
|
364
|
+
# s.rest? # are opposites.
|
365
|
+
#
|
366
|
+
def rest?
|
367
|
+
!eos?
|
368
|
+
end
|
369
|
+
|
370
|
+
# Returns the "rest" of the string (i.e. everything after the scan pointer).
|
371
|
+
# If there is no more data (eos? = true), it returns <tt>""</tt>.
|
372
|
+
#
|
373
|
+
def rest
|
374
|
+
@string.byteslice(@byte_pos..-1) || ""
|
375
|
+
end
|
376
|
+
|
377
|
+
# <tt>s.rest_size</tt> is equivalent to <tt>s.rest.size</tt>.
|
378
|
+
#
|
379
|
+
def rest_size
|
380
|
+
@string.bytesize - @byte_pos
|
381
|
+
end
|
382
|
+
|
383
|
+
# <tt>s.restsize</tt> is equivalent to <tt>s.rest_size</tt>.
|
384
|
+
# This method is obsolete; use #rest_size instead.
|
385
|
+
#
|
386
|
+
def restsize
|
387
|
+
warn "StringScanner#restsize is obsolete; use #rest_size instead" if $VERBOSE
|
388
|
+
rest_size
|
389
|
+
end
|
390
|
+
|
391
|
+
# Returns a string that represents the StringScanner object, showing:
|
392
|
+
# - the current position
|
393
|
+
# - the size of the string
|
394
|
+
# - the characters surrounding the scan pointer
|
395
|
+
#
|
396
|
+
# s = StringScanner.new("Fri Dec 12 1975 14:39")
|
397
|
+
# s.inspect # -> '#<StringScanner 0/21 @ "Fri D...">'
|
398
|
+
# s.scan_until /12/ # -> "Fri Dec 12"
|
399
|
+
# s.inspect # -> '#<StringScanner 10/21 "...ec 12" @ " 1975...">'
|
400
|
+
#
|
401
|
+
def inspect
|
402
|
+
if defined?(@string)
|
403
|
+
rest = @string.size > 5 ? @string[@byte_pos..@byte_pos+4] + "..." : @string
|
404
|
+
to_return = if eos? then
|
405
|
+
"#<StringScanner fin>"
|
406
|
+
elsif @byte_pos > 0 then
|
407
|
+
prev = @string[0...@byte_pos].inspect
|
408
|
+
"#<StringScanner #{@byte_pos}/#{@string.bytesize} #{prev} @ #{rest.inspect}>"
|
409
|
+
else
|
410
|
+
"#<StringScanner #{@byte_pos}/#{@string.bytesize} @ #{rest.inspect}>"
|
411
|
+
end
|
412
|
+
to_return.taint if @string.tainted?
|
413
|
+
to_return
|
414
|
+
else
|
415
|
+
"#<StringScanner (uninitialized)>"
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
# Tests whether the given +pattern+ is matched from the current scan pointer.
|
420
|
+
# Returns the length of the match, or +nil+. The scan pointer is not advanced.
|
421
|
+
#
|
422
|
+
# s = StringScanner.new('test string')
|
423
|
+
# p s.match?(/\w+/) # -> 4
|
424
|
+
# p s.match?(/\w+/) # -> 4
|
425
|
+
# p s.match?(/\s+/) # -> nil
|
426
|
+
#
|
427
|
+
def match?(pattern)
|
428
|
+
_scan(pattern, false, false, true)
|
429
|
+
end
|
430
|
+
|
431
|
+
# Returns the last matched string.
|
432
|
+
#
|
433
|
+
# s = StringScanner.new('test string')
|
434
|
+
# s.match?(/\w+/) # -> 4
|
435
|
+
# s.matched # -> "test"
|
436
|
+
#
|
437
|
+
def matched
|
438
|
+
@match.to_s if matched?
|
439
|
+
end
|
440
|
+
|
441
|
+
# Returns +true+ iff the last match was successful.
|
442
|
+
#
|
443
|
+
# s = StringScanner.new('test string')
|
444
|
+
# s.match?(/\w+/) # => 4
|
445
|
+
# s.matched? # => true
|
446
|
+
# s.match?(/\d+/) # => nil
|
447
|
+
# s.matched? # => false
|
448
|
+
#
|
449
|
+
def matched?
|
450
|
+
!@match.nil?
|
451
|
+
end
|
452
|
+
|
453
|
+
# Returns the last matched string.
|
454
|
+
#
|
455
|
+
# s = StringScanner.new('test string')
|
456
|
+
# s.match?(/\w+/) # -> 4
|
457
|
+
# s.matched # -> "test"
|
458
|
+
#
|
459
|
+
def matched_size
|
460
|
+
# @match.to_s.size if matched?
|
461
|
+
@match.to_s.bytesize if matched?
|
462
|
+
end
|
463
|
+
|
464
|
+
# Equivalent to #matched_size.
|
465
|
+
# This method is obsolete; use #matched_size instead.
|
466
|
+
#
|
467
|
+
def matchedsize
|
468
|
+
warn "StringScanner#matchedsize is obsolete; use #matched_size instead" if $VERBOSE
|
469
|
+
matched_size
|
470
|
+
end
|
471
|
+
|
472
|
+
# Attempts to skip over the given +pattern+ beginning with the scan pointer.
|
473
|
+
# If it matches, the scan pointer is advanced to the end of the match, and the
|
474
|
+
# length of the match is returned. Otherwise, +nil+ is returned.
|
475
|
+
#
|
476
|
+
# It's similar to #scan, but without returning the matched string.
|
477
|
+
#
|
478
|
+
# s = StringScanner.new('test string')
|
479
|
+
# p s.skip(/\w+/) # -> 4
|
480
|
+
# p s.skip(/\w+/) # -> nil
|
481
|
+
# p s.skip(/\s+/) # -> 1
|
482
|
+
# p s.skip(/\w+/) # -> 6
|
483
|
+
# p s.skip(/./) # -> nil
|
484
|
+
#
|
485
|
+
def skip(pattern)
|
486
|
+
_scan(pattern, true, false, true)
|
487
|
+
end
|
488
|
+
|
489
|
+
# Advances the scan pointer until +pattern+ is matched and consumed. Returns
|
490
|
+
# the number of bytes advanced, or +nil+ if no match was found.
|
491
|
+
#
|
492
|
+
# Look ahead to match +pattern+, and advance the scan pointer to the _end_
|
493
|
+
# of the match. Return the number of characters advanced, or +nil+ if the
|
494
|
+
# match was unsuccessful.
|
495
|
+
#
|
496
|
+
# It's similar to #scan_until, but without returning the intervening string.
|
497
|
+
#
|
498
|
+
# s = StringScanner.new("Fri Dec 12 1975 14:39")
|
499
|
+
# s.skip_until /12/ # -> 10
|
500
|
+
# s
|
501
|
+
#
|
502
|
+
def skip_until(pattern)
|
503
|
+
_scan(pattern, true, false, false)
|
504
|
+
end
|
505
|
+
|
506
|
+
# This returns the value that #scan would return, without advancing the scan
|
507
|
+
# pointer. The match register is affected, though.
|
508
|
+
#
|
509
|
+
# s = StringScanner.new("Fri Dec 12 1975 14:39")
|
510
|
+
# s.check /Fri/ # -> "Fri"
|
511
|
+
# s.pos # -> 0
|
512
|
+
# s.matched # -> "Fri"
|
513
|
+
# s.check /12/ # -> nil
|
514
|
+
# s.matched # -> nil
|
515
|
+
#
|
516
|
+
# Mnemonic: it "checks" to see whether a #scan will return a value.
|
517
|
+
#
|
518
|
+
def check(pattern)
|
519
|
+
_scan(pattern, false, true, true)
|
520
|
+
end
|
521
|
+
|
522
|
+
|
523
|
+
# This returns the value that #scan_until would return, without advancing the
|
524
|
+
# scan pointer. The match register is affected, though.
|
525
|
+
#
|
526
|
+
# s = StringScanner.new("Fri Dec 12 1975 14:39")
|
527
|
+
# s.check_until /12/ # -> "Fri Dec 12"
|
528
|
+
# s.pos # -> 0
|
529
|
+
# s.matched # -> 12
|
530
|
+
#
|
531
|
+
# Mnemonic: it "checks" to see whether a #scan_until will return a value.
|
532
|
+
#
|
533
|
+
def check_until(pattern)
|
534
|
+
_scan(pattern, false, true, false)
|
535
|
+
end
|
536
|
+
|
537
|
+
# Looks _ahead_ to see if the +pattern+ exists _anywhere_ in the string,
|
538
|
+
# without advancing the scan pointer. This predicates whether a #scan_until
|
539
|
+
# will return a value.
|
540
|
+
#
|
541
|
+
# s = StringScanner.new('test string')
|
542
|
+
# s.exist? /s/ # -> 3
|
543
|
+
# s.scan /test/ # -> "test"
|
544
|
+
# s.exist? /s/ # -> 6
|
545
|
+
# s.exist? /e/ # -> nil
|
546
|
+
#
|
547
|
+
def exist?(pattern)
|
548
|
+
_scan(pattern, false, false, false)
|
549
|
+
end
|
550
|
+
|
551
|
+
# Extracts a string corresponding to <tt>string[pos,len]</tt>, without
|
552
|
+
# advancing the scan pointer.
|
553
|
+
#
|
554
|
+
# s = StringScanner.new('test string')
|
555
|
+
# s.peek(7) # => "test st"
|
556
|
+
# s.peek(7) # => "test st"
|
557
|
+
#
|
558
|
+
def peek(length)
|
559
|
+
raise TypeError, "can't convert #{length.class.name} into Integer" unless length.respond_to?(:to_int)
|
560
|
+
raise ArgumentError if length < 0
|
561
|
+
length.zero? ? "" : @string.byteslice(@byte_pos, length)
|
562
|
+
end
|
563
|
+
|
564
|
+
# Equivalent to #peek.
|
565
|
+
# This method is obsolete; use #peek instead.
|
566
|
+
#
|
567
|
+
def peep(length)
|
568
|
+
warn "StringScanner#peep is obsolete; use #peek instead" if $VERBOSE
|
569
|
+
peek(length)
|
570
|
+
end
|
571
|
+
|
572
|
+
# Set the scan pointer to the previous position. Only one previous position is
|
573
|
+
# remembered, and it changes with each scanning operation.
|
574
|
+
#
|
575
|
+
# s = StringScanner.new('test string')
|
576
|
+
# s.scan(/\w+/) # => "test"
|
577
|
+
# s.unscan
|
578
|
+
# s.scan(/../) # => "te"
|
579
|
+
# s.scan(/\d/) # => nil
|
580
|
+
# s.unscan # ScanError: unscan failed: previous match record not exist
|
581
|
+
#
|
582
|
+
def unscan
|
583
|
+
raise(ScanError, "unscan failed: previous match record not exist") if @match.nil?
|
584
|
+
@byte_pos = @prev_byte_pos
|
585
|
+
@prev_byte_pos = nil
|
586
|
+
@match = nil
|
587
|
+
self
|
588
|
+
end
|
589
|
+
|
590
|
+
# Returns +true+ iff the scan pointer is at the beginning of the line.
|
591
|
+
#
|
592
|
+
# s = StringScanner.new("test\ntest\n")
|
593
|
+
# s.bol? # => true
|
594
|
+
# s.scan(/te/)
|
595
|
+
# s.bol? # => false
|
596
|
+
# s.scan(/st\n/)
|
597
|
+
# s.bol? # => true
|
598
|
+
# s.terminate
|
599
|
+
# s.bol? # => true
|
600
|
+
#
|
601
|
+
def bol?
|
602
|
+
(@byte_pos == 0) || (@string.getbyte(@byte_pos - 1) == 10) # 10 == '\n'
|
603
|
+
end
|
604
|
+
alias :beginning_of_line? :bol?
|
605
|
+
|
606
|
+
# Return the n-th subgroup in the most recent match.
|
607
|
+
#
|
608
|
+
# s = StringScanner.new("Fri Dec 12 1975 14:39")
|
609
|
+
# s.scan(/(\w+) (\w+) (\d+) /) # -> "Fri Dec 12 "
|
610
|
+
# s[0] # -> "Fri Dec 12 "
|
611
|
+
# s[1] # -> "Fri"
|
612
|
+
# s[2] # -> "Dec"
|
613
|
+
# s[3] # -> "12"
|
614
|
+
# s.post_match # -> "1975 14:39"
|
615
|
+
# s.pre_match # -> ""
|
616
|
+
#
|
617
|
+
def [](n)
|
618
|
+
raise TypeError, "Bad argument #{n.inspect}" unless n.respond_to? :to_int
|
619
|
+
@match.nil? ? nil : @match[n]
|
620
|
+
end
|
621
|
+
|
622
|
+
# Return the <i><b>pre</b>-match</i> (in the regular expression sense) of the last scan.
|
623
|
+
#
|
624
|
+
# s = StringScanner.new('test string')
|
625
|
+
# s.scan(/\w+/) # -> "test"
|
626
|
+
# s.scan(/\s+/) # -> " "
|
627
|
+
# s.pre_match # -> "test"
|
628
|
+
# s.post_match # -> "string"
|
629
|
+
#
|
630
|
+
def pre_match
|
631
|
+
if matched?
|
632
|
+
# match.begin is in characters (not bytes)
|
633
|
+
p = @string.size - @match.string.size + @match.begin(0)
|
634
|
+
@string[0...p]
|
635
|
+
end
|
636
|
+
end
|
637
|
+
|
638
|
+
# Return the <i><b>post</b>-match</i> (in the regular expression sense) of the last scan.
|
639
|
+
#
|
640
|
+
# s = StringScanner.new('test string')
|
641
|
+
# s.scan(/\w+/) # -> "test"
|
642
|
+
# s.scan(/\s+/) # -> " "
|
643
|
+
# s.pre_match # -> "test"
|
644
|
+
# s.post_match # -> "string"
|
645
|
+
#
|
646
|
+
def post_match
|
647
|
+
@match.post_match if matched?
|
648
|
+
end
|
649
|
+
|
650
|
+
private
|
651
|
+
|
652
|
+
def _scan(pattern, succptr, getstr, headonly)
|
653
|
+
raise TypeError, "wrong argument type #{pattern.class.name} (expected Regexp)" unless
|
654
|
+
Regexp === pattern
|
655
|
+
|
656
|
+
@match = nil
|
657
|
+
rest = @byte_pos == 0 ? @string : self.rest
|
658
|
+
|
659
|
+
if headonly
|
660
|
+
headonly_pattern = Regexp.new("\\A(?:#{pattern.source})", pattern.options)
|
661
|
+
@match = headonly_pattern.match rest
|
662
|
+
else
|
663
|
+
@match = pattern.match rest
|
664
|
+
end
|
665
|
+
return nil unless @match
|
666
|
+
|
667
|
+
m = rest[0, @match.end(0)]
|
668
|
+
if succptr
|
669
|
+
@prev_byte_pos = @byte_pos
|
670
|
+
@byte_pos += m.bytesize
|
671
|
+
end
|
672
|
+
|
673
|
+
getstr ? m : m.bytesize
|
674
|
+
end
|
675
|
+
|
676
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
unless defined?(Motion::Project::Config)
|
2
|
+
raise 'The motion-strscan gem must be required within a RubyMotion project Rakefile.'
|
3
|
+
end
|
4
|
+
|
5
|
+
lib_dir_path = File.dirname(File.expand_path(__FILE__))
|
6
|
+
Motion::Project::App.setup do |app|
|
7
|
+
app.files.unshift(Dir.glob(File.join(lib_dir_path, "motion-strscan/**/*.rb")))
|
8
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
#------------------------------------------------------------------------------
|
2
|
+
def it_behaves_like(name, method = nil)
|
3
|
+
@method = method
|
4
|
+
behaves_like(name)
|
5
|
+
end
|
6
|
+
|
7
|
+
class Should
|
8
|
+
|
9
|
+
# usage: .should.be_true and .should.be_false
|
10
|
+
#------------------------------------------------------------------------------
|
11
|
+
def be_true
|
12
|
+
self == true
|
13
|
+
end
|
14
|
+
|
15
|
+
def be_false
|
16
|
+
self == false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# usage: .should.be truthy and .should.be falsey
|
21
|
+
#------------------------------------------------------------------------------
|
22
|
+
def truthy
|
23
|
+
lambda { |obj| obj == true }
|
24
|
+
end
|
25
|
+
def falsey
|
26
|
+
lambda { |obj| obj == false }
|
27
|
+
end
|