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.
이 메서드들의 값은 pos와 string에서 직접 파생돼요:
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개라서 pos와 charpos가 같이 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)
StringScanner는 Regexp 클래스를 통해 패턴 매칭을 구현해요. 매칭 동작은 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의 문자열 표현을 반환해요:
- 현재 위치.
- 저장 문자열의 크기(바이트).
- 현재 위치 앞의 부분 문자열.
- 현재 위치 뒤의 부분 문자열(대상 부분 문자열이기도 함).
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을 반환해요.
base가 16이면, [+-]?(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와 같은 문자열이에요.