motion-strscan 0.5.0
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.
- 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
|