LoginSignup
4
4

More than 5 years have passed since last update.

Ruby: ミニ Prolog パーサ (改)

Last updated at Posted at 2015-01-18

以下の記事で作ったミニPrologパーサを改修しました。

以前の課題はおおむね解消しましたが、まだまだ実験的な実装です。
できたら、そのうちにまた改修します。

スクリプト

GitHub にあります。

ソース本体は prolog_parser.ry です(随時アップデートしています)。
これを racc でコンパイルして prolog_parser.rb を作って使います。

Ruby 上での Prolog の表現

Prolog Ruby Prolog側の例 Ruby側の例
述語 Hash (形式: {Symbol=>Array}) pred(a,b,10) {pred: [:a,:b,10]}
リスト Array [a,b,10]、[] [:a,:b,10]、[]
リスト(「H|T」形式) Hash (形式: {Array=>Array}) [a,b|T] {[:a,:b]=>[{nil=>:T}]}
リスト(文字列) String "abc" "abc"
数値(整数) Integer 1、-10 1、-10
数値(小数) Float 1.1、-10.5 1.1、-10.5
アトム Symbol a、'ABC' :a、:"ABC"
変数 Hash (形式: {nil=>Symbol}) X、_abc、_ {nil=>:X}、{nil=>:_abc}、{nil=>:_}
「{}」で囲まれた項 Hash (形式: {true=>項の表現}) {a(b,c)} {true=>{:a=>[:b,:c]}}
  • 変数の表現は Symbol から Hash に変更しました。それに伴い、「'」で囲まれたアトムの表現も変更しました。
  • 「リスト(「H|T」形式)」、「「{}」で囲まれた項」は今回追加しました。

実行例

ビルド

$ make
racc -g -o prolog_parser.rb prolog_parser.ry

コマンドライン実行

:
: 実行例 (コマンドライン引数に渡す文字列には最後の「.」は含めません)
:

$ ruby prolog_parser.rb "abc"
:abc

$ ruby prolog_parser.rb "a(abc)"
{:a=>[:abc]}

$ ruby prolog_parser.rb "[1,2,3]"
[1, 2, 3]

$ ruby prolog_parser.rb "a(X) :- b(X),!"
{:":-"=>[{:a=>[{nil=>:X}]}, {:","=>[{:b=>[{nil=>:X}]}, :!]}]}

$ ruby prolog_parser.rb ",(,,,),,(,,,,,)"
{:","=>[{:","=>[:",", :","]}, {:","=>[:",", :",", :","]}]}

$ ruby prolog_parser.rb -v "a(abc)"
[:FUNCTOR, "a"]
[:ATOM, "abc"]
[")", ")"]
[".", "."]
nil
{:a=>[:abc]}

コマンドラインオプション

  #
  # $ ruby prolog_parser.rb [options]
  #
  #   options are:
  #     -v   トークンキューをダンプ出力する
  #     -d   パーサデバッグコードを出力する
  #

Prolog::Parser#parse メソッドの使い方注意

  #
  # パーサへのソースの指定の仕方 (同時に指定した場合、下の方が優先)
  #
  # parse メソッドに指定する方法
  #
  #   parser.parse(term: str) .... 項(文字列、最後のピリオド(.)不要)
  #   parser.parse(line: str) .... 行(文字列、最後のピリオド(.)必要)
  #   parser.parse(file: path) ... ファイル名(文字列)
  #   parser.parse(io: io) ....... read により入力文字列を読み出せる IO
  #   parser.parse(enum: enum) ... next により入力文字列を読み出せる Enumerator
  #   parser.parse(reader: pr) ... call により入力文字列を取得できる Proc
  #   parser.parse(&pr) .......... (上と同じ)
  #
  # アクセサで指定する方法
  #
  #   parser.term = str
  #   parser.line = str
  #   parser.file = path
  #   parser.io = io
  #   parser.enum = enum
  #   parser.reader = reader
  #
  #   parser.parse
  #
  # ----
  #
  # parse の戻り値は、解析結果を出力する Enumerator である
  #
  # (I) ワンショット(解析結果を1回のみ取り出す)
  #
  #     (例)
  #        pp result = parser.parse(line: str).first
  #
  # (II) イテレート(解析結果を繰り返し取り出す)
  #
  #     (例)
  #        parser.parse(io: f).each {|result| pp result }
  #
  #        pp results = parser.parse(io: f).entries
  #

Prolog サーバに問い合わせ

上記の記事でもやりましたが、Prolog パーサに問い合わせしてみます。
通信のためのモジュール(以下)を作りました。

prolog_proxy.rb
require 'socket'

module Prolog
  class Proxy
    attr_accessor :host, :port

    def initialize(port:, host:'localhost', &block)
      tap {|my| my.host, my.port = host, port }

      instance_eval(&block) if block
    end

    # Prolog サーバのデータベースに節を問い合わせをする
    def inquire(query, &conv)
      conv ||= -> q { q }
      TCPSocket.open(host, port) do |s|
        ###s.puts "#{conv.(query)}."
        ###s.gets
        # SWI-Prolog 決め打ちでサーバが EUC-JP として処理する
        s.puts "#{conv.(query.encode('EUC-JP'))}."
        s.gets.encode('UTF-8', 'EUC-JP')
      end
    end

    # Prolog サーバのデータベースに節を追加する
    def insert(clause)
      query =
        clause =~ /-->/ ? "dcg_translate_rule((#{clause}),X),assert(X)" :
                          "assert((#{clause}))"
      inquire(query)
    end
  end
end

if __FILE__ == $0
  require 'pp'
  require 'prolog_parser'

  prolog = Prolog::Proxy.new port:3333

  pp Prolog.to_prolog Prolog.to_ruby term:prolog.insert("person(socrates)")
  pp Prolog.to_prolog Prolog.to_ruby term:prolog.insert("mortal(X) :- person(X)")

  pp Prolog.to_prolog Prolog.to_ruby term:prolog.inquire("mortal(Who)")
end

実行例
サーバの実行。(Prolog プログラムserver.proは上の記事で使ったもの。処理系は SWI-Prolog)

$ swipl -l server.pro -g 'create_server(3333).'
                            # サーバは起動したままになる...

問い合わせ。

$ ruby ../prolog_proxy.rb 
"[assert(person(socrates))]"
"[assert(:-(mortal(_G61),person(_G61)))]"
"[mortal(socrates)]"

サーバ側で、「mortal(...) :- person(...)」のように中置形式で応答していますが、 Ruby 側では構文解析をして、一旦オブジェクトにしてから表示しているので元の形式を忘れます。
なので、「:-(mortal(...),person(...))」の形式で出力されています。

おわりに

本稿内容の動作確認は以下の環境で行っています。

  • Ruby 2.1.5 p273
  • SWI-Prolog version 6.6.4 for amd64
  • Ubuntu Linux 14.04
4
4
0

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
  3. You can use dark theme
What you can do with signing up
4
4