Ruby:parslet для парсера системного интерфейса verilog
Я использую Ruby:: Parslet.
Я анализирую документ, похожий на интерфейс SV, например:
interface my_intf;
protocol validonly;
transmit [Bool] valid;
transmit [Bool] pipeid;
transmit [5:0] incr;
transmit [Bool] sample;
endinterface
Вот мой парсер:
class myParse < Parslet::Parser
rule(:lparen) { space? >> str('(') >> space? }
rule(:rparen) { space? >> str(')') >> space? }
rule(:lbox) { space? >> str('[') >> space? }
rule(:rbox) { space? >> str(']') >> space? }
rule(:lcurly) { space? >> str('{') >> space? }
rule(:rcurly) { space? >> str('}') >> space? }
rule(:comma) { space? >> str(',') >> space? }
rule(:semicolon) { space? >> str(';') >> space? }
rule(:eof) { any.absent? }
rule(:space) { match["\t\s"] }
rule(:whitespace) { space.repeat }
rule(:space?) { whitespace.maybe }
rule(:blank_line) { space? >> newline.repeat(1) }
rule(:newline) { str("\n") }
# Things
rule(:integer) { space? >> match('[0-9]').repeat(1).as(:int) >> space? }
rule(:identifier) { match['a-z'].repeat(1) }
rule(:intf_start) { space? >> str('interface') >> space? >> (match['a-zA-Z_'].repeat(1,1) >> match['[:alnum:]_'].repeat(0)).as(:intf_name) >> space? >> str(';') >> space? >> str("\n") }
rule(:protocol) { space? >> str('protocol') >> whitespace >> (str('validonly').maybe).as(:protocol) >> space? >> str(';') >> space? >> str("\n") }
rule(:bool) { lbox >> space? >> str('Bool').as(:bool) >> space? >> rbox }
rule(:transmit_width) { lbox >> space? >> match('[0-9]').repeat.as(:msb) >> space? >> str(':') >> space? >> match('[0-9]').repeat.as(:lsb) >> space? >> rbox }
rule(:transmit) { space? >> str('transmit') >> whitespace >> (bool | transmit_width) >> whitespace >> (match['a-zA-Z_'].repeat(1,1) >> match['[:alnum:]_'].repeat(0)).as(:transmit_name) >> space? >> str(';') >> space? >> str("\n") }
rule(:interface_body) { (protocol | blank_line.maybe) }
rule(:interface) { intf_start >> interface_body }
rule(:expression) { ( interface ).repeat }
root :expression
end
У меня проблема с созданием правила для interface_body
,
Может иметь 0 или больше transmit
линии и 0 или 1 protocol
строка и несколько пробелов, комментарии и т. д.
Может кто-нибудь помочь мне, пожалуйста? Правила, которые я написал в фрагменте кода, работают с одним transmit
и один protocol
т.е. они правильно совпадают, но когда я разбираю весь интерфейс, он не работает.
Заранее спасибо.
1 ответ
Хорошо... это анализирует файл, который вы упомянули. Я не понимаю желаемый формат, поэтому не могу сказать, что он будет работать для всех ваших файлов, но, надеюсь, это поможет вам начать.
require 'parslet'
class MyParse < Parslet::Parser
rule(:lparen) { space? >> str('(') }
rule(:rparen) { space? >> str(')') }
rule(:lbox) { space? >> str('[') }
rule(:rbox) { space? >> str(']') }
rule(:lcurly) { space? >> str('{') }
rule(:rcurly) { space? >> str('}') }
rule(:comma) { space? >> str(',') }
rule(:semicolon) { space? >> str(';') }
rule(:eof) { any.absent? }
rule(:space) { match["\t\s"] }
rule(:whitespace) { space.repeat(1) }
rule(:space?) { space.repeat(0) }
rule(:blank_line) { space? >> newline.repeat(1) }
rule(:newline) { str("\n") }
# Things
rule(:integer) { space? >> match('[0-9]').repeat(1).as(:int) >> space? }
rule(:identifier) { match['a-z'].repeat(1) }
def line( expression )
space? >>
expression >>
space? >>
str(';') >>
space? >>
str("\n")
end
rule(:expression?) { ( interface ).repeat(0) }
rule(:interface) { intf_start >> interface_body.repeat(0) >> intf_end }
rule(:interface_body) {
intf_end.absent? >>
interface_bodyline >>
blank_line.repeat(0)
}
rule(:intf_start) {
line (
str('interface') >>
space? >>
( match['a-zA-Z_'].repeat(1,1) >>
match['[:alnum:]_'].repeat(0)).as(:intf_name)
)
}
rule(:interface_bodyline) {
line ( protocol | transmit )
}
rule(:protocol) {
str('protocol') >> whitespace >>
(str('validonly').maybe).as(:protocol)
}
rule(:transmit) {
str('transmit') >> whitespace >>
(bool | transmit_width) >> whitespace >>
name.as(:transmit_name)
}
rule(:name) {
match('[a-zA-Z_]') >>
(match['[:alnum:]'] | str("_")).repeat(0)
}
rule(:bool) { lbox >> str('Bool').as(:bool) >> rbox }
rule(:transmit_width) {
lbox >>
space? >>
match('[0-9]').repeat(1).as(:msb) >>
space? >>
str(':') >>
space? >>
match('[0-9]').repeat(1).as(:lsb) >>
space? >>
rbox
}
rule(:intf_end) { str('endinterface') }
root :expression?
end
require 'rspec'
require 'parslet/rig/rspec'
RSpec.describe MyParse do
let(:parser) { MyParse.new }
context "simple_rule" do
it "should consume protocol line" do
expect(parser.interface_bodyline).to parse(' protocol validonly;
')
end
it 'name' do
expect(parser.name).to parse('valid')
end
it "bool" do
expect(parser.bool).to parse('[Bool]')
end
it "transmit line" do
expect(parser.transmit).to parse('transmit [Bool] valid')
end
it "transmit as bodyline'" do
expect(parser.interface_bodyline).to parse(' transmit [Bool] valid;
')
end
end
end
RSpec::Core::Runner.run(['--format', 'documentation'])
begin
doc = File.read("test.txt")
MyParse.new.parse(doc)
rescue Parslet::ParseFailed => error
puts error.cause.ascii_tree
end
Основные изменения...
Не используйте пробелы по обе стороны от ваших токенов. У вас были выражения, которые анализировали "[Bool] valid" как LBOX BOOL RBOX SPACE? затем ожидал еще одно ПУСТОЕ пространство, но не смог найти его (как его использовало предыдущее правило).
Когда выражение может правильно проанализировать как нулевую длину (например, что-то с repeat(0)), и возникает проблема с тем, кому оно написано, тогда вы получите странную ошибку. Правило проходит и ничего не соответствует, тогда следующее правило, как правило, потерпит неудачу. Я явно сопоставил "строки тела" как "не конец строки", поэтому он потерпит неудачу с ошибкой.
'repeat' по умолчанию (0) я бы хотел изменить. Я вижу ошибки вокруг этого все время.
x.repeat (1,1) означает сделать одно совпадение. Это то же самое, что иметь х.:)
было больше проблем с пробелами
так....
Напишите свой парсер сверху вниз. Пишите тесты снизу вверх. Когда ваши тесты достигают вершины, вы готовы!:)
Удачи.