porolog 0.0.1

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.
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