Treetop과 함께 Ruby의 TCL 어레이를 구문 분석합니다
문제
TCL 배열에 많은 데이터가 있습니다. 기본적으로 그것은 형태입니다 {a {b c} d {e f} g}
. 그것은 단지 하나의 깊이에만 중첩되었지만 항상 중첩 된 것은 아닙니다. a
그냥 할 수 있습니다 a
아니면 그럴 수도 있습니다 {aa bb}
또는 아마도 {}
, 그러나 결코 {aa {bb cc}}
. 루비에서 사용할 수 있도록이 배열을 추출하고 싶습니다.
나의 첫 번째 생각은 "문제가 없다. 나는 이것을 구문 분석하기 위해 작은 문법을 쓸 것이다." 나는 Treetop 보석을 설치하고 파서를 썼는데, 그것은 잘 작동하는 것처럼 보였다. 구문 분석 된 나무에서 배열을 추출하려고 할 때 문제가 발생하기 시작했습니다. 문제의 원인과 내가 잘못하고있는 일을 더 잘 이해하고 싶습니다.
지금까지 내 파서 코드는 다음과 같습니다. (tcl_array.treetop)
grammar TCLArray
rule array
"{" [\s]* "}" {
def content
[]
end
}
/
"{" [\s]* array_element_list [\s]* "}" {
def content
array_element_list.content
end
}
end
rule array_element_list
array_element {
def content
[array_element.content]
end
}
/
array_element [\s]+ array_element_list {
def content
[array_element.content] + array_element_list.content
end
}
end
rule array_element
[^{}\s]+ {
def content
return text_value
end
}
/
array {
def content
array.content
end
}
end
end
호출 p.parse("{a}").content
수율 tcl_array.rb:99:in 'content': undefined local variable or method 'array_element'
Array_Element_List (Array_Element)의 첫 번째 용어에 따르면 Array_Element는 정의되지 않은 로컬 변수이지만 액세서는 Treetop 문서에 따라 자동으로 정의되어야합니다.
앞서, 나는 규칙이 적지 만 약간 더 복잡한 규칙을 가진 문법을 기반으로 한 솔루션을 시도했습니다.
grammar TCLArray
rule array
"{" ([\s]* array_element ([\s]+ array_element)* )? [\s]* "}"
end
rule array_element
[^{}\s]+ / array
end
end
그러나이 문법을 사용하면 파서가 대체 표현식 (/)을 사용하지 않더라도 배열 규칙에 대해 여러 가지 다른 표현식을 만드는 문제가있었습니다. 그 결과 배열 규칙의 다양한 비트에 액세스하여 루비 어레이로 반환하는 방법을 알 수 없었습니다.
해결책
이 경우 파서 생성기가 과잉 일 수 있습니다. 다음은 간단한 손으로 달린 재귀 살해 파서를 기반으로합니다 James Edward Grey II 의이 JSON 파서:
#!/usr/bin/env ruby
# based on James Edward Gray II's solution to the Parsing JSON
# Ruby Quiz #155: <http://RubyQuiz.Com/quiz155.html>
require 'strscan'
class TclArrayParser < StringScanner
def parse
parse_value
ensure
eos? or error "Unexpected data: '#{rest}'"
end
private
def parse_value
trim_space
parse_string or parse_array
ensure
trim_space
end
def parse_array
return nil unless scan(/\{\s*/)
array = []
while contents = parse_value
array << contents
end
scan(/\}/) or error('Unclosed array')
array
end
def parse_string
scan(/[^{}[:space:]]+/)
end
def trim_space
skip(/\s*/)
end
def error(message)
pos = if eos? then 'end of input' else "position #{self.pos}" end
raise ParseError, "#{message} at #{pos}"
end
class ParseError < StandardError; end
end
다음은 테스트 수양입니다.
require 'test/unit'
class TestTclArrayParser < Test::Unit::TestCase
def test_that_an_empty_string_parses_to_nil
assert_nil TclArrayParser.new('').parse
end
def test_that_a_whitespace_string_parses_to_nil
assert_nil TclArrayParser.new(" \t \n ").parse
end
def test_that_an_empty_array_parses_to_an_empty_array
assert_equal [], TclArrayParser.new('{}').parse
end
def test_that_an_empty_array_with_whitespace_at_the_front_parses_to_an_empty_array
assert_equal [], TclArrayParser.new(' {}').parse
end
def test_that_an_empty_array_with_whitespace_at_the_end_parses_to_an_empty_array
assert_equal [], TclArrayParser.new('{} ').parse
end
def test_that_an_empty_array_with_whitespace_inside_parses_to_an_empty_array
assert_equal [], TclArrayParser.new('{ }').parse
end
def test_that_an_empty_array_surrounded_by_whitespace_parses_to_an_empty_array
assert_equal [], TclArrayParser.new(' {} ').parse
end
def test_that_an_empty_array_with_whitespace_at_the_front_and_inside_parses_to_an_empty_array
assert_equal [], TclArrayParser.new(' { }').parse
end
def test_that_an_empty_array_with_whitespace_at_the_end_and_inside_parses_to_an_empty_array
assert_equal [], TclArrayParser.new('{ } ').parse
end
def test_that_an_empty_array_surrounded_by_whitespace_with_whitespace_inside_parses_to_an_empty_array
assert_equal [], TclArrayParser.new(' { } ').parse
end
def test_that_a_sole_element_parses
assert_equal 'a', TclArrayParser.new('a').parse
end
def test_that_an_array_with_one_element_parses
assert_equal ['a'], TclArrayParser.new('{a}').parse
end
def test_that_a_nested_array_parses
assert_equal [[]], TclArrayParser.new('{{}}').parse
end
def test_that_a_nested_array_with_one_element_parses
assert_equal [['a']], TclArrayParser.new('{{a}}').parse
end
def test_that_whitespace_is_ignored
assert_equal [], TclArrayParser.new(' { } ').parse
end
def test_that_complex_arrays_parse_correctly
assert_equal ['a', %w[b c], 'd', %w[e f], 'g'], TclArrayParser.new('{a {b c} d {e f} g}').parse
assert_equal [%w[aa bb], %w[b c], 'd', %w[e f], 'g'], TclArrayParser.new('{{aa bb} {b c} d {e f} g}').parse
assert_equal [[], %w[b c], 'd', %w[e f], 'g'], TclArrayParser.new('{{} {b c} d {e f} g}').parse
assert_equal [[], ['b', 'c'], 'd', ['e', 'f'], 'g'], TclArrayParser.new("\n{\n{\n}\n{\nb\nc\n}\nd\n{\ne\nf\n}\ng\n}\n").parse
end
end
다른 팁
참조를 위해 이것을 주목했지만 방금 간단한 TCL을 구문 분석하기위한 보석을 발표했습니다.