문제

이 문자열이 있습니다.

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

그리고 이것을 토큰 화하도록 변환하고 싶습니다.

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

나는 stringscanner와 the를 알고 있습니다 구문 보석 그러나 나는 둘 다에 대한 충분한 코드 예제를 찾을 수 없습니다.

어떤 포인터?

도움이 되었습니까?

해결책

실제 언어의 경우 Lexer는 갈 길입니다. Guss가 말한 것처럼. 그러나 전체 언어가 예제만큼 복잡하다면이 빠른 해킹을 사용할 수 있습니다.

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"}]

일반 언어를 구문 분석하려고한다면이 방법으로 충분합니다. 언어를 규제하지 않게 만드는 데 더 많은 합병증이 필요하지 않습니다.

Regex의 빠른 분석 :

  • \w+ 단일 키워드와 일치합니다
  • (?:\\.|[^\\"]])* 캡처되지 않은 괄호를 사용합니다 ((?:...)) 탈출 된 이중 인용 문자열의 내용과 일치하도록 - 탈출 된 기호 (\n, \", \\, 등) 또는 탈출 기호 나 최종 인용문이 아닌 단일 문자.
  • "((?:\\.|[^\\"]])*)" 인용 된 키워드 문구의 내용 만 캡처합니다.
  • (?:(\w+)|"((?:\\.|[^\\"])*)") 모든 키워드와 일치합니다 - 단일 용어 또는 문구, 단일 용어를 다음으로 캡처합니다. $1 그리고 문구 내용 $2
  • \d+ 숫자와 일치합니다.
  • \^(\d+) 돌보러 다음에 숫자를 캡처합니다 (^). 이것은 괄호를 캡처하는 세 번째 세트이므로 $3.
  • (?:\^(\d+))? 코렛이 있으면 숫자를 캡처하면 빈 문자열과 일치합니다.

String#scan(regex) 레지스와 함께 문자열과 가능한 한 여러 번 일치하여 "매치"배열을 출력합니다. REGEX에 캡처 파렌이 포함되어 있으면 "매치"는 캡처 된 다양한 항목입니다. $1 becomes match[0], $2 becomes match[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.

다른 팁

다음은 사용하지 않는 예제입니다 StringScanner. 이것은 내가 방금 적응 한 코드입니다 루비 퀴즈 : 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

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

Lexer (또는 재귀 파서)를 작성하는 것은 실제로 사소한 일이 아닙니다. 프로그래밍에 유용한 연습이지만이 이메일 메시지에서 Ruby Lexers/Parsers 목록을 찾을 수 있습니다. http://newsgroups.derkeiler.com/archive/comp/comp.lang.ruby/2005-11/msg02233.html

RACC는 Ruby 1.8의 표준 모듈로 제공되므로 매뉴얼을 따르기가 쉽지 않더라도 YACC에 익숙해야하더라도 집중하는 것이 좋습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top