Skip to content

Instantly share code, notes, and snippets.

@lnznt
Created January 23, 2015 23:02
Show Gist options
  • Save lnznt/a2e0616e3a59c6ddb865 to your computer and use it in GitHub Desktop.
Save lnznt/a2e0616e3a59c6ddb865 to your computer and use it in GitHub Desktop.
Prolog: Prolog からの Ruby 呼び出し ref: http://qiita.com/lnznt/items/4043f4b75b00d525156c
$ ruby prolog_server.rb # デフォルトでポート 53340/tcp を LISTEN
# 起動したままになる。停止するには Ctrl-C
# --log ログファイル名 を付けて起動するとログ出力します
$ swipl -l ruby_client.pro
:
(Prolog が起動する)
: ターミナル#1 : Ruby サーバ起動
$ ruby prolog_server.rb
# 起動したままになる
: ターミナル#2 : Prolog サーバ起動
$ swipl -l server.pro -g 'create_server(53330).'
# 起動したままになる
: ターミナル#3 : Prolog サーバに ruby_client をロード
$ ruby -r prolog_proxy -e 'Prolog::Proxy.load ["ruby-client.pro"]'
: ターミナル#4 : Proxy デーモン(drbサービス)起動
$ ruby prolog_proxy.rb --drb --daemon # バックグランドで起動
: ターミナル#5 (リモート) : pry で操作
$ pry -r drb # drb を require
:
[1] pry(main)> px = DRbObject.new_with_uri 'druby://:53331'
=> ...
[2] pry(main)> px.q 'ruby(foo(x))'
=> "[ruby(foo(x))]"
:
call method: foo(x), args = 1
require 'prolog_server'
class MyClass # レシーバにするオブジェクトのクラス定義
# 適当にメソッド定義する
# def hoge(...) ... とすると Prolog側から `?- ruby(hoge(...)).` で呼び出せる
end
my_receiver = MyClass.new
th = Prolog::Server.start(my_receiver)
th.join # 戻り値はサーバの Thread オブジェクトなので join できる
Prolog::Server.daemon(my_receiver) # この場合、デーモンになる
server = Prolog::Server.new # インスタンスを作って指定する方法
th = server.start(my_receiver)
th.join
ruby(Pred) % ruby/1 : Host = 'localhost', Port = 53340
ruby(Pred, Port) % ruby/2 : Host = 'localhost'
ruby(Pred, Port, Host) % ruby/3
server = Prolog::Server.new
server.daemon(my_receiver) # この場合、デーモンになる
server = Prolog::Server.new
# on_error でハンドラを指定する。引数はキャプチャする例外タイプ
server.on_error(NoMethodError) {|e| puts "Exception: #{e.class}" }
th = server.start(my_receiver)
th.join
[63] pry(Prolog::Proxy):1> q 'ruby(foo("ABC"))'
=> "[ruby(foo([65,66,67]))]"
# 変換の例
p [65,66,67].pack "C*" #=> "ABC"
p "ABC".unpack "C*" #=> [65, 66, 67]
# バイトの配列か判定する例
arg = [65,66,67]
p arg.kind_of?(Array) && arg.all? {|n| (0..255).include? n } #=> true
require 'kind_of_test'
class Object ; include KindOfTest ; end
p [65,66,67].array? &:byte? #=> true
p [65,66,67,256].array? &:byte? #=> false
?- ruby(foo). % アトムの場合、引数なしでメソッドを呼び出す
true.
?- ruby(bar(a,b,c)). % 述語の場合、述語の引数を渡してメソッドを呼び出す
true.
?- ruby(baz(a,f(x,y),[1,2,3],"ABC")).
true.
?- ruby(1). % 1 はアトム/述語以外なので無視される
true.
$ ruby prolog_server.rb
call method: foo(), args = 0
call method: bar(a,b,c), args = 3
call method: baz(a,{:f=>[:x, :y]},1,2,3,65,66,67), args = 4 # puts なのでこう見えますが、引数は 4 個です。
$ ruby prolog_server.rb # デフォルトでポート 53340/tcp を LISTEN
# 起動したままになる。停止するには Ctrl-C
$ swipl -l server.pro -g 'create_sserver(53330).' # 起動したままになる
$ pry -r prolog_proxy # prolog_proxy.rb を require します。
:
[1] pry(main)> cd Prolog::Proxy
[2] pry(Prolog::Proxy):1> load ['ruby-client.pro']
=> ["./ruby-client.pro"]
[3] pry(Prolog::Proxy):1> q 'ruby(foo(a,b))'
=> "[ruby(foo(a,b))]"
(Ruby サーバが起動しているターミナル)
call method: foo(a,b), args = 2
require 'socket'
require 'resolv-replace'
require 'logger'
require 'prolog_parser'
module Prolog
class Server
HOST, PORT = '0.0.0.0', 53340
def self.daemon(*args, &block)
new(*args, &block).daemon
end
def self.start(*args, &block)
new(*args, &block).start
end
class ServerLogger < Logger
def level=(x)
x = Logger.const_get(x) if %i(DEBUG INFO WARN ERROR FATAL).include?(x)
super(x)
end
end
attr_accessor :log
attr_writer :logger
def logger; @logger ||= (log ? ServerLogger.new(log) : null) ; end
attr_accessor :host, :port, :receiver
def initialize(receiver=nil, port:nil,host:nil, logger:nil,log:nil, &block)
tap {|my| my.receiver, my.port, my.host, my.logger, my.log =
receiver, port, host, logger, log }
instance_eval(&block) if block
end
attr_reader :thread
def daemon(*args, &block)
stop
logger.info "start daemon"
Process.daemon
service_loop *args, &block
end
def start(*args, &block)
stop
logger.info "start server: pid = #{$$}"
@thread = Thread.new { service_loop *args, &block }
end
def stop
return unless @thread
logger.info "stop server: pid = #{$$}"
@thread.kill rescue nil
@thread = nil
end
private
def service_loop(receiver=nil, port:nil, host:nil)
port ||= (self.port || PORT)
host ||= (self.host || HOST)
receiver ||= (self.receiver || null)
logger.info "accept loop: host = #{host}, port = #{port}"
Socket.tcp_server_loop(host, port) do |conn, *|
Thread.new do
begin
serivce(receiver, conn)
ensure
conn.close
end
end
end
end
def serivce(receiver, conn)
msg = conn.read ; logger.debug "request(raw) : #{msg}"
req = Prolog.to_ruby(msg) ; logger.debug "request(ruby): #{req}"
pred?(req) ? receiver.send(req.keys.first, *req.values.first) :
atom?(req) ? receiver.send(req) : nil
rescue Exception => e
logger.error "exception: #{e}"
error_notify(e)
end
def pred?(x)
x.kind_of?(Hash) &&
x.size == 1 &&
x.keys.first.kind_of?(Symbol) &&
x.values.first.kind_of?(Array)
end
def atom?(x)
x.kind_of?(Symbol)
end
def null
@null ||= Class.new{ def method_missing(*) ; end }.new
end
public
def on_error(exception_type=Exception, &block)
handlers[exception_type] << block if block
end
private
def handlers
@handlers ||= Hash.new {|h, k| h[k] = [] }
end
def error_notify(exception)
handlers.select{|type, *| exception.kind_of? type }.values.reduce(:+)
.each {|handler| handler.(exception) }
end
end
end
if __FILE__ == $0
require 'optparse'
require 'ostruct'
class DummyReceiver
def foo(*args)
puts "call method: foo(#{args * ','}), args = #{args.count}"
end
def bar(*args)
puts "call method: bar(#{args * ','}), args = #{args.count}"
end
def baz(*args)
puts "call method: baz(#{args * ','}), args = #{args.count}"
end
end
dummy_receiver = DummyReceiver.new
opt = OpenStruct.new ARGV.getopts '', 'port:','host:','log:','daemon'
if opt.daemon
# (注意) デーモンにすると標準出力などが切り離されます
Prolog::Server.daemon(dummy_receiver,
port:opt.port, host:opt.host, log:opt.log)
else
server = Prolog::Server.new(dummy_receiver,
port:opt.port, host:opt.host, log:opt.log)
server.on_error {|e| puts e }
server.logger.level = :DEBUG
server.start.join
end
end
ruby(Req) :- ruby(Req, 53340).
ruby(Req, Port) :- ruby(Req, 'localhost', Port).
ruby(Req, Host, Port) :- create_client(Req, Host, Port).
create_client(Req, Host, Port) :-
setup_call_catcher_cleanup(tcp_socket(Socket),
tcp_connect(Socket, Host:Port),
exception(_),
tcp_close_socket(Socket)),
setup_call_cleanup(tcp_open_socket(Socket, In, Out),
request_to_server(Req, In, Out),
close_client_connection(In, Out)).
close_client_connection(In, Out) :-
close(In, [force(true)]),
close(Out, [force(true)]).
request_to_server(Req, _, Out) :-
write(Out, Req).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment