porolog 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 255c18b94a31335781ec500cbec763b710def39e9b1c9e331b573e51b18da80e
4
+ data.tar.gz: d676680c7eea7d2fd3df0b3708fd7f09977ed92472616cdf44b3db2eede37688
5
+ SHA512:
6
+ metadata.gz: 2f5018c1e104af58e1decd95f1d55647c6c51c7eb8bf38e3720feb4438d3c52fa24d9cfab000e5251d8160b2980d74234e56dd2c3f1a8991334e98276e519b44
7
+ data.tar.gz: 1b4e60c16c278e5c4f3f276545fc0c1e330b895f01c916f053217dd72df1f5f198c3b8feed2a3c5460e1de3f3238da3b5d481e0405de272ee28142361d6adb88
data/README.md ADDED
@@ -0,0 +1,119 @@
1
+ # porolog
2
+
3
+ Plain Old Ruby Objects Prolog
4
+
5
+ ## Introduction
6
+
7
+ porolog is a Prolog implementation using plain old Ruby objects with the aim that
8
+ logic queries can be called within a regular Ruby program. The goal was not to
9
+ implement a Prolog interpreter that is just implement in Ruby, but rather that
10
+ a logic engine could be embedded in a larger program.
11
+
12
+ The goal was to implement a minimal logic engine in the style of Prolog where
13
+ Ruby objects could be passed in and Ruby objects were passed back.
14
+
15
+ ## Installation
16
+
17
+ gem install porolog
18
+
19
+ ## Usage
20
+
21
+ ### Basic Usage
22
+
23
+ require 'porolog'
24
+
25
+ prime = Predicate.new :prime
26
+
27
+ prime.(2).fact!
28
+ prime.(3).fact!
29
+ prime.(5).fact!
30
+ prime.(7).fact!
31
+
32
+ solutions = prime.(:X).solve
33
+
34
+ solutions.each do |solution|
35
+ puts "#{solution[:X]} is prime"
36
+ end
37
+
38
+
39
+ ### Common Usage
40
+
41
+ require 'porolog'
42
+
43
+ include Porolog
44
+
45
+ predicate :prime
46
+
47
+ prime(2).fact!
48
+ prime(3).fact!
49
+ prime(5).fact!
50
+ prime(7).fact!
51
+
52
+ solutions = prime(:X).solve
53
+
54
+ solutions.each do |solution|
55
+ puts "#{solution[:X]} is prime"
56
+ end
57
+
58
+ ### Scope Usage
59
+
60
+ The Scope class enables you to have multiple logic programs embedded in the same
61
+ Ruby program. A Scope object defines a scope for the predicates of a logic programme.
62
+
63
+ require 'porolog'
64
+
65
+ # -- Prime Numbers Predicate --
66
+ prime = prime1 = Predicate.new :prime, :numbers
67
+
68
+ prime.(2).fact!
69
+ prime.(3).fact!
70
+ prime.(5).fact!
71
+ prime.(7).fact!
72
+ prime.(11).fact!
73
+
74
+ # -- Pump Predicate --
75
+ prime = prime2 = Predicate.new :prime, :pumps
76
+
77
+ prime.('pump A').fact!
78
+ prime.('pump B').fact!
79
+ prime.('pump C').fact!
80
+ prime.('pump D').fact!
81
+
82
+ assert_equal [:default,:numbers,:pumps], Scope.scopes
83
+
84
+ assert_scope Scope[:default], :default, []
85
+ assert_scope Scope[:numbers], :first, [prime1]
86
+ assert_scope Scope[:pumps], :second, [prime2]
87
+
88
+ assert_equal :prime, prime1.name
89
+ assert_equal :prime, prime2.name
90
+
91
+ solutions = [
92
+ { X: 2 },
93
+ { X: 3 },
94
+ { X: 5 },
95
+ { X: 7 },
96
+ { X: 11 },
97
+ ]
98
+ assert_equal solutions, prime1.(:X).solve
99
+
100
+ solutions = [
101
+ { X: 'pump A' },
102
+ { X: 'pump B' },
103
+ { X: 'pump C' },
104
+ { X: 'pump D' },
105
+ ]
106
+ assert_equal solutions, prime2.(:X).solve
107
+
108
+
109
+ ## Testing
110
+
111
+ rake test
112
+
113
+ or
114
+
115
+ rake scope_test
116
+
117
+ ## Author
118
+
119
+ Luis Esteban MSc MTeach
data/Rakefile ADDED
@@ -0,0 +1,40 @@
1
+ #
2
+ # Rakefile - Porolog Rakefile
3
+ #
4
+ # 2 May 2018
5
+ #
6
+
7
+ require 'rake/testtask'
8
+
9
+
10
+ # -- Run All Tests Task --
11
+ Rake::TestTask.new do |task|
12
+ task.libs << 'test'
13
+ task.pattern = 'test/porolog/*_test.rb'
14
+ end
15
+
16
+ # -- Create Separate Test Tasks --
17
+ Dir['test/porolog/*_test.rb'].each do |test_file|
18
+ name = File.basename(test_file, '.rb')
19
+ Rake::TestTask.new(name) do |task|
20
+ task.verbose = true
21
+ task.options = '--verbose'
22
+ task.libs << 'test'
23
+ task.pattern = test_file
24
+ task.warning = nil
25
+ end
26
+ end
27
+
28
+ # -- Tasks --
29
+ task default: :test
30
+
31
+ desc 'Help'
32
+ task :help do
33
+ puts <<-EOF
34
+ Porolog is a Ruby library.
35
+ See README.md for more information.
36
+ Run
37
+ rake -T
38
+ for other tasks.
39
+ EOF
40
+ end
data/bin/porolog ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # bin/porolog - Executable
4
+ #
5
+ # Luis Esteban 19 April 2019
6
+ # created
7
+ #
8
+
9
+ require 'porolog'
10
+
11
+ puts 'Sample Porolog Program'
@@ -0,0 +1,12 @@
1
+ #
2
+ # lib/porolog/error.rb - Plain Old Ruby Objects Prolog Engine -- Error
3
+ #
4
+ # Luis Esteban 2 May 2018
5
+ # created
6
+ #
7
+
8
+ module Porolog
9
+
10
+ class PorologError < RuntimeError ; end
11
+
12
+ end
@@ -0,0 +1,68 @@
1
+ #
2
+ # lib/porolog/scope.rb - Plain Old Ruby Objects Prolog Engine -- Scope
3
+ #
4
+ # Luis Esteban 2 May 2018
5
+ # created
6
+ #
7
+
8
+ # == Porolog::Scope ==
9
+ #
10
+ # A Porolog::Scope is a container for Porolog::Predicates.
11
+ # Its purpose is to allow a Ruby program to contain multiple Prolog programs
12
+ # without the Prolog programs interfering with each other.
13
+ #
14
+
15
+ module Porolog
16
+
17
+ class Scope
18
+
19
+ class ScopeError < PorologError ; end
20
+ class NotPredicateError < ScopeError ; end
21
+
22
+ attr_reader :name
23
+
24
+ @@scopes = {}
25
+
26
+ def self.new(name)
27
+ @@scopes[name] || super
28
+ end
29
+
30
+ def initialize(name)
31
+ @name = name
32
+ @predicates = {}
33
+
34
+ @@scopes[@name] = self
35
+ end
36
+
37
+ def self.reset
38
+ @@scopes = {}
39
+ new(:default)
40
+ end
41
+
42
+ reset
43
+
44
+ def self.[](name)
45
+ @@scopes[name]
46
+ end
47
+
48
+ def self.scopes
49
+ @@scopes.keys
50
+ end
51
+
52
+ def [](name)
53
+ @predicates[name.to_sym]
54
+ end
55
+
56
+ def []=(name,predicate)
57
+ # TODO: Uncomment when Porolog::Predicate has been added.
58
+ #raise NotPredicateError.new("#{predicate.inspect} is not a Predicate") unless predicate.is_a?(Predicate)
59
+ @predicates[name.to_sym] = predicate
60
+ end
61
+
62
+ def predicates
63
+ @predicates.values
64
+ end
65
+
66
+ end
67
+
68
+ end
data/lib/porolog.rb ADDED
@@ -0,0 +1,16 @@
1
+ #
2
+ # lib/porolog.rb - Plain Old Ruby Objects Prolog Engine
3
+ #
4
+ # Luis Esteban 2 May 2018
5
+ # created
6
+ #
7
+
8
+ module Porolog
9
+
10
+ VERSION = '0.0.1'
11
+ VERSION_DATE = '2018-05-02'
12
+
13
+ end
14
+
15
+ require_relative 'porolog/error'
16
+ require_relative 'porolog/scope'
@@ -0,0 +1,270 @@
1
+ #
2
+ # test/porolog/scope_test.rb - Test Suite for Porolog::Scope
3
+ #
4
+ # Luis Esteban 2 May 2018
5
+ # created
6
+ #
7
+
8
+ require_relative '../test_helper'
9
+
10
+ describe 'Porolog' do
11
+
12
+ before(:all) do
13
+ reset
14
+ end
15
+
16
+ describe 'Scope' do
17
+
18
+ it 'should already declare the default scope' do
19
+ assert_equal 1, Scope.scopes.size
20
+ assert_equal [:default], Scope.scopes
21
+
22
+ assert_scope Scope[:default], :default, []
23
+ end
24
+
25
+ it 'should allow predicates with the same name to coexist in different scopes' do
26
+ skip 'until Predicate added'
27
+
28
+ prime = prime1 = Predicate.new :prime, :first
29
+
30
+ prime.(2).fact!
31
+ prime.(3).fact!
32
+ prime.(5).fact!
33
+ prime.(7).fact!
34
+ prime.(11).fact!
35
+
36
+ prime = prime2 = Predicate.new :prime, :second
37
+
38
+ prime.('pump A').fact!
39
+ prime.('pump B').fact!
40
+ prime.('pump C').fact!
41
+ prime.('pump D').fact!
42
+
43
+ assert_equal [:default,:first,:second], Scope.scopes
44
+ assert_scope Scope[:default], :default, []
45
+ assert_scope Scope[:first], :first, [prime1]
46
+ assert_scope Scope[:second], :second, [prime2]
47
+
48
+ assert_equal :prime, prime1.name
49
+ assert_equal :prime, prime2.name
50
+
51
+ solutions = [
52
+ { X: 2 },
53
+ { X: 3 },
54
+ { X: 5 },
55
+ { X: 7 },
56
+ { X: 11 },
57
+ ]
58
+ assert_equal solutions, prime1.(:X).solve
59
+
60
+ solutions = [
61
+ { X: 'pump A' },
62
+ { X: 'pump B' },
63
+ { X: 'pump C' },
64
+ { X: 'pump D' },
65
+ ]
66
+ assert_equal solutions, prime2.(:X).solve
67
+ end
68
+
69
+ describe '.scopes' do
70
+
71
+ it 'should return the names of all registered scopes' do
72
+ Scope.new :alpha
73
+ Scope.new :bravo
74
+ Scope.new :carly
75
+
76
+ assert_equal 4, Scope.scopes.size
77
+ assert_equal [:default, :alpha, :bravo, :carly], Scope.scopes
78
+ end
79
+
80
+ end
81
+
82
+ describe '.reset' do
83
+
84
+ it 'should clear all scopes' do
85
+ skip 'until Predicate added'
86
+
87
+ delta = Predicate.new :delta
88
+
89
+ Scope.new :alpha
90
+ Scope.new :bravo
91
+ Scope.new :carly
92
+
93
+ assert_equal 4, Scope.scopes.size
94
+ assert_equal [:default, :alpha, :bravo, :carly], Scope.scopes
95
+
96
+ assert_scope Scope[:default], :default, [delta]
97
+ assert_scope Scope[:alpha], :alpha, []
98
+ assert_scope Scope[:bravo], :bravo, []
99
+ assert_scope Scope[:carly], :carly, []
100
+
101
+ Scope.reset
102
+
103
+ assert_equal 1, Scope.scopes.size
104
+ assert_equal [:default], Scope.scopes
105
+
106
+ assert_scope Scope[:default], :default, []
107
+
108
+ assert_nil Scope[:alpha]
109
+ assert_nil Scope[:bravo]
110
+ assert_nil Scope[:carly]
111
+ end
112
+
113
+ it 'should clear all scopes and start with a default scope when reset' do
114
+ skip 'until Predicate added'
115
+
116
+ test_predicate0 = Predicate.new 'test_predicate0'
117
+ test_predicate11 = Predicate.new 'test_predicate11', :scope1
118
+ test_predicate12 = Predicate.new 'test_predicate12', :scope1
119
+ test_predicate13 = Predicate.new 'test_predicate13', :scope1
120
+ test_predicate21 = Predicate.new 'test_predicate21', :scope2
121
+ test_predicate22 = Predicate.new 'test_predicate22', :scope2
122
+ test_predicate23 = Predicate.new 'test_predicate23', :scope2
123
+
124
+ assert_equal [:default, :scope1, :scope2], Scope.scopes
125
+ assert_equal [test_predicate0], Scope[:default].predicates
126
+ assert_equal [test_predicate11, test_predicate12, test_predicate13], Scope[:scope1 ].predicates
127
+ assert_equal [test_predicate21, test_predicate22, test_predicate23], Scope[:scope2 ].predicates
128
+
129
+ Scope.reset
130
+
131
+ test_predicate3 = Predicate.new 'test_predicate3'
132
+
133
+ assert_equal [:default], Scope.scopes
134
+ assert_equal [test_predicate3], Scope[:default].predicates
135
+ assert_nil Scope[:scope1 ]
136
+ assert_nil Scope[:scope2 ]
137
+ end
138
+
139
+ end
140
+
141
+ describe '.[]' do
142
+
143
+ it 'should provide access to a scope by name' do
144
+ alpha = Scope.new :alpha
145
+ bravo = Scope.new :bravo
146
+ carly = Scope.new :carly
147
+
148
+ assert_equal alpha, Scope[:alpha]
149
+ assert_equal bravo, Scope[:bravo]
150
+ assert_equal carly, Scope[:carly]
151
+ assert_nil Scope[:delta]
152
+ end
153
+
154
+ end
155
+
156
+ describe '.new' do
157
+
158
+ it 'should not create duplicate scopes (with the same name)' do
159
+ Scope.new :duplicate
160
+ Scope.new :duplicate
161
+ Scope.new :duplicate
162
+ Scope.new :duplicate
163
+
164
+ assert_equal 2, Scope.scopes.size
165
+ assert_equal 1, Scope.scopes.count(:duplicate)
166
+ assert_equal [:default, :duplicate], Scope.scopes
167
+ end
168
+
169
+ end
170
+
171
+ describe '#initialize' do
172
+
173
+ it 'should keep track of created scopes' do
174
+ Scope.new :alpha
175
+ Scope.new :bravo
176
+ Scope.new :carly
177
+
178
+ assert_equal [:default,:alpha,:bravo,:carly], Scope.scopes
179
+ end
180
+
181
+ it 'should create scopes with no predicates' do
182
+ scope = Scope.new('test_scope_name')
183
+
184
+ assert_respond_to scope, :predicates
185
+ assert_equal [], scope.predicates
186
+ end
187
+
188
+ end
189
+
190
+ describe '#name' do
191
+
192
+ it 'should create scopes with a name attribute' do
193
+ scope = Scope.new('test_scope_name')
194
+
195
+ assert_respond_to scope, :name
196
+ assert_equal 'test_scope_name', scope.name
197
+ end
198
+
199
+ end
200
+
201
+ describe '#predicates' do
202
+
203
+ it 'should provide access to all the predicates of a scope' do
204
+ skip 'until Predicate added'
205
+
206
+ test_predicate1 = Predicate.new 'test_predicate1', :test
207
+ test_predicate2 = Predicate.new 'test_predicate2', :test
208
+
209
+ assert_respond_to Scope[:test], :predicates
210
+ assert_equal [test_predicate1,test_predicate2], Scope[:test].predicates
211
+ end
212
+
213
+ end
214
+
215
+ describe '#[]' do
216
+
217
+ it 'should provide access to a predicate of a scope by its name' do
218
+ skip 'until Predicate added'
219
+
220
+ test_predicate3 = Predicate.new 'test_predicate3', :test
221
+ test_predicate4 = Predicate.new 'test_predicate4', :test
222
+
223
+ assert_includes Scope[:test].predicates, test_predicate3
224
+
225
+ assert_instance_of Class, Scope
226
+ assert_instance_of Scope, Scope[:test]
227
+ assert_instance_of Predicate, Scope[:test][:test_predicate3]
228
+
229
+ assert_equal test_predicate3, Scope[:test][:test_predicate3]
230
+ assert_equal test_predicate3, Scope[:test]['test_predicate3']
231
+ assert_equal test_predicate4, Scope[:test]['test_predicate4']
232
+ end
233
+
234
+ end
235
+
236
+ describe '#[]=' do
237
+
238
+ it 'should raise an error when trying to put anthing but a Predicate in a Scope' do
239
+ skip 'until Predicate added'
240
+
241
+ error = assert_raises(Porolog::Scope::NotPredicateError){
242
+ scope = Scope.new :scope
243
+
244
+ scope[:predicate] = 'predicate'
245
+ }
246
+ assert_equal error.message, '"predicate" is not a Predicate'
247
+ end
248
+
249
+ it 'should allow an existing predicate to be assigned to a scope' do
250
+ skip 'until Predicate added'
251
+
252
+ test_predicate = Predicate.new 'test_predicate', :test
253
+
254
+ scope = Scope.new :scope
255
+
256
+ scope[:predicate] = test_predicate
257
+
258
+ assert_equal 3, Scope.scopes.size
259
+ assert_equal [:default, :test, :scope], Scope.scopes
260
+
261
+ assert_scope Scope[:default], :default, []
262
+ assert_scope Scope[:test], :test, [test_predicate]
263
+ assert_scope Scope[:scope], :scope, [test_predicate]
264
+ end
265
+
266
+ end
267
+
268
+ end
269
+
270
+ end
@@ -0,0 +1,21 @@
1
+ #
2
+ # test/test_helper.rb - Test Suite Helper for Porolog
3
+ #
4
+ # Luis Esteban 2 May 2018
5
+ # created
6
+ #
7
+
8
+ require 'minitest/autorun'
9
+ require 'porolog'
10
+
11
+ include Porolog
12
+
13
+ def reset
14
+ Scope.reset
15
+ end
16
+
17
+ def assert_scope(scope, name, predicates)
18
+ assert_instance_of Scope, scope
19
+ assert_equal name, scope.name
20
+ assert_equal predicates, scope.predicates
21
+ end
metadata ADDED
@@ -0,0 +1,54 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: porolog
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Luis Esteban
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-05-02 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Implements a Prolog inference engine using Plain Old Ruby Objects
14
+ email:
15
+ - luis.esteban.consulting@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - README.md
21
+ - Rakefile
22
+ - bin/porolog
23
+ - lib/porolog.rb
24
+ - lib/porolog/error.rb
25
+ - lib/porolog/scope.rb
26
+ - test/porolog/scope_test.rb
27
+ - test/test_helper.rb
28
+ homepage: http://rubygems.org/gems/porolog
29
+ licenses:
30
+ - Unlicense
31
+ metadata: {}
32
+ post_install_message:
33
+ rdoc_options: []
34
+ require_paths:
35
+ - lib
36
+ required_ruby_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ requirements: []
47
+ rubyforge_project:
48
+ rubygems_version: 2.7.6
49
+ signing_key:
50
+ specification_version: 4
51
+ summary: Prolog using Plain Old Ruby Objects
52
+ test_files:
53
+ - test/test_helper.rb
54
+ - test/porolog/scope_test.rb