Analyse von TCL -Arrays in Ruby mit Treetop
Frage
Ich habe eine Reihe von Daten in einem TCL -Array (was ich denke). Grundsätzlich ist es in Form von {a {b c} d {e f} g}
. Es ist nur eine tiefe verschachtelt, ist aber nicht immer verschachtelt, das heißt: a
darf einfach sein a
oder es kann sein {aa bb}
oder möglicherweise {}
, aber nie {aa {bb cc}}
. Ich möchte dieses Array extrahieren, damit ich es in Ruby verwenden kann.
Mein erster Gedanke war: "Kein Problem, ich werde eine kleine Grammatik schreiben, um dies zu analysieren." Ich installierte das Treetop -Juwel und schrieb einen Parser, der gut zu funktionieren schien. Ich begann Probleme zu haben, als ich versuchte, ein Array aus dem analysierten Baum zu extrahieren. Ich möchte die Ursache für die Probleme und das, was ich falsch mache, besser verstehen.
Hier ist mein Parser -Code bisher: (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
Anrufen p.parse("{a}").content
ergibt tcl_array.rb:99:in 'content': undefined local variable or method 'array_element'
Der erste Term in Array_Element_List (Array_Element) besagt, dass Array_Element eine undefinierte lokale Variable ist, die Accessor -Methoden jedoch nach der Baumdokumentation automatisch definiert werden sollen.
Zuvor habe ich eine Lösung ausprobiert, die auf einer Grammatik mit weniger, aber etwas komplizierteren Regeln basiert:
grammar TCLArray
rule array
"{" ([\s]* array_element ([\s]+ array_element)* )? [\s]* "}"
end
rule array_element
[^{}\s]+ / array
end
end
Aber mit dieser Grammatik hatte ich Probleme, bei denen der Parser mehrere verschiedene Ausdrücke für die Array -Regel zu schaffen schien, obwohl er keine alternativen Ausdrücke verwendete (/). Das Ergebnis war, dass ich nicht herausfinden konnte, wie ich auf die verschiedenen Bits der Array -Regel zugreifen sollte, um sie als Ruby -Array zurückzugeben.
Lösung
Vielleicht ist ein Parser -Generator in diesem Fall übertrieben. Hier ist ein einfacher handgerollter rekursiver Parser basiert auf Dieser JSON -Parser von James Edward Gray II.:
#!/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
Hier ist ein Testsuite:
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
Andere Tipps
Als ich dies als Referenz bemerkte, aber ich habe gerade ein Juwel für das Parsen Simple TCL veröffentlicht.