upl 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 +7 -0
- data/.gitignore +15 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +174 -0
- data/Rakefile +6 -0
- data/bin/console +7 -0
- data/bin/setup +8 -0
- data/lib/upl.rb +22 -0
- data/lib/upl/atom.rb +36 -0
- data/lib/upl/extern.rb +242 -0
- data/lib/upl/functor.rb +51 -0
- data/lib/upl/inter.rb +48 -0
- data/lib/upl/runtime.rb +183 -0
- data/lib/upl/term.rb +132 -0
- data/lib/upl/tree.rb +119 -0
- data/lib/upl/variable.rb +61 -0
- data/lib/upl/version.rb +3 -0
- data/upl.gemspec +38 -0
- metadata +135 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d7e5de431546d46c5f4998818e1360e10e5dab6497e0988e783e626393b35cc7
|
4
|
+
data.tar.gz: 4cd776d265de52483fe89e239041ee001ba21856c339fb067b661437c6cbfd25
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a8ab63130414fe7d8ae8775c4a0f11199c76024952541b2820deb8e607361ca26cb07bcec64fc2a2df8e5790a5042caaa1598a0705d0271d6ac4dca4cd9497de
|
7
|
+
data.tar.gz: 58d09609f7888ee559bb626219f7be13576016aa40dff3992c12413a84d3113841e748f7896aa93d204dcceb2b5f0e40528e828bc30b930e7743ccb134efaf1f
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2018 John Anderson
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,174 @@
|
|
1
|
+
# Upl
|
2
|
+
|
3
|
+
A ruby wrapper for SWI-Prolog that goes both ways.
|
4
|
+
|
5
|
+
The main idea being that you want to just put in a chunk of prolog, and have the
|
6
|
+
wrapper give you back your answers in terms of the variable names you specified.
|
7
|
+
Just like what happens in your common-or-garden prolog REPL. But with pry's
|
8
|
+
syntax colouring and pretty-printing :-)
|
9
|
+
|
10
|
+
Also, do prolog-style queries on objects :-DD
|
11
|
+
|
12
|
+
## Tutorial
|
13
|
+
|
14
|
+
### Queries
|
15
|
+
|
16
|
+
To read rules from a prolog file:
|
17
|
+
``` ruby
|
18
|
+
[1] pry(main)> Upl.consult '/home/yours/funky_data.pl'
|
19
|
+
=> true
|
20
|
+
```
|
21
|
+
|
22
|
+
Query a built-in predicate, with a full expression:
|
23
|
+
``` ruby
|
24
|
+
[1] pry(main)> enum = Upl.query 'current_prolog_flag(K,V), member(K,[home,executable,shared_object_extension])'
|
25
|
+
=> #<Enumerator: ...>
|
26
|
+
[12] pry(main) enum.to_a
|
27
|
+
=> [{:K=>home, :V=>/usr/lib64/swipl-7.7.18},
|
28
|
+
{:K=>executable, :V=>/usr/local/rvm/rubies/ruby-2.6.0-preview2/bin/ruby},
|
29
|
+
{:K=>shared_object_extension, :V=>so}]
|
30
|
+
```
|
31
|
+
|
32
|
+
### Facts
|
33
|
+
Also we want to be able to construct prolog-queryable facts from ruby objects.
|
34
|
+
In prolog:
|
35
|
+
|
36
|
+
``` prolog
|
37
|
+
?- assert(person(john,anderson)).
|
38
|
+
true.
|
39
|
+
|
40
|
+
?- person(A,B).
|
41
|
+
A = john,
|
42
|
+
B = anderson.
|
43
|
+
|
44
|
+
?- retract(person(john,anderson)).
|
45
|
+
true.
|
46
|
+
|
47
|
+
?- person(A,B).
|
48
|
+
false.
|
49
|
+
```
|
50
|
+
|
51
|
+
And in Upl:
|
52
|
+
|
53
|
+
``` ruby
|
54
|
+
[1] pry(Upl):1> fact = Term.functor :person, :john, :anderson
|
55
|
+
=> person/2(john,anderson)
|
56
|
+
[2] pry(Upl):1> Runtime.eval Term.functor :assert, fact
|
57
|
+
=> true
|
58
|
+
[3] pry(Upl):1> Array query 'person(A,B)'
|
59
|
+
=> [{:A=>john, :B=>anderson}]
|
60
|
+
[4] pry(Upl):1> Runtime.eval Term.functor :retract, fact
|
61
|
+
=> true
|
62
|
+
[5] pry(Upl):1> Array query 'person(A,B)'
|
63
|
+
=> []
|
64
|
+
```
|
65
|
+
|
66
|
+
### Objective Facts
|
67
|
+
|
68
|
+
Also, with objects other than symbols. Obviously, this is a rabbit-hole of
|
69
|
+
Alician proportions. So, here we GOOOoooo...
|
70
|
+
|
71
|
+
``` ruby
|
72
|
+
[1] pry(Upl):1> fact = Term.functor :person, :john, :anderson, (o = Object.new)
|
73
|
+
=> person/3(john,anderson,#<Object:0x0000563346a08e38 @_upl_atom=439429>)
|
74
|
+
[2] pry(Upl):1> Runtime.eval Term.functor :assert, fact
|
75
|
+
=> true
|
76
|
+
[3] pry(Upl):1> ha, = Array query 'person(A,B,C)'
|
77
|
+
=> [{:A=>john,
|
78
|
+
:B=>anderson,
|
79
|
+
:C=>#<Object:0x0000563346a08e38 @_upl_atom=439429>}]
|
80
|
+
[4] pry(Upl):1> ha[:C].equal? o
|
81
|
+
=> true
|
82
|
+
```
|
83
|
+
|
84
|
+
Woo. An object disappears into prolog, and comes back out again. Having gained
|
85
|
+
much wisdom. Hurhur. And at least one extra instance variable.
|
86
|
+
|
87
|
+
And now, the pièce de résistance - using an object as an input term:
|
88
|
+
|
89
|
+
``` ruby
|
90
|
+
fact = Upl::Term.functor :person, :james, :madison, (o = Object.new)
|
91
|
+
Upl::Runtime.eval Upl::Term.functor :assert, fact
|
92
|
+
|
93
|
+
fact2 = Upl::Term.functor :person, :thomas, :paine, (thing2 = Object.new)
|
94
|
+
Upl::Runtime.eval Upl::Term.functor :assert, fact2
|
95
|
+
|
96
|
+
# Note that both facts are in the result
|
97
|
+
query_term, query_vars = Upl::Runtime.term_vars 'person(A,B,C)'
|
98
|
+
Array Upl::Runtime.term_vars_query query_term, query_vars
|
99
|
+
=>[{:A=>james, :B=>madison, :C=>#<Object:0x0000563f56e35580 @_upl_atom=439429>},
|
100
|
+
{:A=>thomas, :B=>paine, :C=>#<Object:0x0000563f56d2b5b8 @_upl_atom=439813>}]
|
101
|
+
|
102
|
+
# Unify C with thing2. This needs a nicer api :-\
|
103
|
+
query_term, query_vars = Upl::Runtime.term_vars 'person(A,B,C)'
|
104
|
+
Upl::Extern.PL_unify query_vars.last.args.to_a.last, thing2.to_term
|
105
|
+
|
106
|
+
# ... and we get the correct result
|
107
|
+
# Note that the first fact is not in the result.
|
108
|
+
Array Upl::Runtime.term_vars_query query_term, query_vars
|
109
|
+
=> [{:A=>thomas, :B=>paine, :C=>#<Object:0x0000563f56d2b5b8 @_upl_atom=439813>}]
|
110
|
+
```
|
111
|
+
|
112
|
+
## Disclaimer
|
113
|
+
|
114
|
+
This is in-development code. I use it for some things other than just playing with. It might be useful for you too.
|
115
|
+
|
116
|
+
ruby has a GC. swipl has a GC. At some point they will disagree. I haven't reached that point yet.
|
117
|
+
|
118
|
+
## Naming
|
119
|
+
|
120
|
+
```Upl```? Wat!? Why?
|
121
|
+
|
122
|
+
Well. ```swipl``` was taken. ```ripl``` was taken. So maybe in keeping with long tradition: ```rupl```.
|
123
|
+
|
124
|
+
But that leads to ```pry -I. -rrupl``` which is Not Fun.
|
125
|
+
|
126
|
+
But ```upl``` gives you ```pry -I. -rupl``` So it's kinda like ```ubygems```.
|
127
|
+
|
128
|
+
Also, ```Upl``` rhymes with tuple and/or supple. Depending on your pronunciation :-p
|
129
|
+
|
130
|
+
## Installation
|
131
|
+
|
132
|
+
Add this line to your application's Gemfile:
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
gem 'upl'
|
136
|
+
```
|
137
|
+
|
138
|
+
And then execute:
|
139
|
+
|
140
|
+
$ bundle
|
141
|
+
|
142
|
+
Or install it yourself as:
|
143
|
+
|
144
|
+
$ gem install upl
|
145
|
+
|
146
|
+
## Usage
|
147
|
+
|
148
|
+
For a REPL say
|
149
|
+
|
150
|
+
pry -rupl
|
151
|
+
|
152
|
+
or
|
153
|
+
|
154
|
+
bin/console
|
155
|
+
|
156
|
+
|
157
|
+
## Development
|
158
|
+
|
159
|
+
Install SWI-Prolog with both the swipl executable and libswipl.so
|
160
|
+
|
161
|
+
|
162
|
+
And bundler wants me to tell you the following:
|
163
|
+
|
164
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
165
|
+
|
166
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
167
|
+
|
168
|
+
## Contributing
|
169
|
+
|
170
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/djellemah/upl.
|
171
|
+
|
172
|
+
## License
|
173
|
+
|
174
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
data/bin/setup
ADDED
data/lib/upl.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require_relative 'upl/version'
|
3
|
+
|
4
|
+
require_relative 'upl/extern'
|
5
|
+
require_relative 'upl/term'
|
6
|
+
require_relative 'upl/variable'
|
7
|
+
require_relative 'upl/atom'
|
8
|
+
require_relative 'upl/runtime'
|
9
|
+
require_relative 'upl/tree'
|
10
|
+
require_relative 'upl/inter'
|
11
|
+
|
12
|
+
module Upl
|
13
|
+
def self.query st, &blk
|
14
|
+
term, vars = Runtime::term_vars st
|
15
|
+
Runtime::term_vars_query term, vars, &blk
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.consult filename
|
19
|
+
p = Pathname filename
|
20
|
+
Runtime::eval %Q{["#{p.realpath.to_s}"]}
|
21
|
+
end
|
22
|
+
end
|
data/lib/upl/atom.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
module Upl
|
2
|
+
class Atom
|
3
|
+
def initialize( atom_ref )
|
4
|
+
@atom_ptr = atom_ref.ptr
|
5
|
+
atom_chars_ptr = ::Upl::Extern::PL_atom_chars @atom_ptr
|
6
|
+
@_symbol = atom_chars_ptr.to_s.to_sym
|
7
|
+
end
|
8
|
+
|
9
|
+
# drop the term immediately, and just keep the atom pointer
|
10
|
+
def self.of_term( term_t )
|
11
|
+
rv = Extern::PL_get_atom term_t, (atom_ref = Fiddle::Pointer.new(0).ref)
|
12
|
+
raise "can't get atom from term" unless rv == 1
|
13
|
+
new atom_ref
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :atom_ptr
|
17
|
+
|
18
|
+
def == rhs
|
19
|
+
to_sym == rhs.to_sym
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_sym
|
23
|
+
@_symbol or raise "no symbol for atom"
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s
|
27
|
+
@_string ||= to_sym.to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
def inspect; to_sym end
|
31
|
+
|
32
|
+
def pretty_print pp
|
33
|
+
pp.text to_s
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/upl/extern.rb
ADDED
@@ -0,0 +1,242 @@
|
|
1
|
+
require 'fiddle'
|
2
|
+
require 'fiddle/import'
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
module Upl
|
6
|
+
module Extern
|
7
|
+
extend Fiddle::Importer
|
8
|
+
|
9
|
+
# use swipl config to find the .so file
|
10
|
+
def self.so_path
|
11
|
+
begin
|
12
|
+
swipl_exe = 'swipl'
|
13
|
+
values = `#{swipl_exe} --dump-runtime-variables=sh`.each_line.with_object Hash.new do |line,ha|
|
14
|
+
line.chomp!
|
15
|
+
# split by = and for rhs strip surrounding quotes and trailing ;
|
16
|
+
line =~ /^([^=]+)="([^"]*)";$/
|
17
|
+
ha[$1] = $2.strip
|
18
|
+
end
|
19
|
+
rescue Errno::ENOENT => ex
|
20
|
+
puts "#{swipl_exe} not found on path #{ENV['PATH']}"
|
21
|
+
exit 1
|
22
|
+
end
|
23
|
+
|
24
|
+
begin
|
25
|
+
# should result in something like
|
26
|
+
# /usr/lib64/swipl-7.7.18/lib/x86_64-linux/libswipl.so
|
27
|
+
# which should actually exist
|
28
|
+
p = Pathname "#{values['PLBASE']}/lib/#{values['PLARCH']}/#{values['PLLIB'].gsub('-l', 'lib')}.#{values['PLSOEXT']}"
|
29
|
+
p.realpath.to_s
|
30
|
+
rescue Errno::ENOENT => ex
|
31
|
+
puts "problem with library #{p.to_s}: #{ex.message}"
|
32
|
+
exit 1
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
dlload so_path
|
37
|
+
|
38
|
+
NULL = Fiddle::Pointer.new 0
|
39
|
+
|
40
|
+
typealias 'term_t', 'void *'
|
41
|
+
typealias 'module_t', 'void *'
|
42
|
+
typealias 'predicate_t', 'void *'
|
43
|
+
typealias 'atom_t', 'void *'
|
44
|
+
typealias 'qid_t', 'void *'
|
45
|
+
typealias 'fid_t', 'uintptr_t'
|
46
|
+
typealias 'functor_t', 'void *'
|
47
|
+
|
48
|
+
extern 'int PL_initialise(int argc, char **argv)'
|
49
|
+
extern 'int PL_halt(int status)'
|
50
|
+
|
51
|
+
# for constructing types see https://stackoverflow.com/questions/30293406/is-it-possible-to-use-fiddle-to-pass-or-return-a-struct-to-native-code
|
52
|
+
# Predicate_t = struct ['char *data','char *more_data','size_t len']
|
53
|
+
|
54
|
+
# terms
|
55
|
+
extern 'predicate_t PL_predicate(const char *name, int arity, const char *module)'
|
56
|
+
|
57
|
+
##############
|
58
|
+
# querying and getting results
|
59
|
+
|
60
|
+
# copied from SWI-Prolog.h
|
61
|
+
module Flags
|
62
|
+
PL_Q_DEBUG = 0x0001 # = TRUE for backward compatibility
|
63
|
+
PL_Q_NORMAL = 0x0002 # normal usage
|
64
|
+
PL_Q_NODEBUG = 0x0004 # use this one
|
65
|
+
PL_Q_CATCH_EXCEPTION = 0x0008 # handle exceptions in C
|
66
|
+
PL_Q_PASS_EXCEPTION = 0x0010 # pass to parent environment
|
67
|
+
PL_Q_ALLOW_YIELD = 0x0020 # Support I_YIELD
|
68
|
+
PL_Q_EXT_STATUS = 0x0040 # Return extended status
|
69
|
+
PL_Q_DETERMINISTIC = 0x0100 # call was deterministic
|
70
|
+
end
|
71
|
+
|
72
|
+
extern 'fid_t PL_open_foreign_frame(void)'
|
73
|
+
extern 'void PL_rewind_foreign_frame(fid_t cid)'
|
74
|
+
extern 'void PL_close_foreign_frame(fid_t cid)'
|
75
|
+
extern 'void PL_discard_foreign_frame(fid_t cid)'
|
76
|
+
|
77
|
+
PL_VARIABLE = (1)
|
78
|
+
PL_ATOM = (2)
|
79
|
+
PL_INTEGER = (3)
|
80
|
+
PL_FLOAT = (4)
|
81
|
+
PL_STRING = (5)
|
82
|
+
PL_TERM = (6)
|
83
|
+
PL_NIL = (7)
|
84
|
+
PL_BLOB = (8)
|
85
|
+
PL_LIST_PAIR = (9)
|
86
|
+
PL_FUNCTOR = (10)
|
87
|
+
PL_LIST = (11)
|
88
|
+
PL_CHARS = (12)
|
89
|
+
PL_POINTER = (13)
|
90
|
+
PL_CODE_LIST = (14)
|
91
|
+
PL_CHAR_LIST = (15)
|
92
|
+
PL_BOOL = (16)
|
93
|
+
PL_FUNCTOR_CHARS = (17)
|
94
|
+
_PL_PREDICATE_INDICATOR = (18)
|
95
|
+
PL_SHORT = (19)
|
96
|
+
PL_INT = (20)
|
97
|
+
PL_LONG = (21)
|
98
|
+
PL_DOUBLE = (22)
|
99
|
+
PL_NCHARS = (23)
|
100
|
+
PL_UTF8_CHARS = (24)
|
101
|
+
PL_UTF8_STRING = (25)
|
102
|
+
PL_INT64 = (26)
|
103
|
+
PL_NUTF8_CHARS = (27)
|
104
|
+
PL_NUTF8_CODES = (29)
|
105
|
+
PL_NUTF8_STRING = (30)
|
106
|
+
PL_NWCHARS = (31)
|
107
|
+
PL_NWCODES = (32)
|
108
|
+
PL_NWSTRING = (33)
|
109
|
+
PL_MBCHARS = (34)
|
110
|
+
PL_MBCODES = (35)
|
111
|
+
PL_MBSTRING = (36)
|
112
|
+
PL_INTPTR = (37)
|
113
|
+
PL_CHAR = (38)
|
114
|
+
PL_CODE = (39)
|
115
|
+
PL_BYTE = (40)
|
116
|
+
PL_PARTIAL_LIST = (41)
|
117
|
+
PL_CYCLIC_TERM = (42)
|
118
|
+
PL_NOT_A_LIST = (43)
|
119
|
+
PL_DICT = (44)
|
120
|
+
|
121
|
+
module Convert
|
122
|
+
REP_UTF8 = 0x1000
|
123
|
+
BUF_MALLOC = 0x0200
|
124
|
+
|
125
|
+
CVT_ATOM = 0x0001
|
126
|
+
CVT_STRING = 0x0002
|
127
|
+
CVT_LIST = 0x0004
|
128
|
+
CVT_INTEGER = 0x0008
|
129
|
+
CVT_FLOAT = 0x0010
|
130
|
+
CVT_VARIABLE = 0x0020
|
131
|
+
CVT_NUMBER = CVT_INTEGER|CVT_FLOAT
|
132
|
+
CVT_ATOMIC = CVT_NUMBER|CVT_ATOM|CVT_STRING
|
133
|
+
CVT_WRITE = 0x0040
|
134
|
+
CVT_WRITE_CANONICAL = 0x0080
|
135
|
+
CVT_WRITEQ = 0x00C0
|
136
|
+
CVT_ALL = CVT_ATOMIC|CVT_LIST
|
137
|
+
end
|
138
|
+
|
139
|
+
extern 'predicate_t PL_pred(functor_t f, module_t m)'
|
140
|
+
|
141
|
+
# ctx can be NULL, p is the predicate with arity, t0 is the collection of terms to be filled by the query
|
142
|
+
extern 'qid_t PL_open_query(module_t ctx, int pl_q_flags, predicate_t p, term_t t0)'
|
143
|
+
extern 'int PL_next_solution(qid_t qid)'
|
144
|
+
extern 'void PL_cut_query(qid_t qid)'
|
145
|
+
extern 'void PL_close_query(qid_t qid)'
|
146
|
+
|
147
|
+
# shortcut when there will only be one answer
|
148
|
+
extern 'int PL_call_predicate(module_t m, int pl_q_flags, predicate_t p, term_t t0)'
|
149
|
+
|
150
|
+
extern 'term_t PL_exception(qid_t qid)'
|
151
|
+
extern 'int PL_raise_exception(term_t exception)'
|
152
|
+
extern 'int PL_throw(term_t exception)'
|
153
|
+
extern 'void PL_clear_exception(void)'
|
154
|
+
|
155
|
+
# qid_t PL_current_query(void)
|
156
|
+
|
157
|
+
# module can be NULL
|
158
|
+
extern 'int PL_call(term_t t, module_t m)'
|
159
|
+
|
160
|
+
####################
|
161
|
+
# storage of db records. Not sure if it's useful
|
162
|
+
# http://www.swi-prolog.org/pldoc/man?section=foreign-misc
|
163
|
+
# void PL_erase(record_t record)
|
164
|
+
# record_t PL_record(term_t +t)
|
165
|
+
|
166
|
+
# create empty term(s)
|
167
|
+
extern 'term_t PL_new_term_ref()'
|
168
|
+
extern 'term_t PL_new_term_refs(int n)'
|
169
|
+
extern 'term_t PL_copy_term_ref(term_t from)'
|
170
|
+
|
171
|
+
extern 'int PL_new_atom(const char *s)'
|
172
|
+
extern 'int PL_new_atom_nchars(size_t len, const char *s)'
|
173
|
+
extern 'functor_t PL_new_functor(atom_t f, int a)'
|
174
|
+
|
175
|
+
extern 'const char * PL_atom_chars(atom_t a)'
|
176
|
+
extern 'int PL_get_atom(term_t t, atom_t * a)'
|
177
|
+
|
178
|
+
# get the type
|
179
|
+
extern 'int PL_term_type(term_t t)'
|
180
|
+
extern 'int PL_is_variable(term_t t)'
|
181
|
+
extern 'int PL_is_ground(term_t t)'
|
182
|
+
extern 'int PL_is_atom(term_t t)'
|
183
|
+
extern 'int PL_is_integer(term_t t)'
|
184
|
+
extern 'int PL_is_string(term_t t)'
|
185
|
+
extern 'int PL_is_float(term_t t)'
|
186
|
+
extern 'int PL_is_rational(term_t t)'
|
187
|
+
extern 'int PL_is_compound(term_t t)'
|
188
|
+
extern 'int PL_is_callable(term_t t)'
|
189
|
+
extern 'int PL_is_functor(term_t t, functor_t f)'
|
190
|
+
extern 'int PL_is_list(term_t t)'
|
191
|
+
extern 'int PL_is_pair(term_t t)'
|
192
|
+
extern 'int PL_is_atomic(term_t t)'
|
193
|
+
extern 'int PL_is_number(term_t t)'
|
194
|
+
extern 'int PL_is_acyclic(term_t t)'
|
195
|
+
|
196
|
+
extern 'int PL_put_atom(term_t t, atom_t a)'
|
197
|
+
extern 'int PL_put_variable(term_t t)'
|
198
|
+
extern 'int PL_put_functor(term_t t, functor_t functor)'
|
199
|
+
|
200
|
+
extern 'int PL_cons_functor_v(term_t h, functor_t fd, term_t a0)'
|
201
|
+
|
202
|
+
extern 'int PL_get_atom_chars(term_t t, char **a)'
|
203
|
+
extern 'int PL_get_string(term_t t, char **s, size_t *len)'
|
204
|
+
extern 'int PL_get_integer(term_t t, int *i)'
|
205
|
+
extern 'int PL_get_chars(term_t t, char **s, unsigned int flags)'
|
206
|
+
extern 'int PL_get_name_arity(term_t t, atom_t *name, int *arity)'
|
207
|
+
extern 'int PL_get_arg(int index, term_t t, term_t a)'
|
208
|
+
extern "int PL_get_blob(term_t t, void **blob, size_t *len, PL_blob_t **type)"
|
209
|
+
|
210
|
+
extern 'int PL_get_functor(term_t t, functor_t *f)'
|
211
|
+
|
212
|
+
extern 'int PL_get_nil(term_t l)'
|
213
|
+
extern 'int PL_get_list(term_t l, term_t h, term_t t)'
|
214
|
+
extern 'int PL_get_head(term_t l, term_t h)'
|
215
|
+
extern 'int PL_get_tail(term_t l, term_t t)'
|
216
|
+
|
217
|
+
extern 'int PL_unify(term_t t1, term_t t2)'
|
218
|
+
|
219
|
+
extern 'int PL_skip_list(term_t list, term_t tail, size_t *len)'
|
220
|
+
|
221
|
+
##################
|
222
|
+
# attributed variables. Don't do what I thought they did.
|
223
|
+
extern 'int PL_is_attvar(term_t t)'
|
224
|
+
extern 'int PL_get_attr(term_t v, term_t a)'
|
225
|
+
|
226
|
+
##################
|
227
|
+
# memory
|
228
|
+
extern 'void PL_free(void *mem)'
|
229
|
+
|
230
|
+
####################
|
231
|
+
# looks like parsing of terms
|
232
|
+
# only >= 7.6.0
|
233
|
+
# get version, eval current_prolog_flag(version_data,swi(M,I,P,E)). Major, mInor, Patch, Extra[]
|
234
|
+
# PL_EXPORT(int) PL_put_term_from_chars(term_t t, int flags, size_t len, const char *s);
|
235
|
+
# extern 'int PL_put_term_from_chars(term_t t, int flags, size_t len, const char *s)'
|
236
|
+
|
237
|
+
extern 'int PL_chars_to_term(const char *chars, term_t term)'
|
238
|
+
extern 'int PL_wchars_to_term(const pl_wchar_t *chars, term_t term)'
|
239
|
+
|
240
|
+
extern 'void PL_unregister_atom(atom_t a)'
|
241
|
+
end
|
242
|
+
end
|
data/lib/upl/functor.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
module Upl
|
2
|
+
# Just an idea, not used yet.
|
3
|
+
class Functor
|
4
|
+
def initialize( atom, args_or_arity )
|
5
|
+
@atom = atom
|
6
|
+
|
7
|
+
case args_or_arity
|
8
|
+
when Array
|
9
|
+
@args = args_or_arity
|
10
|
+
@arity = args.size
|
11
|
+
when Integer
|
12
|
+
@arity = args_or_arity
|
13
|
+
else
|
14
|
+
"dunno bout #{args_or_arity.inspect}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :atom
|
19
|
+
def args; @args || [] end
|
20
|
+
def arity; @arity || args.size end
|
21
|
+
|
22
|
+
# create a functor_t pointer
|
23
|
+
def functor_t
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
|
27
|
+
# create a predicate_t
|
28
|
+
def predicate_t
|
29
|
+
raise NotImplementedError
|
30
|
+
end
|
31
|
+
|
32
|
+
def pretty_print(pp)
|
33
|
+
unless atom.to_sym == :','
|
34
|
+
pp.text atom.to_s
|
35
|
+
if arity > 0
|
36
|
+
pp.text ?/
|
37
|
+
pp.text arity.to_s
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
if arity > 0
|
42
|
+
pp.group 1, ?(, ?) do
|
43
|
+
args.each_with_index do |ruby_term,i|
|
44
|
+
ruby_term.pretty_print pp
|
45
|
+
pp.text ?, if i < arity - 1
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/upl/inter.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# TODO not used
|
2
|
+
class Object
|
3
|
+
def to_atom
|
4
|
+
if frozen?
|
5
|
+
# TODO must check instance variable here
|
6
|
+
_upl_atomize
|
7
|
+
else
|
8
|
+
@_upl_atom ||= _upl_atomize
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_term
|
13
|
+
if frozen?
|
14
|
+
# TODO must check instance variable here
|
15
|
+
_upl_termize
|
16
|
+
else
|
17
|
+
# @_upl_termize ||= _upl_termize
|
18
|
+
_upl_termize
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
|
24
|
+
def _upl_termize
|
25
|
+
term_t = Upl::Extern.PL_new_term_ref
|
26
|
+
rv = Upl::Extern.PL_put_atom term_t, to_atom
|
27
|
+
rv == 1 or raise "can't create atom from #{self}"
|
28
|
+
term_t
|
29
|
+
end
|
30
|
+
|
31
|
+
def _upl_atomize
|
32
|
+
# see also PL_agc_hook for hooking into the swipl GC
|
33
|
+
ObjectSpace.define_finalizer self do |this_obj|
|
34
|
+
# TODO PL_unregister_atom? Finalizer?
|
35
|
+
Upl::Extern.PL_unregister_atom this_obj.instance_variable_get :@_upl_atom
|
36
|
+
end
|
37
|
+
Upl::Extern.PL_new_atom "ruby-#{object_id.to_s}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Symbol
|
42
|
+
def to_atom
|
43
|
+
Upl::Extern.PL_new_atom to_s
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
module Inter
|
48
|
+
end
|
data/lib/upl/runtime.rb
ADDED
@@ -0,0 +1,183 @@
|
|
1
|
+
require 'fiddle'
|
2
|
+
|
3
|
+
require_relative 'extern'
|
4
|
+
|
5
|
+
class Fiddle::Pointer
|
6
|
+
def term_type
|
7
|
+
::Upl::Extern.PL_term_type self
|
8
|
+
end
|
9
|
+
|
10
|
+
def type_string
|
11
|
+
type_int = term_type
|
12
|
+
::Upl::Extern.constants.find{|c| (::Upl::Extern.const_get c) == type_int}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module Upl
|
17
|
+
module Runtime
|
18
|
+
Ptr = Fiddle::Pointer
|
19
|
+
|
20
|
+
def self.init
|
21
|
+
# set up no output so we don't get swipl command line interfering in ruby
|
22
|
+
# TODO exception handling should not kick off a prolog terminal
|
23
|
+
# TODO see gem-swipl for more useful stuff here
|
24
|
+
args = %w[upl -q --tty=false --nosignals]
|
25
|
+
|
26
|
+
# convert args to char **
|
27
|
+
ptr_size = Extern.sizeof 'char*'
|
28
|
+
arg_ptrs = Ptr.malloc(ptr_size * args.size)
|
29
|
+
args.each_with_index do |rg,i|
|
30
|
+
(arg_ptrs + i*ptr_size)[0,ptr_size] = Ptr[rg].ref
|
31
|
+
end
|
32
|
+
|
33
|
+
# call init
|
34
|
+
rv = Extern.PL_initialise args.size, arg_ptrs
|
35
|
+
rv == 1 or raise 'PL_initialise failed'
|
36
|
+
end
|
37
|
+
|
38
|
+
# once_only. Should probably be a singleton or something.
|
39
|
+
@inited ||= init
|
40
|
+
|
41
|
+
def self.predicate name, arity, module_name = nil
|
42
|
+
Extern.PL_predicate Fiddle::Pointer[name.to_s], arity, NULL
|
43
|
+
end
|
44
|
+
|
45
|
+
# Use prolog predicate to parse the string into a term with its named variables
|
46
|
+
def self.term_vars st
|
47
|
+
# atom_to_term('your_pred(A,B,C,D)',Term,Options).
|
48
|
+
terms = Extern.PL_new_term_refs 3
|
49
|
+
atom, term, options = terms+0, terms+1, terms+2
|
50
|
+
|
51
|
+
Extern::PL_put_atom atom, (Extern::PL_new_atom Fiddle::Pointer[st])
|
52
|
+
Extern::PL_put_variable term
|
53
|
+
Extern::PL_put_variable options
|
54
|
+
|
55
|
+
# docs say to use read_term_from_atom/3, but it fails with uninstantiated variables for 7.7.18
|
56
|
+
rv = Extern::PL_call_predicate \
|
57
|
+
Extern::NULL, # module
|
58
|
+
0, # flags, see PL_open_query
|
59
|
+
(predicate 'atom_to_term', 3),
|
60
|
+
terms
|
61
|
+
|
62
|
+
# first must be Term.new otherwise Term unhooks the term_t pointer
|
63
|
+
# vars *must* be unhooked though ¯\_(ツ)_/¯
|
64
|
+
return (Term.new term), (list_to_ary options do |elt| Term.new elt end)
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.unify( term_a, term_b )
|
68
|
+
rv = Extern::PL_unify term_a.term_t, term_a.term_t
|
69
|
+
rv == 1 or raise "can't unify #{term_a} and #{term_b}"
|
70
|
+
end
|
71
|
+
|
72
|
+
# do a query for the given term and vars, as parsed by term_vars
|
73
|
+
def self.term_vars_query qterm, qvars
|
74
|
+
raise "not a term" unless Term === qterm
|
75
|
+
return enum_for __method__, qterm, qvars unless block_given?
|
76
|
+
|
77
|
+
fid_t = Extern.PL_open_foreign_frame
|
78
|
+
|
79
|
+
begin
|
80
|
+
# input values
|
81
|
+
terms_ptr = Extern.PL_new_term_refs qterm.arity
|
82
|
+
qterm.args.each_with_index do |arg,idx|
|
83
|
+
Extern::PL_unify (terms_ptr+idx), arg
|
84
|
+
end
|
85
|
+
|
86
|
+
# module is NULL, flags is 0
|
87
|
+
query_id_p = Extern.PL_open_query Extern::NULL, 0, qterm.to_predicate, terms_ptr
|
88
|
+
query_id_p != 0 or raise 'no space on environment stack, see SWI-Prolog docs for PL_open_query'
|
89
|
+
|
90
|
+
loop do
|
91
|
+
# TODO handle PL_Q_EXT_STATUS
|
92
|
+
res = Extern.PL_next_solution query_id_p
|
93
|
+
break if res == 0
|
94
|
+
|
95
|
+
hash = qvars.each_with_object Hash.new do |name_var,ha|
|
96
|
+
name_term_t, var_term_t = name_var.args.to_a
|
97
|
+
name = Term.new name_term_t
|
98
|
+
|
99
|
+
# term_t will be invalidated by the next call to PL_next_solution,
|
100
|
+
# so we need to construct a ruby tree of the value term
|
101
|
+
val = ha[name.atom.to_sym] = Tree.of_term var_term_t
|
102
|
+
# binding.pry if val.to_sym == :query_debug_settings rescue false
|
103
|
+
end
|
104
|
+
|
105
|
+
yield hash
|
106
|
+
end
|
107
|
+
|
108
|
+
ensure
|
109
|
+
query_id_p&.to_i and Extern.PL_close_query query_id_p
|
110
|
+
end
|
111
|
+
|
112
|
+
ensure
|
113
|
+
# this also gets called after enum_for, so test for fid_t
|
114
|
+
fid_t and Extern.PL_close_foreign_frame fid_t
|
115
|
+
end
|
116
|
+
|
117
|
+
def self.eval st_or_term
|
118
|
+
p_term =
|
119
|
+
case st_or_term
|
120
|
+
when String
|
121
|
+
rv = Extern.PL_chars_to_term Fiddle::Pointer[st_or_term], (p_term = Extern.PL_new_term_ref)
|
122
|
+
raise "failure parsing term #{st_or_term}" unless rv == 1
|
123
|
+
p_term
|
124
|
+
when Term
|
125
|
+
st_or_term.term_t
|
126
|
+
else
|
127
|
+
raise "dunno bout #{st_or_term}"
|
128
|
+
end
|
129
|
+
|
130
|
+
rv = Extern.PL_call p_term, Extern::NULL
|
131
|
+
rv == 1 or raise "failure executing term #{st}"
|
132
|
+
end
|
133
|
+
|
134
|
+
def self.predicate name, arity
|
135
|
+
pred_p = Extern.PL_predicate Ptr[name.to_s], arity, Extern::NULL
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.list_to_ary lst, &elt_converter
|
139
|
+
rv = []
|
140
|
+
|
141
|
+
while Extern::PL_get_nil(lst) != 1 # not end of list
|
142
|
+
res = Extern::PL_get_list \
|
143
|
+
lst,
|
144
|
+
(head = Extern.PL_new_term_ref),
|
145
|
+
(rst = Extern.PL_new_term_ref)
|
146
|
+
|
147
|
+
break unless res == 1
|
148
|
+
|
149
|
+
rv << (elt_converter.call head)
|
150
|
+
lst = rst
|
151
|
+
end
|
152
|
+
|
153
|
+
rv
|
154
|
+
end
|
155
|
+
|
156
|
+
# simple query with predicate / arity
|
157
|
+
def self.squery predicate_str, arity
|
158
|
+
return enum_for :squery, predicate_str, arity unless block_given?
|
159
|
+
p_atom = Extern::PL_new_atom Fiddle::Pointer[predicate_str]
|
160
|
+
p_functor = Extern::PL_new_functor p_atom, arity
|
161
|
+
p_predicate = Extern::PL_pred p_functor, Extern::NULL
|
162
|
+
|
163
|
+
answer_lst = Extern.PL_new_term_refs arity
|
164
|
+
query_id_p = Extern.PL_open_query Extern::NULL, 0, p_predicate, answer_lst
|
165
|
+
|
166
|
+
loop do
|
167
|
+
res = Extern.PL_next_solution query_id_p
|
168
|
+
break if res == 0
|
169
|
+
|
170
|
+
answrs =
|
171
|
+
arity.times.map do |i|
|
172
|
+
term_to_ruby answer_lst+i
|
173
|
+
end
|
174
|
+
|
175
|
+
yield answrs
|
176
|
+
end
|
177
|
+
|
178
|
+
ensure
|
179
|
+
# NOTE this also gets called after enum_for
|
180
|
+
query_id_p&.to_i and Extern.PL_close_query query_id_p
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
data/lib/upl/term.rb
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
module Upl
|
2
|
+
# So Prolog terms are a rose tree. Who woulda thunkit?
|
3
|
+
|
4
|
+
# OK, so I guess this thing's job is interacting with term_t, whereas Tree's
|
5
|
+
# job is being a ruby copy of a term-tree.
|
6
|
+
class Term
|
7
|
+
def initialize term_or_string
|
8
|
+
case term_or_string
|
9
|
+
when String
|
10
|
+
raise "can't do strings yet"
|
11
|
+
# PL_chars_to_term term_or_string
|
12
|
+
when Fiddle::Pointer
|
13
|
+
# assume this is a pointer to a term. Unsafe, but there's no choice really
|
14
|
+
@term_t = term_or_string
|
15
|
+
else
|
16
|
+
raise "can't handle #{term_or_string}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :term_t
|
21
|
+
|
22
|
+
def to_term; term_t end
|
23
|
+
|
24
|
+
# Make a copy of all the term information. Useful for passing in to queries, apparently.
|
25
|
+
def self.copy term_t
|
26
|
+
copy_term_t = Extern.PL_copy_term_ref term_t
|
27
|
+
new copy_term_t
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.of_atom atom
|
31
|
+
term_t = Extern.PL_new_term_ref
|
32
|
+
rv = Extern.PL_put_atom term_t, atom.to_atom
|
33
|
+
rv == 1 or raise "can't set term to atom #{atom}"
|
34
|
+
term_t
|
35
|
+
end
|
36
|
+
|
37
|
+
# args are things that can be converted to term_t pointers using to_term method
|
38
|
+
def self.functor name, *args
|
39
|
+
# TODO maybe use a frame or something because this allocates quite a few sub-terms
|
40
|
+
functor_t = Extern.PL_new_functor name.to_sym.to_atom, args.size
|
41
|
+
|
42
|
+
arg_terms = Extern.PL_new_term_refs args.size
|
43
|
+
args.each_with_index do |arg,i|
|
44
|
+
Extern::PL_unify (arg_terms+i), arg.to_term
|
45
|
+
end
|
46
|
+
|
47
|
+
term_t = Extern.PL_new_term_ref
|
48
|
+
rv = Extern.PL_cons_functor_v term_t, functor_t, arg_terms
|
49
|
+
rv == 1 or raise "can't populate functor #{name}"
|
50
|
+
|
51
|
+
new term_t
|
52
|
+
end
|
53
|
+
|
54
|
+
def populate
|
55
|
+
int_ptr = Runtime::Ptr[0].ref
|
56
|
+
atom_ptr = Runtime::Ptr[0].ref
|
57
|
+
rv = Extern::PL_get_name_arity term_t, atom_ptr, int_ptr
|
58
|
+
# This happens when the term_t is not a PL_TERM (ie a compound)
|
59
|
+
raise "can't populate term" unless rv == 1
|
60
|
+
|
61
|
+
@arity = int_ptr.ptr.to_i
|
62
|
+
@atom = Atom.new atom_ptr
|
63
|
+
|
64
|
+
self
|
65
|
+
end
|
66
|
+
|
67
|
+
def == rhs
|
68
|
+
@atom == rhs.atom && @arity == rhs.arity && args == rhs.args
|
69
|
+
end
|
70
|
+
|
71
|
+
def <=> rhs
|
72
|
+
[@atom, @arity] <=> [rhs.atom, rhs.arity]
|
73
|
+
end
|
74
|
+
|
75
|
+
# attr_reader :atom, :arity
|
76
|
+
|
77
|
+
def atom
|
78
|
+
@atom or begin
|
79
|
+
populate
|
80
|
+
@atom
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def arity
|
85
|
+
@arity or begin
|
86
|
+
populate
|
87
|
+
@arity
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def to_functor
|
92
|
+
Extern::PL_new_functor atom.atom_ptr, arity
|
93
|
+
end
|
94
|
+
|
95
|
+
def to_predicate
|
96
|
+
Extern::PL_pred to_functor, Extern::NULL
|
97
|
+
end
|
98
|
+
|
99
|
+
def tree; @tree || (Tree.new self) end
|
100
|
+
def to_ruby; tree end
|
101
|
+
|
102
|
+
# Assume that term_t still has a value. Which means we have to copy all
|
103
|
+
# values before the underlying term_t is changed.
|
104
|
+
# TODO leaning hard towards each with Enumerable
|
105
|
+
def args
|
106
|
+
return enum_for :args unless block_given?
|
107
|
+
|
108
|
+
(1..arity).each do |i|
|
109
|
+
rv = Extern::PL_get_arg i, term_t, (subterm = Extern.PL_new_term_ref)
|
110
|
+
if rv == 1
|
111
|
+
yield subterm
|
112
|
+
else
|
113
|
+
puts "#{rv}: can't convert #{i} arg of #{atom}"
|
114
|
+
yield subterm
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def []=( idx, val_term_t)
|
120
|
+
Extern::PL_get_arg idx+1, term_t, (subterm = Extern.PL_new_term_ref)
|
121
|
+
rv = Extern.PL_unify subterm, val_term_t
|
122
|
+
rv == 1 or raise "can't set index #{idx}"
|
123
|
+
end
|
124
|
+
|
125
|
+
def pretty_print(pp)
|
126
|
+
# to_ruby.pretty_print pp
|
127
|
+
pp.text atom.to_s
|
128
|
+
pp.text ?/
|
129
|
+
pp.text arity.to_s
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
data/lib/upl/tree.rb
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
module Upl
|
2
|
+
# Convert a term into a tree of ruby objects. This is necessary because
|
3
|
+
# queries give back their results as terms which are invalidated as soon as
|
4
|
+
# the next set of results is calculated. So we need to turn those terms into a
|
5
|
+
# ruby representation and keep them around.
|
6
|
+
class Tree
|
7
|
+
# term is either a Term instance, or a Fiddle::Pointer to a term_t
|
8
|
+
def initialize( term )
|
9
|
+
init term
|
10
|
+
end
|
11
|
+
|
12
|
+
def init term
|
13
|
+
case term
|
14
|
+
when Term
|
15
|
+
@atom = term.atom
|
16
|
+
@args = term.args.map do |arg|
|
17
|
+
self.class.term_to_ruby arg
|
18
|
+
end
|
19
|
+
when Fiddle::Pointer
|
20
|
+
init Term.new term
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :atom, :args
|
25
|
+
|
26
|
+
def self.of_term term_t
|
27
|
+
term_to_ruby term_t
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.term_to_ruby term
|
31
|
+
case term.term_type
|
32
|
+
when Extern::PL_VARIABLE
|
33
|
+
Variable.copy term
|
34
|
+
|
35
|
+
when Extern::PL_ATOM
|
36
|
+
atom = Atom.of_term term
|
37
|
+
if atom.to_s =~ /^ruby-(\d+)/
|
38
|
+
ObjectSpace._id2ref $1.to_i
|
39
|
+
else
|
40
|
+
atom
|
41
|
+
end
|
42
|
+
|
43
|
+
when Extern::PL_INTEGER
|
44
|
+
Extern.PL_get_integer term, (int_ptr = Fiddle::Pointer[0].ref)
|
45
|
+
int_ptr.ptr.to_i
|
46
|
+
|
47
|
+
when Extern::PL_STRING
|
48
|
+
rv = Extern.PL_get_string term, (str_ptr = Fiddle::Pointer[0].ref), (len_ptr = Fiddle::Pointer[0].ref)
|
49
|
+
value_ptr = Fiddle::Pointer.new str_ptr.ptr, len_ptr.ptr.to_i
|
50
|
+
value_ptr.to_s
|
51
|
+
|
52
|
+
when Extern::PL_NIL
|
53
|
+
# TODO maybe this should be [] - see what happens when term_vars has no vars
|
54
|
+
# although nil.to_a == []
|
55
|
+
nil
|
56
|
+
|
57
|
+
when Extern::PL_TERM
|
58
|
+
Tree.new term
|
59
|
+
|
60
|
+
when Extern::PL_LIST_PAIR
|
61
|
+
list_to_ary term
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.list_to_ary lst
|
67
|
+
rv = []
|
68
|
+
|
69
|
+
while Extern::PL_get_nil(lst) != 1 # not end of list
|
70
|
+
res = Extern::PL_get_list \
|
71
|
+
lst,
|
72
|
+
(head = Extern.PL_new_term_ref),
|
73
|
+
(rst = Extern.PL_new_term_ref)
|
74
|
+
|
75
|
+
break unless res == 1
|
76
|
+
|
77
|
+
rv << (term_to_ruby head)
|
78
|
+
lst = rst
|
79
|
+
end
|
80
|
+
|
81
|
+
rv
|
82
|
+
end
|
83
|
+
|
84
|
+
def arity; args.size end
|
85
|
+
|
86
|
+
def pretty_print(pp)
|
87
|
+
unless atom == :','
|
88
|
+
pp.text atom.to_s
|
89
|
+
if arity > 0
|
90
|
+
pp.text ?/
|
91
|
+
pp.text arity.to_s
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
if arity > 0
|
96
|
+
pp.group 1, ?(, ?) do
|
97
|
+
args.each_with_index do |ruby_term,i|
|
98
|
+
ruby_term.pretty_print pp
|
99
|
+
pp.text ?, if i < arity - 1
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
protected
|
106
|
+
|
107
|
+
def populate_args count
|
108
|
+
(1..arity).each do |i|
|
109
|
+
rv = Extern::PL_get_arg i, term_t, (subterm = Extern.PL_new_term_ref)
|
110
|
+
if rv == 1
|
111
|
+
yield subterm
|
112
|
+
else
|
113
|
+
puts "#{rv}: can't convert #{i} arg of #{atom}"
|
114
|
+
yield subterm
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
data/lib/upl/variable.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
module Upl
|
2
|
+
# Really this is just an empty term.
|
3
|
+
class Variable
|
4
|
+
def initialize term_t = nil
|
5
|
+
@term_t = term_t || Extern.PL_new_term_ref
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_reader :term_t
|
9
|
+
|
10
|
+
def self.copy term_t
|
11
|
+
inst = new term_t
|
12
|
+
|
13
|
+
inst.attributed? and inst.attribute
|
14
|
+
inst.to_s
|
15
|
+
|
16
|
+
inst
|
17
|
+
end
|
18
|
+
|
19
|
+
# bit of a hack to create empty variables for a functor
|
20
|
+
def self.to_term
|
21
|
+
new
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s; _string end
|
25
|
+
|
26
|
+
def _string
|
27
|
+
@_string ||= begin
|
28
|
+
Extern::PL_get_chars \
|
29
|
+
term_t,
|
30
|
+
(str_ref = Runtime::Ptr[''].ref),
|
31
|
+
Extern::Convert::CVT_VARIABLE | Extern::Convert::REP_UTF8 | Extern::Convert::BUF_MALLOC # | Extern::CVT_ALL
|
32
|
+
|
33
|
+
str_ref.ptr.to_s
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def attributed?
|
38
|
+
if instance_variable_defined? :@attributed
|
39
|
+
@attributed
|
40
|
+
else
|
41
|
+
@attributed = (Extern::PL_is_attvar term_t) == 1
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def attribute
|
46
|
+
@attribute ||= begin
|
47
|
+
rv = Extern::PL_get_attr term_t, (val = Extern.PL_new_term_ref)
|
48
|
+
rv == 1 or raise "can't get attribute for variable"
|
49
|
+
Tree.of_term val
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def pretty_print pp
|
54
|
+
if attributed?
|
55
|
+
attribute.pretty_print pp
|
56
|
+
else
|
57
|
+
pp.text to_s
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/upl/version.rb
ADDED
data/upl.gemspec
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'upl/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'upl'
|
7
|
+
spec.version = Upl::VERSION
|
8
|
+
spec.authors = ['John Anderson']
|
9
|
+
spec.email = ['panic@semiosix.com']
|
10
|
+
|
11
|
+
spec.summary = %q{Access SWI-Prolog engine from ruby}
|
12
|
+
spec.description = %q{Access SWI-Prolog engine from ruby}
|
13
|
+
spec.homepage = 'https://github.com/djellemah/upl'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
17
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
18
|
+
if spec.respond_to?(:metadata)
|
19
|
+
spec.metadata["allowed_push_host"] = 'https://rubygems.org'
|
20
|
+
else
|
21
|
+
raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.'
|
22
|
+
end
|
23
|
+
|
24
|
+
# Specify which files should be added to the gem when it is released.
|
25
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
26
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
27
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
28
|
+
end
|
29
|
+
spec.bindir = 'exe'
|
30
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
31
|
+
spec.require_paths = ['lib']
|
32
|
+
|
33
|
+
spec.add_development_dependency 'bundler', '~> 1.16'
|
34
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
35
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
36
|
+
spec.add_dependency 'pry'
|
37
|
+
spec.add_dependency 'fiddle'
|
38
|
+
end
|
metadata
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: upl
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- John Anderson
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-09-13 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.16'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.16'
|
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: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: fiddle
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: Access SWI-Prolog engine from ruby
|
84
|
+
email:
|
85
|
+
- panic@semiosix.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".gitignore"
|
91
|
+
- ".rspec"
|
92
|
+
- ".travis.yml"
|
93
|
+
- Gemfile
|
94
|
+
- LICENSE.txt
|
95
|
+
- README.md
|
96
|
+
- Rakefile
|
97
|
+
- bin/console
|
98
|
+
- bin/setup
|
99
|
+
- lib/upl.rb
|
100
|
+
- lib/upl/atom.rb
|
101
|
+
- lib/upl/extern.rb
|
102
|
+
- lib/upl/functor.rb
|
103
|
+
- lib/upl/inter.rb
|
104
|
+
- lib/upl/runtime.rb
|
105
|
+
- lib/upl/term.rb
|
106
|
+
- lib/upl/tree.rb
|
107
|
+
- lib/upl/variable.rb
|
108
|
+
- lib/upl/version.rb
|
109
|
+
- upl.gemspec
|
110
|
+
homepage: https://github.com/djellemah/upl
|
111
|
+
licenses:
|
112
|
+
- MIT
|
113
|
+
metadata:
|
114
|
+
allowed_push_host: https://rubygems.org
|
115
|
+
post_install_message:
|
116
|
+
rdoc_options: []
|
117
|
+
require_paths:
|
118
|
+
- lib
|
119
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - ">="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
125
|
+
requirements:
|
126
|
+
- - ">="
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '0'
|
129
|
+
requirements: []
|
130
|
+
rubyforge_project:
|
131
|
+
rubygems_version: 2.7.7
|
132
|
+
signing_key:
|
133
|
+
specification_version: 4
|
134
|
+
summary: Access SWI-Prolog engine from ruby
|
135
|
+
test_files: []
|