루비 에서이 문자열을 어떻게 토큰 화하려고합니까?
-
23-08-2019 - |
문제
이 문자열이 있습니다.
%{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에 익숙해야하더라도 집중하는 것이 좋습니다.