Превращение дерева разбора Treetop в абстрактное синтаксическое дерево (AST)
Я упростил грамматику, выраженную в Treetop, и пытаюсь отфильтровать вывод синтаксического анализатора в AST, используя пользовательские узлы.
grammar Elem
rule top
lpar 'top' space
args_:(lpar 'args' space ((ident / number) space?)* rpar) space?
rpar <Top>
end
rule ident
[a-zA-Z] [a-zA-Z0-9_]* <Ident>
end
rule number
[0-9]+ <Number>
end
rule space
[\s]+
end
rule lpar
space? '(' space?
end
rule rpar
space? ')' space?
end
end
По сути, он может анализировать следующий пример:
(top (args foo bar 42))
Все пользовательские узлы наследуют Treetop::Runtime::SyntaxNode
Теперь мне нужно отфильтровать дерево разбора, сгенерированное Treetop, в AST.
Я следую стратегии, описанной здесь, но безуспешно: мой AST просто пуст...
Мой драйвер компилятора выглядит следующим образом:
require 'treetop'
require 'pp'
require_relative 'elem'
require_relative 'node_extension'
class ElemCompiler
def initialize
@parser=ElemParser.new
end
def compile filename
puts "==> compiling #{filename}"
@ast=parse(filename)
puts "==> AST in memory. Good."
end
def parse filename
pp tree=@parser.parse(IO.read(filename))
pp clean(tree)
end
private
def clean(root_node)
return if(root_node.elements.nil?)
pp root_node.elements.collect {|node| node.class.name =="Treetop::Runtime::SyntaxNode" }
pp root_node.elements.delete_if{|node| node.class.name == "Treetop::Runtime::SyntaxNode" }
root_node.elements.each {|node| clean(node) }
end
end
compiler=ElemCompiler.new.parse ARGV[0]
Что мне не хватает?
1 ответ
Ваш код действительно правильно анализирует предоставленное выражение.
Однако в методе clean есть небольшая ошибка:
def clean(root_node)
return if(root_node.elements.nil?)
pp root_node.elements.collect {|node| node.class.name =="Treetop::Runtime::SyntaxNode" }
pp root_node.elements.delete_if{|node| node.class.name == "Treetop::Runtime::SyntaxNode" }
root_node.elements.each {|node| clean(node) }
end
Метод clean возвращает последнее вычисленное выражение, которое является каждым методом элементов массива. То, что вы хотите вернуть, на самом деле является корневым узлом, поэтому линия pp clean(tree)
будет на самом деле печатать результирующее чистое дерево, а не результат каждого выражения.
Вы можете решить двумя способами, один добавляет root_node в качестве выражения возврата:
def clean(root_node)
(...)
pp root_node.elements.delete_if{|node| node.class.name == "Treetop::Runtime::SyntaxNode" }
root_node # here
end
Или вы можете изменить метод разбора на следующий:
def parse filename
pp tree = @parser.parse(IO.read(filename))
clean(tree) # we clean the tree
pp tree # since tree is an object, side-effects will persist here
end
Однако я бы не советовал чистить дерево. У меня был действительно плохой опыт при этом. Это правда, что вы получаете более чистую структуру, которую вы можете понять гораздо лучше, поскольку Treetop часто хранит много информации, которая вам на самом деле не нужна, но вы рискуете потерять, например, возможность ссылаться на проанализированные выражения с их идентификаторами (пользовательские метки). или автоматически определенные методы доступа к элементам для нетерминальных символов) (это ссылка на веб-архив).
Кроме того, в некоторых случаях очистка узла только потому, что его имя класса "Treetop::Runtime::SyntaxNode" просто неверна, потому что в некоторых случаях вам нужно использовать модуль, а не класс, для расширения ваших узлов, и в этом случае имя класса узла по-прежнему будет "Treetop::Runtime::SyntaxNode", но узел будет очищен от дерева, и вы потеряете функциональность смешанного модуля.
Дайте мне знать, если я был ясен (к сожалению, сайт документации, кажется, не работает, у него было довольно много полезных примеров, которые я хотел бы показать вам, и, поскольку я уже давно не играю с грамматиками, я не очень Помни это).