パーサ初心者、かつ PEG 初心者だけども必要に狩られてパーサを書いてみる。
少しずつ更新していきます。
SystemVerilog の規格書
とりあえず正確な規格書がなければと思い、IEEE 1800-2012 から規格書を入手。1300ページ以上あるが、文法に関しては100ページ無いくらい。
Parslet のインストール
gem でインストール出来るので、インストールはらくちん
gem install parslet
2進数リテラルの Parse
とりあえず練習がてらに Verilog の 2進数リテラルをパース出来るものを考える。
Verilog の 数値リテラルは以下の用に [bitwidth
]'[s|S
][base
]数値 となっていて、
s|S
は signed, base
は 2 進数なら b|B
となる。また数値は桁数を
数えやすいように 0000_1111
の用にアンダースコアを混ぜて良い。
wire a = 1'b1; // 1bit
wire [1:0] b = 2'b10; // 2bit で 2
wire [7:0] c = 8'b1111_0000;
パーサ実装
とりあえず Parslet の Getting Started を参考にしつつパーサを書いてみる。
というものの、ほとんど規格書の定義どおり。
require 'parslet'
class VBinaryParser < Parslet::Parser
rule(:nz_dec_digit) { match('[1-9]') }
rule(:dec_digit) {
str('0') | nz_dec_digit
}
rule(:nz_us) {
nz_dec_digit >> (str('_') | dec_digit).repeat
}
rule(:binary_digit) { match('[01]') }
rule(:binary_base) {
str('\'') >> (match('[sS]').maybe >> match('[bB]').as(:base))
}
rule(:binary_val) {
binary_digit >> (str('_') | binary_digit).repeat
}
rule(:binary_num) {
nz_us.maybe.as(:size) >> binary_base >> binary_val.as(:value)
}
root(:binary_num)
end
# test
require 'pp'
parser = VBinaryParser.new
pattern = [
"'b1",
"1'b1",
"2'b10",
"8'b1111_0000",
"2'b2"
]
pattern.each do |str|
pp parser.parse(str)
end
実行結果
$ ruby v_bin.rb (git)-[master]
{:size=>nil, :base=>"b"@1, :value=>"1"@2}
{:size=>"1"@0, :base=>"b"@2, :value=>"1"@3}
{:size=>"2"@0, :base=>"b"@2, :value=>"10"@3}
{:size=>"8"@0, :base=>"b"@2, :value=>"1111_0000"@3}
/usr/lib/ruby/gems/2.3.0/gems/parslet-1.7.1/lib/parslet/cause.rb:70:in `raise': Failed to match sequence (size:(NZ_US?) BINARY_BASE value:BINARY_VAL) at line 1 char 4. (Parslet::ParseFailed)
from /usr/lib/ruby/gems/2.3.0/gems/parslet-1.7.1/lib/parslet/atoms/base.rb:49:in `parse'
from v_bin.rb:44:in `block in <main>'
from v_bin.rb:43:in `each'
from v_bin.rb:43:in `<main>'
最初の3つのケースは問題無くパースできているようだし、最後のパターンは正しくエラーと
認識されている。大丈夫な様子。
8進数, 10進数, 16進数についても同様に出来るだろう。ただ、コードが冗長になるので
うまく記述出来る方法を考えねば…