1
Help us understand the problem. What are the problem?

posted at

updated at

Ruby/Racc: パースに失敗した位置(行、桁)を得る

パースに失敗したとき、失敗した位置をたとえば次のように表示したいですよね?

parse error: line 2, col 5
foo bar
    ^

短いのでとりあえず全部貼ります。

# parser.y

class Parser

rule
  program: "a" "b" "c" "d" ";"
  {
    puts "program found"
    result = val
  }
end

---- header

TokenValue = Struct.new(:value, :pos)

---- inner

def initialize(src, tokens)
  @src = src
  @tokens = tokens
end

def next_token
  @tokens.shift
end

def parse
  do_parse
end

def get_lineno_and_range(pos)
  lineno = 1
  line_start = 0
  range = nil

  @src.each_line do |line|
    next_line_start = line_start + line.size
    range = line_start .. (next_line_start - 1)
    break if range.include?(pos)

    line_start = next_line_start
    lineno += 1
  end

  [lineno, range]
end

def on_error(token_id, val, vstack)
  lineno, range = get_lineno_and_range(val.pos)
  colno = val.pos - range.begin + 1
  line = @src[range]

  puts "parse error: line #{lineno}, col #{colno}"

  puts line
  print " " * (colno - 1), "^\n"
end

---- footer

def tokenize(src)
  tokens = []

  pos = 0
  while pos < src.size
    c = src[pos]
    case c
    when /\s/
      pos += 1
    else
      tokens << [c, TokenValue.new(c, pos)]
      pos += 1
    end
  end

  tokens
end

src = File.read(ARGV[0])
tokens = tokenize(src)
parser = Parser.new(src, tokens)
result = parser.parse()
puts "result: " + result.inspect

a b c d ; というトークン列だけを受理するパーサです。
入力となるソースコードではトークン同士をスペースや改行で区切ります。

パースに成功する例:

a
b

c d
;

たとえば dX に置き換えた次のソースコードを与えるとパースに失敗します。

a
b

c X
;

失敗する場合の実行例:

$ racc parser.y -o parser.rb

$ ruby parser.rb sample_ng.txt
parse error: line 4, col 3
c X
  ^
result: nil

短いので上に貼ったコードを読んだ方が早いと思いますが、一応簡単にメモ。

  • トークンの開始位置(元のソースコードで何文字目か)をトークン値に持たせておく
  • 元のソースコードを Parser#src として保持しておく
  • パースに失敗したら Parser#src と失敗したトークンの開始位置を使って 元のソースコード上の行と桁を割り出す

メモ

終端/非終端にかかわらず、すべての記号はそれに対応する値(かっこよく言うと semantic value)を持っていて、これを使って情報をやりとりすることができる。
(略)
値としては好きなRubyのオブジェクト1個が使えるので、これはつまり なんでも送れる と同義だ。

Rubyを256倍使うための本 無道編Amazon)』 p46

とのことなので、上記ではトークンの値として TokenValue クラスのインスタンスを使っています。

関連

以下は Racc 関連ではありませんが、Ruby + パーサ関連で書いたものということで。

他に Ruby 関連で書いたもの

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
1
Help us understand the problem. What are the problem?