cycr 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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:
|