Skip to content

Instantly share code, notes, and snippets.

@lnznt
Last active August 29, 2015 14:13
Show Gist options
  • Save lnznt/61c9c4bfb5eeca72bf72 to your computer and use it in GitHub Desktop.
Save lnznt/61c9c4bfb5eeca72bf72 to your computer and use it in GitHub Desktop.
Ruby: Prologで翻訳「時の蝿はひとつの矢を好む」 ref: http://qiita.com/lnznt/items/72abba6b045ef4ffafae
s(s(NP,VP)) --> np(NP), vp(VP). % 文 -> 名詞句 動詞句
s(s(NP)) --> np(NP). % 文 -> 名詞句
s(s(VP)) --> vp(VP). % 文 -> 動詞句
np(np(N)) --> n(N). % 名詞句 -> 名詞
np(np(A,N)) --> adj(A), n(N). % 名詞句 -> 形容詞 名詞
np(np(D,N)) --> det(D), n(N). % 名詞句 -> 冠詞 名詞
vp(vp(V)) --> v(V). % 動詞句 -> 動詞
vp(vp(V,NP)) --> v(V), np(NP). % 動詞句 -> 動詞 名詞句
vp(vp(V,PP)) --> v(V), pp(PP). % 動詞句 -> 動詞 前置詞句
vp(vp(V,NP,PP)) --> v(V), np(NP), pp(PP). % 動詞句 -> 動詞 名詞句 前置詞句
pp(pp(P,NP)) --> prep(P), np(NP). % 前置詞句 -> 前置詞 名詞句
n(n(time)) --> [time].
n(n(flies)) --> [flies].
n(n(arrow)) --> [arrow].
v(v(time)) --> [time].
v(v(flies)) --> [flies].
v(v(like)) --> [like].
adj(adj(time)) --> [time].
prep(prep(like)) --> [like].
det(det(an)) --> [an].
js(s(NP,VP)) --> jnp(NP),['は'],jvp(VP). % 文 -> 名詞句 'は' 動詞句
js(s(NP)) --> jnp(NP). % 文 -> 名詞句
js(s(VP)) --> jvp(VP). % 文 -> 動詞句
jnp(np(N)) --> jn(N). % 名詞句 -> 名詞
jnp(np(A,N)) --> jadj(A), jn(N). % 名詞句 -> 形容詞 名詞
jnp(np(D,N)) --> jdet(D), jn(N). % 名詞句 -> 冠詞 名詞
jvp(vp(V)) --> jv(V). % 動詞句 -> 動詞
jvp(vp(V,NP)) --> jnp(NP),['を'],jv(V). % 動詞句 -> 名詞句 'を' 動詞
jvp(vp(V,PP)) --> jpp(PP),jv(V). % 動詞句 -> 前置詞句 動詞
jvp(vp(V,NP,PP)) --> jnp(NP),['を'],jpp(PP),jv(V). % 動詞句 -> 名詞句 'を' 前置詞句 動詞
jpp(pp(P,NP)) --> jnp(NP),jprep(P). % 前置詞句 -> 名詞句 前置詞句
translate(Japanese, English, Semantics) :-
s(Semantics, English, []),
js(Semantics, Japanese, []).
jn(n(time)) --> ['時間'].
jn(n(flies)) --> ['蝿'].
jn(n(arrow)) --> ['矢'].
jv(v(time)) --> ['拍子'],['を'],['合わせる'].
jv(v(flies)) --> ['飛ぶ'].
jv(v(like)) --> ['好む'].
jadj(adj(time)) --> ['時'],['の'].
jprep(prep(like)) --> ['の'],['よう'],['に'].
jdet(det(an)) --> ['ひとつ'],['の'].
rake daemon[drb_uri,port,host,encoding] # drb デーモンとして起動
rake en[sentence] # 翻訳: 日本語 -> 英語
rake jp[sentence] # 翻訳: 英語 -> 日本語
rake load[files] # ファイルのロード
rake server[port,server_program] # Prolog サーバの起動
:
: server は 'server.pro' があるディレクトリで、
: load は 'dcg-*.pro' があるディレクトリで実行します。
:
$ rake server # サーバが起動されます(ポート 53330/tcp を LISTEN)
:
$ rake load
: # DCG がロードされて応答メッセージがたくさん出ます
$ pry -r drb # drb を require する
:
[1] pry(main)> DRb.start_service # DRbサービスを起動する
=> ...
:
[2] pry(main)> tr = DRbObject.new_with_uri 'druby://:53333' # リモートオブジェクト取得
=> ...
[3] pry(main)> tr.jp "time flies like an arrow"
=> ["時間はひとつの矢のように飛ぶ", "時の蝿はひとつの矢を好む", "蝿をひとつの矢のように拍子を合わせる"]
:
tr = DRbObject.new_with_uri 'druby://192.168.1.101:53333'
^^^^^^^^^^^^^
: テスト実行直前
$ sudo ufw disable # 私は ufw を使っています
ファイアウォールを無効にし、システム起動時にも無効にします
: テスト実行終了後
$ sudo ufw enable
ファイアウォールはアクティブかつシステムの起動時に有効化されます。
[4] pry(main)> tr.proxy.q "mortal(Who)" # 問い合わせ
=> "[mortal(plato)]" # この結果は、既に事実・規則が登録済の場合
$ ps aul | grep -w [r]ake # rake のプロセス情報を抽出表示
$ pidof rake # rake のプロセスの PID 表示 (PID は複数表示がありえる)
$ kill 12345 # PID 12345(例) のプロセスを kill
$ killall rake # (注) この場合、起動している全ての rake プロセスが kill されます
:
: racc で prolog_parser.ry から prolog_parser.rb をコンパイル
:
$ racc -g -o prolog_parser.rb prolog_parser.ry
: ポート 53330/tcp が LISTEN されているか確認
$ ss -tl | grep -w 53330
tcp LISTEN 0 128 *:53330 *:*
$ rake "jp[time flies like an arrow]" # 和訳。 結果は以下の3つ
時間はひとつの矢のように飛ぶ
時の蝿はひとつの矢を好む
蝿をひとつの矢のように拍子を合わせる
$ rake "en[時間はひとつの矢のように飛ぶ]" # 英訳(1)
time flies like an arrow
$ rake "en[時の蝿はひとつの矢を好む]" # 英訳(2)
time flies like an arrow
$ rake "en[蝿をひとつの矢のように拍子を合わせる]" # 英語(3)
time flies like an arrow
$ ps aul | grep -w [s]wipl # swipl のプロセス情報を抽出表示
$ pidof swipl # swipl のプロセスの PID 表示 (PID は複数表示がありえる)
$ kill 12345 # PID 12345(例) のプロセスを kill
$ killall swipl # (注) この場合、起動している全ての swipl プロセスが kill されます
$ rake "jp[arrow flies]"
矢は飛ぶ
$ rake "en[時の矢]"
time arrow
: rake server
: rake load
: ... の後に、
$ pry -r prolog_translator # prolog_translator を require する
:
[1] pry(main)> cd Prolog::Translator
[2] pry(Prolog::Translator):1> jp "time flies like an arrow"
=> ["時間はひとつの矢のように飛ぶ", "時の蝿はひとつの矢を好む", "蝿をひとつの矢のように拍子を合わせる"]
[3] pry(Prolog::Translator):1> en "時間はひとつの矢のように飛ぶ"
=> ["time flies like an arrow"]
:
[16] pry(Prolog::Translator):1> cd - # main に戻って
[17] pry(main)> cd Prolog::Proxy # Proxy に cd して
[18] pry(Prolog::Proxy):1> ins "person(plato)" # 事実の登録
=> "[assert(person(plato))]"
[19] pry(Prolog::Proxy):1> ins "mortal(X) :- person(X)" # 規則の登録
=> "[assert((mortal(_G61):-person(_G61)))]"
[20] pry(Prolog::Proxy):1> q "mortal(Who)" # 問い合わせ
=> "[mortal(plato)]"
[1] pry(main)> cd Prolog::Translator
[2] pry(Prolog::Translator):1> tr = new
=> ...
[3] pry(Prolog::Translator):1> tr.en "時の蝿はひとつの矢を好む"
=> ["time flies like an arrow"]
[4] pry(Prolog::Translator):1> tr.proxy.ins "person(socrates)"
=> "[assert(person(socrates))]"
[5] pry(Prolog::Translator):1> tr.proxy.q "person(Who)"
=> "[person(socrates)]"
: rake server
: rake load
: ... の後に、
$ rake daemon # drb デーモンが起動する
require 'drb'
require 'socket'
require 'timeout'
require 'resolv-replace'
require 'prolog_parser'
module Prolog
def self.connect(*args, &block)
Proxy.new(*args, &block)
end
class Proxy
include DRb::DRbUndumped
DRB_URI = 'druby://:53331'
PORT = 53330
HOST = 'localhost'
ENCODING = 'EUC-JP' # SWI-Prolog向け設定
class << self
def inquire(query, *args, &enc)
new(*args).inquire(query, &enc)
end
alias q inquire
def insert(clause, *args, &enc)
new(*args).insert(clause, &enc)
end
alias ins insert
def load(files, dir=nil, *args, &enc)
new(*args).load(files, dir)
end
def remove(clause, *args, &enc)
new(*args).remove(clause, &enc)
end
def drb(uri=nil, *args, &block)
DRb.start_service (uri || DRB_URI), new(*args, &block)
DRb.thread.join
end
def daemon(*args, &block)
Process.daemon
drb *args, &block
end
end
attr_writer :port, :host, :encoding
attr_accessor :tmout
def port ; @port ||= PORT ; end
def host ; @host ||= HOST ; end
def encoding ; @encoding ||= ENCODING ; end
def initialize(port:nil, host:nil, encoding:nil, &block)
tap {|my| my.host, my.port, my.encoding =
host, port, encoding }
instance_eval(&block) if block
end
# Prolog サーバのデータベースに節を問い合わせをする
def inquire(query=nil, ruby:nil, &enc)
query = Prolog.to_prolog(ruby) if ruby
timeout(tmout) do
TCPSocket.open(host, port) do |s|
s.puts "#{enc_to(query, &enc)}."
enc_from(s.gets, &enc)
end
end
end
alias q inquire
# Prolog サーバのデータベースに節を追加する
def insert(clause=nil, ruby:nil, &enc)
clause = Prolog.to_prolog(ruby) if ruby
query = dcg?(clause) ? "dcg_translate_rule((#{clause}),X),assert(X)"
: "assert((#{clause}))"
inquire(query, &enc)
end
alias ins insert
# Prolog サーバのデータベースにファイルの内容(節)を追加する
def load(files, dir=nil)
files.map{|fname| File.join(dir || '.', fname)}.each do |file|
Prolog.parse(file:file).map {|term| insert(ruby:term) }
end
end
# Prolog サーバのデータベースから節を削除する
def remove(clause=nil, ruby:nil, &enc)
clause = Prolog.to_prolog(ruby) if ruby
query = dcg?(clause) ? "dcg_translate_rule((#{clause}),X),retract(X)"
: "retract((#{clause}))"
inquire(query, &enc)
end
private
def enc_to(s, &block)
enc = block ? block.() : encoding
(s && enc) ? s.encode(enc) : s
end
def enc_from(s, &block)
enc = block ? block.() : encoding
(s && enc) ? s.encode('UTF-8', enc) : s
end
def dcg?(clause)
# TODO: もう少し厳密な判定に
clause =~ /-->/
end
end
end
if __FILE__ == $0
#
# $ ruby prolog_proxy.rb --drb [options] [URI] # drb server
#
# $ ruby prolog_proxy.rb --drb --daemon [options] [URI] # drb server (daemon)
#
require 'ostruct'
require 'optparse'
opt = OpenStruct.new ARGV.getopts '', 'drb', 'daemon',
'port:', 'host:', 'encoding:'
uri = ARGV.shift
if opt.drb
Prolog::Proxy.send(opt.daemon ? :daemon : :drb, uri) do |my|
my.port = opt.port if opt.port
my.host = opt.host if opt.host
my.encoding = opt.encoding if opt.encoding
end
end
end
require 'drb'
require 'natto'
require 'prolog_proxy'
module Prolog
class Translator
include DRb::DRbUndumped
DRB_URI = 'druby://:53333'
DCG_FILES = %w(dcg-en-grammar.pro dcg-en-vocabulary.pro
dcg-jp-grammar.pro dcg-jp-vocabulary.pro)
class << self
def load(files=nil, dir=nil, *args)
new(*args).load(files, dir)
end
def jp(sentence, *args)
new(*args).jp(sentence)
end
def en(sentence, *args)
new(*args).en(sentence)
end
def drb(uri=nil, *args, &block)
DRb.start_service (uri || DRB_URI), new(*args, &block)
DRb.thread.join
end
def daemon(*args, &block)
Process.daemon
drb *args, &block
end
end
attr_writer :proxy, :port, :host, :encoding
def port ; @port ||= Prolog::Proxy::PORT ; end
def host ; @host ||= Prolog::Proxy::HOST ; end
def encoding ; @encoding ||= Prolog::Proxy::ENCODING ; end
def proxy
@proxy ||= Prolog::Proxy.new(port:port, host:host, encoding:encoding)
end
def initialize(proxy:nil, port:nil, host:nil, encoding:nil, &block)
tap {|my| my.proxy, my.host, my.port, my.encoding =
proxy, host, port, encoding }
instance_eval(&block) if block
end
# DCG ファイルのロード
def load(files=nil, dir=nil)
files = DCG_FILES if !files || files.empty?
Prolog::Proxy.load(files, dir)
end
# 翻訳: 英語 -> 日本語
def jp(sentence)
ss = sentence.split.map(&:to_sym)
query = {translate:[var(:J), ss, var()]} # 述語 translate/3
Prolog.to_ruby(proxy.inquire(ruby:query)).map{|pred| arg(pred,0).join }
end
# 翻訳: 日本語 -> 英語
def en(sentence)
ss = jsplit(sentence).map(&:to_sym)
query = {translate:[ss, var(:E), var()]} # 述語 translate/3
Prolog.to_ruby(proxy.inquire(ruby:query)).map{|pred| arg(pred,1) * ' '}
end
private
def var(sym=:_)
{ nil => sym.to_sym }
end
def arg(pred, n)
pred.values.first[n]
end
def jsplit(s)
ss = []
Natto::MeCab.new.parse(s) {|n| ss << n.surface }
ss.compact
end
end
end
if __FILE__ == $0
#
# $ ruby prolog_translator.rb --drb [options] [URI] # drb server
#
# $ ruby prolog_translator.rb --drb --daemon [options] [URI] # drb daemon
#
require 'ostruct'
require 'optparse'
opt = OpenStruct.new ARGV.getopts '', 'drb', 'daemon',
'port:', 'host:', 'encoding:'
uri = ARGV.shift
if opt.drb
Prolog::Translator.send(opt.daemon ? :daemon : :drb, uri) do |my|
my.port = opt.port if opt.port
my.host = opt.host if opt.host
my.encoding = opt.encoding if opt.encoding
end
end
end
require 'prolog_translator'
SERVER = 'server.pro'
PORT = Prolog::Proxy::PORT
#
# Prolog サーバへの起動と設定
#
desc 'Prolog サーバの起動'
task:server, [:port, :server_program] do |t, args|
port = args[:port] || PORT
server = args[:server_program] || SERVER
Process.spawn "swipl -l #{server} -g 'create_server(#{port}).'"
end
desc 'ファイルのロード'
task:load, [:files] do |t, args|
files = (args[:files]||'').split(',')
Prolog::Translator.load files
end
desc 'drb デーモンとして起動'
task:daemon, [:drb_uri, :port, :host, :encoding] do |t, args|
Prolog::Translator.daemon(args[:drb_uri]) do |my|
my.port = args[:port] if args[:port]
my.host = args[:host] if args[:host]
my.encoding = args[:encoding] if args[:encoding]
end
end
desc '翻訳: 英語 -> 日本語'
task:jp, [:sentence] do |t, args|
Prolog::Translator.jp(args[:sentence] || '').each {|j| puts j }
end
desc '翻訳: 日本語 -> 英語'
task:en, [:sentence] do |t, args|
Prolog::Translator.en(args[:sentence] || '').each {|e| puts e }
end
create_server(Port) :-
tcp_socket(Socket),
tcp_setopt(Socket, reuseaddr),
tcp_bind(Socket, Port),
tcp_listen(Socket, 5),
tcp_open_socket(Socket, AcceptFd, _),
dispatch(AcceptFd).
dispatch(AcceptFd) :-
tcp_accept(AcceptFd, Socket, Peer),
thread_create(process_client(Socket, Peer), _, [detached(true)]),
dispatch(AcceptFd).
process_client(Socket, _) :-
setup_call_cleanup( tcp_open_socket(Socket, In, Out),
handle_service(In, Out),
close_connection(In, Out)).
close_connection(In, Out) :-
close(In, [force(true)]),
close(Out, [force(true)]).
handle_service(In, Out) :-
read(In, Chars),
findall(Chars, Chars, L),
write(Out, L),
writeln(L).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment