StringScanner
StringScanner
StringScanner는 문자열에 대한 어휘 스캔(lexical scanning) 연산을 제공해요. 정규 표현식으로 문자열을 앞에서부터 조각조각 읽어내는 데 특화된 도구예요.
출처: Ruby 3.3 API
본문
사용 예시를 먼저 볼게요.
require 'strscan'
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
스캔한다는 것은 **스캔 포인터(scan pointer)**의 위치를 기억하는 것이에요. 스캔 포인터는 그냥 인덱스예요. 핵심은 한 번에 조금씩 앞으로 나아간다는 것이고, 그래서 매치를 스캔 포인터 뒤에서 찾아요. 보통은 바로 뒤에서요.
"test string" 문자열에서 스캔 포인터 위치는 이렇게 돼요.
t e s t s t r i n g
0 1 2 ... 1
0
패턴(정규 표현식)을 스캔할 때 매치는 스캔 포인터 바로 다음 문자에서 일어나야 해요. scan_until을 쓰면 매치가 스캔 포인터 뒤 어디서든 일어날 수 있어요. 두 경우 모두 스캔 포인터는 매치의 마지막 문자 바로 다음으로 이동해서, 다음 문자부터 다시 스캔할 준비를 해요.
메서드 분류
스캔 포인터 전진(Advancing)
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— 스캔 포인터를 0으로.terminate— 스캔 포인터를 문자열 끝으로.pos=— 위치를 직접 설정.
매치 데이터(Match Data)
matched,matched?,matched_size,[],pre_match,post_match.
기타(Miscellaneous)
<<,concat,string,string=,unscan.
Public Class Methods
must_C_version
이전 버전과의 하위 호환을 위해 정의된 메서드예요.
new(string, fixed_anchor: false)
new(string, dup = false)
주어진 string을 스캔할 새 StringScanner 객체를 만들어요.
fixed_anchor가 true면 \A가 항상 문자열의 시작과 일치하고, 아니면 항상 현재 위치와 일치해요. dup 인자는 구식이라 지금은 쓰이지 않아요.
Public Instance Methods
<<(str)
스캔 중인 문자열에 str을 덧붙여요. 스캔 포인터에는 영향이 없어요. concat의 별칭이에요.
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"
[](n)
가장 최근 매치의 n번째 부분그룹을 돌려줘요. 이름 있는 캡처(s[:wday])도 가능해요.
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 # -> ""
beginning_of_line?()
스캔 포인터가 줄의 시작에 있으면 true를 돌려줘요.
captures
가장 최근 매치의 부분그룹들(전체 매치 제외)을 돌려줘요. 이전에 일치한 게 없으면 nil을 돌려줘요.
charpos()
스캔 포인터의 문자 위치를 돌려줘요. 'reset' 위치에선 0, 'terminated' 위치(문자열 소진)에선 문자열 크기예요. 요컨대 0-기반 인덱스예요.
check(pattern)
scan이 반환할 값을, 스캔 포인터를 전진시키지 않고 돌려줘요. 단 매치 레지스터에는 영향이 있어요.
s = StringScanner.new("Fri Dec 12 1975 14:39")
s.check /Fri/ # -> "Fri"
s.pos # -> 0
check_until(pattern)
scan_until이 반환할 값을, 스캔 포인터를 전진시키지 않고 돌려줘요.
clear()
terminate와 동등한데 구식이에요. terminate를 쓰세요.
concat(str)
스캔 중인 문자열에 str을 덧붙여요. 스캔 포인터에는 영향이 없어요. <<의 별칭이에요.
empty?()
eos?와 동등한데 구식이에요. eos?를 쓰세요.
eos?()
스캔 포인터가 문자열 끝에 있으면 true를 돌려줘요.
s = StringScanner.new('test string')
s.eos? # => false
s.scan(/test/)
s.eos? # => false
s.terminate
s.eos? # => true
exist?(pattern)
패턴이 문자열 어디에든 존재하는지, 스캔 포인터를 전진시키지 않고 확인해요. scan_until이 값을 반환할지 예측해 줘요.
fixed_anchor? → true or false
스캐너가 fixed anchor 모드를 쓰는지 여부예요. \A가 문자열 시작(모드 켜짐)과 일치할지 현재 위치와 일치할지 결정해요.
get_byte()
한 바이트를 스캔해 돌려줘요. 멀티바이트 문자에 민감하지 않아요. getch도 참고하세요.
getbyte()
get_byte와 동등한데 구식이에요.
getch()
한 문자를 스캔해 돌려줘요. 멀티바이트 문자에 민감해요.
s = StringScanner.new("ab")
s.getch # => "a"
s.getch # => "b"
s.getch # => nil
inspect()
StringScanner 객체를 나타내는 문자열을 돌려줘요. 현재 위치, 문자열 크기, 스캔 포인터 주변 문자들을 보여줘요.
match?(pattern)
현재 스캔 포인터에서 주어진 패턴이 매치되는지 검사하고, 매치의 길이 또는 nil을 돌려줘요. 스캔 포인터는 전진하지 않아요.
matched()
마지막에 매치된 문자열을 돌려줘요.
matched?()
마지막 매치가 성공했으면 true, 아니면 false를 돌려줘요.
matched_size()
가장 최근 매치의 크기를 바이트로 돌려줘요. matched.size(문자 수)와는 달라요.
named_captures → hash
정규 표현식과 일치하는 문자열 변수들의 해시를 돌려줘요.
peek(len)
스캔 포인터를 전진시키지 않고 string[pos,len]에 해당하는 문자열을 추출해요.
peep(p1)
peek과 동등한데 구식이에요.
pointer()
스캔 포인터의 바이트 위치를 돌려줘요. pos의 별칭이에요.
pos()
스캔 포인터의 바이트 위치를 돌려줘요.
pos=(n)
스캔 포인터의 바이트 위치를 설정해요.
post_match()
마지막 스캔의 post-match(정규 표현식 의미에서)를 돌려줘요.
pre_match()
마지막 스캔의 pre-match를 돌려줘요.
reset()
스캔 포인터(인덱스 0)를 재설정하고 매치 데이터를 지워요.
rest()
스캔 포인터 뒤의 '나머지' 문자열을 돌려줘요. 더 이상 데이터가 없으면(eos?가 true) ""를 돌려줘요.
rest?()
문자열에 더 많은 데이터가 있으면 true를 돌려줘요. eos?의 반대예요. 구식이니 eos?를 쓰세요.
rest_size()
s.rest_size는 s.rest.size와 동등해요.
restsize()
rest_size와 동등한데 구식이에요.
scan(pattern) → String
현재 위치에서 pattern과 매치를 시도해요. 매치되면 스캐너가 '스캔 포인터'를 전진하고 매치된 문자열을 돌려주고, 아니면 nil을 돌려줘요.
s = StringScanner.new('test string')
p s.scan(/\w+/) # -> "test"
p s.scan(/\w+/) # -> nil
p s.scan(/\s+/) # -> " "
p s.scan("str") # -> "str"
p s.scan(/\w+/) # -> "ing"
scan_full(pattern, advance_pointer_p, return_string_p)
현재 스캔 포인터에서 주어진 패턴이 매치되는지 검사해요. advance_pointer_p가 참이면 스캔 포인터를 전진시키고, return_string_p가 참이면 매치된 문자열을 돌려줘요. 매치 레지스터에 영향이 있어요.
scan_until(pattern)
패턴이 매치될 때까지 문자열을 스캔해요. 매치 끝까지(포함)의 부분 문자열을 돌려주고 스캔 포인터를 그 위치로 전진시켜요. 매치가 없으면 nil을 돌려줘요.
search_full(pattern, advance_pointer_p, return_string_p)
패턴이 매치될 때까지 문자열을 스캔해요. advance_pointer_p가 참이면 스캔 포인터를, 아니면 그렇지 않게 전진시켜요. return_string_p가 참이면 매치된 문자열을, 아니면 전진한 바이트 수를 돌려줘요.
size
가장 최근 매치의 부분그룹 수를 돌려줘요. 전체 매치도 포함해요.
skip(pattern)
스캔 포인터에서 시작해 주어진 패턴을 건너뛰려 해요. 매치되면 스캔 포인터를 매치 끝으로 전진하고 매치 길이를 돌려주고, 아니면 nil을 돌려줘요. 매치된 문자열을 돌려주지 않는다는 점에서 scan과 비슷해요.
skip_until(pattern)
패턴이 매치·소비될 때까지 스캔 포인터를 전진시켜요. 전진한 바이트 수를 돌려주고, 매치가 없으면 nil을 돌려줘요. 중간 문자열을 돌려주지 않는다는 점에서 scan_until과 비슷해요.
string()
스캔 중인 문자열을 돌려줘요.
string=(str)
스캔 중인 문자열을 str로 바꾸고 스캐너를 재설정해요. str을 돌려줘요.
terminate
스캔 포인터를 문자열 끝으로 설정하고 매치 데이터를 지워요.
unscan()
스캔 포인터를 이전 위치로 설정해요. 이전 위치 하나만 기억되고, 각 스캔 연산마다 바뀌어요. unscan 전에 매치 기록이 없으면 ScanError가 나요.
values_at( i1, i2, ... iN ) → an_array
가장 최근 매치의 주어진 인덱스들의 부분그룹을 돌려줘요. 이전에 일치한 게 없으면 nil을 돌려줘요.
s = StringScanner.new("Fri Dec 12 1975 14:39")
s.scan(/(\w+) (\w+) (\d+) /) # -> "Fri Dec 12 "
s.values_at 0, -1, 5, 2 # -> ["Fri Dec 12 ", "12", nil, "Dec"]