search
LoginSignup
1

More than 1 year has passed since last update.

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 関連で書いたもの

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
1