Parsing array TCL rubino con treetop
Domanda
Ho un po 'di dati in (quello che penso sia) una matrice di TCL. In sostanza è in forma di {a {b c} d {e f} g}
. E 'annidato solo un profondo, ma non è sempre annidato, vale a dire, a
può solo essere a
o può essere {aa bb}
o possibilmente {}
, ma mai {aa {bb cc}}
. Voglio estrarre questo array in modo da poter utilizzare in Ruby.
Il mio primo pensiero è stato: "Nessun problema, scriverò un po 'di grammatica per analizzare questo". Ho installato la gemma cime degli alberi, e ha scritto un parser, che sembrava funzionare bene. Ho iniziato ad avere problemi quando ho cercato di estrarre una matrice dall'albero analizzato. Vorrei capire meglio la causa dei problemi e quello che sto facendo male.
Ecco il mio codice parser finora: (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
rendimenti Invocare p.parse("{a}").content
tcl_array.rb:99:in 'content': undefined local variable or method 'array_element'
Il primo termine a array_element_list (array_element) dice che array_element è una variabile locale non definita, ma metodi di accesso si suppone essere definita automaticamente in base alla documentazione cime degli alberi.
In precedenza, ho provato una soluzione che si basa fuori di una grammatica con meno, ma un po 'più complicate regole:
grammar TCLArray
rule array
"{" ([\s]* array_element ([\s]+ array_element)* )? [\s]* "}"
end
rule array_element
[^{}\s]+ / array
end
end
Ma con questa grammatica ho avuto problemi in cui il parser sembrava essere la creazione di diverse espressioni diverse per la regola di serie, anche se non ha utilizzato alcun espressioni alternative (/). Il risultato è stato che non ho potuto capire come accedere ai vari pezzi di regola matrice di restituirli come array rubino.
Soluzione
Forse un generatore di parser è eccessivo in questo caso. Ecco un semplice parser ricorsivo discesa-arrotolato a mano sulla base di questo JSON parser da James Edward Grey 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
Ecco una suite di test:
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
Altri suggerimenti
Notando questo per riferimento, ma ho appena rilasciato un gioiello per l'analisi semplice TCL.