cycr 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/README.rdoc +102 -47
- data/Rakefile +8 -0
- data/changelog.txt +15 -0
- data/cycr.gemspec +8 -4
- data/integration/client.rb +136 -18
- data/lib/cyc/assertion.rb +1 -1
- data/lib/cyc/builder.rb +3 -3
- data/lib/cyc/client.rb +142 -88
- data/lib/cyc/connection/buffer.rb +69 -0
- data/lib/cyc/connection/driver.rb +12 -0
- data/lib/cyc/connection/socket.rb +95 -0
- data/lib/cyc/connection/synchrony.rb +167 -0
- data/lib/cyc/connection.rb +2 -0
- data/lib/cyc/exception.rb +8 -0
- data/lib/cyc/parser.rb +2 -0
- data/lib/cyc/version.rb +3 -0
- data/lib/cycr.rb +1 -0
- metadata +23 -5
data/.gitignore
CHANGED
data/README.rdoc
CHANGED
@@ -1,35 +1,52 @@
|
|
1
1
|
= cycr
|
2
2
|
|
3
|
-
* http://github.com/apohllo/cycr
|
3
|
+
* http://github.com/apohllo/cycr - code
|
4
|
+
* http://rubydoc.info/gems/cycr/frames - documentation
|
4
5
|
|
5
|
-
|
6
|
+
== DESCRIPTION
|
6
7
|
|
7
|
-
|
8
|
+
*cycr* is a Ruby client for the (Open)Cyc server http://www.opencyc.org.
|
8
9
|
|
9
|
-
|
10
|
+
== FEATURES
|
10
11
|
|
11
12
|
* The text protocol is used to talk with Cyc
|
12
|
-
* Ruby symbols are converted to Cyc
|
13
|
+
* Ruby symbols are converted to Cyc terms
|
13
14
|
* Ruby arrays are converted to SubL arrays
|
14
15
|
* Ruby calls on the client are transparently translated to SubL
|
15
|
-
* Support for subcalls (like
|
16
|
-
*
|
16
|
+
* Support for subcalls (like +with-any-mt+)
|
17
|
+
* connection drivers: regular socket and synchrony based communication
|
18
|
+
* auto reconnecting the client after server downtime
|
19
|
+
* thread safe client as an option
|
20
|
+
|
21
|
+
== PROBLEMS
|
22
|
+
|
23
|
+
* Support for NARTs might not be fully functional (works only with ResearchCyc)
|
17
24
|
* Support for CycL queries not implemented yet!
|
18
25
|
|
19
|
-
|
26
|
+
== SYNOPSIS
|
20
27
|
|
21
|
-
|
28
|
+
*cycr* is a Ruby client for the (Open)Cyc server. It is designed as a
|
22
29
|
substitution for the original Java client. It allows for conversation
|
23
30
|
with the ontology with regular Ruby objects (like symbols, arrays)
|
24
|
-
and also exposes
|
31
|
+
and also exposes a raw text protocol.
|
32
|
+
|
33
|
+
If you use this library you might be intereste in:
|
34
|
+
|
35
|
+
* https://github.com/apohllo/cyc-console - replacement for Cyc console
|
36
|
+
* https://github.com/apohllo/navicyc - Rails and ExtJS replacement for Cyc browser
|
37
|
+
|
25
38
|
|
26
|
-
|
39
|
+
== REQUIREMENTS
|
27
40
|
|
28
41
|
(Open)Cyc server with TCP communication enabled.
|
29
42
|
|
30
|
-
|
43
|
+
== INSTALL
|
44
|
+
|
45
|
+
The gem is available at rubygems.org, so you can install it with:
|
46
|
+
|
47
|
+
$ sudo gem install cycr
|
31
48
|
|
32
|
-
|
49
|
+
In case of problems just make sure that you have RubyGems v.1.3.5 at least:
|
33
50
|
|
34
51
|
$ gem -v
|
35
52
|
1.2.0 # => not OK
|
@@ -38,61 +55,65 @@ You need RubyGems v.1.3.5 at least:
|
|
38
55
|
$ gem -v
|
39
56
|
1.3.7 # => OK
|
40
57
|
|
41
|
-
The gem is available at rubygems.org, so you can install it with:
|
42
|
-
|
43
|
-
$ sudo gem install cycr
|
44
58
|
|
45
59
|
== BASIC USAGE
|
46
60
|
|
47
61
|
Prerequisites:
|
48
62
|
|
49
|
-
* Cyc server
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
63
|
+
* running Cyc server - you can download it from http://www.opencyc.org
|
64
|
+
* Telnet connection is turned on.
|
65
|
+
Type +(enable-tcp-server :cyc-api 3601)+ in the cyc console
|
66
|
+
or Cyc Browser -> Tools -> Interactor
|
67
|
+
|
68
|
+
Then you can start +irb+ session to see it in action.
|
54
69
|
|
55
|
-
|
70
|
+
Include the cycr gem first
|
56
71
|
|
57
|
-
$ irb
|
58
|
-
# include the cycr gem
|
59
72
|
require 'cycr'
|
60
73
|
|
61
|
-
|
74
|
+
Create a cyc client object, default host: localhost, port: 3601
|
75
|
+
|
62
76
|
cyc = Cyc::Client.new
|
63
77
|
|
64
|
-
|
78
|
+
Check if Dog generalizes to Animal
|
79
|
+
|
65
80
|
cyc.genls? :Dog, :Animal # => true
|
66
81
|
|
67
|
-
|
82
|
+
Check if Animal generalizes to Dog
|
83
|
+
|
68
84
|
cyc.genls? :Animal, :Dog # => nil
|
69
85
|
|
70
|
-
|
86
|
+
Check the minimal generalizations of Animal
|
87
|
+
|
71
88
|
genls = cyc.min_genls :Animal
|
72
89
|
# => [:"Organism-Whole", :"PerceptualAgent-Embodied",
|
73
|
-
|
74
|
-
|
90
|
+
# :"Agent-NonArtifactual", :SolidTangibleThing, [:CollectionUnionFn,
|
91
|
+
# [:TheSet, :Animal, [:GroupFn, :Animal]]],...
|
92
|
+
|
93
|
+
Check the maximal specializations of the first of the above results
|
75
94
|
|
76
|
-
# check the maximal specializations of the first of the above results
|
77
95
|
cyc.max_specs genls.first
|
78
96
|
# => [:Microorganism, :EukaryoticOrganism, :"Unvaccinated-Generic",
|
79
|
-
|
80
|
-
|
81
|
-
|
97
|
+
# :"Vaccinated-Generic", :MulticellularOrganism, :Heterotroph, :Autotroph,
|
98
|
+
# :Lichen, :TerrestrialOrganism, :Animal, :AquaticOrganism, :Mutant,
|
99
|
+
# :Carnivore, :Extraterrestrial, :"Exotic-Foreign",...
|
100
|
+
|
101
|
+
It works with NARTs (but I didn't tested this functionality extensively,
|
102
|
+
so this might cause some problems - beware):
|
82
103
|
|
83
|
-
# It works with NARTs (but I didn't tested this functionality extensively,
|
84
|
-
# so this might cause some problems - beware):
|
85
104
|
genls[4]
|
86
105
|
# => [:CollectionUnionFn, [:TheSet, :Animal, [:GroupFn, :Animal]]]
|
87
106
|
|
88
107
|
cyc.max_specs genls[4]
|
89
108
|
# => [:Animal, [:GroupFn, :Animal]]
|
90
109
|
|
91
|
-
|
110
|
+
What is more, you might even build complex subcalls, such as:
|
111
|
+
|
92
112
|
cyc.genls? :Person, :HomoSapiens # => nil
|
93
113
|
cyc.with_any_mt{|cyc| cyc.genls? :Person, :HomoSapiens} # => true
|
94
114
|
|
95
|
-
|
115
|
+
The assertions are parsed, as well as Cyc symbols and variables
|
116
|
+
|
96
117
|
keys = cyc.key_predicate_rule_index :isa
|
97
118
|
# => [:POS, :NEG]
|
98
119
|
keys[0].class
|
@@ -106,8 +127,8 @@ The you can start 'irb' to see it in action:
|
|
106
127
|
# => [:BACKWARD]
|
107
128
|
rules = cyc.gather_predicate_rule_index :isa, keys[0], keys1[2], keys2[0]
|
108
129
|
# => [[:implies, [:and, [:objectOfPossessionTransfer, :TheBuying, ?OBJ],
|
109
|
-
|
110
|
-
|
130
|
+
# [:activityObjectType, :TheSelectingAProduct, ?PREFERED]],
|
131
|
+
# [:isa, ?OBJ, ?PREFERED]] : BuyingMt]
|
111
132
|
cyc.assertion_p rules[0]
|
112
133
|
# => true
|
113
134
|
|
@@ -115,28 +136,62 @@ The you can start 'irb' to see it in action:
|
|
115
136
|
# => ?OBJ
|
116
137
|
rules[0].formula[1][1][2].class
|
117
138
|
# => Cyc::Variable
|
118
|
-
|
139
|
+
|
140
|
+
The variable cannot be checked for type, since it won't be bound.
|
119
141
|
|
120
142
|
|
121
|
-
|
122
|
-
|
143
|
+
If you want to see the query which is send to Cyc, just turn on
|
144
|
+
debugging:
|
145
|
+
|
123
146
|
cyc.debug = true
|
124
|
-
cyc.genls? :Dog, :
|
147
|
+
cyc.genls? :Dog, :Animal
|
125
148
|
# Send: (genls? #$Dog #$Animal)
|
126
149
|
# Recv: 200 T
|
127
150
|
# => true
|
128
151
|
|
129
|
-
|
152
|
+
The same way, you can turn it off:
|
153
|
+
|
130
154
|
cyc.debug = false
|
131
155
|
|
132
|
-
|
156
|
+
Remember to close the client on exit
|
157
|
+
|
133
158
|
cyc.close
|
134
159
|
|
160
|
+
By default a Cyc client uses regular TCP socket communication and is not
|
161
|
+
thread safe. To turn-on thread-safety pass a +thread_safe+ option to the
|
162
|
+
constructor:
|
163
|
+
|
164
|
+
cyc = Cyc::Client.new(:thread_safe => true)
|
165
|
+
|
166
|
+
Alternatively you can use a client with fibers and Event Machine synchrony
|
167
|
+
To set-up event machine driver before requiring +cycr+:
|
168
|
+
|
169
|
+
require 'cyc/connection/synchrony'
|
170
|
+
require 'cycr'
|
171
|
+
|
172
|
+
Make sure that you have 'em-synchrony' gem installed. A sample code looks as
|
173
|
+
follows:
|
174
|
+
|
175
|
+
EM.synchrony do
|
176
|
+
cyc = EM::Synchrony::ConnectionPool.new(size => 5) do
|
177
|
+
Cyc::Client.new :url => 'cyc://localhost:3601', :debug => true
|
178
|
+
end
|
179
|
+
Fiber.new do
|
180
|
+
puts "Ani", cyc.fi_complete("Ani").inspect
|
181
|
+
end.resume
|
182
|
+
puts "Mi", cyc.talk('(fi-complete "Mi")').inspect
|
183
|
+
EM.stop
|
184
|
+
end
|
185
|
+
|
186
|
+
`Mi` will arrive before `Ani`
|
187
|
+
|
188
|
+
Warning: always use +EM::Synchrony::ConnectionPool+ to handle Fiber concurrency race conditions.
|
189
|
+
|
135
190
|
== LICENSE:
|
136
191
|
|
137
192
|
(The MIT/X11 License)
|
138
193
|
|
139
|
-
Copyright (c) 2008-2012 Aleksander Pohl
|
194
|
+
Copyright (c) 2008-2012 Aleksander Pohl, Rafal Michalski
|
140
195
|
|
141
196
|
Permission is hereby granted, free of charge, to any person obtaining
|
142
197
|
a copy of this software and associated documentation files (the
|
data/Rakefile
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
$:.unshift "lib"
|
2
|
+
require 'cyc/version'
|
3
|
+
|
1
4
|
task :default => [:test]
|
2
5
|
|
3
6
|
$gem_name = "cycr"
|
@@ -29,3 +32,8 @@ desc "Clean"
|
|
29
32
|
task :clean do
|
30
33
|
sh "rm #$gem_name*.gem"
|
31
34
|
end
|
35
|
+
|
36
|
+
desc "Show changelog from the last release"
|
37
|
+
task :changelog do
|
38
|
+
sh "git log v#{Cyc::VERSION}.. --pretty=%s | tee"
|
39
|
+
end
|
data/changelog.txt
CHANGED
@@ -1,3 +1,18 @@
|
|
1
|
+
0.2.0
|
2
|
+
- Rake task for changelog
|
3
|
+
- Support for Ruby 1.8 dropped
|
4
|
+
- Remove support for Client.new(host,port) syntax (use options instead)
|
5
|
+
- Documentation updates.
|
6
|
+
- Fix README formatting
|
7
|
+
- :host & :port options added to client constructor
|
8
|
+
- Fixed multiprocess tests
|
9
|
+
- Removal of unused code
|
10
|
+
- Early detect broken connection to allow conn.write again; connected? return value
|
11
|
+
- Client reuses driver connection while reconnecting
|
12
|
+
- Client#check_parenthesis much faster
|
13
|
+
- Parser: include NIL in results - missing NILs may brake partial results
|
14
|
+
- Thread safety support
|
15
|
+
- Cyc::Client rework to allow multiple connection drivers
|
1
16
|
0.1.0
|
2
17
|
- Add license info to some files
|
3
18
|
- Add tests to Rakefile
|
data/cycr.gemspec
CHANGED
@@ -1,18 +1,22 @@
|
|
1
|
+
$:.unshift "lib"
|
2
|
+
require 'cyc/version'
|
3
|
+
|
1
4
|
Gem::Specification.new do |s|
|
2
5
|
s.name = "cycr"
|
3
|
-
s.version =
|
6
|
+
s.version = Cyc::VERSION.to_s
|
7
|
+
s.required_ruby_version = '>= 1.9.2'
|
4
8
|
s.date = "#{Time.now.strftime("%Y-%m-%d")}"
|
5
9
|
s.summary = "Ruby client for the (Open)Cyc server"
|
6
10
|
s.email = "apohllo@o2.pl"
|
7
11
|
s.homepage = "http://github.com/apohllo/cycr"
|
8
12
|
s.require_path = "lib"
|
9
13
|
s.description = "Ruby wrapper for (Open)Cyc server and ontology"
|
10
|
-
s.
|
11
|
-
s.authors = ['Aleksander Pohl']
|
14
|
+
s.authors = ['Aleksander Pohl', 'Rafal Michalski']
|
12
15
|
s.files = `git ls-files`.split("\n")
|
13
16
|
s.test_files = Dir.glob("spec/**/*") + Dir.glob("integration/**/*")
|
14
17
|
s.rdoc_options = ["--main", "README.rdoc"]
|
15
18
|
s.has_rdoc = true
|
16
19
|
s.extra_rdoc_files = ["README.rdoc"]
|
17
|
-
s.add_development_dependency("rspec", ["
|
20
|
+
s.add_development_dependency("rspec", ["~> 2.8.0"])
|
21
|
+
s.add_development_dependency("em-synchrony", ["~> 1.0.0"])
|
18
22
|
end
|
data/integration/client.rb
CHANGED
@@ -1,16 +1,8 @@
|
|
1
1
|
$:.unshift "lib"
|
2
2
|
require 'cycr'
|
3
|
+
require 'cyc/connection/synchrony' if defined? Fiber
|
3
4
|
|
4
|
-
|
5
|
-
before(:each) do
|
6
|
-
@client = Cyc::Client.new()
|
7
|
-
# @client.debug = true
|
8
|
-
end
|
9
|
-
|
10
|
-
after(:each) do
|
11
|
-
@client.close
|
12
|
-
end
|
13
|
-
|
5
|
+
shared_examples Cyc::Client do
|
14
6
|
it "should allow to talk to the server" do
|
15
7
|
@client.talk("(constant-count)").should_not == nil
|
16
8
|
end
|
@@ -53,15 +45,141 @@ describe Cyc::Client do
|
|
53
45
|
@client.talk('(gather-predicate-extent-index #$minimizeExtent)').size.should > 100
|
54
46
|
end
|
55
47
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
48
|
+
end
|
49
|
+
|
50
|
+
describe Cyc::Client do
|
51
|
+
include_examples Cyc::Client
|
52
|
+
|
53
|
+
it "should have socket driver" do
|
54
|
+
@client.driver.type.should == :socket
|
55
|
+
end
|
56
|
+
|
57
|
+
before(:all) do
|
58
|
+
Cyc::Connection.driver = Cyc::Connection::SocketDriver
|
59
|
+
@client = Cyc::Client.new()
|
60
|
+
# @client.debug = true
|
61
|
+
end
|
62
|
+
|
63
|
+
after(:each) do
|
64
|
+
@client.close
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
if defined? Cyc::Connection::SynchronyDriver
|
70
|
+
describe Cyc::Connection::SynchronyDriver do
|
71
|
+
include_examples Cyc::Client
|
72
|
+
|
73
|
+
it "should have synchrony driver" do
|
74
|
+
@client.driver.type.should == :synchrony
|
75
|
+
end
|
76
|
+
|
77
|
+
around(:each) do |test_case|
|
78
|
+
EM.synchrony do
|
79
|
+
@client = Cyc::Client.new(:driver => Cyc::Connection::SynchronyDriver)
|
80
|
+
# @client.debug = true
|
81
|
+
test_case.call
|
82
|
+
@client.close
|
83
|
+
EM.stop
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
describe "synchrony fiber concurrency" do
|
90
|
+
around(:each) do |test_case|
|
91
|
+
EM.synchrony do
|
92
|
+
@client = EM::Synchrony::ConnectionPool.new(size: 1) do
|
93
|
+
Cyc::Client.new(:driver => Cyc::Connection::SynchronyDriver, :debug => false)
|
94
|
+
end
|
95
|
+
test_case.call
|
96
|
+
@client.close
|
97
|
+
EM.stop
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# this is a little bit loooong test
|
102
|
+
# but tests aync nature of Fibers and composite results (subseq x)
|
103
|
+
it "should have consistent results running long query in separate fibers" do
|
104
|
+
@fiber = Fiber.current
|
105
|
+
done = 0
|
106
|
+
size = ('A'..'Z').to_a.each do |char|
|
107
|
+
Fiber.new do
|
108
|
+
result_size = @client.fi_complete(char).each do |value|
|
109
|
+
value.to_s[0].upcase.should == char
|
110
|
+
end.length
|
111
|
+
result_size.should > 0
|
112
|
+
done += 1
|
113
|
+
EM.next_tick { @fiber.resume }
|
114
|
+
end.resume
|
115
|
+
end.size
|
116
|
+
while done < size
|
117
|
+
@fiber = Fiber.current
|
118
|
+
Fiber.yield
|
119
|
+
end
|
62
120
|
end
|
63
|
-
|
64
|
-
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe "client thread concurrency" do
|
125
|
+
|
126
|
+
before(:all) do
|
127
|
+
Cyc::Connection.driver = Cyc::Connection::SocketDriver
|
128
|
+
@client = Cyc::Client.new :thread_safe => true
|
129
|
+
# @client.debug = true
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should have socket driver" do
|
133
|
+
@client.driver.type.should == :socket
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should have thread_safe? flag set" do
|
137
|
+
@client.thread_safe?.should == true
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should have consistent results running long query in separate threads" do
|
141
|
+
results = {}
|
142
|
+
mutex = Mutex.new
|
143
|
+
('A'..'Z').map do |char|
|
144
|
+
Thread.new do
|
145
|
+
Thread.pass
|
146
|
+
result = @client.fi_complete char
|
147
|
+
@client.close
|
148
|
+
mutex.synchronize { results[char] = result }
|
149
|
+
end
|
150
|
+
end.each {|t| t.join }
|
151
|
+
results.each_pair do |char, result|
|
152
|
+
result.should_not == nil
|
153
|
+
size = result.each do |value|
|
154
|
+
value.to_s[0].upcase.should == char
|
155
|
+
end.length
|
156
|
+
size.should > 0
|
65
157
|
end
|
66
158
|
end
|
159
|
+
|
160
|
+
end
|
161
|
+
|
162
|
+
describe "client multiple processes" do
|
163
|
+
|
164
|
+
before(:all) do
|
165
|
+
Cyc::Connection.driver = Cyc::Connection::SocketDriver
|
166
|
+
@client = Cyc::Client.new()
|
167
|
+
# @client.debug = true
|
168
|
+
end
|
169
|
+
|
170
|
+
after(:each) do
|
171
|
+
@client.close
|
172
|
+
end
|
173
|
+
|
174
|
+
it "should have socket driver" do
|
175
|
+
@client.driver.type.should == :socket
|
176
|
+
end
|
177
|
+
|
178
|
+
it "should allow multiple processes to use the client" do
|
179
|
+
fork { @client.find_constant("Cat").should == :Cat }
|
180
|
+
fork { @client.find_constant("Dog").should == :Dog }
|
181
|
+
@client.find_constant("Animal").should == :Animal
|
182
|
+
Process.waitall
|
183
|
+
end
|
184
|
+
|
67
185
|
end
|
data/lib/cyc/assertion.rb
CHANGED
data/lib/cyc/builder.rb
CHANGED
@@ -2,9 +2,9 @@ module Cyc
|
|
2
2
|
# This class is used to capture calls to the Cyc client, to allow
|
3
3
|
# nested calls, like
|
4
4
|
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
5
|
+
# cyc.with_any_mt do |cyc|
|
6
|
+
# cyc.comment :Collection
|
7
|
+
# end
|
8
8
|
class Builder
|
9
9
|
def initialize
|
10
10
|
reset
|
data/lib/cyc/client.rb
CHANGED
@@ -1,99 +1,169 @@
|
|
1
|
-
require '
|
1
|
+
require 'uri'
|
2
|
+
require 'cyc/connection'
|
2
3
|
require 'cyc/exception'
|
3
4
|
|
4
|
-
module Cyc
|
5
|
+
module Cyc #:nodoc:
|
5
6
|
# Author:: Aleksander Pohl (mailto:apohllo@o2.pl)
|
6
7
|
# License:: MIT/X11 License
|
7
8
|
#
|
8
|
-
# This class is the implementation of
|
9
|
+
# This class is the implementation of a Cyc client.
|
10
|
+
#
|
11
|
+
# Example:
|
12
|
+
#
|
13
|
+
# cyc = Cyc::Client.new
|
14
|
+
# cyc.genls? :Dog, :Animal # checks if Dog generalizes to Animal
|
15
|
+
# #=> true
|
16
|
+
# cyc.genls? :Animal, :Dog # checks if Animal generalizes to Dog
|
17
|
+
# #=> nil
|
9
18
|
class Client
|
10
19
|
# If set to true, all communication with the server is logged
|
11
20
|
# to standard output
|
12
21
|
attr_accessor :debug
|
13
|
-
|
22
|
+
|
23
|
+
# The +host+ the client connects to.
|
24
|
+
attr_reader :host
|
25
|
+
|
26
|
+
# The +port+ the client connects to.
|
27
|
+
attr_reader :port
|
28
|
+
|
29
|
+
# The +driver+ the client uses to connect to the server.
|
30
|
+
attr_reader :driver
|
31
|
+
|
32
|
+
# +true+ if the client is thread safe.
|
33
|
+
attr_reader :thread_safe
|
34
|
+
alias_method :thread_safe?, :thread_safe
|
35
|
+
|
36
|
+
# The +connection+ object - direct usage is discouraged.
|
37
|
+
# Use connection() call instead.
|
38
|
+
attr_accessor :conn
|
39
|
+
protected :conn
|
14
40
|
|
15
41
|
# Creates new Client.
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
42
|
+
# Usage:
|
43
|
+
# Cyc::Client.new [options = {}]
|
44
|
+
#
|
45
|
+
# options:
|
46
|
+
# - +:host+ = +localhost+ server address
|
47
|
+
# - +:port+ = +3601+ server port
|
48
|
+
# - +:debug+ = +false+ initial debug flag
|
49
|
+
# - +:timeout+ = +0.2+ connection timeout in seconds
|
50
|
+
# - +:url+ (String): +cyc://host:port+ overrides +:host+, +:port+
|
51
|
+
# - +:driver+ (Class) = Cyc::Connection::Socket client connection driver class
|
52
|
+
# - +:thread_safe+ = +true+ set to +true+ if you want to share client between
|
53
|
+
# threads
|
54
|
+
#
|
55
|
+
# Example:
|
56
|
+
# Cyc::Client.new
|
57
|
+
# Cyc::Client.new :host => 'cyc.example', :port => 3661, :debug => true
|
58
|
+
# Cyc::Client.new :debug => true, :url => 'cyc://localhost/3661',
|
59
|
+
# :timeout => 1.5, :driver => Cyc::Connection::SynchronyDriver
|
60
|
+
#
|
61
|
+
# Thread safe client:
|
62
|
+
# Cyc::Client.new :thread_safe => true
|
63
|
+
def initialize(options={})
|
20
64
|
@pid = Process.pid
|
21
|
-
|
22
|
-
|
23
|
-
|
65
|
+
unless Hash === options
|
66
|
+
raise ArgumentError.new("The Client.new(host,port) API is no longer supported.")
|
67
|
+
end
|
68
|
+
@host = options[:host] || "localhost"
|
69
|
+
@port = (options[:port] || 3601).to_i
|
70
|
+
if url = options[:url]
|
71
|
+
url = URI.parse(url)
|
72
|
+
@host = url.host || @host
|
73
|
+
@port = url.port || @port
|
74
|
+
end
|
75
|
+
@timeout = (options[:timeout] || 0.2).to_f
|
76
|
+
@driver = options[:driver] || Connection.driver
|
77
|
+
@debug = !!options[:debug]
|
78
|
+
@thread_safe = !!options[:thread_safe]
|
79
|
+
|
80
|
+
if @thread_safe
|
81
|
+
self.extend ThreadSafeClientExtension
|
82
|
+
else
|
83
|
+
@conn = nil
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Returns +true+ if the client is connected with the server.
|
88
|
+
def connected?
|
89
|
+
(conn=self.conn) && conn.connected? && @pid == Process.pid || false
|
24
90
|
end
|
25
91
|
|
26
92
|
# (Re)connects to the cyc server.
|
27
93
|
def reconnect
|
94
|
+
# reuse existing connection driver
|
95
|
+
# to prevent race condition between fibers
|
96
|
+
conn = (self.conn||= @driver.new)
|
97
|
+
conn.disconnect if connected?
|
28
98
|
@pid = Process.pid
|
29
|
-
|
30
|
-
|
99
|
+
puts "connecting: #@host:#@port $$#@pid" if @debug
|
100
|
+
conn.connect(@host, @port, @timeout)
|
101
|
+
self
|
31
102
|
end
|
32
103
|
|
104
|
+
# Usually the connection will be established on first use
|
105
|
+
# however connect() allows to force early connection:
|
106
|
+
# Cyc::Client.new().connect
|
107
|
+
# Usefull in fiber concurrent environment to setup connection early.
|
108
|
+
alias_method :connect, :reconnect
|
33
109
|
# Returns the connection object. Ensures that the pid of current
|
34
110
|
# process is the same as the pid, the connection was initialized with.
|
35
111
|
#
|
36
112
|
# If the block is given, the command is guarded by assertion, that
|
37
113
|
# it will be performed, even if the connection was reset.
|
38
114
|
def connection
|
39
|
-
|
40
|
-
reconnect
|
41
|
-
end
|
115
|
+
reconnect unless connected?
|
42
116
|
if block_given?
|
43
117
|
begin
|
44
|
-
yield
|
118
|
+
yield conn
|
45
119
|
rescue Errno::ECONNRESET
|
46
120
|
reconnect
|
47
|
-
yield
|
121
|
+
yield conn
|
48
122
|
end
|
49
123
|
else
|
50
|
-
|
124
|
+
conn
|
51
125
|
end
|
52
126
|
end
|
53
127
|
|
54
128
|
protected :connection, :reconnect
|
55
129
|
|
56
|
-
#
|
57
|
-
def clear_cache
|
58
|
-
@mts_cache = {}
|
59
|
-
end
|
60
|
-
|
61
|
-
# Closes connection with the server
|
130
|
+
# Closes connection with the server.
|
62
131
|
def close
|
63
|
-
|
64
|
-
|
132
|
+
conn.write("(api-quit)") if connected?
|
133
|
+
rescue Errno::ECONNRESET
|
134
|
+
ensure
|
135
|
+
self.conn = nil
|
65
136
|
end
|
66
137
|
|
67
|
-
# Sends
|
68
|
-
def talk(
|
69
|
-
send_message(
|
138
|
+
# Sends the +messsage+ to the Cyc server and returns a parsed answer.
|
139
|
+
def talk(message, options={})
|
140
|
+
send_message(message)
|
70
141
|
receive_answer(options)
|
71
142
|
end
|
72
143
|
|
73
|
-
# Sends
|
144
|
+
# Sends the +message+ to the Cyc server and
|
74
145
|
# returns the raw answer (i.e. not parsed).
|
75
|
-
def raw_talk(
|
76
|
-
send_message(
|
146
|
+
def raw_talk(message, options={})
|
147
|
+
send_message(message)
|
77
148
|
receive_raw_answer(options)
|
78
149
|
end
|
79
150
|
|
80
|
-
# Scans the
|
81
|
-
# Raises UnbalancedClosingParenthesis exception if there is not matched closing
|
151
|
+
# Scans the +message+ to find out if the parenthesis are matched.
|
152
|
+
# Raises UnbalancedClosingParenthesis exception if there is a not matched closing
|
82
153
|
# parenthesis. The message of the exception contains the string with the
|
83
154
|
# unmatched parenthesis highlighted.
|
84
|
-
# Raises UnbalancedOpeningParenthesis exception if there is not matched opening
|
155
|
+
# Raises UnbalancedOpeningParenthesis exception if there is a not matched opening
|
85
156
|
# parenthesis.
|
86
157
|
def check_parenthesis(message)
|
87
158
|
count = 0
|
88
|
-
|
89
|
-
message.scan(/./) do |char|
|
90
|
-
position += 1
|
91
|
-
next if char !~ /\(|\)/
|
159
|
+
message.scan(/[()]/) do |char|
|
92
160
|
count += (char == "(" ? 1 : -1)
|
93
161
|
if count < 0
|
162
|
+
# this *is* thread safe
|
163
|
+
position = $~.offset(0)[0]
|
94
164
|
raise UnbalancedClosingParenthesis.
|
95
|
-
new((position > 1 ? message[0
|
96
|
-
"<error>)</error>" + message[position..-1])
|
165
|
+
new((position > 1 ? message[0...position] : "") +
|
166
|
+
"<error>)</error>" + message[position+1..-1])
|
97
167
|
end
|
98
168
|
end
|
99
169
|
raise UnbalancedOpeningParenthesis.new(count) if count > 0
|
@@ -101,74 +171,44 @@ module Cyc
|
|
101
171
|
|
102
172
|
# Sends a raw message to the Cyc server. The user is
|
103
173
|
# responsible for receiving the answer by calling
|
104
|
-
#
|
174
|
+
# receive_answer or receive_raw_answer.
|
105
175
|
def send_message(message)
|
106
|
-
position = 0
|
107
176
|
check_parenthesis(message)
|
108
|
-
@last_message = message
|
109
177
|
puts "Send: #{message}" if @debug
|
110
|
-
connection{|c| c.
|
178
|
+
connection{|c| c.write(message)}
|
111
179
|
end
|
112
180
|
|
113
181
|
# Receives and parses an answer for a message from the Cyc server.
|
114
182
|
def receive_answer(options={})
|
115
|
-
receive_raw_answer do |answer|
|
183
|
+
receive_raw_answer do |answer, last_message|
|
116
184
|
begin
|
117
|
-
result =
|
185
|
+
result = Parser.new.parse answer, options[:stack]
|
118
186
|
rescue ContinueParsing => ex
|
119
|
-
result = ex.stack
|
120
|
-
current_result = result
|
121
|
-
last_message = @last_message
|
187
|
+
current_result = result = ex.stack
|
122
188
|
while current_result.size == 100 do
|
123
189
|
send_message("(subseq #{last_message} #{result.size} " +
|
124
190
|
"#{result.size + 100})")
|
125
191
|
current_result = receive_answer(options) || []
|
126
192
|
result.concat(current_result)
|
127
193
|
end
|
128
|
-
rescue CycError => ex
|
129
|
-
puts ex.to_s
|
130
|
-
return nil
|
131
194
|
end
|
132
195
|
return result
|
133
196
|
end
|
134
197
|
end
|
135
198
|
|
136
|
-
# Receives raw answer from server. If a
|
199
|
+
# Receives raw answer from server. If a block is given
|
137
200
|
# the answer is yield to the block, otherwise the answer is returned.
|
138
201
|
def receive_raw_answer(options={})
|
139
|
-
answer = connection{|c| c.
|
140
|
-
puts "Recv: #{answer}" if @debug
|
141
|
-
if
|
142
|
-
raise CycError.new("Unknwon error occured. " +
|
143
|
-
"Check the submitted query in detail:\n" +
|
144
|
-
@last_message)
|
145
|
-
end
|
146
|
-
while not answer =~ /\n/ do
|
147
|
-
next_answer = connection{|c| c.waitfor(/./)}
|
148
|
-
puts "Recv: #{next_answer}" if @debug
|
149
|
-
if answer.nil?
|
150
|
-
answer = next_answer
|
151
|
-
else
|
152
|
-
answer += next_answer
|
153
|
-
end
|
154
|
-
end
|
155
|
-
# XXX ignore some potential asynchronous answers
|
156
|
-
# XXX check if everything works ok
|
157
|
-
#answer = answer.split("\n")[-1]
|
158
|
-
answer = answer.sub(/(\d\d\d) (.*)/,"\\2")
|
159
|
-
if($1.to_i == 200)
|
202
|
+
status, answer, last_message = connection{|c| c.read}
|
203
|
+
puts "Recv: #{last_message} -> #{status} #{answer}" if @debug
|
204
|
+
if status == 200
|
160
205
|
if block_given?
|
161
|
-
yield answer
|
206
|
+
yield answer, last_message
|
162
207
|
else
|
163
208
|
return answer
|
164
209
|
end
|
165
210
|
else
|
166
|
-
|
167
|
-
raise CycError.new($2.sub(/^"/,"").sub(/"$/,"") + "\n" + @last_message)
|
168
|
-
else
|
169
|
-
raise CycError.new("Unknown error! #{answer}")
|
170
|
-
end
|
171
|
-
nil
|
211
|
+
raise CycError.new(answer.sub(/^"/,"").sub(/"$/,"") + "\n" + last_message)
|
172
212
|
end
|
173
213
|
end
|
174
214
|
|
@@ -176,11 +216,12 @@ module Cyc
|
|
176
216
|
# are translated into corresponding calls for Cyc server.
|
177
217
|
#
|
178
218
|
# E.g. if users initializes the client and calls some Ruby method
|
219
|
+
#
|
179
220
|
# cyc = Cyc::Client.new
|
180
221
|
# cyc.genls? :Dog, :Animal
|
181
222
|
#
|
182
223
|
# He/She returns a parsed answer from the server:
|
183
|
-
# =>
|
224
|
+
# => true
|
184
225
|
#
|
185
226
|
# Since dashes are not allowed in Ruby method names they are replaced
|
186
227
|
# with underscores:
|
@@ -193,7 +234,7 @@ module Cyc
|
|
193
234
|
#
|
194
235
|
# As you see the Ruby symbols are translated into Cyc terms (not Cyc symbols!).
|
195
236
|
#
|
196
|
-
# It is also possible to nest the calls to build more complex
|
237
|
+
# It is also possible to nest the calls to build more complex calls:
|
197
238
|
#
|
198
239
|
# cyc.with_any_mt do |cyc|
|
199
240
|
# cyc.min_genls :Dog
|
@@ -204,9 +245,22 @@ module Cyc
|
|
204
245
|
# (with-any-mt (min-genls #$Dog))
|
205
246
|
#
|
206
247
|
def method_missing(name,*args,&block)
|
207
|
-
|
208
|
-
|
209
|
-
talk(
|
248
|
+
builder = Builder.new
|
249
|
+
builder.send(name,*args,&block)
|
250
|
+
talk(builder.to_cyc)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
module ThreadSafeClientExtension #:nodoc:
|
255
|
+
THR_VAR_TEMLPATE='_cyc_client_$%s_%s'
|
256
|
+
|
257
|
+
def self.extend_object(obj)
|
258
|
+
obj.instance_variable_set "@thrconn", (THR_VAR_TEMLPATE%[obj.object_id, 'conn']).intern
|
259
|
+
super
|
210
260
|
end
|
261
|
+
|
262
|
+
protected
|
263
|
+
def conn; Thread.current[@thrconn]; end
|
264
|
+
def conn=(conn); Thread.current[@thrconn] = conn; end
|
211
265
|
end
|
212
266
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require "cyc/exception"
|
2
|
+
|
3
|
+
module Cyc #:nodoc:
|
4
|
+
module Connection #:nodoc:
|
5
|
+
# Author:: Rafal Michalski (mailto:royaltm75@gmail.com)
|
6
|
+
# Licence:: MIT/X11 License
|
7
|
+
#
|
8
|
+
# DataBuffer is chunky text data to server answer assembly class.
|
9
|
+
#
|
10
|
+
# Usage:
|
11
|
+
#
|
12
|
+
# b = DataBuffer.new
|
13
|
+
# b << "200 Some response\n" << "300 Some other" << " response\n" << "additional data\n"
|
14
|
+
# b.next_result
|
15
|
+
# => [200, "Some response"]
|
16
|
+
# b.next_result
|
17
|
+
# => [300, "Some other response\nadditional data"]
|
18
|
+
# b.next_result
|
19
|
+
# => nil
|
20
|
+
# b << "Invalid response\n"
|
21
|
+
# b.next_result("meta data")
|
22
|
+
# Cyc::ProtocolError:
|
23
|
+
# Unexpected data from server: "Invalid response", check the submitted query in detail:
|
24
|
+
# "meta data"
|
25
|
+
#
|
26
|
+
class DataBuffer
|
27
|
+
EOL = "\n"
|
28
|
+
RESULT_MATCH = /^(\d\d\d) (.*)$/m
|
29
|
+
|
30
|
+
# Initializes an empty buffer.
|
31
|
+
def initialize
|
32
|
+
@buffer = ""
|
33
|
+
end
|
34
|
+
|
35
|
+
# Appends +data+ to the buffer.
|
36
|
+
def <<(data)
|
37
|
+
@buffer << data
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns the next complete result stored in the buffer.
|
41
|
+
def next_result(org_message=nil)
|
42
|
+
res_end = 0
|
43
|
+
size = @buffer.length
|
44
|
+
while res_end = @buffer.index(EOL, res_end)
|
45
|
+
res_end += 1
|
46
|
+
if res_end == size || RESULT_MATCH === @buffer[res_end..-1]
|
47
|
+
result = @buffer.slice!(0, res_end).chomp EOL
|
48
|
+
if RESULT_MATCH === result
|
49
|
+
return [$1.to_i, $2]
|
50
|
+
else
|
51
|
+
raise ProtocolError.new("Unexpected data from server: #{result.inspect}, " +
|
52
|
+
"check the submitted query in detail:\n#{org_message.inspect}")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# String representation of the buffer.
|
59
|
+
def to_s
|
60
|
+
@buffer
|
61
|
+
end
|
62
|
+
|
63
|
+
# Clears the buffer contents.
|
64
|
+
def discard!
|
65
|
+
@buffer.clear
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Cyc #:nodoc:
|
2
|
+
# Use Cyc::Connection.driver = Cyc::Connection::SomeDriver
|
3
|
+
# to set default connection driver.
|
4
|
+
module Connection
|
5
|
+
EOL = "\n"
|
6
|
+
class << self
|
7
|
+
# The driver used to connect to the Cyc server.
|
8
|
+
attr_accessor :driver
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
Cyc::Connection.driver = nil
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require "socket"
|
2
|
+
require "cyc/connection/driver"
|
3
|
+
require "cyc/exception"
|
4
|
+
require "cyc/connection/buffer"
|
5
|
+
|
6
|
+
module Cyc #:nodoc:
|
7
|
+
module Connection #:nodoc:
|
8
|
+
# TCPSocket Cyc::Client driver
|
9
|
+
# Author:: Rafal Michalski (mailto:royaltm75@gmail.com)
|
10
|
+
# Licence:: MIT/X11 License
|
11
|
+
#
|
12
|
+
# Default driver for Cyc::Client.
|
13
|
+
#
|
14
|
+
class SocketDriver
|
15
|
+
# The type of the driver, i.e. +:socket+.
|
16
|
+
def self.type; :socket; end
|
17
|
+
|
18
|
+
# Initialize a new driver.
|
19
|
+
def initialize
|
20
|
+
@sock = nil
|
21
|
+
@buffer = DataBuffer.new
|
22
|
+
@last_message = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns true if the driver is connected to the server.
|
26
|
+
def connected?
|
27
|
+
!! @sock
|
28
|
+
end
|
29
|
+
|
30
|
+
# Connects to the server on +host+ and +port+ with given
|
31
|
+
# connection +timeout+.
|
32
|
+
def connect(host, port, timeout=0.2)
|
33
|
+
with_timeout(timeout.to_f) do
|
34
|
+
@sock = TCPSocket.new(host, port)
|
35
|
+
@sock.sync = true
|
36
|
+
@sock.binmode
|
37
|
+
#@sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Disconnects the driver from the server.
|
42
|
+
def disconnect
|
43
|
+
@sock.close if @sock
|
44
|
+
rescue
|
45
|
+
# This should go to log #2.
|
46
|
+
ensure
|
47
|
+
@sock = nil
|
48
|
+
@buffer.discard!
|
49
|
+
end
|
50
|
+
|
51
|
+
# Send a message to the server.
|
52
|
+
def write(rawmsg)
|
53
|
+
@last_message = rawmsg
|
54
|
+
@sock.write(rawmsg + EOL)
|
55
|
+
# ensure that the connection is still with a server
|
56
|
+
# and wait for an answer at the same time
|
57
|
+
if @sock.eof?
|
58
|
+
disconnect
|
59
|
+
raise Errno::ECONNRESET
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Read next message from the server.
|
64
|
+
def read
|
65
|
+
begin
|
66
|
+
@buffer << @sock.readpartial(4096)
|
67
|
+
end until result = @buffer.next_result(@last_message)
|
68
|
+
result << @last_message
|
69
|
+
rescue IOError, EOFError, Errno::ECONNRESET
|
70
|
+
disconnect
|
71
|
+
raise Errno::ECONNRESET
|
72
|
+
end
|
73
|
+
|
74
|
+
protected
|
75
|
+
|
76
|
+
# borrowed from redis-rb
|
77
|
+
begin
|
78
|
+
require "system_timer"
|
79
|
+
|
80
|
+
def with_timeout(seconds, &block)
|
81
|
+
SystemTimer.timeout_after(seconds, &block)
|
82
|
+
end
|
83
|
+
|
84
|
+
rescue LoadError
|
85
|
+
require "timeout"
|
86
|
+
|
87
|
+
def with_timeout(seconds, &block)
|
88
|
+
Timeout.timeout(seconds, &block)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
Cyc::Connection.driver = Cyc::Connection::SocketDriver
|
@@ -0,0 +1,167 @@
|
|
1
|
+
require "em-synchrony"
|
2
|
+
require "cyc/connection/driver"
|
3
|
+
require "cyc/exception"
|
4
|
+
require "cyc/connection/buffer"
|
5
|
+
module Cyc #:nodoc:
|
6
|
+
module Connection #:nodoc:
|
7
|
+
class ConnectionClient < EventMachine::Connection
|
8
|
+
include EventMachine::Deferrable
|
9
|
+
|
10
|
+
def post_init
|
11
|
+
@req = []
|
12
|
+
@connected = false
|
13
|
+
@buffer = DataBuffer.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def connection_completed
|
17
|
+
@connected = true
|
18
|
+
succeed
|
19
|
+
end
|
20
|
+
|
21
|
+
def connected?
|
22
|
+
@connected
|
23
|
+
end
|
24
|
+
|
25
|
+
def receive_data(data)
|
26
|
+
@buffer << data
|
27
|
+
|
28
|
+
begin
|
29
|
+
while (result = @buffer.next_result(@req.first))
|
30
|
+
unless @req.empty?
|
31
|
+
msg, req = @req.shift(2)
|
32
|
+
req.succeed(result << msg)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
rescue RuntimeError => err
|
36
|
+
@req.each_slice(2) {|_, r| r.fail [:error, err]}
|
37
|
+
@req.clear
|
38
|
+
close_connection
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def read
|
43
|
+
EventMachine::Synchrony.sync @req.last unless @req.empty?
|
44
|
+
end
|
45
|
+
|
46
|
+
def send(data)
|
47
|
+
@req << data << EventMachine::DefaultDeferrable.new
|
48
|
+
callback { send_data data + EOL }
|
49
|
+
@req.last
|
50
|
+
end
|
51
|
+
|
52
|
+
def unbind
|
53
|
+
@connected = false
|
54
|
+
@buffer.discard!
|
55
|
+
unless @req.empty?
|
56
|
+
@req.each_slice(2) {|_, r| r.fail [:error, Errno::ECONNRESET]}
|
57
|
+
@req.clear
|
58
|
+
end
|
59
|
+
fail
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# EM-Synchrony Cyc::Client driver
|
64
|
+
# Author:: Rafal Michalski (mailto:royaltm75@gmail.com)
|
65
|
+
# Licence:: MIT/X11 License
|
66
|
+
#
|
67
|
+
# Requires: igrigorik/em-synchrony
|
68
|
+
#
|
69
|
+
# To use this driver simply require this file
|
70
|
+
# e.g.
|
71
|
+
# require 'cyc/connection/synchrony'
|
72
|
+
# beside 'cycr'
|
73
|
+
#
|
74
|
+
# If required before 'cycr' then the SocketDriver will not be loaded
|
75
|
+
# (however you can still load it by hand: require 'cyc/connection/socket'
|
76
|
+
# but be carefull though it will override default Cyc::Connection.driver).
|
77
|
+
#
|
78
|
+
# If required after 'cycr' then the SocketDriver will be preserved but
|
79
|
+
# the default driver will be set to SynchronyDriver.
|
80
|
+
#
|
81
|
+
# Async fiber example:
|
82
|
+
#
|
83
|
+
# require 'cyc/connection/synchrony'
|
84
|
+
# require 'cycr'
|
85
|
+
# EM.synchrony do
|
86
|
+
# cyc = EM::Synchrony::ConnectionPool.new(size: 5) do
|
87
|
+
# Cyc::Client.new :url => 'cyc://localhost:3601', :debug => true
|
88
|
+
# end
|
89
|
+
# puts cyc.driver, cyc.driver.type.inspect
|
90
|
+
# Fiber.new do
|
91
|
+
# puts "Ani", cyc.fi_complete("Ani").inspect
|
92
|
+
# end.resume
|
93
|
+
# puts "Mi", cyc.talk('(fi-complete "Mi")').inspect
|
94
|
+
# EM.stop
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
# `Mi` will arrive before `Ani`
|
98
|
+
#
|
99
|
+
# Warning: always use EM::Synchrony::ConnectionPool to handle Fiber
|
100
|
+
# concurrency race conditions.
|
101
|
+
class SynchronyDriver
|
102
|
+
# The type of the driver, i.e. +:synchrony+.
|
103
|
+
def self.type; :synchrony; end
|
104
|
+
|
105
|
+
# Initialize a new driver.
|
106
|
+
def initialize
|
107
|
+
@connection = nil
|
108
|
+
end
|
109
|
+
|
110
|
+
# Returns true if the driver is connected to the server.
|
111
|
+
def connected?
|
112
|
+
@connection && @connection.connected?
|
113
|
+
end
|
114
|
+
|
115
|
+
# Connects to the server on +host+ and +port+ with given
|
116
|
+
# connection time-out.
|
117
|
+
def connect(host, port, timeout)
|
118
|
+
conn = EventMachine.connect(host, port, ConnectionClient) do |c|
|
119
|
+
c.pending_connect_timeout = [Float(timeout), 0.1].max
|
120
|
+
end
|
121
|
+
|
122
|
+
setup_connect_callbacks(conn, Fiber.current)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Disconnects the driver from the server.
|
126
|
+
def disconnect
|
127
|
+
@connection.close_connection
|
128
|
+
@connection = nil
|
129
|
+
end
|
130
|
+
|
131
|
+
# Send a message to the server.
|
132
|
+
def write(rawmsg)
|
133
|
+
@connection.send(rawmsg)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Read next message from the server.
|
137
|
+
def read
|
138
|
+
status, answer, msg = @connection.read
|
139
|
+
if status == :error
|
140
|
+
raise answer
|
141
|
+
else
|
142
|
+
return [status, answer, msg]
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
def setup_connect_callbacks(conn, f)
|
149
|
+
conn.callback do
|
150
|
+
@connection = conn
|
151
|
+
f.resume conn
|
152
|
+
end
|
153
|
+
|
154
|
+
conn.errback do
|
155
|
+
@connection = conn
|
156
|
+
f.resume :refused
|
157
|
+
end
|
158
|
+
|
159
|
+
r = Fiber.yield
|
160
|
+
raise Errno::ECONNREFUSED if r == :refused
|
161
|
+
r
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
Cyc::Connection.driver = Cyc::Connection::SynchronyDriver
|
data/lib/cyc/exception.rb
CHANGED
@@ -22,6 +22,9 @@ module Cyc
|
|
22
22
|
|
23
23
|
# Error raised if the message sent to the server has
|
24
24
|
# more closing parentheses than opening parentheses.
|
25
|
+
# The exception message should indicate where is the
|
26
|
+
# first unbalanced closing parenthesis using 'error'
|
27
|
+
# tag.
|
25
28
|
class UnbalancedClosingParenthesis < CycError
|
26
29
|
end
|
27
30
|
|
@@ -40,4 +43,9 @@ module Cyc
|
|
40
43
|
end
|
41
44
|
end
|
42
45
|
|
46
|
+
# Exception raised by the parser if data received from server
|
47
|
+
# is not in expected format.
|
48
|
+
class ProtocolError < CycError
|
49
|
+
end
|
50
|
+
|
43
51
|
end
|
data/lib/cyc/parser.rb
CHANGED
data/lib/cyc/version.rb
ADDED
data/lib/cycr.rb
CHANGED
metadata
CHANGED
@@ -2,15 +2,16 @@
|
|
2
2
|
name: cycr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.
|
5
|
+
version: 0.2.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Aleksander Pohl
|
9
|
+
- Rafal Michalski
|
9
10
|
autorequire:
|
10
11
|
bindir: bin
|
11
12
|
cert_chain: []
|
12
13
|
|
13
|
-
date: 2012-
|
14
|
+
date: 2012-03-16 00:00:00 Z
|
14
15
|
dependencies:
|
15
16
|
- !ruby/object:Gem::Dependency
|
16
17
|
name: rspec
|
@@ -18,11 +19,22 @@ dependencies:
|
|
18
19
|
requirement: &id001 !ruby/object:Gem::Requirement
|
19
20
|
none: false
|
20
21
|
requirements:
|
21
|
-
- -
|
22
|
+
- - ~>
|
22
23
|
- !ruby/object:Gem::Version
|
23
|
-
version:
|
24
|
+
version: 2.8.0
|
24
25
|
type: :development
|
25
26
|
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: em-synchrony
|
29
|
+
prerelease: false
|
30
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
31
|
+
none: false
|
32
|
+
requirements:
|
33
|
+
- - ~>
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: 1.0.0
|
36
|
+
type: :development
|
37
|
+
version_requirements: *id002
|
26
38
|
description: Ruby wrapper for (Open)Cyc server and ontology
|
27
39
|
email: apohllo@o2.pl
|
28
40
|
executables: []
|
@@ -43,6 +55,11 @@ files:
|
|
43
55
|
- lib/cyc/builder.rb
|
44
56
|
- lib/cyc/client.rb
|
45
57
|
- lib/cyc/collection.rb
|
58
|
+
- lib/cyc/connection.rb
|
59
|
+
- lib/cyc/connection/buffer.rb
|
60
|
+
- lib/cyc/connection/driver.rb
|
61
|
+
- lib/cyc/connection/socket.rb
|
62
|
+
- lib/cyc/connection/synchrony.rb
|
46
63
|
- lib/cyc/exception.rb
|
47
64
|
- lib/cyc/extensions.rb
|
48
65
|
- lib/cyc/parser.rb
|
@@ -50,6 +67,7 @@ files:
|
|
50
67
|
- lib/cyc/sexpr.rex.rb
|
51
68
|
- lib/cyc/symbol.rb
|
52
69
|
- lib/cyc/variable.rb
|
70
|
+
- lib/cyc/version.rb
|
53
71
|
- lib/cycr.rb
|
54
72
|
- spec/parser.rb
|
55
73
|
homepage: http://github.com/apohllo/cycr
|
@@ -66,7 +84,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
66
84
|
requirements:
|
67
85
|
- - ">="
|
68
86
|
- !ruby/object:Gem::Version
|
69
|
-
version:
|
87
|
+
version: 1.9.2
|
70
88
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
89
|
none: false
|
72
90
|
requirements:
|