upl 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|