StringScanner 클래스

StringScanner 클래스

저장된 문자열을 스트림처럼 처리하면서, 문자열을 앞에서부터 한 조각씩 스캔하고 싶을 때 쓰는 클래스가 StringScanner예요. 정규표현식 기반으로 토큰을 뽑아내는 스캐너죠.

출처: Ruby 4.0 API

본문

클래스 StringScanner는 저장된 문자열을 스트림처럼 처리할 수 있게 해 줘요. 이 코드는 문자열 'foobarbaz'로 새 StringScanner 객체를 만들어요:

require 'strscan'
scanner = StringScanner.new('foobarbaz')

예시에 관해 (About the Examples)

이 페이지의 예시는 StringScanner가 require되어 있다고 가정해요:

require 'strscan'

일부 예시는 다음 상수들이 정의되어 있다고 가정해요:

MULTILINE_TEXT = <<~EOT
Go placidly amid the noise and haste,
and remember what peace there may be in silence.
EOT

HIRAGANA_TEXT = 'こんにちは'

ENGLISH_TEXT = 'Hello'

일부 예시는 특정 헬퍼 메서드가 정의되어 있다고 가정해요:

  • put_situation(scanner): 스캐너의 pos, charpos, rest, rest_size 값들을 표시해요.
  • put_match_values(scanner): 스캐너의 매치 값들을 표시해요.
  • match_values_cleared?(scanner): 스캐너의 매치 값들이 지워졌는지 여부를 반환해요.

StringScanner 객체 (The StringScanner Object)

이 코드는 StringScanner 객체(줄여서 스캐너)를 만들고 기본 속성 몇 개를 보여줘요:

scanner = StringScanner.new('foobarbaz')
scanner.string # => "foobarbaz"
put_situation(scanner)
# Situation:
#   pos:       0
#   charpos:   0
#   rest:      "foobarbaz"
#   rest_size: 9

스캐너에는 다음이 있어요:

  • 저장된 문자열(stored string), 즉:
    • StringScanner.new(string)에 의해 처음엔 주어진 문자열로 설정돼요.
    • string=(new_string)concat(more_string) 메서드로 수정돼요.
    • string 메서드로 반환돼요.
  • 위치(position). 저장된 문자열의 바이트(문자가 아니라)를 가리키는 0 기반 인덱스예요:
    • StringScanner.new에 의해 처음엔 0으로 설정돼요.
    • pos 메서드로 반환돼요.
    • reset, terminate, pos=(new_pos) 메서드로 명시적으로 수정돼요.
    • (여러 탐색 메서드 등에 의해) 암시적으로 수정돼요.
  • 대상 부분 문자열(target substring). 현재 위치에서 저장된 문자열 끝까지 뻗어 있는 저장 문자열의 뒷부분이에요:
    • StringScanner.new(string)에 의해 처음엔 주어진 문자열로 설정돼요.
    • rest 메서드로 반환돼요.
    • 저장된 문자열이나 위치가 수정되면 수정돼요.
    • 가장 중요한 점: 검색·탐색 메서드들이 이 대상 부분 문자열 위에서 동작해요. 이 대상은 저장된 문자열 전체보다 짧을 수 있고(흔히 그렇고) 전체일 수도 있어요.

저장된 문자열 (Stored String)

저장된 문자열StringScanner 객체에 저장된 문자열이에요.

이 메서드들이 저장된 문자열을 설정·수정·반환해요:

메서드 효과
::new(string) 주어진 문자열로 새 스캐너를 만들어요.
string=(new_string) 기존 저장 문자열을 교체해요.
concat(more_string) 기존 저장 문자열에 문자열을 추가해요.
string 저장된 문자열을 반환해요.

위치 (Positions)

StringScanner 객체는 0 기반 바이트 위치와 0 기반 문자 위치를 유지해요.

이 메서드들이 위치를 명시적으로 설정해요:

메서드 효과
reset 두 위치 모두 0으로 설정(저장 문자열의 시작).
terminate 두 위치 모두 저장 문자열의 끝으로 설정.
pos=(new_byte_position) 바이트 위치 설정; 문자 위치 조정.

바이트 위치 (Byte Position)

바이트 위치(또는 그냥 위치)는 스캐너의 저장 문자열에 있는 바이트를 가리키는 0 기반 인덱스예요. 새 StringScanner 객체의 바이트 위치는 0이에요.

바이트 위치가:

  • 0이면(시작), 대상 부분 문자열은 저장 문자열 전체예요.
  • 저장 문자열 크기와 같으면(끝), 대상 부분 문자열은 빈 문자열 ''이에요.

바이트 위치 가져오기·설정하기:

  • pos: 바이트 위치를 반환해요.
  • pos=(new_pos): 바이트 위치를 설정해요.

많은 메서드가 바이트 위치를 매치 찾기의 기준으로 쓰고, 다른 많은 메서드는 바이트 위치를 설정·증가·감소시켜요:

scanner = StringScanner.new('foobar')
scanner.pos # => 0
scanner.scan(/foo/) # => "foo" # Match found.
scanner.pos         # => 3     # Byte position incremented.
scanner.scan(/foo/) # => nil   # Match not found.
scanner.pos # => 3             # Byte position not changed.

이 메서드들의 값은 posstring에서 직접 파생돼요:

  • charpos: 문자 위치.
  • rest: 대상 부분 문자열.
  • rest_size: rest.size.

문자 위치 (Character Position)

문자 위치는 저장 문자열의 문자를 가리키는 0 기반 인덱스예요. 새 StringScanner 객체의 문자 위치는 0이에요.

charpos 메서드가 문자 위치를 반환해요. 그 값은 명시적으로 초기화할 수 없어요.

일부 메서드가 문자 위치를 변경(증가 또는 초기화)해요.

멀티바이트 문자가 있는 예시:

scanner = StringScanner.new(ENGLISH_TEXT) # Five 1-byte characters.
scanner.concat(HIRAGANA_TEXT)             # Five 3-byte characters
scanner.string # => "Helloこんにちは"       # Twenty bytes in all.
put_situation(scanner)
# Situation:
#   pos:       0
#   charpos:   0
#   rest:      "Helloこんにちは"
#   rest_size: 20
scanner.scan(/Hello/) # => "Hello" # Five 1-byte characters.
put_situation(scanner)
# Situation:
#   pos:       5
#   charpos:   5
#   rest:      "こんにちは"
#   rest_size: 15
scanner.getch         # => "こ"    # One 3-byte character.
put_situation(scanner)
# Situation:
#   pos:       8
#   charpos:   6
#   rest:      "んにちは"
#   rest_size: 12

Hello는 문자 5개 = 바이트 5개라서 poscharpos가 같이 5로 올라가지만, getch로 3바이트 문자 를 읽으면 pos는 8로, charpos는 6으로 달라지는 걸 볼 수 있어요.

대상 부분 문자열 (Target Substring)

대상 부분 문자열은 현재 바이트 위치에서 저장 문자열 끝까지 뻗는 저장 문자열의 일부예요. 항상 다음 중 하나예요:

  • 저장 문자열 전체(바이트 위치가 0).
  • 저장 문자열의 뒷부분(바이트 위치가 양수).

대상 부분 문자열은 rest 메서드로 반환되고, 그 크기는 rest_size 메서드로 반환돼요.

scanner = StringScanner.new('foobarbaz')
put_situation(scanner)
# Situation:
#   pos:       0
#   charpos:   0
#   rest:      "foobarbaz"
#   rest_size: 9
scanner.pos = 3
put_situation(scanner)
# Situation:
#   pos:       3
#   charpos:   3
#   rest:      "barbaz"
#   rest_size: 6
scanner.pos = 9
put_situation(scanner)
# Situation:
#   pos:       9
#   charpos:   9
#   rest:      ""
#   rest_size: 0

대상 부분 문자열 설정 (Setting)

대상 부분 문자열은 다음 때마다 설정돼요:

  • 저장 문자열이 설정될 때(위치는 0으로 초기화, 대상 부분 문자열은 저장 문자열로 설정).
  • 바이트 위치가 설정될 때(대상 부분 문자열도 그에 맞게 조정).

대상 부분 문자열 질의 (Querying)

메서드 반환
rest 대상 부분 문자열.
rest_size 대상 부분 문자열의 크기(바이트).

대상 부분 문자열 검색 (Searching)

검색(search) 메서드는 대상 부분 문자열을 살펴보지만 위치를 앞으로 옮기지 않아요(따라서 대상 부분 문자열도 줄어들지 않음).

메서드 반환 매치 값 설정?
check(pattern) 일치하는 앞부분 부분 문자열 또는 nil. Yes.
check_until(pattern) 일치하는 부분 문자열(어디든) 또는 nil. Yes.
exist?(pattern) 일치하는 부분 문자열(어디든)의 끝 인덱스. Yes.
match?(pattern) 일치하는 앞부분 부분 문자열의 크기 또는 nil. Yes.
peek(size) 주어진 길이(바이트)의 앞부분 부분 문자열. No.
peek_byte 정수 앞부분 바이트 또는 nil. No.
rest 대상 부분 문자열(바이트 위치부터 끝까지). No.

대상 부분 문자열 탐색 (Traversing)

탐색(traversal) 메서드는 대상 부분 문자열을 살펴보고, 성공하면:

  • 위치를 앞으로 옮겨요.
  • 대상 부분 문자열을 줄여요.
메서드 반환 매치 값 설정?
get_byte 앞부분 바이트 또는 nil. No.
getch 앞부분 문자 또는 nil. No.
scan(pattern) 일치하는 앞부분 부분 문자열 또는 nil. Yes.
scan_byte 정수 앞부분 바이트 또는 nil. No.
scan_until(pattern) 일치하는 부분 문자열(어디든) 또는 nil. Yes.
skip(pattern) 일치하는 앞부분 부분 문자열의 크기 또는 nil. Yes.
skip_until(pattern) 일치하는 부분 문자열 끝까지의 위치 델타 또는 nil. Yes.
unscan self. No.

스캐너 질의 (Querying the Scanner)

이 메서드들은 스캐너 객체를 수정하지 않고 질의해요:

메서드 반환
beginning_of_line? true 또는 false.
charpos 문자 위치.
eos? true 또는 false.
fixed_anchor? true 또는 false.
inspect self의 문자열 표현.
pos 바이트 위치.
rest 대상 부분 문자열.
rest_size 대상 부분 문자열의 크기.
string 저장된 문자열.

매칭 (Matching)

StringScannerRegexp 클래스를 통해 패턴 매칭을 구현해요. 매칭 동작은 fixed-anchor 속성을 제외하면 Ruby의 것과 같아요.

매처 메서드 (Matcher Methods)

매처 메서드는 단일 인자 pattern을 받고, 대상 부분 문자열에서 일치하는 부분 문자열을 찾으려 시도해요.

메서드 패턴 타입 대상 부분 문자열에서의 위치 성공 반환 위치 갱신 가능?
check Regexp 또는 String. 시작. 일치하는 부분 문자열. No.
check_until Regexp 또는 String. 어디든. 부분 문자열. No.
match? Regexp 또는 String. 시작. 매치 크기. No.
exist? Regexp 또는 String. 어디든. 부분 문자열 크기. No.
scan Regexp 또는 String. 시작. 일치하는 부분 문자열. Yes.
scan_until Regexp 또는 String. 어디든. 부분 문자열. Yes.
skip Regexp 또는 String. 시작. 매치 크기. Yes.
skip_until Regexp 또는 String. 어디든. 부분 문자열 크기. Yes.

어떤 매처를 고를지는 다음에 달려 있어요:

  • 어디서 매치를 찾고 싶은가:
    • 대상 부분 문자열의 시작에서만: check, match?, scan, skip.
    • 대상 부분 문자열 어디든: check_until, exist?, scan_until, skip_until.
  • 위치를 앞으로 옮길 것인가(탐색):
    • 위치를 앞으로 옮김: scan, scan_until, skip, skip_until.
    • 위치를 그대로 둠: check, check_until, match?, exist?.
  • 어떤 반환값을 원하는가:
    • 일치하는 부분 문자열: check, scan.
    • 부분 문자열: check_until, scan_until.
    • 매치 크기: match?, skip.
    • 부분 문자열 크기: exist?, skip_until.

매치 값 (Match Values)

StringScanner 객체의 매치 값(match values) 은 일반적으로 가장 최근 시도한 매치의 결과를 담아요.

각 매치 값은 다음과 같이 생각할 수 있어요:

  • Clear: 처음이거나 성공하지 못한 매치 시도 후에. 보통 false, nil, {}.
  • Set: 성공한 매치 시도 후에. true, 문자열, 배열 또는 해시.

이 메서드들은 매치 값을 지워요:

  • ::new(string).
  • reset.
  • terminate.

이 메서드들은 패턴을 기반으로 매치를 시도하고, 성공하면 매치 값을 설정하거나 실패하면 지워요:

  • check(pattern)
  • check_until(pattern)
  • exist?(pattern)
  • match?(pattern)
  • scan(pattern)
  • scan_until(pattern)
  • skip(pattern)
  • skip_until(pattern)

기본 매치 값 (Basic Match Values)

기본 매치 값은 캡처와 관련 없는 값들이에요.

메서드 매치 후 반환 매치 실패 후 반환
matched? true. false.
matched_size 일치 부분 문자열의 크기. nil.
matched 일치한 부분 문자열. nil.
pre_match 일치한 부분 문자열 앞의 부분 문자열. nil.
post_match 일치한 부분 문자열 뒤의 부분 문자열. nil.

캡처 매치 값 (Captured Match Values)

캡처 매치 값은 캡처와 관련된 값들이에요.

메서드 매치 후 반환 매치 실패 후 반환
size 캡처된 부분 문자열의 개수. nil.
[](n) n번째 캡처된 부분 문자열. nil.
captures 모든 캡처된 부분 문자열의 배열. nil.
values_at(*n) 지정된 캡처된 부분 문자열들의 배열. nil.
named_captures 이름붙은 캡처들의 해시. {}.

매치 값 예시

캡처 없는 성공한 기본 매치 시도:

scanner = StringScanner.new('foobarbaz')
scanner.exist?(/bar/)
put_match_values(scanner)
# Basic match values:
#   matched?:       true
#   matched_size:   3
#   pre_match:      "foo"
#   matched  :      "bar"
#   post_match:     "baz"
# Captured match values:
#   size:           1
#   captures:       []
#   named_captures: {}
#   values_at:      ["bar", nil]
#   []:
#     [0]:          "bar"
#     [1]:          nil

캡처 없는 실패한 기본 매치 시도:

scanner = StringScanner.new('foobarbaz')
scanner.exist?(/nope/)
match_values_cleared?(scanner) # => true

이름 없는 캡처가 있는 성공한 매치 시도:

scanner = StringScanner.new('foobarbazbatbam')
scanner.exist?(/(foo)bar(baz)bat(bam)/)
put_match_values(scanner)
# Basic match values:
#   matched?:       true
#   matched_size:   15
#   pre_match:      ""
#   matched  :      "foobarbazbatbam"
#   post_match:     ""
# Captured match values:
#   size:           4
#   captures:       ["foo", "baz", "bam"]
#   named_captures: {}
#   values_at:      ["foobarbazbatbam", "foo", "baz", "bam", nil]
#   []:
#     [0]:          "foobarbazbatbam"
#     [1]:          "foo"
#     [2]:          "baz"
#     [3]:          "bam"
#     [4]:          nil

이름붙은 캡처가 있는 성공한 매치 시도. 위의 이름 없는 것과 같지만 named_captures만 달라요:

scanner = StringScanner.new('foobarbazbatbam')
scanner.exist?(/(?<x>foo)bar(?<y>baz)bat(?<z>bam)/)
scanner.named_captures # => {"x"=>"foo", "y"=>"baz", "z"=>"bam"}

고정 앵커 속성 (Fixed-Anchor Property)

StringScanner의 패턴 매칭은 Ruby의 것과 같지만, fixed-anchor 속성 하나가 달라요. 이 속성은 '\A'의 의미를 결정해요:

  • false(기본값): 현재 바이트 위치에서 매치해요.
scanner = StringScanner.new('foobar')
scanner.scan(/\A./) # => "f"
scanner.scan(/\A./) # => "o"
scanner.scan(/\A./) # => "o"
scanner.scan(/\A./) # => "b"
  • true: 대상 부분 문자열의 시작에서 매치해요. 바이트 위치가 0이 아니면 절대 매치하지 않아요:
scanner = StringScanner.new('foobar', fixed_anchor: true)
scanner.scan(/\A./) # => "f"
scanner.scan(/\A./) # => nil
scanner.reset
scanner.scan(/\A./) # => "f"

fixed-anchor 속성은 StringScanner 객체를 만들 때 설정되며 수정할 수 없어요(StringScanner.new 참고). fixed_anchor? 메서드가 그 설정을 반환해요.

Class Methods

new(string, fixed_anchor: false) → string_scanner

저장된 문자열이 주어진 string인 새 StringScanner 객체를 반환하고, fixed-anchor 속성을 설정해요:

scanner = StringScanner.new('foobarbaz')
scanner.string        # => "foobarbaz"
scanner.fixed_anchor? # => false
put_situation(scanner)
# Situation:
#   pos:       0
#   charpos:   0
#   rest:      "foobarbaz"
#   rest_size: 9

Instance Methods

→ substring or nil

캡처된 부분 문자열 또는 nil을 반환해요. Captured Match Values 참고.

캡처가 있을 때:

scanner = StringScanner.new('Fri Dec 12 1975 14:39')
scanner.scan(/(?<wday>\w+) (?<month>\w+) (?<day>\d+) /)
  • 지정자 0: 전체 일치 부분 문자열을 반환해요:
scanner[0]         # => "Fri Dec 12 "
scanner.pre_match  # => ""
scanner.post_match # => "1975 14:39"
  • 양수 정수 지정자: n번째 캡처를 반환해요. 범위를 벗어나면 nil:
scanner[1] # => "Fri"
scanner[2] # => "Dec"
scanner[3] # => "12"
scanner[4] # => nil
  • 음수 정수 지정자: 마지막 하위 그룹에서 역으로 센다:
scanner[-1] # => "12"
scanner[-4] # => "Fri Dec 12 "
scanner[-5] # => nil
  • 심볼 또는 문자열 지정자: 이름붙은 하위 그룹을 반환해요. 없으면 nil:
scanner[:wday]  # => "Fri"
scanner['wday'] # => "Fri"
scanner[:month] # => "Dec"
scanner[:day]   # => "12"
scanner[:nope]  # => nil

캡처가 없을 때는 [0]nil이 아닌 값을 반환해요:

scanner = StringScanner.new('foobarbaz')
scanner.exist?(/bar/)
scanner[0] # => "bar"
scanner[1] # => nil

실패한 매치에서는 [0]조차 nil을 반환해요:

scanner.scan(/nope/) # => nil
scanner[0]           # => nil
scanner[1]           # => nil

beginning_of_line? → true or false

위치가 줄의 시작인지 여부를 반환해요. 즉 저장 문자열의 시작이거나 개행 바로 뒤인지요:

scanner = StringScanner.new(MULTILINE_TEXT)
scanner.string
# => "Go placidly amid the noise and haste,\nand remember what peace there may be in silence.\n"
scanner.pos                # => 0
scanner.beginning_of_line? # => true

scanner.scan_until(/,/)    # => "Go placidly amid the noise and haste,"
scanner.beginning_of_line? # => false

scanner.scan(/\n/)         # => "\n"
scanner.beginning_of_line? # => true

scanner.terminate
scanner.beginning_of_line? # => true

scanner.concat('x')
scanner.terminate
scanner.beginning_of_line? # => false

StringScanner#bol?StringScanner#beginning_of_line?의 별칭이에요.

captures → substring_array or nil

가장 최근 매치 시도가 성공했다면 인덱스 (1..)의 캡처된 매치 값 배열을, 아니면 nil을 반환해요:

scanner = StringScanner.new('Fri Dec 12 1975 14:39')
scanner.captures         # => nil

scanner.exist?(/(?<wday>\w+) (?<month>\w+) (?<day>\d+) /)
scanner.captures         # => ["Fri", "Dec", "12"]
scanner.values_at(*0..4) # => ["Fri Dec 12 ", "Fri", "Dec", "12", nil]

scanner.exist?(/Fri/)
scanner.captures         # => []

scanner.scan(/nope/)
scanner.captures         # => nil

charpos → character_position

문자 위치(처음엔 0)를 반환해요. pos 메서드가 주는 바이트 위치와 다를 수 있어요:

scanner = StringScanner.new(HIRAGANA_TEXT)
scanner.string # => "こんにちは"
scanner.getch  # => "こ" # 3-byte character.
scanner.getch  # => "ん" # 3-byte character.
put_situation(scanner)
# Situation:
#   pos:       6
#   charpos:   2
#   rest:      "にちは"
#   rest_size: 9

check(pattern) → matched_substring or nil

대상 부분 문자열의 시작에서 주어진 pattern에 매치를 시도해요. 위치를 수정하지 않아요.

매치가 성공하면:

  • 일치한 부분 문자열을 반환해요.
  • 모든 매치 값을 설정해요.
scanner = StringScanner.new('foobarbaz')
scanner.pos = 3
scanner.check('bar') # => "bar"
put_match_values(scanner)
# Basic match values:
#   matched?:       true
#   matched_size:   3
#   pre_match:      "foo"
#   matched  :      "bar"
#   post_match:     "baz"
# Captured match values:
#   size:           1
#   captures:       []
#   named_captures: {}
#   values_at:      ["bar", nil]
#   []:
#     [0]:          "bar"
#     [1]:          nil
# => 0..1
put_situation(scanner)
# Situation:
#   pos:       3
#   charpos:   3
#   rest:      "barbaz"
#   rest_size: 6

매치가 실패하면:

  • nil을 반환해요.
  • 모든 매치 값을 지워요.
scanner.check(/nope/)          # => nil
match_values_cleared?(scanner) # => true

check_until(pattern) → substring or nil

대상 부분 문자열 어디든(어떤 위치에서든) 주어진 pattern에 매치를 시도해요. 위치를 수정하지 않아요.

매치가 성공하면:

  • 모든 매치 값을 설정해요.
  • 현재 위치부터 일치하는 부분 문자열 끝까지 뻗는 일치하는 부분 문자열을 반환해요.
scanner = StringScanner.new('foobarbazbatbam')
scanner.pos = 6
scanner.check_until(/bat/) # => "bazbat"
put_match_values(scanner)
# Basic match values:
#   matched?:       true
#   matched_size:   3
#   pre_match:      "foobarbaz"
#   matched  :      "bat"
#   post_match:     "bam"
# Captured match values:
#   size:           1
#   captures:       []
#   named_captures: {}
#   values_at:      ["bat", nil]
#   []:
#     [0]:          "bat"
#     [1]:          nil
put_situation(scanner)
# Situation:
#   pos:       6
#   charpos:   6
#   rest:      "bazbatbam"
#   rest_size: 9

매치가 실패하면:

  • 모든 매치 값을 지워요.
  • nil을 반환해요.

concat(more_string) → self

  • 주어진 more_string을 저장된 문자열에 추가해요.
  • self를 반환해요.
  • 위치나 매치 값에는 영향을 주지 않아요.
scanner = StringScanner.new('foo')
scanner.string           # => "foo"
scanner.terminate
scanner.concat('barbaz') # => #<StringScanner 3/9 "foo" @ "barba...">
scanner.string           # => "foobarbaz"
put_situation(scanner)
# Situation:
#   pos:       3
#   charpos:   3
#   rest:      "barbaz"
#   rest_size: 6

eos? → true or false

위치가 저장 문자열의 끝인지 여부를 반환해요:

scanner = StringScanner.new('foobarbaz')
scanner.eos? # => false
pos = 3
scanner.eos? # => false
scanner.terminate
scanner.eos? # => true

exist?(pattern) → byte_offset or nil

대상 부분 문자열 어디든(어떤 위치에서든) 주어진 pattern에 매치를 시도해요. 위치를 수정하지 않아요.

매치가 성공하면:

  • 바이트 오프셋을 반환해요. 현재 위치에서 일치하는 부분 문자열 끝까지의 바이트 거리예요.
  • 모든 매치 값을 설정해요.
scanner = StringScanner.new('foobarbazbatbam')
scanner.pos = 6
scanner.exist?(/bat/) # => 6

매치가 실패하면:

  • nil을 반환해요.
  • 모든 매치 값을 지워요.

fixed_anchor? → true or false

fixed-anchor 속성이 설정됐는지 여부를 반환해요.

get_byte → byte_as_character or nil

가능하다면 다음 바이트를 반환해요:

  • 저장 문자열의 끝이 아니면:
    • 다음 바이트를 반환해요.
    • 바이트 위치를 증가시켜요.
    • 문자 위치를 조정해요.
scanner = StringScanner.new(HIRAGANA_TEXT)
# => #<StringScanner 0/15 @ "\xE3\x81\x93\xE3\x82...">
scanner.string                                   # => "こんにちは"
[scanner.get_byte, scanner.pos, scanner.charpos] # => ["\xE3", 1, 1]
[scanner.get_byte, scanner.pos, scanner.charpos] # => ["\x81", 2, 2]
[scanner.get_byte, scanner.pos, scanner.charpos] # => ["\x93", 3, 1]
[scanner.get_byte, scanner.pos, scanner.charpos] # => ["\xE3", 4, 2]
[scanner.get_byte, scanner.pos, scanner.charpos] # => ["\x82", 5, 3]
[scanner.get_byte, scanner.pos, scanner.charpos] # => ["\x93", 6, 2]
  • 그렇지 않으면 nil을 반환하고 위치를 바꾸지 않아요.
scanner.terminate
[scanner.get_byte, scanner.pos, scanner.charpos] # => [nil, 15, 5]

getch → character or nil

가능하다면 다음 (어쩌면 멀티바이트) 문자를 반환해요:

  • 위치가 문자의 시작에 있으면:
    • 문자를 반환해요.
    • 문자 위치를 1 증가시켜요.
    • 바이트 위치를 문자의 크기(바이트)만큼 증가시켜요.
scanner = StringScanner.new(HIRAGANA_TEXT)
scanner.string                                # => "こんにちは"
[scanner.getch, scanner.pos, scanner.charpos] # => ["こ", 3, 1]
[scanner.getch, scanner.pos, scanner.charpos] # => ["ん", 6, 2]
[scanner.getch, scanner.pos, scanner.charpos] # => ["に", 9, 3]
[scanner.getch, scanner.pos, scanner.charpos] # => ["ち", 12, 4]
[scanner.getch, scanner.pos, scanner.charpos] # => ["は", 15, 5]
[scanner.getch, scanner.pos, scanner.charpos] # => [nil, 15, 5]
  • 위치가 멀티바이트 문자 안(시작이 아닌)에 있으면 get_byte처럼 동작해요(1바이트 문자를 반환):
scanner.pos = 1
[scanner.getch, scanner.pos, scanner.charpos] # => ["\x81", 2, 2]
[scanner.getch, scanner.pos, scanner.charpos] # => ["\x93", 3, 1]
[scanner.getch, scanner.pos, scanner.charpos] # => ["ん", 6, 2]
  • 위치가 저장 문자열의 끝이면 nil을 반환하고 위치를 수정하지 않아요:
scanner.terminate
[scanner.getch, scanner.pos, scanner.charpos] # => [nil, 15, 5]

inspect → string

다음을 보여줄 수 있는 self의 문자열 표현을 반환해요:

  1. 현재 위치.
  2. 저장 문자열의 크기(바이트).
  3. 현재 위치 앞의 부분 문자열.
  4. 현재 위치 뒤의 부분 문자열(대상 부분 문자열이기도 함).
scanner = StringScanner.new("Fri Dec 12 1975 14:39")
scanner.pos = 11
scanner.inspect # => "#<StringScanner 11/21 \"...c 12 \" @ \"1975 ...\">"

문자열 시작에 있으면 위 4번은 생략돼요:

scanner.reset
scanner.inspect # => "#<StringScanner 0/21 @ \"Fri D...\">"

문자열 끝에 있으면 위 항목 모두 생략돼요:

scanner.terminate
scanner.inspect # => "#<StringScanner fin>"

match?(pattern) → updated_position or nil

대상 부분 문자열의 시작에서 주어진 pattern에 매치를 시도해요. 위치를 수정하지 않아요.

매치가 성공하면:

  • 매치 값을 설정해요.
  • 일치한 부분 문자열의 크기(바이트)를 반환해요.
scanner = StringScanner.new('foobarbaz')
scanner.pos = 3
scanner.match?(/bar/) => 3

매치가 실패하면:

  • 매치 값을 지워요.
  • nil을 반환해요.
  • 위치를 증가시키지 않아요.

matched → matched_substring or nil

가장 최근 매치 시도가 성공했다면 일치한 부분 문자열을, 아니면 nil을 반환해요:

scanner = StringScanner.new('foobarbaz')
scanner.matched        # => nil
scanner.pos = 3
scanner.match?(/bar/)  # => 3
scanner.matched        # => "bar"
scanner.match?(/nope/) # => nil
scanner.matched        # => nil

matched? → true or false

가장 최근 매치 시도가 성공했으면 true, 아니면 false를 반환해요:

scanner = StringScanner.new('foobarbaz')
scanner.matched?       # => false
scanner.pos = 3
scanner.exist?(/baz/)  # => 6
scanner.matched?       # => true
scanner.exist?(/nope/) # => nil
scanner.matched?       # => false

matched_size → substring_size or nil

가장 최근 매치 시도가 성공했다면 일치한 부분 문자열의 크기(바이트)를, 아니면 nil을 반환해요:

scanner = StringScanner.new('foobarbaz')
scanner.matched_size   # => nil

pos = 3
scanner.exist?(/baz/)  # => 9
scanner.matched_size   # => 3

scanner.exist?(/nope/) # => nil
scanner.matched_size   # => nil

named_captures → hash

가장 최근 매치 시도가 성공했다면 인덱스 (1..)의 캡처된 매치 값 배열을, 아니면 nil을 반환해요:

scanner = StringScanner.new('Fri Dec 12 1975 14:39')
scanner.named_captures # => {}

pattern = /(?<wday>\w+) (?<month>\w+) (?<day>\d+) /
scanner.match?(pattern)
scanner.named_captures # => {"wday"=>"Fri", "month"=>"Dec", "day"=>"12"}

scanner.string = 'nope'
scanner.match?(pattern)
scanner.named_captures # => {"wday"=>nil, "month"=>nil, "day"=>nil}

scanner.match?(/nosuch/)
scanner.named_captures # => {}

peek(length) → substring

부분 문자열 string[pos, length]을 반환해요. 매치 값이나 위치를 갱신하지 않아요:

scanner = StringScanner.new('foobarbaz')
scanner.pos = 3
scanner.peek(3)   # => "bar"
scanner.terminate
scanner.peek(3)   # => ""

peek_byte → integer or nil

현재 바이트를 들여다보고(peek) 정수로 반환해요:

s = StringScanner.new('ab')
s.peek_byte         # => 97

pos → byte_position

정수 바이트 위치를 반환해요. 문자 위치와 다를 수 있어요:

scanner = StringScanner.new(HIRAGANA_TEXT)
scanner.string  # => "こんにちは"
scanner.pos     # => 0
scanner.getch   # => "こ" # 3-byte character.
scanner.charpos # => 1
scanner.pos     # => 3

pos = n → n

바이트 위치와 문자 위치를 설정하고 n을 반환해요. 매치 값에는 영향을 주지 않아요.

음이 아닌 n에 대해서는 위치를 n으로 설정해요:

scanner = StringScanner.new(HIRAGANA_TEXT)
scanner.string  # => "こんにちは"
scanner.pos = 3 # => 3
scanner.rest    # => "んにちは"
scanner.charpos # => 1

음수 n에 대해서는 저장 문자열의 끝에서부터 세어요:

scanner.pos = -9 # => -9
scanner.pos      # => 6
scanner.rest     # => "にちは"
scanner.charpos  # => 2

post_match → substring

가장 최근 매치 시도가 성공했다면 일치한 부분 문자열 뒤에 이어지는 부분 문자열을, 아니면 nil을 반환해요:

scanner = StringScanner.new('foobarbaz')
scanner.post_match     # => nil

scanner.pos = 3
scanner.match?(/bar/)  # => 3
scanner.post_match     # => "baz"

scanner.match?(/nope/) # => nil
scanner.post_match     # => nil

pre_match → substring

가장 최근 매치 시도가 성공했다면 일치한 부분 문자열 앞에 오는 부분 문자열을, 아니면 nil을 반환해요:

scanner = StringScanner.new('foobarbaz')
scanner.pre_match      # => nil

scanner.pos = 3
scanner.exist?(/baz/)  # => 6
scanner.pre_match      # => "foobar" # Substring of entire string, not just target string.

scanner.exist?(/nope/) # => nil
scanner.pre_match      # => nil

reset → self

바이트 위치와 문자 위치를 모두 0으로 설정하고 매치 값을 지운 뒤 self를 반환해요:

scanner = StringScanner.new('foobarbaz')
scanner.exist?(/bar/)          # => 6
scanner.reset                  # => #<StringScanner 0/9 @ "fooba...">
put_situation(scanner)
# Situation:
#   pos:       0
#   charpos:   0
#   rest:      "foobarbaz"
#   rest_size: 9
# => nil
match_values_cleared?(scanner) # => true

rest → target_substring

저장 문자열의 "나머지"(현재 위치 뒤의 전부), 즉 대상 부분 문자열을 반환해요:

scanner = StringScanner.new('foobarbaz')
scanner.rest # => "foobarbaz"
scanner.pos = 3
scanner.rest # => "barbaz"
scanner.terminate
scanner.rest # => ""

rest_size → integer

저장 문자열의 나머지 크기(바이트)를 반환해요:

scanner = StringScanner.new('foobarbaz')
scanner.rest      # => "foobarbaz"
scanner.rest_size # => 9
scanner.pos = 3
scanner.rest      # => "barbaz"
scanner.rest_size # => 6
scanner.terminate
scanner.rest      # => ""
scanner.rest_size # => 0

scan(pattern) → substring or nil

대상 부분 문자열의 시작에서 주어진 pattern에 매치를 시도해요.

매치가 성공하면:

  • 일치한 부분 문자열을 반환해요.
  • 바이트 위치를 substring.bytesize만큼 증가시키고 문자 위치를 증가시킬 수 있어요.
  • 매치 값을 설정해요.
scanner = StringScanner.new(HIRAGANA_TEXT)
scanner.string     # => "こんにちは"
scanner.pos = 6
scanner.scan(/に/) # => "に"
put_match_values(scanner)
# Basic match values:
#   matched?:       true
#   matched_size:   3
#   pre_match:      "こん"
#   matched  :      "に"
#   post_match:     "ちは"
# Captured match values:
#   size:           1
#   captures:       []
#   named_captures: {}
#   values_at:      ["に", nil]
#   []:
#     [0]:          "に"
#     [1]:          nil
put_situation(scanner)
# Situation:
#   pos:       9
#   charpos:   3
#   rest:      "ちは"
#   rest_size: 6

매치가 실패하면:

  • nil을 반환해요.
  • 바이트·문자 위치를 증가시키지 않아요.
  • 매치 값을 지워요.

scan_byte → integer_byte

1바이트를 스캔하고 정수로 반환해요. 이 메서드는 멀티바이트 문자에 민감하지 않아요. getch도 함께 보세요.

scan_integer(base: 10)

base가 주어지지 않거나 10이면, [+-]?\d+ 패턴으로 #scan을 호출하는 것과 같고 Integer 또는 nil을 반환해요.

base16이면, [+-]?(0x)?\d+ 패턴으로 #scan을 호출하는 것과 같고 Integer 또는 nil을 반환해요.

스캔하는 문자열은 ASCII 호환 인코딩으로 인코딩되어야 해요. 그렇지 않으면 Encoding::CompatibilityError가 발생해요.

scan_until(pattern) → substring or nil

대상 부분 문자열 어디든(어떤 위치에서든) 주어진 pattern에 매치를 시도해요.

매치 시도가 성공하면:

  • 매치 값을 설정해요.
  • 바이트 위치를 일치한 부분 문자열의 끝으로 설정하고 문자 위치를 조정할 수 있어요.
  • 일치한 부분 문자열을 반환해요.
scanner = StringScanner.new(HIRAGANA_TEXT)
scanner.string           # => "こんにちは"
scanner.pos = 6
scanner.scan_until(/ち/) # => "にち"
put_match_values(scanner)
# Basic match values:
#   matched?:       true
#   matched_size:   3
#   pre_match:      "こんに"
#   matched  :      "ち"
#   post_match:     "は"
# Captured match values:
#   size:           1
#   captures:       []
#   named_captures: {}
#   values_at:      ["ち", nil]
#   []:
#     [0]:          "ち"
#     [1]:          nil
put_situation(scanner)
# Situation:
#   pos:       12
#   charpos:   4
#   rest:      "は"
#   rest_size: 3

매치 시도가 실패하면:

  • 매치 데이터를 지워요.
  • nil을 반환해요.
  • 위치를 갱신하지 않아요.

size → captures_count

가장 최근 매치 시도가 성공했다면 캡처 개수를, 아니면 nil을 반환해요:

scanner = StringScanner.new('Fri Dec 12 1975 14:39')
scanner.size                        # => nil

pattern = /(?<wday>\w+) (?<month>\w+) (?<day>\d+) /
scanner.match?(pattern)
scanner.values_at(*0..scanner.size) # => ["Fri Dec 12 ", "Fri", "Dec", "12", nil]
scanner.size                        # => 4

scanner.match?(/nope/)              # => nil
scanner.size                        # => nil

skip(pattern) → match_size or nil

대상 부분 문자열의 시작에서 주어진 pattern에 매치를 시도해요.

매치가 성공하면:

  • 바이트 위치를 substring.bytesize만큼 증가시키고 문자 위치를 증가시킬 수 있어요.
  • 매치 값을 설정해요.
  • 일치한 부분 문자열의 크기(바이트)를 반환해요.
scanner = StringScanner.new(HIRAGANA_TEXT)
scanner.string                  # => "こんにちは"
scanner.pos = 6
scanner.skip(/に/)              # => 3
put_match_values(scanner)
# Basic match values:
#   matched?:       true
#   matched_size:   3
#   pre_match:      "こん"
#   matched  :      "に"
#   post_match:     "ちは"
# Captured match values:
#   size:           1
#   captures:       []
#   named_captures: {}
#   values_at:      ["に", nil]
#   []:
#     [0]:          "に"
#     [1]:          nil
put_situation(scanner)
# Situation:
#   pos:       9
#   charpos:   3
#   rest:      "ちは"
#   rest_size: 6

scanner.skip(/nope/)            # => nil
match_values_cleared?(scanner)  # => true

skip_until(pattern) → matched_substring_size or nil

대상 부분 문자열 어디든(어떤 위치에서든) 주어진 pattern에 매치를 시도해요. 위치를 수정하지 않아요.

매치 시도가 성공하면:

  • 매치 값을 설정해요.
  • 일치한 부분 문자열의 크기를 반환해요.
scanner = StringScanner.new(HIRAGANA_TEXT)
scanner.string           # => "こんにちは"
scanner.pos = 6
scanner.skip_until(/ち/) # => 6
put_match_values(scanner)
# Basic match values:
#   matched?:       true
#   matched_size:   3
#   pre_match:      "こんに"
#   matched  :      "ち"
#   post_match:     "は"
# Captured match values:
#   size:           1
#   captures:       []
#   named_captures: {}
#   values_at:      ["ち", nil]
#   []:
#     [0]:          "ち"
#     [1]:          nil
put_situation(scanner)
# Situation:
#   pos:       12
#   charpos:   4
#   rest:      "は"
#   rest_size: 3

매치 시도가 실패하면:

  • 매치 값을 지워요.
  • nil을 반환해요.

string → stored_string

저장된 문자열을 반환해요:

scanner = StringScanner.new('foobar')
scanner.string # => "foobar"
scanner.concat('baz')
scanner.string # => "foobarbaz"

string = other_string → other_string

저장된 문자열을 주어진 other_string으로 교체해요:

  • 두 위치를 모두 0으로 설정해요.
  • 매치 값을 지워요.
  • other_string을 반환해요.
scanner = StringScanner.new('foobar')
scanner.scan(/foo/)
put_situation(scanner)
# Situation:
#   pos:       3
#   charpos:   3
#   rest:      "bar"
#   rest_size: 3
match_values_cleared?(scanner) # => false

scanner.string = 'baz'         # => "baz"
put_situation(scanner)
# Situation:
#   pos:       0
#   charpos:   0
#   rest:      "baz"
#   rest_size: 3
match_values_cleared?(scanner) # => true

terminate → self

스캐너를 문자열 끝으로 설정하고 self를 반환해요:

  • 두 위치를 모두 스트림 끝으로 설정해요.
  • 매치 값을 지워요.
scanner = StringScanner.new(HIRAGANA_TEXT)
scanner.string                 # => "こんにちは"
scanner.scan_until(/に/)
put_situation(scanner)
# Situation:
#   pos:       9
#   charpos:   3
#   rest:      "ちは"
#   rest_size: 6
match_values_cleared?(scanner) # => false

scanner.terminate              # => #<StringScanner fin>
put_situation(scanner)
# Situation:
#   pos:       15
#   charpos:   5
#   rest:      ""
#   rest_size: 0
match_values_cleared?(scanner) # => true

unscan → self

위치를 가장 최근 성공한 매치 시도 이전의 값으로 설정해요:

scanner = StringScanner.new('foobarbaz')
scanner.scan(/foo/)
put_situation(scanner)
# Situation:
#   pos:       3
#   charpos:   3
#   rest:      "barbaz"
#   rest_size: 6
scanner.unscan
# => #<StringScanner 0/9 @ "fooba...">
put_situation(scanner)
# Situation:
#   pos:       0
#   charpos:   0
#   rest:      "foobarbaz"
#   rest_size: 9

매치 값이 지워져 있으면 예외를 발생시켜요:

scanner.scan(/nope/)           # => nil
match_values_cleared?(scanner) # => true
scanner.unscan                 # Raises StringScanner::Error.

values_at(*specifiers) → array_of_captures or nil

캡처된 부분 문자열들의 배열 또는 nil을 반환해요.

specifier에 대해 반환되는 부분 문자열은 [specifier]예요.

scanner = StringScanner.new('Fri Dec 12 1975 14:39')
pattern = /(?<wday>\w+) (?<month>\w+) (?<day>\d+) /
scanner.match?(pattern)
scanner.values_at(*0..3)               # => ["Fri Dec 12 ", "Fri", "Dec", "12"]
scanner.values_at(*%i[wday month day]) # => ["Fri", "Dec", "12"]

dup → shallow_copy

self의 얕은 복사본을 반환해요. 복사본의 저장 문자열은 self와 같은 문자열이에요.