Les tableaux Tcl en Parsing rubis avec TreeTop
Question
J'ai un tas de données (ce que je pense est) un tableau Tcl. Fondamentalement, il est sous la forme de {a {b c} d {e f} g}
. Il est seulement emboîtés l'un profond, mais pas toujours imbriquée, c'est-à-dire, a
peut simplement être a
ou il peut être {aa bb}
ou peut-être {}
, mais jamais {aa {bb cc}}
. Je veux extraire ce tableau afin que je puisse l'utiliser en rubis.
Ma première pensée a été: « Pas de problème, je vais écrire un peu de grammaire pour analyser cela. » J'ai installé le joyau de la cime des arbres, et écrit un analyseur qui semblait fonctionner très bien. J'ai commencé à avoir des problèmes quand j'ai essayé d'extraire un tableau de l'arbre analysé. Je voudrais mieux comprendre la cause des problèmes et ce que je fais mal.
Voici mon code d'analyseur jusqu'à présent: (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
Invoquer les rendements p.parse("{a}").content
tcl_array.rb:99:in 'content': undefined local variable or method 'array_element'
Le premier terme array_element_list (array_element) dit que array_element est une variable locale non définie, mais les méthodes d'accès sont censés être automatiquement définis en fonction de la documentation TreeTop.
Un peu plus tôt, j'ai essayé une solution qui était basée hors d'une grammaire avec des règles moins nombreuses, mais un peu plus compliquées:
grammar TCLArray
rule array
"{" ([\s]* array_element ([\s]+ array_element)* )? [\s]* "}"
end
rule array_element
[^{}\s]+ / array
end
end
Mais avec cette grammaire que j'avais des problèmes où l'analyseur semblait être créer plusieurs expressions différentes pour la règle de tableau, même si elle n'a pas utilisé toutes les expressions alternatives (/). Le résultat était que je ne pouvais pas comprendre comment accéder aux différents bits de la règle de tableau pour les restituer sous forme de tableau rubis.
La solution
Peut-être un générateur d'analyseur syntaxique est surpuissant dans ce cas. Voici un analyseur simple descente récursive roulées à la main basée sur cet analyseur de JSON par James Edward II Gris :
#!/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
Voici une suite de tests:
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
Autres conseils
Notant cela pour référence, mais je viens de publier un petit bijou pour l'analyse syntaxique TCL simple.