Class: StringScanner

Inherits:
Object
  • Object
show all
Defined in:
lib/motion-strscan/strscan.rb

Overview

StringScanner provides for lexical scanning operations on a String. Here is an example of its usage:

s = StringScanner.new('This is an example string')
s.eos?               # -> false

p s.scan(/\w+/)      # -> "This"
p s.scan(/\w+/)      # -> nil
p s.scan(/\s+/)      # -> " "
p s.scan(/\s+/)      # -> nil
p s.scan(/\w+/)      # -> "is"
s.eos?               # -> false

p s.scan(/\s+/)      # -> " "
p s.scan(/\w+/)      # -> "an"
p s.scan(/\s+/)      # -> " "
p s.scan(/\w+/)      # -> "example"
p s.scan(/\s+/)      # -> " "
p s.scan(/\w+/)      # -> "string"
s.eos?               # -> true

p s.scan(/\s+/)      # -> nil
p s.scan(/\w+/)      # -> nil

Scanning a string means remembering the position of a scan pointer, which is just an index. The point of scanning is to move forward a bit at a time, so matches are sought after the scan pointer; usually immediately after it.

Given the string “test string”, here are the pertinent scan pointer positions:

  t e s t   s t r i n g
0 1 2 ...             1
                      0

When you #scan for a pattern (a regular expression), the match must occur at the character after the scan pointer. If you use #scan_until, then the match can occur anywhere after the scan pointer. In both cases, the scan pointer moves just beyond the last character of the match, ready to scan again from the next character onwards. This is demonstrated by the example above.

Method Categories

There are other methods besides the plain scanners. You can look ahead in the string without actually scanning. You can access the most recent match. You can modify the string being scanned, reset or terminate the scanner, find out or change the position of the scan pointer, skip ahead, and so on.

Advancing the Scan Pointer

  • #getch

  • #get_byte

  • #scan

  • #scan_until

  • #skip

  • #skip_until

Looking Ahead

  • #check

  • #check_until

  • #exist?

  • #match?

  • #peek

Finding Where we Are

  • #beginning_of_line? (#bol?)

  • #eos?

  • #rest?

  • #rest_size

  • #pos

Setting Where we Are

  • #reset

  • #terminate

  • #pos=

Match Data

  • #matched

  • #matched?

  • #matched_size

  • #pre_match

  • #post_match

Miscellaneous

  • <<

  • #concat

  • #string

  • #string=

  • #unscan

There are aliases to several of the methods.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(string, dup = false) ⇒ StringScanner

StringScanner.new(string, dup = false)

Creates a new StringScanner object to scan over the given string. dup argument is obsolete and not used now.



154
155
156
157
158
159
160
161
# File 'lib/motion-strscan/strscan.rb', line 154

def initialize(string, dup = false)
  begin
    @string = string.to_str
  rescue
    raise TypeError, "can't convert #{string.class.name} into String"
  end
  @byte_pos = 0
end

Instance Attribute Details

#byte_posObject (readonly)

string <String>

The string to scan

pos <Integer>

The position of the scan pointer. In the ‘reset’ position, this

value is zero.  In the 'terminated' position (i.e. the string is exhausted),
this value is the length of the string.

In short, it's a 0-based index into the string.

  s = StringScanner.new('test string')
  s.pos               # -> 0
  s.scan_until /str/  # -> "test str"
  s.pos               # -> 8
  s.terminate         # -> #<StringScanner fin>
  s.pos               # -> 11
match <String>

Matched string



141
142
143
# File 'lib/motion-strscan/strscan.rb', line 141

def byte_pos
  @byte_pos
end

#char_posObject (readonly)

string <String>

The string to scan

pos <Integer>

The position of the scan pointer. In the ‘reset’ position, this

value is zero.  In the 'terminated' position (i.e. the string is exhausted),
this value is the length of the string.

In short, it's a 0-based index into the string.

  s = StringScanner.new('test string')
  s.pos               # -> 0
  s.scan_until /str/  # -> "test str"
  s.pos               # -> 8
  s.terminate         # -> #<StringScanner fin>
  s.pos               # -> 11
match <String>

Matched string



141
142
143
# File 'lib/motion-strscan/strscan.rb', line 141

def char_pos
  @char_pos
end

#stringObject

string <String>

The string to scan

pos <Integer>

The position of the scan pointer. In the ‘reset’ position, this

value is zero.  In the 'terminated' position (i.e. the string is exhausted),
this value is the length of the string.

In short, it's a 0-based index into the string.

  s = StringScanner.new('test string')
  s.pos               # -> 0
  s.scan_until /str/  # -> "test str"
  s.pos               # -> 8
  s.terminate         # -> #<StringScanner fin>
  s.pos               # -> 11
match <String>

Matched string



141
142
143
# File 'lib/motion-strscan/strscan.rb', line 141

def string
  @string
end

Class Method Details

.must_C_versionObject

This method is defined for backward compatibility.



145
146
147
# File 'lib/motion-strscan/strscan.rb', line 145

def self.must_C_version
  self
end

Instance Method Details

#[](n) ⇒ Object

Return the n-th subgroup in the most recent match.

s = StringScanner.new("Fri Dec 12 1975 14:39")
s.scan(/(\w+) (\w+) (\d+) /)       # -> "Fri Dec 12 "
s[0]                               # -> "Fri Dec 12 "
s[1]                               # -> "Fri"
s[2]                               # -> "Dec"
s[3]                               # -> "12"
s.post_match                       # -> "1975 14:39"
s.pre_match                        # -> ""

Raises:

  • (TypeError)


617
618
619
620
# File 'lib/motion-strscan/strscan.rb', line 617

def [](n)
  raise TypeError, "Bad argument #{n.inspect}" unless n.respond_to? :to_int
  @match.nil? ? nil : @match[n]
end

#bol?Boolean Also known as: beginning_of_line?

Returns true iff the scan pointer is at the beginning of the line.

s = StringScanner.new("test\ntest\n")
s.bol?           # => true
s.scan(/te/)
s.bol?           # => false
s.scan(/st\n/)
s.bol?           # => true
s.terminate
s.bol?           # => true

Returns:

  • (Boolean)


601
602
603
# File 'lib/motion-strscan/strscan.rb', line 601

def bol?
  (@byte_pos == 0) || (@string.getbyte(@byte_pos - 1) == 10) # 10 == '\n'
end

#check(pattern) ⇒ Object

This returns the value that #scan would return, without advancing the scan pointer. The match register is affected, though.

s = StringScanner.new("Fri Dec 12 1975 14:39")
s.check /Fri/               # -> "Fri"
s.pos                       # -> 0
s.matched                   # -> "Fri"
s.check /12/                # -> nil
s.matched                   # -> nil

Mnemonic: it “checks” to see whether a #scan will return a value.



518
519
520
# File 'lib/motion-strscan/strscan.rb', line 518

def check(pattern)
  _scan(pattern, false, true, true)
end

#check_until(pattern) ⇒ Object

This returns the value that #scan_until would return, without advancing the scan pointer. The match register is affected, though.

s = StringScanner.new("Fri Dec 12 1975 14:39")
s.check_until /12/          # -> "Fri Dec 12"
s.pos                       # -> 0
s.matched                   # -> 12

Mnemonic: it “checks” to see whether a #scan_until will return a value.



533
534
535
# File 'lib/motion-strscan/strscan.rb', line 533

def check_until(pattern)
  _scan(pattern, false, true, false)
end

#clearObject

Equivalent to #terminate. This method is obsolete; use #terminate instead.



191
192
193
194
# File 'lib/motion-strscan/strscan.rb', line 191

def clear
  warn "StringScanner#clear is obsolete; use #terminate instead" if $VERBOSE
  terminate
end

#concat(str) ⇒ Object Also known as: <<

Appends str to the string being scanned. This method does not affect scan pointer.

s = StringScanner.new("Fri Dec 12 1975 14:39")
s.scan(/Fri /)
s << " +1000 GMT"
s.string            # -> "Fri Dec 12 1975 14:39 +1000 GMT"
s.scan(/Dec/)       # -> "Dec"


217
218
219
220
221
222
223
224
# File 'lib/motion-strscan/strscan.rb', line 217

def concat(str)
  begin
    @string << str.to_str
  rescue
    raise TypeError, "can't convert #{str.class.name} into String"
  end
  self
end

#empty?Boolean

Equivalent to #eos?. This method is obsolete, use #eos? instead.

Returns:

  • (Boolean)


354
355
356
357
# File 'lib/motion-strscan/strscan.rb', line 354

def empty?
  warn "StringScanner#empty? is obsolete; use #eos? instead" if $VERBOSE
  eos?
end

#eos?Boolean

Returns true if the scan pointer is at the end of the string.

s = StringScanner.new('test string')
p s.eos?          # => false
s.scan(/test/)
p s.eos?          # => false
s.terminate
p s.eos?          # => true

Returns:

  • (Boolean)


347
348
349
# File 'lib/motion-strscan/strscan.rb', line 347

def eos?
  @byte_pos >= @string.bytesize
end

#exist?(pattern) ⇒ Boolean

Looks ahead to see if the pattern exists anywhere in the string, without advancing the scan pointer. This predicates whether a #scan_until will return a value.

s = StringScanner.new('test string')
s.exist? /s/            # -> 3
s.scan /test/           # -> "test"
s.exist? /s/            # -> 6
s.exist? /e/            # -> nil

Returns:

  • (Boolean)


547
548
549
# File 'lib/motion-strscan/strscan.rb', line 547

def exist?(pattern)
  _scan(pattern, false, false, false)
end

#get_byteObject

Scans one byte and returns it. This method is not multibyte sensitive. See also: #getch.

s = StringScanner.new('ab')
s.get_byte         # => "a"
s.get_byte         # => "b"
s.get_byte         # => nil

# encoding: EUC-JP
s = StringScanner.new("\244\242")
s.get_byte         # => "244"
s.get_byte         # => "242"
s.get_byte         # => nil


263
264
265
266
267
# File 'lib/motion-strscan/strscan.rb', line 263

def get_byte
  # temp hack
  # TODO replace by a solution that will work with UTF-8
  scan(/./mn)
end

#getbyteObject

Equivalent to #get_byte. This method is obsolete; use #get_byte instead.



272
273
274
275
# File 'lib/motion-strscan/strscan.rb', line 272

def getbyte
  warn "StringScanner#getbyte is obsolete; use #get_byte instead" if $VERBOSE
  get_byte
end

#getchObject

Scans one character and returns it. This method is multibyte character sensitive.

s = StringScanner.new("ab")
s.getch           # => "a"
s.getch           # => "b"
s.getch           # => nil


334
335
336
# File 'lib/motion-strscan/strscan.rb', line 334

def getch
  scan(/./m)
end

#initialize_copy(orig) ⇒ Object

Duplicates a StringScanner object when dup or clone are called on the object.



166
167
168
169
170
# File 'lib/motion-strscan/strscan.rb', line 166

def initialize_copy(orig)
  @string     = orig.string
  @byte_pos   = orig.byte_pos
  @match      = orig.instance_variable_get("@match")
end

#inspectObject

Returns a string that represents the StringScanner object, showing:

  • the current position

  • the size of the string

  • the characters surrounding the scan pointer

    s = StringScanner.new(“Fri Dec 12 1975 14:39”) s.inspect # -> ‘#<StringScanner 0/21 @ “Fri D…”>’ s.scan_until /12/ # -> “Fri Dec 12” s.inspect # -> ‘#<StringScanner 10/21 “…ec 12” @ “ 1975…”>’



401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
# File 'lib/motion-strscan/strscan.rb', line 401

def inspect
  if defined?(@string)
    rest = @string.size > 5 ? @string[@byte_pos..@byte_pos+4] + "..." : @string
    to_return =  if eos? then
                  "#<StringScanner fin>"
                elsif @byte_pos > 0 then
                  prev = @string[0...@byte_pos].inspect
                  "#<StringScanner #{@byte_pos}/#{@string.bytesize} #{prev} @ #{rest.inspect}>"
                else
                  "#<StringScanner #{@byte_pos}/#{@string.bytesize} @ #{rest.inspect}>"
                end
    to_return.taint if @string.tainted?
    to_return
  else
    "#<StringScanner (uninitialized)>"
  end
end

#match?(pattern) ⇒ Boolean

Tests whether the given pattern is matched from the current scan pointer. Returns the length of the match, or nil. The scan pointer is not advanced.

s = StringScanner.new('test string')
p s.match?(/\w+/)   # -> 4
p s.match?(/\w+/)   # -> 4
p s.match?(/\s+/)   # -> nil

Returns:

  • (Boolean)


427
428
429
# File 'lib/motion-strscan/strscan.rb', line 427

def match?(pattern)
  _scan(pattern, false, false, true)
end

#matchedObject

Returns the last matched string.

s = StringScanner.new('test string')
s.match?(/\w+/)     # -> 4
s.matched           # -> "test"


437
438
439
# File 'lib/motion-strscan/strscan.rb', line 437

def matched
  @match.to_s if matched?
end

#matched?Boolean

Returns true iff the last match was successful.

s = StringScanner.new('test string')
s.match?(/\w+/)     # => 4
s.matched?          # => true
s.match?(/\d+/)     # => nil
s.matched?          # => false

Returns:

  • (Boolean)


449
450
451
# File 'lib/motion-strscan/strscan.rb', line 449

def matched?
  !@match.nil?
end

#matched_sizeObject

Returns the last matched string.

s = StringScanner.new('test string')
s.match?(/\w+/)     # -> 4
s.matched           # -> "test"


459
460
461
462
# File 'lib/motion-strscan/strscan.rb', line 459

def matched_size
  # @match.to_s.size if matched?
  @match.to_s.bytesize if matched?
end

#matchedsizeObject

Equivalent to #matched_size. This method is obsolete; use #matched_size instead.



467
468
469
470
# File 'lib/motion-strscan/strscan.rb', line 467

def matchedsize
  warn "StringScanner#matchedsize is obsolete; use #matched_size instead" if $VERBOSE
  matched_size
end

#peek(length) ⇒ Object

Extracts a string corresponding to string[pos,len], without advancing the scan pointer.

s = StringScanner.new('test string')
s.peek(7)          # => "test st"
s.peek(7)          # => "test st"

Raises:

  • (TypeError)


558
559
560
561
562
# File 'lib/motion-strscan/strscan.rb', line 558

def peek(length)
  raise TypeError, "can't convert #{length.class.name} into Integer" unless length.respond_to?(:to_int)
  raise ArgumentError if length < 0
  length.zero? ? "" : @string.byteslice(@byte_pos, length)
end

#peep(length) ⇒ Object

Equivalent to #peek. This method is obsolete; use #peek instead.



567
568
569
570
# File 'lib/motion-strscan/strscan.rb', line 567

def peep(length)
  warn "StringScanner#peep is obsolete; use #peek instead" if $VERBOSE
  peek(length)
end

#posObject Also known as: pointer

Returns the byte postition of the scan pointer (not the character position)




229
230
231
# File 'lib/motion-strscan/strscan.rb', line 229

def pos
  @byte_pos
end

#pos=(n) ⇒ Object Also known as: pointer=

Modify the scan pointer.

s = StringScanner.new('test string')
s.pos = 7            # -> 7
s.rest               # -> "ring"

Raises:

  • (RangeError)


239
240
241
242
243
# File 'lib/motion-strscan/strscan.rb', line 239

def pos=(n)
  n = (n + @string.bytesize) if (n < 0)
  raise RangeError, "index out of range" if (n < 0 || (@string && n > @string.bytesize))
  @byte_pos = n
end

#post_matchObject

Return the post-match (in the regular expression sense) of the last scan.

s = StringScanner.new('test string')
s.scan(/\w+/)           # -> "test"
s.scan(/\s+/)           # -> " "
s.pre_match             # -> "test"
s.post_match            # -> "string"


646
647
648
# File 'lib/motion-strscan/strscan.rb', line 646

def post_match
  @match.post_match if matched?
end

#pre_matchObject

Return the pre-match (in the regular expression sense) of the last scan.

s = StringScanner.new('test string')
s.scan(/\w+/)           # -> "test"
s.scan(/\s+/)           # -> " "
s.pre_match             # -> "test"
s.post_match            # -> "string"


630
631
632
633
634
635
636
# File 'lib/motion-strscan/strscan.rb', line 630

def pre_match
  if matched?
    # match.begin is in characters (not bytes)
    p = @string.size - @match.string.size + @match.begin(0)
    @string[0...p]
  end
end

#resetObject

Reset the scan pointer (index 0) and clear matching data.



174
175
176
177
178
# File 'lib/motion-strscan/strscan.rb', line 174

def reset
  @byte_pos = 0
  @match = nil
  self
end

#restObject

Returns the “rest” of the string (i.e. everything after the scan pointer). If there is no more data (eos? = true), it returns "".



373
374
375
# File 'lib/motion-strscan/strscan.rb', line 373

def rest
  @string.byteslice(@byte_pos..-1) || ""
end

#rest?Boolean

Returns true iff there is more data in the string. See #eos?. This method is obsolete; use #eos? instead.

s = StringScanner.new('test string')
s.eos?              # These two
s.rest?             # are opposites.

Returns:

  • (Boolean)


366
367
368
# File 'lib/motion-strscan/strscan.rb', line 366

def rest?
  !eos?
end

#rest_sizeObject

s.rest_size is equivalent to s.rest.size.



379
380
381
# File 'lib/motion-strscan/strscan.rb', line 379

def rest_size
  @string.bytesize - @byte_pos
end

#restsizeObject

s.restsize is equivalent to s.rest_size. This method is obsolete; use #rest_size instead.



386
387
388
389
# File 'lib/motion-strscan/strscan.rb', line 386

def restsize
  warn "StringScanner#restsize is obsolete; use #rest_size instead" if $VERBOSE
  rest_size
end

#scan(pattern) ⇒ Object

Tries to match with pattern at the current position. If there’s a match, the scanner advances the “scan pointer” and returns the matched string. Otherwise, the scanner returns nil.

s = StringScanner.new('test string')
p s.scan(/\w+/)   # -> "test"
p s.scan(/\w+/)   # -> nil
p s.scan(/\s+/)   # -> " "
p s.scan(/\w+/)   # -> "string"
p s.scan(/./)     # -> nil


288
289
290
# File 'lib/motion-strscan/strscan.rb', line 288

def scan(pattern)
  _scan(pattern, true, true, true)
end

#scan_full(pattern, succptr, getstr) ⇒ Object

Tests whether the given pattern is matched from the current scan pointer. Returns the matched string if return_string_p is true. Advances the scan pointer if advance_pointer_p is true. The match register is affected.

“full” means “#scan with full parameters”.



312
313
314
# File 'lib/motion-strscan/strscan.rb', line 312

def scan_full(pattern, succptr, getstr)
  _scan(pattern, succptr, getstr, true)
end

#scan_until(pattern) ⇒ Object

Scans the string until the pattern is matched. Returns the substring up to and including the end of the match, advancing the scan pointer to that location. If there is no match, nil is returned.

s = StringScanner.new("Fri Dec 12 1975 14:39")
s.scan_until(/1/)        # -> "Fri Dec 1"
s.pre_match              # -> "Fri Dec "
s.scan_until(/XYZ/)      # -> nil


301
302
303
# File 'lib/motion-strscan/strscan.rb', line 301

def scan_until(pattern)
  _scan(pattern, true, true, false)
end

#search_full(pattern, succptr, getstr) ⇒ Object

Scans the string until the pattern is matched. Returns the matched string if return_string_p is true, otherwise returns the number of bytes advanced. Advances the scan pointer if advance_pointer_p, otherwise not. This method does affect the match register.



322
323
324
# File 'lib/motion-strscan/strscan.rb', line 322

def search_full(pattern, succptr, getstr)
  _scan(pattern, succptr, getstr, false)
end

#skip(pattern) ⇒ Object

Attempts to skip over the given pattern beginning with the scan pointer. If it matches, the scan pointer is advanced to the end of the match, and the length of the match is returned. Otherwise, nil is returned.

It’s similar to #scan, but without returning the matched string.

s = StringScanner.new('test string')
p s.skip(/\w+/)   # -> 4
p s.skip(/\w+/)   # -> nil
p s.skip(/\s+/)   # -> 1
p s.skip(/\w+/)   # -> 6
p s.skip(/./)     # -> nil


485
486
487
# File 'lib/motion-strscan/strscan.rb', line 485

def skip(pattern)
  _scan(pattern, true, false, true)
end

#skip_until(pattern) ⇒ Object

Advances the scan pointer until pattern is matched and consumed. Returns the number of bytes advanced, or nil if no match was found.

Look ahead to match pattern, and advance the scan pointer to the end of the match. Return the number of characters advanced, or nil if the match was unsuccessful.

It’s similar to #scan_until, but without returning the intervening string.

s = StringScanner.new("Fri Dec 12 1975 14:39")
s.skip_until /12/           # -> 10
s


502
503
504
# File 'lib/motion-strscan/strscan.rb', line 502

def skip_until(pattern)
  _scan(pattern, true, false, false)
end

#terminateObject

Set the scan pointer to the end of the string and clear matching data.



182
183
184
185
186
# File 'lib/motion-strscan/strscan.rb', line 182

def terminate
  @match    = nil
  @byte_pos = @string.bytesize
  self
end

#unscanObject

Set the scan pointer to the previous position. Only one previous position is remembered, and it changes with each scanning operation.

s = StringScanner.new('test string')
s.scan(/\w+/)        # => "test"
s.unscan
s.scan(/../)         # => "te"
s.scan(/\d/)         # => nil
s.unscan             # ScanError: unscan failed: previous match record not exist

Raises:



582
583
584
585
586
587
588
# File 'lib/motion-strscan/strscan.rb', line 582

def unscan
  raise(ScanError, "unscan failed: previous match record not exist") if @match.nil?
  @byte_pos       = @prev_byte_pos
  @prev_byte_pos  = nil
  @match          = nil
  self
end