upl 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +3 -0
- data/History.txt +3 -0
- data/README.md +32 -20
- data/lib/upl.rb +19 -13
- data/lib/upl/atom.rb +51 -12
- data/lib/upl/extern.rb +21 -0
- data/lib/upl/foreign.rb +55 -0
- data/lib/upl/inter.rb +93 -6
- data/lib/upl/runtime.rb +20 -12
- data/lib/upl/term.rb +2 -3
- data/lib/upl/tree.rb +7 -10
- data/lib/upl/variable.rb +17 -3
- data/lib/upl/variables.rb +42 -0
- data/lib/upl/version.rb +1 -1
- data/scratch/register_predicate.rb +33 -0
- data/upl.gemspec +3 -3
- metadata +13 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 74895ae80e02af92445e7d4fb47413dbb67b0996ced75014b250c18bba5bb444
|
4
|
+
data.tar.gz: 7288b2b9b91bb4a7127ff05341ccb053d5a159e7dcdd70537d4d6a21cfc186bb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 516ebd68a80b61d75d12d1ca91833409cce8bc0d84d3d44346e07fc7b444c70f3d9d11eaca42cdbe39ff78f28e06fcc15af53c0659fa29ece17e316364e23706
|
7
|
+
data.tar.gz: ded90962d4080696c29e87cf83ec3a429e8feca7b693b01ae2b2b2596fa7980a6ea0d44af876c014fff8b8f371bf27b3810dd8f2515bf5f157ccc9efb4e7cf5a
|
data/Gemfile
CHANGED
data/History.txt
ADDED
data/README.md
CHANGED
@@ -17,7 +17,7 @@ Query a built-in predicate, with a full expression:
|
|
17
17
|
``` ruby
|
18
18
|
[1] pry(main)> enum = Upl.query 'current_prolog_flag(K,V), member(K,[home,executable,shared_object_extension])'
|
19
19
|
=> #<Enumerator: ...>
|
20
|
-
[
|
20
|
+
[2] pry(main) enum.to_a
|
21
21
|
=> [{:K=>home, :V=>/usr/lib64/swipl-7.7.18},
|
22
22
|
{:K=>executable, :V=>/usr/local/rvm/rubies/ruby-2.6.0-preview2/bin/ruby},
|
23
23
|
{:K=>shared_object_extension, :V=>so}]
|
@@ -51,15 +51,15 @@ false.
|
|
51
51
|
And in Upl:
|
52
52
|
|
53
53
|
``` ruby
|
54
|
-
[
|
54
|
+
[1] pry(main)> fact = Upl::Term.functor :person, :john, :anderson
|
55
55
|
=> person/2(john,anderson)
|
56
|
-
[
|
56
|
+
[2] pry(main)> Upl.assertz fact
|
57
57
|
=> true
|
58
|
-
[
|
58
|
+
[3] pry(main)> Array Upl.query 'person(A,B)'
|
59
59
|
=> [{:A=>john, :B=>anderson}]
|
60
|
-
[
|
60
|
+
[4] pry(main)> Upl.retract fact
|
61
61
|
=> true
|
62
|
-
[
|
62
|
+
[5] pry(main)> Array Upl.query 'person(A,B)'
|
63
63
|
=> []
|
64
64
|
```
|
65
65
|
|
@@ -69,15 +69,15 @@ Also, with objects other than symbols. Obviously, this is a rabbit-hole of
|
|
69
69
|
Alician proportions. So, here we GOOOoooo...
|
70
70
|
|
71
71
|
``` ruby
|
72
|
-
[
|
72
|
+
[1] pry(main)> fact = Upl::Term.functor :person, :john, :anderson, (o = Object.new)
|
73
73
|
=> person/3(john,anderson,#<Object:0x0000563346a08e38 @_upl_atom=439429>)
|
74
|
-
[
|
74
|
+
[2] pry(main)> Upl.assertz fact
|
75
75
|
=> true
|
76
|
-
[
|
76
|
+
[3] pry(main)> ha, = Array Upl.query 'person(A,B,C)'
|
77
77
|
=> [{:A=>john,
|
78
78
|
:B=>anderson,
|
79
79
|
:C=>#<Object:0x0000563346a08e38 @_upl_atom=439429>}]
|
80
|
-
[
|
80
|
+
[4] pry(main)> ha[:C].equal? o
|
81
81
|
=> true
|
82
82
|
```
|
83
83
|
|
@@ -87,28 +87,40 @@ much wisdom. Hurhur. And at least one extra instance variable.
|
|
87
87
|
And now, the pièce de résistance - using an object as an input term:
|
88
88
|
|
89
89
|
``` ruby
|
90
|
-
fact =
|
90
|
+
fact = Upl::Term.functor :person, :james, :madison, (o = Object.new)
|
91
91
|
Upl.assertz fact
|
92
92
|
|
93
93
|
fact2 = Upl::Term.functor :person, :thomas, :paine, (thing2 = Object.new)
|
94
94
|
Upl.assertz fact2
|
95
95
|
|
96
|
-
# Note that both facts are in the result
|
97
|
-
query_term,
|
98
|
-
Array Upl::Runtime.term_vars_query query_term,
|
99
|
-
=>[
|
100
|
-
|
96
|
+
# Note that both facts are in the result and the values for C are different
|
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
|
+
=>[
|
100
|
+
{:A=>james, :B=>madison, :C=>#<Object:0x0000563f56e35580 @_upl_atom=439429>},
|
101
|
+
{:A=>thomas, :B=>paine, :C=>#<Object:0x0000563f56d2b5b8 @_upl_atom=439813>}
|
102
|
+
]
|
101
103
|
|
102
|
-
# Unify C with thing2
|
103
|
-
query_term,
|
104
|
-
|
104
|
+
# Unify C with thing2
|
105
|
+
query_term, query_vars = Upl::Runtime.term_vars 'person(A,B,C)'
|
106
|
+
query_vars.C = thing2
|
105
107
|
|
106
108
|
# ... and we get the correct result
|
107
109
|
# Note that the first fact is not in the result.
|
108
|
-
Array Upl::Runtime.term_vars_query query_term,
|
110
|
+
Array Upl::Runtime.term_vars_query query_term, query_vars
|
109
111
|
=> [{:A=>thomas, :B=>paine, :C=>#<Object:0x0000563f56d2b5b8 @_upl_atom=439813>}]
|
110
112
|
```
|
111
113
|
|
114
|
+
### Ruby Predicates
|
115
|
+
|
116
|
+
You can define predicates in ruby.
|
117
|
+
``` ruby
|
118
|
+
|
119
|
+
|
120
|
+
```
|
121
|
+
|
122
|
+
So you can (theoretically) define a query in prolog that searches a ruby object graph.
|
123
|
+
|
112
124
|
## Disclaimer
|
113
125
|
|
114
126
|
This is in-development code. I use it for some things other than just playing with. It might be useful for you too.
|
data/lib/upl.rb
CHANGED
@@ -4,41 +4,47 @@ require_relative 'upl/version'
|
|
4
4
|
require_relative 'upl/extern'
|
5
5
|
require_relative 'upl/term'
|
6
6
|
require_relative 'upl/variable'
|
7
|
+
require_relative 'upl/variables'
|
7
8
|
require_relative 'upl/atom'
|
8
9
|
require_relative 'upl/runtime'
|
9
10
|
require_relative 'upl/dict'
|
10
11
|
require_relative 'upl/tree'
|
11
12
|
require_relative 'upl/inter'
|
12
13
|
require_relative 'upl/term_vector'
|
14
|
+
require_relative 'upl/foreign'
|
13
15
|
|
14
16
|
module Upl
|
15
|
-
def
|
16
|
-
|
17
|
-
|
18
|
-
Runtime.query string_or_term
|
19
|
-
when String
|
20
|
-
term, vars = Runtime.term_vars string_or_term
|
21
|
-
Runtime.term_vars_query term, vars, &blk
|
17
|
+
module_function def query string_or_term, vars = nil, &blk
|
18
|
+
if string_or_term.is_a?(Term) && vars
|
19
|
+
Runtime.term_vars_query string_or_term, vars
|
22
20
|
else
|
23
|
-
|
21
|
+
case string_or_term
|
22
|
+
when Term
|
23
|
+
Runtime.query string_or_term
|
24
|
+
when String
|
25
|
+
term, vars = Runtime.term_vars string_or_term
|
26
|
+
Runtime.term_vars_query term, vars, &blk
|
27
|
+
else
|
28
|
+
raise "dunno about #{string_or_term.inspect}"
|
29
|
+
end
|
24
30
|
end
|
25
31
|
end
|
26
32
|
|
27
|
-
def
|
33
|
+
module_function def consult filename
|
28
34
|
p = Pathname filename
|
29
35
|
Runtime::call %Q{["#{p.realpath.to_s}"]}
|
30
36
|
end
|
31
37
|
|
32
|
-
def
|
38
|
+
module_function def asserta term
|
33
39
|
Runtime.call Term.functor :asserta, term
|
34
40
|
end
|
35
41
|
|
36
|
-
def
|
42
|
+
module_function def assertz term
|
37
43
|
Runtime.call Term.functor :assertz, term
|
38
44
|
end
|
39
45
|
|
40
46
|
# behaves as if run under once, cos of the way call works
|
41
|
-
def
|
47
|
+
module_function def retract term
|
42
48
|
Runtime.call Term.functor :retract, term
|
43
49
|
end
|
44
50
|
|
@@ -57,7 +63,7 @@ module Upl
|
|
57
63
|
#
|
58
64
|
# Upl.query Term :current_prolog_flag, Variable.new, Variable.new
|
59
65
|
#
|
60
|
-
def
|
66
|
+
module_function def Term name, *args
|
61
67
|
Term.functor name, *args
|
62
68
|
end
|
63
69
|
end
|
data/lib/upl/atom.rb
CHANGED
@@ -1,35 +1,74 @@
|
|
1
1
|
module Upl
|
2
2
|
class Atom
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
# NOTE these are atom_t, NOT term_t and NOT Fiddle::Pointer to atom_t
|
4
|
+
def initialize( atom_t )
|
5
|
+
@atom_t = atom_t
|
6
|
+
|
7
|
+
# pretty much all other methods need chars, so just do it now.
|
8
|
+
@chars = (::Upl::Extern::PL_atom_chars @atom_t).to_s.freeze
|
7
9
|
end
|
8
10
|
|
9
|
-
# drop the term immediately, and just keep the atom
|
11
|
+
# drop the term immediately, and just keep the atom value
|
10
12
|
def self.of_term( term_t )
|
11
|
-
rv = Extern::PL_get_atom term_t, (
|
12
|
-
raise "can't get atom from term"
|
13
|
-
new
|
13
|
+
rv = Extern::PL_get_atom term_t, (atom_t = Fiddle::Pointer[0].ref)
|
14
|
+
rv == 1 or raise "can't get atom from term"
|
15
|
+
new atom_t.ptr
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :atom_t
|
19
|
+
|
20
|
+
# NOTE this returns an atom_t for obj.object_id embedded in an atom
|
21
|
+
def self.t_of_ruby obj
|
22
|
+
Upl::Extern.PL_new_atom "ruby-#{obj.object_id.to_s}"
|
14
23
|
end
|
15
24
|
|
16
|
-
|
25
|
+
# return the object_id embedded in the atom, or nil if it's not an embedded
|
26
|
+
# object_id
|
27
|
+
def to_obj_id
|
28
|
+
if instance_variable_defined? :@to_obj_id
|
29
|
+
@to_obj_id
|
30
|
+
else
|
31
|
+
@to_obj_id =
|
32
|
+
if @chars =~ /^ruby-(\d+)/
|
33
|
+
$1.to_i
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# return the ruby object associated with the object_id embedded in the atom
|
39
|
+
def to_ruby
|
40
|
+
if to_obj_id
|
41
|
+
ObjectSpace._id2ref to_obj_id
|
42
|
+
else
|
43
|
+
case _sym = to_sym
|
44
|
+
when :false; false
|
45
|
+
when :true; true
|
46
|
+
else _sym
|
47
|
+
end
|
48
|
+
end
|
49
|
+
rescue RangeError
|
50
|
+
# object with the given obj_id no longer exists in ruby, so just return
|
51
|
+
# the symbol with the embedded object_id.
|
52
|
+
to_sym
|
53
|
+
end
|
17
54
|
|
18
55
|
def == rhs
|
19
|
-
|
56
|
+
atom_t == rhs.atom_t
|
20
57
|
end
|
21
58
|
|
22
59
|
def to_sym
|
23
|
-
@
|
60
|
+
@chars.to_sym
|
24
61
|
end
|
25
62
|
|
26
63
|
def to_s
|
27
|
-
@
|
64
|
+
@chars
|
28
65
|
end
|
29
66
|
|
30
67
|
def inspect; to_sym end
|
31
68
|
|
32
69
|
def pretty_print pp
|
70
|
+
pp.text atom_t
|
71
|
+
pp.text '-'
|
33
72
|
pp.text to_s
|
34
73
|
end
|
35
74
|
end
|
data/lib/upl/extern.rb
CHANGED
@@ -54,6 +54,9 @@ module Upl
|
|
54
54
|
# terms
|
55
55
|
extern 'predicate_t PL_predicate(const char *name, int arity, const char *module)'
|
56
56
|
|
57
|
+
TRUE = (1)
|
58
|
+
FALSE = (0)
|
59
|
+
|
57
60
|
##############
|
58
61
|
# querying and getting results
|
59
62
|
|
@@ -118,6 +121,17 @@ module Upl
|
|
118
121
|
PL_NOT_A_LIST = (43)
|
119
122
|
PL_DICT = (44)
|
120
123
|
|
124
|
+
# Foreign predicate flags
|
125
|
+
PL_FA_NOTRACE = (0x01) # foreign cannot be traced
|
126
|
+
# PL_FA_TRANSPARENT = (0x02) # foreign is module transparent. Deprecated.
|
127
|
+
PL_FA_NONDETERMINISTIC = (0x04) # foreign is non-deterministic
|
128
|
+
PL_FA_VARARGS = (0x08) # call using t0, ac, ctx
|
129
|
+
PL_FA_CREF = (0x10) # Internal: has clause-reference
|
130
|
+
PL_FA_ISO = (0x20) # Internal: ISO core predicate
|
131
|
+
PL_FA_META = (0x40) # Additional meta-argument spec
|
132
|
+
|
133
|
+
extern 'int PL_register_foreign_in_module(char *mod, char *name, int arity, foreign_t (*f)(), int flags, ...)'
|
134
|
+
|
121
135
|
module Convert
|
122
136
|
REP_UTF8 = 0x1000
|
123
137
|
BUF_MALLOC = 0x0200
|
@@ -197,6 +211,10 @@ module Upl
|
|
197
211
|
extern 'int PL_put_variable(term_t t)'
|
198
212
|
extern 'int PL_put_functor(term_t t, functor_t functor)'
|
199
213
|
extern 'int PL_put_term(term_t t1, term_t t2)' # Make t1 point to the same term as t2.
|
214
|
+
extern 'int PL_put_integer(term_t t, long i)'
|
215
|
+
extern 'int PL_put_int64(term_t t, int64_t i)'
|
216
|
+
|
217
|
+
extern 'int PL_put_string_nchars(term_t t, size_t len, const char *chars)'
|
200
218
|
|
201
219
|
extern 'int PL_cons_functor_v(term_t h, functor_t fd, term_t a0)'
|
202
220
|
|
@@ -243,5 +261,8 @@ module Upl
|
|
243
261
|
extern 'int PL_wchars_to_term(const pl_wchar_t *chars, term_t term)'
|
244
262
|
|
245
263
|
extern 'void PL_unregister_atom(atom_t a)'
|
264
|
+
|
265
|
+
# signature is actually PL_agc_hook_t, not void*
|
266
|
+
extern 'void* PL_agc_hook(void*)'
|
246
267
|
end
|
247
268
|
end
|
data/lib/upl/foreign.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
module Upl
|
2
|
+
# Register a foreign predicate in prolog
|
3
|
+
# Upl::Extern.PL_register_foreign_in_module(char *mod, char *name, int arity, foreign_t (*f)(), int flags, ...)
|
4
|
+
|
5
|
+
# create the foreign predicate as a class in ruby
|
6
|
+
#
|
7
|
+
# closure = Class.new(Fiddle::Closure) {
|
8
|
+
# def call
|
9
|
+
# 10
|
10
|
+
# end
|
11
|
+
# }.new(Fiddle::TYPE_INT, [])
|
12
|
+
#
|
13
|
+
# func = Fiddle::Function.new(closure, [], Fiddle::TYPE_INT)
|
14
|
+
|
15
|
+
# create the foreign predicate as a block in ruby
|
16
|
+
#
|
17
|
+
# new(ctype, args, abi = Fiddle::Function::DEFAULT, &block)
|
18
|
+
# cb = Closure::BlockCaller.new(TYPE_INT, [TYPE_INT]) do |one|
|
19
|
+
# one
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# func = Function.new(cb, [TYPE_INT], TYPE_INT)
|
23
|
+
|
24
|
+
module Foreign
|
25
|
+
def self.predicates
|
26
|
+
@predicates ||= Hash.new
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.register_semidet name, arity = nil, module: nil, &blk
|
30
|
+
arity ||= blk.arity
|
31
|
+
arg_types = arity.times.map{Fiddle::TYPE_VOIDP}
|
32
|
+
ruby_pred = Fiddle::Closure::BlockCaller.new Fiddle::TYPE_INT, arg_types do |*args|
|
33
|
+
case (rv = blk.call *args)
|
34
|
+
when true; Upl::Extern::TRUE
|
35
|
+
when false, NilClass; Upl::Extern::FALSE
|
36
|
+
when 0, 1; rv
|
37
|
+
else Upl::Extern::TRUE
|
38
|
+
end
|
39
|
+
rescue
|
40
|
+
# TODO raise an exception here, otherwise errors get lost in an empty result set.
|
41
|
+
Upl::Extern::FALSE
|
42
|
+
end
|
43
|
+
|
44
|
+
module_name = Fiddle::Pointer[module_name&.to_s || 0]
|
45
|
+
|
46
|
+
fn = Fiddle::Function.new ruby_pred, arg_types, Fiddle::TYPE_INT
|
47
|
+
rv = Upl::Extern.PL_register_foreign_in_module module_name, name.to_s, arity, fn, 0
|
48
|
+
rv == 1 or raise "can't register ruby predicate #{name}/#{arity}"
|
49
|
+
|
50
|
+
# NOTE you have to keep ruby_pred and fn around somewhere, otherwise they
|
51
|
+
# get garbage collected, and then the callback segfaults.
|
52
|
+
predicates[[name,arity].join('/').to_sym] = ruby_pred, fn
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/upl/inter.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
require_relative 'extern'
|
2
|
+
require_relative 'foreign'
|
3
|
+
|
1
4
|
module Upl
|
2
5
|
module Inter
|
3
6
|
# Try Term, then Fiddle::Pointer, then to_term_t.
|
@@ -13,6 +16,19 @@ module Upl
|
|
13
16
|
end
|
14
17
|
end
|
15
18
|
|
19
|
+
# call any method on any object, from prolog
|
20
|
+
def self.register_mcall_predicate
|
21
|
+
Upl::Foreign.register_semidet :mcall do |obj_term_t,meth_term_t,v_term_t|
|
22
|
+
obj = Upl::Tree.of_term obj_term_t
|
23
|
+
meth = Upl::Tree.of_term meth_term_t
|
24
|
+
v = obj.send meth
|
25
|
+
|
26
|
+
Upl::Extern::PL_unify v_term_t, v.to_term_t
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
register_mcall_predicate
|
31
|
+
|
16
32
|
# lst_term is a Term, or a Fiddle::Pointer to term_t
|
17
33
|
# yield term_t items of the lst_term
|
18
34
|
def self.each_of_list lst_term, &blk
|
@@ -31,6 +47,57 @@ module Upl
|
|
31
47
|
lst_term = rst_t
|
32
48
|
end
|
33
49
|
end
|
50
|
+
|
51
|
+
# keep track of object_id atoms that were assigned in prolog, and prolog now
|
52
|
+
# wants to garbage collect them.
|
53
|
+
class Agc
|
54
|
+
def initialize
|
55
|
+
@id_objects = {}
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.instance
|
59
|
+
@instance ||= new
|
60
|
+
end
|
61
|
+
|
62
|
+
def register obj
|
63
|
+
@id_objects[obj.object_id] = obj
|
64
|
+
end
|
65
|
+
|
66
|
+
def deregister obj
|
67
|
+
@id_objects.delete obj.object_id
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
module_function def attach_atom_hook
|
72
|
+
@atom_hook = atom_hook = Fiddle::Closure::BlockCaller.new Fiddle::TYPE_INT, [Fiddle::TYPE_VOIDP] do |atom_t|
|
73
|
+
atom = Upl::Atom.new atom_t
|
74
|
+
p atom_t: atom_t.to_i, atom: atom
|
75
|
+
if atom.to_obj_id
|
76
|
+
obj = atom.to_ruby
|
77
|
+
p obj: obj, dereg: (Agc.instance.deregister obj)
|
78
|
+
end
|
79
|
+
|
80
|
+
# FALSE here will prevent garbage collection
|
81
|
+
Upl::Extern::TRUE
|
82
|
+
end
|
83
|
+
|
84
|
+
# NOTENOTE this must NOT be garbage-collected, otherwise the callback to it will fail.
|
85
|
+
@atom_hook_fn = Fiddle::Function.new atom_hook, atom_hook.args, atom_hook.ctype
|
86
|
+
|
87
|
+
# returns old fn ptr
|
88
|
+
Upl::Extern.PL_agc_hook @atom_hook_fn
|
89
|
+
end
|
90
|
+
|
91
|
+
def register_mcall_predicate
|
92
|
+
Upl::Foreign.register_semidet :mcall do |obj_term_t,meth_term_t,v_term_t|
|
93
|
+
obj = Upl::Tree.of_term obj_term_t
|
94
|
+
meth = Upl::Tree.of_term meth_term_t
|
95
|
+
v = obj.send meth
|
96
|
+
|
97
|
+
Upl::Extern::PL_unify v_term_t, v.to_term_t
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
34
101
|
end
|
35
102
|
end
|
36
103
|
|
@@ -70,12 +137,16 @@ protected
|
|
70
137
|
end
|
71
138
|
|
72
139
|
def _upl_atomize
|
73
|
-
# see also PL_agc_hook for hooking into the swipl GC
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
140
|
+
# TODO see also PL_agc_hook for hooking into the swipl GC
|
141
|
+
atom_t = Upl::Atom.t_of_ruby self
|
142
|
+
ObjectSpace.define_finalizer self, &self.class._upl_finalizer_blk(atom_t)
|
143
|
+
atom_t
|
144
|
+
end
|
145
|
+
|
146
|
+
# Have to put this in a separate method, otherwise the finalizer block's
|
147
|
+
# binding holds onto the obj it's trying to finalize.
|
148
|
+
def self._upl_finalizer_blk atom_t
|
149
|
+
proc do |objid| Upl::Extern.PL_unregister_atom atom_t end
|
79
150
|
end
|
80
151
|
end
|
81
152
|
|
@@ -84,3 +155,19 @@ class Symbol
|
|
84
155
|
Upl::Extern.PL_new_atom to_s
|
85
156
|
end
|
86
157
|
end
|
158
|
+
|
159
|
+
class Integer
|
160
|
+
def to_term_t
|
161
|
+
rv = Upl::Extern.PL_put_int64 (term_t = Upl::Extern.PL_new_term_ref), self
|
162
|
+
rv == 1 or raise "can't convert #{self} to term. Maybe too big."
|
163
|
+
term_t
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
class String
|
168
|
+
def to_term_t
|
169
|
+
rv = Upl::Extern.PL_put_string_nchars (term_t = Upl::Extern.PL_new_term_ref), length, Fiddle::Pointer[self]
|
170
|
+
rv == 1 or raise "can't convert #{self} to term"
|
171
|
+
term_t
|
172
|
+
end
|
173
|
+
end
|
data/lib/upl/runtime.rb
CHANGED
@@ -9,7 +9,7 @@ class Fiddle::Pointer
|
|
9
9
|
|
10
10
|
def type_string
|
11
11
|
type_int = term_type
|
12
|
-
::Upl::Extern.constants.find{|c| (::Upl::Extern.const_get c) == type_int}
|
12
|
+
::Upl::Extern.constants.find{|c| (::Upl::Extern.const_get c) == type_int} || type_int
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
@@ -32,15 +32,23 @@ module Upl
|
|
32
32
|
rv == 1 # don't raise
|
33
33
|
end
|
34
34
|
|
35
|
+
def self.ruby_free_fn
|
36
|
+
@ruby_free_fn ||= Fiddle::Function.new Fiddle::RUBY_FREE, [Fiddle::TYPE_VOIDP], Fiddle::TYPE_VOID
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.swipl_free_fn
|
40
|
+
@swipl_free_fn ||= Fiddle::Function.new Extern['PL_free'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_VOID
|
41
|
+
end
|
42
|
+
|
35
43
|
def self.init
|
36
44
|
# set up no output so we don't get swipl command line interfering in ruby
|
37
45
|
# TODO exception handling should not kick off a prolog terminal
|
38
|
-
# TODO
|
46
|
+
# TODO from gem-swipl args = [ @swipl_lib, "-tty", "-q", "-t", "true", "-g", "true", "--nodebug", "--nosignals" ]
|
39
47
|
args = %w[upl --tty=false --signals=false --debug=false --quiet=true]
|
40
48
|
|
41
49
|
# convert args to char **
|
42
50
|
ptr_size = Extern.sizeof 'char*'
|
43
|
-
arg_ptrs = Ptr.malloc
|
51
|
+
arg_ptrs = Ptr.malloc ptr_size * args.size, ruby_free_fn
|
44
52
|
args.each_with_index do |rg,i|
|
45
53
|
(arg_ptrs + i*ptr_size)[0,ptr_size] = Ptr[rg].ref
|
46
54
|
end
|
@@ -70,13 +78,13 @@ module Upl
|
|
70
78
|
(predicate 'atom_to_term', 3),
|
71
79
|
(args = TermVector[st.to_sym, nil, nil]).terms
|
72
80
|
|
73
|
-
|
81
|
+
vars = Inter.each_of_list(args[2]).each_with_object Variables.new do |term_t, vars|
|
74
82
|
# each of these is =(Atom,variable), and we want Atom => variable
|
75
83
|
t = Term.new term_t
|
76
|
-
|
77
|
-
end
|
84
|
+
vars.store t.first.atom.to_sym, (Variable.new t.last.term_t, name: t.first.atom.to_sym)
|
85
|
+
end
|
78
86
|
|
79
|
-
return args[1],
|
87
|
+
return args[1], vars
|
80
88
|
end
|
81
89
|
|
82
90
|
def self.unify( term_a, term_b )
|
@@ -85,6 +93,7 @@ module Upl
|
|
85
93
|
end
|
86
94
|
|
87
95
|
# do a query for the given term and vars, as parsed by term_vars
|
96
|
+
# qvars_hash is a hash of :VariableName => Term(PL_VARIABLE)
|
88
97
|
def self.term_vars_query qterm, qvars_hash
|
89
98
|
raise "not a term" unless Term === qterm
|
90
99
|
return enum_for __method__, qterm, qvars_hash unless block_given?
|
@@ -105,10 +114,9 @@ module Upl
|
|
105
114
|
break if res == 0
|
106
115
|
|
107
116
|
hash = qvars_hash.each_with_object Hash.new do |(name_sym,var),ha|
|
108
|
-
#
|
109
|
-
# so we need to construct a ruby tree of the value term
|
110
|
-
|
111
|
-
# binding.pry if val.to_sym == :query_debug_settings rescue false
|
117
|
+
# var will be invalidated by the next call to PL_next_solution,
|
118
|
+
# so we need to construct a ruby tree copy of the value term.
|
119
|
+
ha[name_sym] = var.to_ruby
|
112
120
|
end
|
113
121
|
|
114
122
|
yield hash
|
@@ -151,7 +159,7 @@ module Upl
|
|
151
159
|
|
152
160
|
def self.query term
|
153
161
|
raise "not a Term" unless Term === term
|
154
|
-
return enum_for :
|
162
|
+
return enum_for :query, term unless block_given?
|
155
163
|
|
156
164
|
answer_lst = TermVector.new term.arity do |idx| term[idx] end
|
157
165
|
query_id_p = Extern.PL_open_query Extern::NULL, 0, term.to_predicate, answer_lst.terms
|
data/lib/upl/term.rb
CHANGED
@@ -56,7 +56,7 @@ module Upl
|
|
56
56
|
rv == 1 or raise "can't populate term"
|
57
57
|
|
58
58
|
@arity = int_ptr.ptr.to_i
|
59
|
-
@atom = Atom.new atom_ptr
|
59
|
+
@atom = Atom.new atom_ptr.ptr
|
60
60
|
|
61
61
|
self
|
62
62
|
end
|
@@ -84,7 +84,7 @@ module Upl
|
|
84
84
|
end
|
85
85
|
|
86
86
|
def to_functor
|
87
|
-
Extern::PL_new_functor atom.
|
87
|
+
Extern::PL_new_functor atom.atom_t, arity
|
88
88
|
end
|
89
89
|
|
90
90
|
def to_predicate
|
@@ -94,7 +94,6 @@ module Upl
|
|
94
94
|
def tree; @tree || (Tree.of_term term_t) end
|
95
95
|
alias to_ruby tree
|
96
96
|
|
97
|
-
# TODO leaning hard towards each with Enumerable
|
98
97
|
def each
|
99
98
|
return enum_for :args unless block_given?
|
100
99
|
|
data/lib/upl/tree.rb
CHANGED
@@ -27,18 +27,15 @@ module Upl
|
|
27
27
|
term_to_ruby term_t
|
28
28
|
end
|
29
29
|
|
30
|
+
def to_ruby; self end
|
31
|
+
|
30
32
|
def self.term_to_ruby term_t
|
31
33
|
case term_t.term_type
|
32
34
|
when Extern::PL_VARIABLE
|
33
35
|
Variable.copy term_t
|
34
36
|
|
35
37
|
when Extern::PL_ATOM
|
36
|
-
|
37
|
-
if atom.to_s =~ /^ruby-(\d+)/
|
38
|
-
ObjectSpace._id2ref $1.to_i
|
39
|
-
else
|
40
|
-
atom.to_sym
|
41
|
-
end
|
38
|
+
Atom.of_term(term_t).to_ruby
|
42
39
|
|
43
40
|
# I think integers > 63 bits can be fetched with PL_get_mpz
|
44
41
|
# Other than PL_INTEGER, most of these seem to be unused?
|
@@ -56,7 +53,7 @@ module Upl
|
|
56
53
|
when Extern::PL_STRING
|
57
54
|
rv = Extern.PL_get_string term_t, (str_ptr = Fiddle::Pointer[0].ref), (len_ptr = Fiddle::Pointer[0].ref)
|
58
55
|
value_ptr = Fiddle::Pointer.new str_ptr.ptr, len_ptr.ptr.to_i
|
59
|
-
value_ptr.to_s
|
56
|
+
value_ptr.to_s[0,len_ptr.ptr.to_i]
|
60
57
|
|
61
58
|
when Extern::PL_NIL
|
62
59
|
# TODO maybe this should be [] - see what happens when term_vars has no vars
|
@@ -67,13 +64,13 @@ module Upl
|
|
67
64
|
Tree.new term_t
|
68
65
|
|
69
66
|
when Extern::PL_LIST_PAIR
|
70
|
-
Inter.each_of_list(term_t).
|
67
|
+
Inter.each_of_list(term_t).map{|term_t| Term.new(term_t).to_ruby}
|
71
68
|
|
72
69
|
when Extern::PL_DICT
|
73
70
|
Dict.of_term term_t
|
74
71
|
|
75
72
|
else
|
76
|
-
:NotImplemented
|
73
|
+
:"#{term_t.type_string} NotImplemented"
|
77
74
|
|
78
75
|
end
|
79
76
|
end
|
@@ -81,7 +78,7 @@ module Upl
|
|
81
78
|
def arity; args.size end
|
82
79
|
|
83
80
|
def pretty_print(pp)
|
84
|
-
unless atom == :','
|
81
|
+
unless atom.to_sym == :','
|
85
82
|
pp.text atom.to_s
|
86
83
|
if arity > 0
|
87
84
|
pp.text ?/
|
data/lib/upl/variable.rb
CHANGED
@@ -1,13 +1,17 @@
|
|
1
1
|
module Upl
|
2
2
|
# Really this is just an empty term.
|
3
3
|
class Variable
|
4
|
-
def initialize term_t = nil
|
4
|
+
def initialize term_t = nil, name: nil
|
5
5
|
@term_t = term_t || self.class.to_term
|
6
|
+
@name = name
|
6
7
|
end
|
7
8
|
|
8
|
-
attr_reader :term_t
|
9
|
+
attr_reader :term_t, :name
|
9
10
|
alias to_term_t term_t
|
10
11
|
|
12
|
+
# create a ruby represetation of the term_t
|
13
|
+
def to_ruby; Tree.of_term term_t end
|
14
|
+
|
11
15
|
def self.copy term_t
|
12
16
|
inst = new term_t
|
13
17
|
|
@@ -22,15 +26,21 @@ module Upl
|
|
22
26
|
Extern.PL_new_term_ref
|
23
27
|
end
|
24
28
|
|
29
|
+
def self.[]( *names )
|
30
|
+
vars = names.map{|name| new name: name}
|
31
|
+
if vars.size == 1 then vars.first else vars end
|
32
|
+
end
|
33
|
+
|
25
34
|
def to_s; _string end
|
26
35
|
|
27
36
|
def _string
|
28
37
|
@_string ||= begin
|
29
38
|
Extern::PL_get_chars \
|
30
39
|
term_t,
|
31
|
-
(str_ref = Runtime::Ptr[
|
40
|
+
(str_ref = Runtime::Ptr[0].ref),
|
32
41
|
Extern::Convert::CVT_VARIABLE | Extern::Convert::REP_UTF8 | Extern::Convert::BUF_MALLOC # | Extern::CVT_ALL
|
33
42
|
|
43
|
+
str_ref.ptr.free = Runtime.swipl_free_fn
|
34
44
|
str_ref.ptr.to_s
|
35
45
|
end
|
36
46
|
end
|
@@ -55,6 +65,10 @@ module Upl
|
|
55
65
|
if attributed?
|
56
66
|
attribute.pretty_print pp
|
57
67
|
else
|
68
|
+
if name
|
69
|
+
pp.text name
|
70
|
+
pp.text '='
|
71
|
+
end
|
58
72
|
pp.text to_s
|
59
73
|
end
|
60
74
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Upl
|
2
|
+
# Storage from variables, where setting one just calls unify on the underlying terms.
|
3
|
+
# Cos it's hard to hang unify on a single variable.
|
4
|
+
class Variables < Hash
|
5
|
+
def initialize *names
|
6
|
+
super
|
7
|
+
names.each do |name|
|
8
|
+
self.store name, Variable.new
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# calls unify, so you can't set a given variable more than once.
|
13
|
+
def []=( name, term )
|
14
|
+
Extern::PL_unify self[name.to_sym].to_term_t, term.to_term_t
|
15
|
+
end
|
16
|
+
|
17
|
+
def method_missing meth, *args
|
18
|
+
name = meth.to_s
|
19
|
+
|
20
|
+
the_method =
|
21
|
+
if name.chomp! '='
|
22
|
+
# set the value
|
23
|
+
:'[]='
|
24
|
+
else
|
25
|
+
# fetch the value
|
26
|
+
:'[]'
|
27
|
+
end
|
28
|
+
|
29
|
+
var_name = name.to_sym
|
30
|
+
|
31
|
+
if has_key? var_name
|
32
|
+
send the_method, var_name, *args
|
33
|
+
else
|
34
|
+
super
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def pretty_print pp
|
39
|
+
transform_values{|v| v.to_ruby}.pretty_print pp
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/upl/version.rb
CHANGED
@@ -0,0 +1,33 @@
|
|
1
|
+
def register_test_predicate
|
2
|
+
Upl::Foreign.register_semidet :upl_block do |term_t0, term_t1|
|
3
|
+
fterm = (Upl::Tree.of_term term_t0)
|
4
|
+
p foreign: fterm
|
5
|
+
|
6
|
+
if Symbol === fterm then
|
7
|
+
Upl::Extern::PL_unify term_t1, :there.to_term_t
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
Array Upl.query "upl_block(hello,A)"
|
12
|
+
end
|
13
|
+
|
14
|
+
=begin
|
15
|
+
include UPL
|
16
|
+
vars = Variables.new :V
|
17
|
+
term = Term :mcall, (o = Object.new), :to_s, vars.V
|
18
|
+
def o.to_s; "This is from Ruby, with Love :-D"; end
|
19
|
+
Array Runtime.term_vars_query term, vars
|
20
|
+
=> [{:V=>"This is from Ruby, with Love :-D"}]
|
21
|
+
|
22
|
+
mcall(+Object, +Method, -Result)
|
23
|
+
=end
|
24
|
+
|
25
|
+
def doit
|
26
|
+
fact = Upl::Term.functor :person, :john, :anderson, Object.new
|
27
|
+
Upl.assertz fact
|
28
|
+
vs = Array Upl.query 'person(A,B,C)'
|
29
|
+
p vs
|
30
|
+
1000.times{Array Upl.query 'current_prolog_flag(K,V)'}
|
31
|
+
Upl.retract fact
|
32
|
+
Upl::Runtime.call Upl::Term :garbage_collect_atoms
|
33
|
+
end
|
data/upl.gemspec
CHANGED
@@ -30,9 +30,9 @@ Gem::Specification.new do |spec|
|
|
30
30
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
31
31
|
spec.require_paths = ['lib']
|
32
32
|
|
33
|
-
spec.add_development_dependency 'bundler', '
|
34
|
-
spec.add_development_dependency 'rake', '
|
35
|
-
spec.add_development_dependency 'rspec', '
|
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
36
|
spec.add_dependency 'pry'
|
37
37
|
spec.add_dependency 'fiddle'
|
38
38
|
end
|
metadata
CHANGED
@@ -1,55 +1,55 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: upl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Anderson
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-11-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '1.16'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.16'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '10.0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '10.0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rspec
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - "
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '3.0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - "
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '3.0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
@@ -91,6 +91,7 @@ files:
|
|
91
91
|
- ".rspec"
|
92
92
|
- ".travis.yml"
|
93
93
|
- Gemfile
|
94
|
+
- History.txt
|
94
95
|
- LICENSE.txt
|
95
96
|
- README.md
|
96
97
|
- Rakefile
|
@@ -100,6 +101,7 @@ files:
|
|
100
101
|
- lib/upl/atom.rb
|
101
102
|
- lib/upl/dict.rb
|
102
103
|
- lib/upl/extern.rb
|
104
|
+
- lib/upl/foreign.rb
|
103
105
|
- lib/upl/functor.rb
|
104
106
|
- lib/upl/inter.rb
|
105
107
|
- lib/upl/runtime.rb
|
@@ -107,7 +109,9 @@ files:
|
|
107
109
|
- lib/upl/term_vector.rb
|
108
110
|
- lib/upl/tree.rb
|
109
111
|
- lib/upl/variable.rb
|
112
|
+
- lib/upl/variables.rb
|
110
113
|
- lib/upl/version.rb
|
114
|
+
- scratch/register_predicate.rb
|
111
115
|
- upl.gemspec
|
112
116
|
homepage: https://github.com/djellemah/upl
|
113
117
|
licenses:
|
@@ -129,8 +133,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
129
133
|
- !ruby/object:Gem::Version
|
130
134
|
version: '0'
|
131
135
|
requirements: []
|
132
|
-
|
133
|
-
rubygems_version: 2.7.7
|
136
|
+
rubygems_version: 3.0.6
|
134
137
|
signing_key:
|
135
138
|
specification_version: 4
|
136
139
|
summary: Access SWI-Prolog engine from ruby
|