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) означает сделать одно совпадение. Это то же самое, что иметь х.:)

  • было больше проблем с пробелами

так....

Напишите свой парсер сверху вниз. Пишите тесты снизу вверх. Когда ваши тесты достигают вершины, вы готовы!:)

Удачи.

Другие вопросы по тегам