swipl 0.3.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 187e536fbdc31b19a4622f713887c8060eaf4d4f
4
+ data.tar.gz: 674e107ad57b73640a26af99299202b4b3e3a856
5
+ SHA512:
6
+ metadata.gz: 28674680661610bf44248641440c8955d55c575bba0c390cf4594a89d23b431f15bdb3f6da04c39a613d7cf6a07b27e96bbd19545d1663a048fadbb802f373a2
7
+ data.tar.gz: b2d235f8204c8023bd097ef2664213eeeb608406f48f3c49326b31d75f09f25fc123f61e41e5884c7ce0e7970befec0538bee27d2b104da472cd35798dad2b09
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.swo
11
+ *.swp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.3
4
+ before_install: gem install bundler -v 1.11.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in swipl.gemspec
4
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Mark Eschbach
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,84 @@
1
+ # SWIPL
2
+
3
+ A Ruby Gem for binding to SWI Prolog. This uses Ruby's FFI gem for binding.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'swipl'
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ Set `SWI_LIB` to the where you have the libswipl.{dylib,so,dll} file. Unforunately I haven't figured out a better method for locating the library; open to ideas and pull requests to make this easier for client applications.
16
+
17
+ ### Basic Usage
18
+
19
+ You can query if a statement is truthy by passing it as string as follows:
20
+ ```ruby
21
+ SWIPL::verify('true')
22
+ ```
23
+
24
+ This will boot up the engine and run the query the Prolog for the answer. Your programs can be arbitrarily complex, however this will only result in true or false.
25
+
26
+ ### Usage Level 2
27
+
28
+ Let's say you have the following prolog database in foods.pl:
29
+
30
+ ```prolog
31
+ food( beef ).
32
+ food( broccoli ).
33
+ food( potatoes ).
34
+
35
+ enjoys( mark, broccoli ).
36
+ ```
37
+
38
+ You can they load the database (assuming in the same directory) an query for all solutions as follows:
39
+
40
+ ```ruby
41
+ SWIPL::truth( "consult('foods.pl')" )
42
+ foods = SWIPL::query( "food", 1 )
43
+ ```
44
+
45
+ The variable foods should now contain the follow (note: order of the elements may change):
46
+ ```ruby
47
+ [ ["beef"], ["broccoli"], ["potatoes"] ]
48
+ ```
49
+
50
+ ### Advanced Usage
51
+
52
+ This is kind of a gnarly API right now. My goal is to clean it up, but querying with bound variables is
53
+ possible right now.
54
+
55
+ ```ruby
56
+ SWIPL::PrologFrame.on do |frame|
57
+ human = frame.atom_from_string( "mark" )
58
+ predicate = SWIPL::Predicate.find( "enjoys", 2 )
59
+ query = predicate.query_normally_with( frame, [human, nil ] ) # nil will result in an unground variable
60
+ begin
61
+ query.each_solution do |solution|
62
+ puts solution
63
+ end
64
+ ensure
65
+ query.close # if you forget this there will probably be some strange statement about no foreign frame
66
+ end
67
+ end
68
+ ```
69
+
70
+ Resulting output:
71
+ ```ruby
72
+ ["mark", "broccoli"]
73
+ ```
74
+
75
+
76
+ ## Contributing
77
+
78
+ Bug reports and pull requests are welcome on GitHub at https://github.com/meschbach/gem-swipl.
79
+
80
+
81
+ ## License
82
+
83
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
84
+
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "swipl"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,43 @@
1
+ require "swipl/cffi"
2
+ require 'swipl/predicate'
3
+ require 'swipl/prologframe'
4
+ require 'swipl/query'
5
+ require 'swipl/term'
6
+ require "swipl/version"
7
+
8
+ module SWIPL
9
+ PL_TRUE = 1
10
+ PL_FALSE = 2
11
+ PL_FAIL = PL_FALSE
12
+ PL_Q_NORMAL = 2
13
+
14
+ def self.verify( fact )
15
+ CFFI.init
16
+ PrologFrame.on do |frame|
17
+ atom = frame.atom_from_string( fact )
18
+ CFFI.PL_call( atom.term_id, nil ) == PL_TRUE
19
+ end
20
+ end
21
+
22
+ def self.query( predicateName, arity )
23
+ solutions = []
24
+ PrologFrame.on do |frame|
25
+ predicate = Predicate.find( predicateName, arity)
26
+ query = predicate.query_normally( frame )
27
+ begin
28
+ query.each_solution do |p|
29
+ solutions.push([ p[0].as_atom ] )
30
+ end
31
+ ensure
32
+ query.close
33
+ end
34
+ end
35
+ solutions
36
+ end
37
+
38
+ def self.truth( fact )
39
+ unless self.verify( fact )
40
+ raise "Truth '#{fact}' failed"
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,46 @@
1
+ require 'ffi'
2
+
3
+ module SWIPL
4
+ module CFFI
5
+ extend FFI::Library
6
+ ffi_lib ENV["SWI_LIB"]
7
+
8
+ attach_function :PL_open_foreign_frame, [], :ulong
9
+ attach_function :PL_discard_foreign_frame, [:ulong], :int
10
+
11
+ # Warning: the following method repeatedly failed
12
+ attach_function :PL_atom_chars, [:ulong], :pointer
13
+
14
+ attach_function :PL_call, [:ulong, :pointer], :int
15
+ attach_function :PL_chars_to_term, [:pointer, :ulong], :int
16
+ attach_function :PL_close_query, [:ulong], :void
17
+ attach_function :PL_get_atom_chars, [:ulong, :pointer], :int
18
+ attach_function :PL_initialise, [:int, :pointer], :int
19
+ attach_function :PL_is_atom, [:ulong], :int
20
+ attach_function :PL_is_ground, [:ulong], :int
21
+ attach_function :PL_new_atom, [:pointer], :ulong
22
+ attach_function :PL_new_term_ref, [], :ulong
23
+ attach_function :PL_new_term_refs, [:int], :ulong
24
+ attach_function :PL_next_solution, [:ulong], :int
25
+ attach_function :PL_open_query, [:pointer, :int, :ulong, :ulong], :ulong
26
+ attach_function :PL_predicate, [:pointer, :int, :pointer], :ulong
27
+ attach_function :PL_thread_self, [], :int
28
+ attach_function :PL_unify, [ :ulong, :ulong ], :int
29
+
30
+ @is_initialized = false
31
+ def self.init
32
+ return if @is_initialized
33
+
34
+ libptr = ::FFI::MemoryPointer.from_string( ENV["SWI_LIB"] )
35
+ plargv = ::FFI::MemoryPointer.new( :pointer, 1 )
36
+ plargv.write_pointer( libptr )
37
+
38
+ value = PL_initialise( 1, plargv )
39
+ if value != 1
40
+ raise "SWI failed to initialize"
41
+ end
42
+
43
+ @is_initialized = true
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,37 @@
1
+
2
+ module SWIPL
3
+ class Predicate
4
+ def initialize( id, arity )
5
+ @pred_id = id
6
+ @arity = arity
7
+ end
8
+
9
+ def self.find( name, arity )
10
+ name_ptr = FFI::MemoryPointer.from_string( name.to_s )
11
+ id = CFFI.PL_predicate( name_ptr, arity, nil )
12
+ Predicate.new( id, arity )
13
+ end
14
+
15
+ # @param frame the frame to allocate the parameters
16
+ def query_normally( frame )
17
+ params = frame.refs( @arity )
18
+ query_id = CFFI.PL_open_query( nil, PL_Q_NORMAL, @pred_id, params[0].id )
19
+ Query.new( query_id, params )
20
+ end
21
+
22
+ def query_normally_with( frame, inputs )
23
+ raise "insufficent parameters for arity" if inputs.length != @arity
24
+
25
+ params = frame.refs( @arity )
26
+ (0..(@arity-1)).each do |index|
27
+ source = inputs[ index ]
28
+ if source
29
+ params[index].unify_with( inputs[index] )
30
+ end
31
+ end
32
+
33
+ query_id = CFFI.PL_open_query( nil, PL_Q_NORMAL, @pred_id, params[0].id )
34
+ Query.new( query_id, params )
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,55 @@
1
+
2
+ module SWIPL
3
+ class PrologFrame
4
+ def initialize( frame_id )
5
+ @frame_id = frame_id
6
+ end
7
+
8
+ def close
9
+ result = CFFI.PL_discard_foreign_frame( @frame_id )
10
+ if result == PL_FALSE
11
+ raise "Failed to close frame"
12
+ end
13
+ end
14
+
15
+ # Opens a foreign frame
16
+ def self.open
17
+ frame_id = CFFI.PL_open_foreign_frame
18
+ if frame_id == PL_FALSE
19
+ raise "failed to open frame"
20
+ end
21
+ PrologFrame.new( frame_id )
22
+ end
23
+
24
+ def self.on( &block )
25
+ frame = self.open
26
+ begin
27
+ block.call( frame )
28
+ ensure
29
+ frame.close
30
+ end
31
+ end
32
+
33
+ # allocates teh number of terms and returns an array of those terms
34
+ #
35
+ # NOTE: SWI requires continous terms from time to time (ie: PL_open_query)
36
+ def refs( count )
37
+ return [] if count == 0
38
+
39
+ base = CFFI.PL_new_term_refs( count )
40
+ #TODO: Verify the result of the query
41
+ (0..(count-1)).map do |index|
42
+ Term.new( base + index )
43
+ end
44
+ end
45
+
46
+ def atom_from_string( string )
47
+ atom_ptr = FFI::MemoryPointer.from_string( string.to_s )
48
+ atom_term = CFFI.PL_new_term_ref
49
+ if CFFI.PL_chars_to_term( atom_ptr, atom_term ) == 0
50
+ raise "failed to create atom from terms"
51
+ end
52
+ Term.new( atom_term )
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,23 @@
1
+
2
+ module SWIPL
3
+ class Query
4
+ def initialize( qid, terms )
5
+ @query_id = qid
6
+ @terms = terms
7
+ end
8
+
9
+ def next_solution?
10
+ CFFI.PL_next_solution( @query_id ) == PL_TRUE
11
+ end
12
+
13
+ def each_solution
14
+ while next_solution?
15
+ yield(@terms)
16
+ end
17
+ end
18
+
19
+ def close
20
+ CFFI.PL_close_query( @query_id )
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,43 @@
1
+
2
+ module SWIPL
3
+ class Term
4
+ def initialize( term_id )
5
+ @term_id = term_id
6
+ end
7
+
8
+ def id; self.term_id; end
9
+ def term_id; @term_id; end
10
+
11
+ def unify_with( other_term )
12
+ CFFI.PL_unify( @term_id, other_term.term_id ) != PL_FAIL
13
+ end
14
+
15
+ def ground?
16
+ CFFI.PL_is_ground( @term_id ) == PL_TRUE
17
+ end
18
+
19
+ def atom?
20
+ CFFI.PL_is_atom( @term_id )
21
+ end
22
+
23
+ def as_atom
24
+ str_ptr = FFI::MemoryPointer.new( :pointer, 1 )
25
+ if CFFI.PL_get_atom_chars( @term_id, str_ptr ) == PL_FALSE
26
+ raise "failed to get term #{@term_id} as an atom"
27
+ end
28
+ str_ptr.read_pointer.read_string
29
+ end
30
+
31
+ def to_s
32
+ if self.ground?
33
+ if self.atom?
34
+ self.as_atom
35
+ else
36
+ "ground"
37
+ end
38
+ else
39
+ "variable"
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,3 @@
1
+ module SWIPL
2
+ VERSION = "0.3.0"
3
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'swipl/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "swipl"
8
+ spec.version = SWIPL::VERSION
9
+ spec.authors = ["Mark Eschbach"]
10
+ spec.email = ["meschbach@gmail.com"]
11
+
12
+ spec.summary = %q{Ruby bindings for SWI Prolog}
13
+ spec.description = %q{Interact with the SWI Prolog system in ruby. Currently uses FFI to bind using the C interface.}
14
+ spec.homepage = "https://github.com/meschbach/gem-swipl"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.11"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "rspec", "~> 3.0"
25
+ spec.add_dependency "ffi", "~> 1.9"
26
+ end
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: swipl
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Mark Eschbach
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-02-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.11'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.11'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: ffi
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.9'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.9'
69
+ description: Interact with the SWI Prolog system in ruby. Currently uses FFI to bind
70
+ using the C interface.
71
+ email:
72
+ - meschbach@gmail.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - ".rspec"
79
+ - ".travis.yml"
80
+ - Gemfile
81
+ - LICENSE.txt
82
+ - README.md
83
+ - Rakefile
84
+ - bin/console
85
+ - lib/swipl.rb
86
+ - lib/swipl/cffi.rb
87
+ - lib/swipl/predicate.rb
88
+ - lib/swipl/prologframe.rb
89
+ - lib/swipl/query.rb
90
+ - lib/swipl/term.rb
91
+ - lib/swipl/version.rb
92
+ - swipl.gemspec
93
+ homepage: https://github.com/meschbach/gem-swipl
94
+ licenses:
95
+ - MIT
96
+ metadata: {}
97
+ post_install_message:
98
+ rdoc_options: []
99
+ require_paths:
100
+ - lib
101
+ required_ruby_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ requirements: []
112
+ rubyforge_project:
113
+ rubygems_version: 2.4.5.1
114
+ signing_key:
115
+ specification_version: 4
116
+ summary: Ruby bindings for SWI Prolog
117
+ test_files: []