2009-04-03 10 views
12

나는이 문자열이 있습니다Ruby에서이 문자열을 토큰 화하는 방법은 무엇입니까?

%{Children^10 Health "sanitation management"^5} 

을 그리고 해시의 배열로를 토큰 화를 변환 할 :

[{:keywords=>"children", :boost=>10}, {:keywords=>"health", :boost=>nil}, {:keywords=>"sanitation management", :boost=>5}] 

내가 StringScanner 알고 있어요와 Syntax gem하지만 그럴 수 없어 둘 모두에 대한 충분한 코드 예제를 찾으십시오.

모든 포인터?

답변

18

실제 언어는 렉서가 가야합니다 - like Guss said. 이 많이받지 않을 것 - 비록 당신이이 메소드는 충분 정규 언어를 분석하려는 경우

irb> text = %{Children^10 Health "sanitation management"^5} 
irb> text.scan(/(?:(\w+)|"((?:\\.|[^\\"])*)")(?:\^(\d+))?/).map do |word,phrase,boost| 
     { :keywords => (word || phrase).downcase, :boost => (boost.nil? ? nil : boost.to_i) } 
    end 
#=> [{:boost=>10, :keywords=>"children"}, {:boost=>nil, :keywords=>"health"}, {:boost=>5, :keywords=>"sanitation management"}] 

: 전체 언어가 예로서 복잡 전용으로 경우에, 당신이 빠른 해킹을 사용할 수 있습니다 언어를 비정규 화하기 위해 더 많은 합병증이 있습니다.

정규식의 빠른 고장 :

  • \w+
  • (?:\\.|[^\\"]])* 비 캡처 괄호 ((?:...))는 이스케이프 이중 인용 된 문자열의 내용과 일치하기 위해 사용하는 단일 용어 키워드와 일치 - 어느 쪽 탈출 기호 (\n, \", \\ 등) 또는 이스케이프 기호 또는 끝 인용 부호가 아닌 임의의 단일 문자.
  • "((?:\\.|[^\\"]])*)"은 인용 된 키워드 구문의 내용 만 캡처합니다. 숫자와 일치 $2
  • \d+$1 및 구문 내용에 하나의 용어를 포착, 하나의 단어 나 구 -
  • (?:(\w+)|"((?:\\.|[^\\"])*)")는 키워드와 일치합니다.
  • \^(\d+)은 캐럿 (^) 뒤에 오는 번호를 캡처합니다. 이것이 캡쳐 링 괄호의 세 번째 세트이기 때문에, $3에 caputred 될 것입니다.
  • (?:\^(\d+))?은 캐럿 다음에 오는 번호를 캡처하고, 그렇지 않으면 빈 문자열과 일치시킵니다.

String#scan(regex)은 "matches"의 배열을 출력하면서 가능한 한 여러 번 문자열에 대해 정규 표현식을 찾습니다. 정규식에 캡처 괄호가 포함되어 있으면 "일치"는 캡처 된 항목의 배열이므로 match[0], $2match[1]이됩니다.문자열의 일부와 일치하지 않는 모든 캡처 괄호는 결과 "일치"의 nil 항목에 매핑됩니다.

#map 다음 일치를 취하고 캡처 된 각 용어를 다른 변수로 나누기 위해 블록 마법을 사용하고 (do |match| ; word,phrase,boost = *match 일 수 있음) 원하는 해시를 만듭니다. word 또는 phrase 중 하나가 nil이됩니다. 둘 모두를 입력과 비교할 수 없으므로 (word || phrase)nil이 아닌 값을 반환하고 #downcase은 모두 소문자로 변환합니다. boost.to_i은 문자열을 정수로 변환하고 (boost.nil? ? nil : boost.to_i)nil 부스트가 nil으로 유지되도록합니다.

+0

코드에서 사용하는 경우 // x를 사용하고 주석을 추가 할 수 있습니다. – rampion

+0

가능하다면 2 개의 upvotes를 줄 것입니다. 실용적인 접근을 위해 1 개, 잘 설명 된 정규식을 위해 1 개. – slothbear

3

여기에있는 것은 임의의 문법이며, 실제로 원하는 것은 렉서입니다. 구문을 설명하는 문법 파일을 작성한 다음 렉서를 사용하여 문법에서 재귀 파서를 생성 할 수 있습니다.

렉서를 작성 (또는 재귀 파서는) 정말 사소한 아니다 - 그것은 프로그래밍에 유용한 운동이지만 -하지만 당신은 여기이 전자 메일 메시지에서 루비 렉서/파서의 목록을 찾을 수 있습니다 http://newsgroups.derkeiler.com/Archive/Comp/comp.lang.ruby/2005-11/msg02233.html

을 RACC는 Ruby 1.8의 표준 모듈로 제공됩니다. 따라서 매뉴얼을 따르기가 쉽지 않고 yacc에 익숙해야한다고해도 집중할 것을 제안합니다.

12

StringScanner을 사용하는 비 강건한 예입니다. 이 코드는 방금 Ruby Quiz: Parsing JSON에서 수정 한 코드로 훌륭한 설명이 있습니다.

require 'strscan' 

def test_parse 
    text = %{Children^10 Health "sanitation management"^5} 
    expected = [{:keywords=>"children", :boost=>10}, {:keywords=>"health", :boost=>nil}, {:keywords=>"sanitation management", :boost=>5}] 


    assert_equal(expected, parse(text)) 
end 

def parse(text) 
    @input = StringScanner.new(text) 

    output = [] 

    while keyword = parse_string || parse_quoted_string 
    output << { 
     :keywords => keyword, 
     :boost => parse_boost 
    } 
    trim_space 
    end 

    output 
end 

def parse_string 
    if @input.scan(/\w+/) 
    @input.matched.downcase 
    else 
    nil 
    end 
end 

def parse_quoted_string 
    if @input.scan(/"/) 
    str = parse_quoted_contents 
    @input.scan(/"/) or raise "unclosed string" 
    str 
    else 
    nil 
    end 
end 

def parse_quoted_contents 
    @input.scan(/[^\\"]+/) and @input.matched 
end 

def parse_boost 
    if @input.scan(/\^/) 
    boost = @input.scan(/\d+/) 
    raise 'missing boost value' if boost.nil? 
    boost.to_i 
    else 
    nil 
    end 
end 

def trim_space 
    @input.scan(/\s+/) 
end